Merge pull request #2400 from Owain94/externalscrash
externals: Don't crash the client if updating a plugin fails
This commit is contained in:
@@ -85,6 +85,7 @@ import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay;
|
|||||||
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
||||||
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
||||||
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
|
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
|
||||||
|
import net.runelite.client.util.AppLock;
|
||||||
import net.runelite.client.util.WorldUtil;
|
import net.runelite.client.util.WorldUtil;
|
||||||
import net.runelite.client.ws.PartyService;
|
import net.runelite.client.ws.PartyService;
|
||||||
import net.runelite.http.api.worlds.World;
|
import net.runelite.http.api.worlds.World;
|
||||||
@@ -201,6 +202,9 @@ public class RuneLite
|
|||||||
@Inject
|
@Inject
|
||||||
private Scheduler scheduler;
|
private Scheduler scheduler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private AppLock appLock;
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception
|
public static void main(String[] args) throws Exception
|
||||||
{
|
{
|
||||||
Locale.setDefault(Locale.ENGLISH);
|
Locale.setDefault(Locale.ENGLISH);
|
||||||
@@ -368,9 +372,11 @@ public class RuneLite
|
|||||||
externalPluginManager.startExternalUpdateManager();
|
externalPluginManager.startExternalUpdateManager();
|
||||||
externalPluginManager.startExternalPluginManager();
|
externalPluginManager.startExternalPluginManager();
|
||||||
|
|
||||||
|
if (appLock.lock(this.getClass().getName()))
|
||||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
{
|
||||||
externalPluginManager.update();
|
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||||
|
externalPluginManager.update();
|
||||||
|
}
|
||||||
|
|
||||||
// Load the plugins, but does not start them yet.
|
// Load the plugins, but does not start them yet.
|
||||||
// This will initialize configuration
|
// This will initialize configuration
|
||||||
@@ -490,5 +496,6 @@ public class RuneLite
|
|||||||
configManager.sendConfig();
|
configManager.sendConfig();
|
||||||
clientSessionManager.shutdown();
|
clientSessionManager.shutdown();
|
||||||
discordService.close();
|
discordService.close();
|
||||||
|
appLock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class ExternalPluginManager
|
|||||||
{
|
{
|
||||||
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
|
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
|
||||||
private final PluginManager runelitePluginManager;
|
private final PluginManager runelitePluginManager;
|
||||||
private final org.pf4j.PluginManager externalPluginManager;
|
private org.pf4j.PluginManager externalPluginManager;
|
||||||
@Getter(AccessLevel.PUBLIC)
|
@Getter(AccessLevel.PUBLIC)
|
||||||
private final List<UpdateRepository> repositories = new ArrayList<>();
|
private final List<UpdateRepository> repositories = new ArrayList<>();
|
||||||
private final OpenOSRSConfig openOSRSConfig;
|
private final OpenOSRSConfig openOSRSConfig;
|
||||||
@@ -104,6 +104,11 @@ class ExternalPluginManager
|
|||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
EXTERNALPLUGIN_DIR.mkdirs();
|
EXTERNALPLUGIN_DIR.mkdirs();
|
||||||
|
|
||||||
|
initPluginManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPluginManager()
|
||||||
|
{
|
||||||
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
|
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
|
||||||
|
|
||||||
this.externalPluginManager = new DefaultPluginManager(debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release") : EXTERNALPLUGIN_DIR.toPath())
|
this.externalPluginManager = new DefaultPluginManager(debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release") : EXTERNALPLUGIN_DIR.toPath())
|
||||||
@@ -241,6 +246,8 @@ class ExternalPluginManager
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
repositories.clear();
|
||||||
|
|
||||||
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
|
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
|
||||||
{
|
{
|
||||||
String id = keyval.substring(0, keyval.lastIndexOf(":https"));
|
String id = keyval.substring(0, keyval.lastIndexOf(":https"));
|
||||||
@@ -474,6 +481,21 @@ class ExternalPluginManager
|
|||||||
|
|
||||||
for (PluginWrapper plugin : startedPlugins)
|
for (PluginWrapper plugin : startedPlugins)
|
||||||
{
|
{
|
||||||
|
boolean depsLoaded = true;
|
||||||
|
for (PluginDependency dependency : plugin.getDescriptor().getDependencies())
|
||||||
|
{
|
||||||
|
if (startedPlugins.stream().noneMatch(pl -> pl.getPluginId().equals(dependency.getPluginId())))
|
||||||
|
{
|
||||||
|
depsLoaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!depsLoaded)
|
||||||
|
{
|
||||||
|
// This should never happen but can crash the client
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
scannedPlugins.addAll(loadPlugin(plugin.getPluginId()));
|
scannedPlugins.addAll(loadPlugin(plugin.getPluginId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,6 +695,7 @@ class ExternalPluginManager
|
|||||||
|
|
||||||
public void update()
|
public void update()
|
||||||
{
|
{
|
||||||
|
boolean error = false;
|
||||||
if (updateManager.hasUpdates())
|
if (updateManager.hasUpdates())
|
||||||
{
|
{
|
||||||
List<PluginInfo> updates = updateManager.getUpdates();
|
List<PluginInfo> updates = updateManager.getUpdates();
|
||||||
@@ -687,14 +710,24 @@ class ExternalPluginManager
|
|||||||
if (!updated)
|
if (!updated)
|
||||||
{
|
{
|
||||||
log.warn("Cannot update plugin '{}'", plugin.id);
|
log.warn("Cannot update plugin '{}'", plugin.id);
|
||||||
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (PluginRuntimeException ex)
|
catch (PluginRuntimeException ex)
|
||||||
{
|
{
|
||||||
log.warn("Cannot update plugin '{}', the user probably has another client open", plugin.id);
|
log.warn("Cannot update plugin '{}', the user probably has another client open", plugin.id);
|
||||||
|
error = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
initPluginManager();
|
||||||
|
startExternalUpdateManager();
|
||||||
|
startExternalPluginManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getDependencies()
|
public Set<String> getDependencies()
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 by rumatoest at github.com
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class AppLock.
|
||||||
|
*
|
||||||
|
* @author Vladislav Zablotsky
|
||||||
|
*/
|
||||||
|
public class AppLock
|
||||||
|
{
|
||||||
|
|
||||||
|
private static CrossLock lockInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set lock for application instance.
|
||||||
|
* Method must be run only one time at application start.
|
||||||
|
*
|
||||||
|
* @param lockId Unique lock identifiers
|
||||||
|
* @return true if succeeded
|
||||||
|
*/
|
||||||
|
public synchronized boolean lock(String lockId)
|
||||||
|
{
|
||||||
|
if (lockInstance == null)
|
||||||
|
{
|
||||||
|
lockInstance = new CrossLock("application_" + lockId);
|
||||||
|
}
|
||||||
|
return lockInstance.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trying to release application lock.
|
||||||
|
* Thus another application instances will be able to use lock with current ID.
|
||||||
|
*/
|
||||||
|
public synchronized void release()
|
||||||
|
{
|
||||||
|
if (lockInstance != null)
|
||||||
|
{
|
||||||
|
lockInstance.clear();
|
||||||
|
}
|
||||||
|
lockInstance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 by rumatoest at github.com
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import static net.runelite.client.RuneLite.RUNELITE_DIR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal cross application instances locker.
|
||||||
|
* Allow you to create simple lock like object which can be used for
|
||||||
|
* different application instances. Basic idea is simple - just a simple file lock.
|
||||||
|
* <br />
|
||||||
|
* All you need is to define unique key for each lock type.
|
||||||
|
*
|
||||||
|
* @author Vladislav Zablotsky
|
||||||
|
*/
|
||||||
|
public class CrossLock
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final HashMap<String, CrossLock> locks = new HashMap<>();
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
private final File fileToLock;
|
||||||
|
|
||||||
|
private FileOutputStream fileStream;
|
||||||
|
|
||||||
|
private FileChannel fileStreamChannel;
|
||||||
|
|
||||||
|
private FileLock lockOnFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create or retrieve lock instance.
|
||||||
|
* Each lock id is unique among all you instances,
|
||||||
|
* thus only one instance can acquire lock for this id.
|
||||||
|
*
|
||||||
|
* @param lockId Unique lock identifier
|
||||||
|
* @return Not null
|
||||||
|
*/
|
||||||
|
public static CrossLock get(String lockId)
|
||||||
|
{
|
||||||
|
if (locks.containsKey(lockId))
|
||||||
|
{
|
||||||
|
return locks.get(lockId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
synchronized (CrossLock.class)
|
||||||
|
{
|
||||||
|
if (locks.containsKey(lockId))
|
||||||
|
{
|
||||||
|
return locks.get(lockId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CrossLock cl = new CrossLock(lockId);
|
||||||
|
locks.put(lockId, cl);
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will remove lock object for specific id and release lock if any.
|
||||||
|
*
|
||||||
|
* @param lockId Unique lock identifier
|
||||||
|
*/
|
||||||
|
public static void remove(String lockId)
|
||||||
|
{
|
||||||
|
if (locks.containsKey(lockId))
|
||||||
|
{
|
||||||
|
CrossLock lock = null;
|
||||||
|
synchronized (CrossLock.class)
|
||||||
|
{
|
||||||
|
if (locks.containsKey(lockId))
|
||||||
|
{
|
||||||
|
lock = locks.remove(lockId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lock != null)
|
||||||
|
{
|
||||||
|
lock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossLock(String lockId)
|
||||||
|
{
|
||||||
|
this.id = lockId;
|
||||||
|
fileToLock = new File(RUNELITE_DIR, lockId + ".app_lock");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return lock instance identifier.
|
||||||
|
*/
|
||||||
|
public String id()
|
||||||
|
{
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate lock.
|
||||||
|
* Note! This is only cross application (cross instances) lock. It will not work
|
||||||
|
* as lock inside single application instance.
|
||||||
|
*
|
||||||
|
* @return true if lock was acquire or false
|
||||||
|
*/
|
||||||
|
public synchronized boolean lock()
|
||||||
|
{
|
||||||
|
if (lockOnFile != null && lockOnFile.isValid())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
|
||||||
|
String lockContent = "#Java AppLock Object\n#Locked by key: " + id() + "\r\n";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (fileToLock.exists())
|
||||||
|
{
|
||||||
|
fileToLock.createNewFile();
|
||||||
|
}
|
||||||
|
fileStream = new FileOutputStream(fileToLock);
|
||||||
|
fileStreamChannel = fileStream.getChannel();
|
||||||
|
lockOnFile = fileStreamChannel.tryLock();
|
||||||
|
if (lockOnFile != null)
|
||||||
|
{
|
||||||
|
fileStream.write(lockContent.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!(ex instanceof OverlappingFileLockException))
|
||||||
|
{
|
||||||
|
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||||
|
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lockOnFile != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release lock associated with this object.
|
||||||
|
*/
|
||||||
|
public synchronized void release()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (lockOnFile != null && lockOnFile.isValid())
|
||||||
|
{
|
||||||
|
lockOnFile.release();
|
||||||
|
}
|
||||||
|
lockOnFile = null;
|
||||||
|
|
||||||
|
if (fileStream != null)
|
||||||
|
{
|
||||||
|
fileStream.close();
|
||||||
|
fileStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileStreamChannel != null && fileStreamChannel.isOpen())
|
||||||
|
{
|
||||||
|
fileStreamChannel.close();
|
||||||
|
}
|
||||||
|
fileStreamChannel = null;
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||||
|
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release lock and remove lock file.
|
||||||
|
*/
|
||||||
|
public synchronized void clear()
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
if (fileToLock.exists())
|
||||||
|
{
|
||||||
|
fileToLock.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable
|
||||||
|
{
|
||||||
|
this.clear();
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user