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.tooltip.TooltipOverlay;
|
||||
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
|
||||
import net.runelite.client.util.AppLock;
|
||||
import net.runelite.client.util.WorldUtil;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import net.runelite.http.api.worlds.World;
|
||||
@@ -201,6 +202,9 @@ public class RuneLite
|
||||
@Inject
|
||||
private Scheduler scheduler;
|
||||
|
||||
@Inject
|
||||
private AppLock appLock;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
@@ -368,9 +372,11 @@ public class RuneLite
|
||||
externalPluginManager.startExternalUpdateManager();
|
||||
externalPluginManager.startExternalPluginManager();
|
||||
|
||||
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
externalPluginManager.update();
|
||||
if (appLock.lock(this.getClass().getName()))
|
||||
{
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
externalPluginManager.update();
|
||||
}
|
||||
|
||||
// Load the plugins, but does not start them yet.
|
||||
// This will initialize configuration
|
||||
@@ -490,5 +496,6 @@ public class RuneLite
|
||||
configManager.sendConfig();
|
||||
clientSessionManager.shutdown();
|
||||
discordService.close();
|
||||
appLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class ExternalPluginManager
|
||||
{
|
||||
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
|
||||
private final PluginManager runelitePluginManager;
|
||||
private final org.pf4j.PluginManager externalPluginManager;
|
||||
private org.pf4j.PluginManager externalPluginManager;
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private final List<UpdateRepository> repositories = new ArrayList<>();
|
||||
private final OpenOSRSConfig openOSRSConfig;
|
||||
@@ -104,6 +104,11 @@ class ExternalPluginManager
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
EXTERNALPLUGIN_DIR.mkdirs();
|
||||
|
||||
initPluginManager();
|
||||
}
|
||||
|
||||
private void initPluginManager()
|
||||
{
|
||||
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
|
||||
|
||||
this.externalPluginManager = new DefaultPluginManager(debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release") : EXTERNALPLUGIN_DIR.toPath())
|
||||
@@ -241,6 +246,8 @@ class ExternalPluginManager
|
||||
{
|
||||
try
|
||||
{
|
||||
repositories.clear();
|
||||
|
||||
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
|
||||
{
|
||||
String id = keyval.substring(0, keyval.lastIndexOf(":https"));
|
||||
@@ -474,6 +481,21 @@ class ExternalPluginManager
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -673,6 +695,7 @@ class ExternalPluginManager
|
||||
|
||||
public void update()
|
||||
{
|
||||
boolean error = false;
|
||||
if (updateManager.hasUpdates())
|
||||
{
|
||||
List<PluginInfo> updates = updateManager.getUpdates();
|
||||
@@ -687,14 +710,24 @@ class ExternalPluginManager
|
||||
if (!updated)
|
||||
{
|
||||
log.warn("Cannot update plugin '{}'", plugin.id);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
catch (PluginRuntimeException ex)
|
||||
{
|
||||
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()
|
||||
|
||||
@@ -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