2010-02-07 13:32:49 +00:00
|
|
|
package net.i2p.router.web;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
2010-02-07 17:13:44 +00:00
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.net.URLClassLoader;
|
2010-02-10 15:35:00 +00:00
|
|
|
import java.util.ArrayList;
|
2010-02-26 16:58:01 +00:00
|
|
|
import java.util.Arrays;
|
2010-04-16 03:58:48 +00:00
|
|
|
import java.util.Collection;
|
2012-03-19 12:37:39 +00:00
|
|
|
import java.util.Collections;
|
2010-02-10 15:35:00 +00:00
|
|
|
import java.util.HashMap;
|
2010-02-07 13:32:49 +00:00
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
2010-02-10 15:35:00 +00:00
|
|
|
import java.util.Map;
|
2010-02-07 13:32:49 +00:00
|
|
|
import java.util.Properties;
|
|
|
|
import java.util.StringTokenizer;
|
2010-04-16 03:58:48 +00:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2010-02-07 13:32:49 +00:00
|
|
|
|
2011-12-24 00:48:30 +00:00
|
|
|
import net.i2p.CoreVersion;
|
2010-02-07 13:32:49 +00:00
|
|
|
import net.i2p.I2PAppContext;
|
|
|
|
import net.i2p.data.DataHelper;
|
2010-04-16 03:58:48 +00:00
|
|
|
import net.i2p.router.Job;
|
2010-02-07 13:32:49 +00:00
|
|
|
import net.i2p.router.RouterContext;
|
2012-01-15 21:15:08 +00:00
|
|
|
import net.i2p.router.RouterVersion;
|
2010-02-07 13:32:49 +00:00
|
|
|
import net.i2p.router.startup.ClientAppConfig;
|
|
|
|
import net.i2p.router.startup.LoadClientAppsJob;
|
2010-04-16 03:58:48 +00:00
|
|
|
import net.i2p.util.ConcurrentHashSet;
|
2010-02-10 19:09:35 +00:00
|
|
|
import net.i2p.util.FileUtil;
|
2012-01-15 21:15:08 +00:00
|
|
|
import net.i2p.util.I2PAppThread;
|
2010-02-07 13:32:49 +00:00
|
|
|
import net.i2p.util.Log;
|
2010-02-07 18:18:31 +00:00
|
|
|
import net.i2p.util.Translate;
|
2011-12-24 00:48:30 +00:00
|
|
|
import net.i2p.util.VersionComparator;
|
2010-02-07 13:32:49 +00:00
|
|
|
|
2011-12-23 00:56:48 +00:00
|
|
|
import org.mortbay.jetty.handler.ContextHandlerCollection;
|
2010-02-07 13:32:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2010-02-26 16:58:01 +00:00
|
|
|
* Start/stop/delete plugins that are already installed
|
|
|
|
* Get properties of installed plugins
|
|
|
|
* Get or change settings in plugins.config
|
2010-02-07 13:32:49 +00:00
|
|
|
*
|
|
|
|
* @since 0.7.12
|
|
|
|
* @author zzz
|
|
|
|
*/
|
|
|
|
public class PluginStarter implements Runnable {
|
2010-03-29 21:20:48 +00:00
|
|
|
protected RouterContext _context;
|
2010-02-07 13:32:49 +00:00
|
|
|
static final String PREFIX = "plugin.";
|
|
|
|
static final String ENABLED = ".startOnLoad";
|
2010-02-26 16:58:01 +00:00
|
|
|
private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
|
|
|
|
"susimail", "addressbook", "routerconsole" };
|
|
|
|
private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic",
|
|
|
|
"midnight" };
|
2010-04-16 03:58:48 +00:00
|
|
|
private static Map<String, ThreadGroup> pluginThreadGroups = new ConcurrentHashMap<String, ThreadGroup>(); // one thread group per plugin (map key=plugin name)
|
|
|
|
private static Map<String, Collection<Job>> pluginJobs = new ConcurrentHashMap<String, Collection<Job>>();
|
2010-05-05 19:34:03 +00:00
|
|
|
private static Map<String, ClassLoader> _clCache = new ConcurrentHashMap();
|
2010-11-24 14:31:54 +00:00
|
|
|
private static Map<String, Collection<String>> pluginWars = new ConcurrentHashMap<String, Collection<String>>();
|
2010-02-07 13:32:49 +00:00
|
|
|
|
|
|
|
public PluginStarter(RouterContext ctx) {
|
|
|
|
_context = ctx;
|
|
|
|
}
|
|
|
|
|
2010-02-10 19:09:35 +00:00
|
|
|
static boolean pluginsEnabled(I2PAppContext ctx) {
|
2012-09-28 17:50:41 +00:00
|
|
|
return ctx.getBooleanPropertyDefaultTrue("router.enablePlugins");
|
2010-02-10 19:09:35 +00:00
|
|
|
}
|
|
|
|
|
2010-02-07 13:32:49 +00:00
|
|
|
public void run() {
|
2012-01-15 21:15:08 +00:00
|
|
|
if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") &&
|
2012-09-28 17:50:41 +00:00
|
|
|
(!Boolean.parseBoolean(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) &&
|
2012-01-15 21:15:08 +00:00
|
|
|
(!RouterVersion.VERSION.equals(_context.getProperty("router.previousVersion"))))
|
|
|
|
updateAll(_context, true);
|
2010-02-07 13:32:49 +00:00
|
|
|
startPlugins(_context);
|
|
|
|
}
|
|
|
|
|
2012-01-15 21:15:08 +00:00
|
|
|
/**
|
|
|
|
* threaded
|
|
|
|
* @since 0.8.13
|
|
|
|
*/
|
|
|
|
static void updateAll(RouterContext ctx) {
|
|
|
|
Thread t = new I2PAppThread(new PluginUpdater(ctx), "PluginUpdater", true);
|
|
|
|
t.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* thread
|
|
|
|
* @since 0.8.13
|
|
|
|
*/
|
|
|
|
private static class PluginUpdater implements Runnable {
|
|
|
|
private final RouterContext _ctx;
|
|
|
|
|
|
|
|
public PluginUpdater(RouterContext ctx) {
|
|
|
|
_ctx = ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
updateAll(_ctx, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* inline
|
|
|
|
* @since 0.8.13
|
|
|
|
*/
|
|
|
|
private static void updateAll(RouterContext ctx, boolean delay) {
|
|
|
|
List<String> plugins = getPlugins();
|
|
|
|
Map<String, String> toUpdate = new HashMap();
|
|
|
|
for (String appName : plugins) {
|
|
|
|
Properties props = pluginProperties(ctx, appName);
|
|
|
|
String url = props.getProperty("updateURL");
|
|
|
|
if (url != null)
|
|
|
|
toUpdate.put(appName, url);
|
|
|
|
}
|
|
|
|
if (toUpdate.isEmpty())
|
|
|
|
return;
|
|
|
|
PluginUpdateChecker puc = PluginUpdateChecker.getInstance(ctx);
|
|
|
|
if (puc.isRunning())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (delay) {
|
|
|
|
// wait for proxy
|
|
|
|
System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "true");
|
|
|
|
puc.setAppStatus(Messages.getString("Checking for plugin updates", ctx));
|
|
|
|
try {
|
|
|
|
Thread.sleep(3*60*1000);
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
System.setProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS, "false");
|
|
|
|
}
|
|
|
|
|
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2012-01-23 17:53:59 +00:00
|
|
|
int updated = 0;
|
2012-01-15 21:15:08 +00:00
|
|
|
for (Map.Entry<String, String> entry : toUpdate.entrySet()) {
|
|
|
|
String appName = entry.getKey();
|
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("Checking for update plugin: " + appName);
|
|
|
|
puc.update(appName);
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
Thread.sleep(5*1000);
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
} while (puc.isRunning());
|
|
|
|
if (!puc.isNewerAvailable()) {
|
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("No update available for plugin: " + appName);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
PluginUpdateHandler puh = PluginUpdateHandler.getInstance(ctx);
|
|
|
|
String url = entry.getValue();
|
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("Updating plugin: " + appName);
|
|
|
|
puh.update(url);
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
Thread.sleep(5*1000);
|
|
|
|
} catch (InterruptedException ie) {}
|
|
|
|
} while (puh.isRunning());
|
2012-01-23 17:53:59 +00:00
|
|
|
if (puh.wasUpdateSuccessful())
|
|
|
|
updated++;
|
2012-01-15 21:15:08 +00:00
|
|
|
}
|
2012-01-23 17:53:59 +00:00
|
|
|
if (updated > 0)
|
|
|
|
puc.setDoneStatus(ngettext("1 plugin updated", "{0} plugins updated", updated, ctx));
|
|
|
|
else
|
|
|
|
puc.setDoneStatus(Messages.getString("Plugin update check complete", ctx));
|
2012-01-15 21:15:08 +00:00
|
|
|
}
|
|
|
|
|
2010-02-11 21:41:54 +00:00
|
|
|
/** this shouldn't throw anything */
|
2010-02-07 13:32:49 +00:00
|
|
|
static void startPlugins(RouterContext ctx) {
|
2010-02-07 17:13:44 +00:00
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2010-02-07 13:32:49 +00:00
|
|
|
Properties props = pluginProperties();
|
|
|
|
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
|
|
|
String name = (String)iter.next();
|
2010-02-10 19:09:35 +00:00
|
|
|
if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) {
|
2012-09-28 17:50:41 +00:00
|
|
|
if (Boolean.parseBoolean(props.getProperty(name))) {
|
2010-02-10 19:09:35 +00:00
|
|
|
String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED));
|
2012-01-15 21:15:08 +00:00
|
|
|
// plugins could have been started after update
|
|
|
|
if (isPluginRunning(app, ctx))
|
|
|
|
continue;
|
2010-02-07 13:32:49 +00:00
|
|
|
try {
|
|
|
|
if (!startPlugin(ctx, app))
|
2010-02-07 17:13:44 +00:00
|
|
|
log.error("Failed to start plugin: " + app);
|
2010-02-11 21:41:54 +00:00
|
|
|
} catch (Throwable e) {
|
2010-02-07 17:13:44 +00:00
|
|
|
log.error("Failed to start plugin: " + app, e);
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-11 21:41:54 +00:00
|
|
|
/**
|
|
|
|
* @return true on success
|
|
|
|
* @throws just about anything, caller would be wise to catch Throwable
|
|
|
|
*/
|
2010-02-07 13:32:49 +00:00
|
|
|
static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
2010-02-07 17:13:44 +00:00
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2010-05-05 19:34:03 +00:00
|
|
|
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
2010-02-07 13:32:49 +00:00
|
|
|
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
2010-02-07 17:13:44 +00:00
|
|
|
log.error("Cannot start nonexistent plugin: " + appName);
|
2012-01-15 16:59:33 +00:00
|
|
|
disablePlugin(appName);
|
2010-02-07 13:32:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2011-12-24 00:48:30 +00:00
|
|
|
|
2012-03-13 09:49:15 +00:00
|
|
|
// Do we need to extract an update?
|
|
|
|
File pluginUpdate = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + "/app.xpi2p.zip" );
|
|
|
|
if(pluginUpdate.exists()) {
|
|
|
|
// Compare the start time of the router with the plugin.
|
2012-03-15 00:39:11 +00:00
|
|
|
if(ctx.router().getWhenStarted() > pluginUpdate.lastModified()) {
|
2012-03-13 09:49:15 +00:00
|
|
|
if (!FileUtil.extractZip(pluginUpdate, pluginDir)) {
|
|
|
|
pluginUpdate.delete();
|
|
|
|
String foo = "Plugin '" + appName + "' failed to update! File '" + pluginUpdate +"' deleted. You may need to remove and install the plugin again.";
|
|
|
|
log.error(foo);
|
|
|
|
disablePlugin(appName);
|
|
|
|
throw new Exception(foo);
|
|
|
|
} else {
|
|
|
|
pluginUpdate.delete();
|
|
|
|
// Need to always log this, and log.logAlways() did not work for me.
|
|
|
|
System.err.println("INFO: Plugin updated: " + appName);
|
|
|
|
}
|
|
|
|
} // silently fail to update, because we have not restarted.
|
|
|
|
}
|
|
|
|
|
2011-12-24 00:48:30 +00:00
|
|
|
Properties props = pluginProperties(ctx, appName);
|
2012-03-13 09:49:15 +00:00
|
|
|
|
2011-12-24 00:48:30 +00:00
|
|
|
String minVersion = ConfigClientsHelper.stripHTML(props, "min-i2p-version");
|
|
|
|
if (minVersion != null &&
|
|
|
|
(new VersionComparator()).compare(CoreVersion.VERSION, minVersion) < 0) {
|
|
|
|
String foo = "Plugin " + appName + " requires I2P version " + minVersion + " or higher";
|
|
|
|
log.error(foo);
|
2012-01-15 16:59:33 +00:00
|
|
|
disablePlugin(appName);
|
2011-12-24 00:48:30 +00:00
|
|
|
throw new Exception(foo);
|
|
|
|
}
|
|
|
|
|
|
|
|
minVersion = ConfigClientsHelper.stripHTML(props, "min-java-version");
|
|
|
|
if (minVersion != null &&
|
|
|
|
(new VersionComparator()).compare(System.getProperty("java.version"), minVersion) < 0) {
|
|
|
|
String foo = "Plugin " + appName + " requires Java version " + minVersion + " or higher";
|
|
|
|
log.error(foo);
|
2012-01-15 16:59:33 +00:00
|
|
|
disablePlugin(appName);
|
2011-12-24 00:48:30 +00:00
|
|
|
throw new Exception(foo);
|
|
|
|
}
|
|
|
|
|
|
|
|
String jVersion = LogsHelper.jettyVersion();
|
|
|
|
minVersion = ConfigClientsHelper.stripHTML(props, "min-jetty-version");
|
|
|
|
if (minVersion != null &&
|
|
|
|
(new VersionComparator()).compare(minVersion, jVersion) > 0) {
|
|
|
|
String foo = "Plugin " + appName + " requires Jetty version " + minVersion + " or higher";
|
|
|
|
log.error(foo);
|
2012-01-15 16:59:33 +00:00
|
|
|
disablePlugin(appName);
|
2011-12-24 00:48:30 +00:00
|
|
|
throw new Exception(foo);
|
|
|
|
}
|
|
|
|
|
|
|
|
String maxVersion = ConfigClientsHelper.stripHTML(props, "max-jetty-version");
|
|
|
|
if (maxVersion != null &&
|
|
|
|
(new VersionComparator()).compare(maxVersion, jVersion) < 0) {
|
|
|
|
String foo = "Plugin " + appName + " requires Jetty version " + maxVersion + " or lower";
|
|
|
|
log.error(foo);
|
2012-01-15 16:59:33 +00:00
|
|
|
disablePlugin(appName);
|
2011-12-24 00:48:30 +00:00
|
|
|
throw new Exception(foo);
|
|
|
|
}
|
|
|
|
|
2011-04-10 18:22:43 +00:00
|
|
|
if (log.shouldLog(Log.INFO))
|
|
|
|
log.info("Starting plugin: " + appName);
|
2010-02-07 13:32:49 +00:00
|
|
|
|
2010-02-26 16:58:01 +00:00
|
|
|
// register themes
|
|
|
|
File dir = new File(pluginDir, "console/themes");
|
|
|
|
File[] tfiles = dir.listFiles();
|
|
|
|
if (tfiles != null) {
|
|
|
|
for (int i = 0; i < tfiles.length; i++) {
|
|
|
|
String name = tfiles[i].getName();
|
|
|
|
if (tfiles[i].isDirectory() && (!Arrays.asList(STANDARD_THEMES).contains(tfiles[i])))
|
|
|
|
ctx.router().setConfigSetting(ConfigUIHelper.PROP_THEME_PFX + name, tfiles[i].getAbsolutePath());
|
|
|
|
// we don't need to save
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-07 13:32:49 +00:00
|
|
|
// load and start things in clients.config
|
|
|
|
File clientConfig = new File(pluginDir, "clients.config");
|
|
|
|
if (clientConfig.exists()) {
|
2011-12-24 00:48:30 +00:00
|
|
|
Properties cprops = new Properties();
|
|
|
|
DataHelper.loadProps(cprops, clientConfig);
|
2010-02-07 13:32:49 +00:00
|
|
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
2010-02-11 19:18:26 +00:00
|
|
|
runClientApps(ctx, pluginDir, clients, "start");
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// start console webapps in console/webapps
|
2011-12-23 00:56:48 +00:00
|
|
|
ContextHandlerCollection server = WebAppStarter.getConsoleServer();
|
2010-02-07 13:32:49 +00:00
|
|
|
if (server != null) {
|
|
|
|
File consoleDir = new File(pluginDir, "console");
|
2011-12-24 00:48:30 +00:00
|
|
|
Properties wprops = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
|
2010-02-08 22:19:59 +00:00
|
|
|
File webappDir = new File(consoleDir, "webapps");
|
2010-02-07 13:32:49 +00:00
|
|
|
String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
|
|
|
|
if (fileNames != null) {
|
2010-11-24 14:31:54 +00:00
|
|
|
if(!pluginWars.containsKey(appName))
|
|
|
|
pluginWars.put(appName, new ConcurrentHashSet<String>());
|
2010-02-07 13:32:49 +00:00
|
|
|
for (int i = 0; i < fileNames.length; i++) {
|
|
|
|
try {
|
|
|
|
String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
|
2010-02-11 19:18:26 +00:00
|
|
|
//log.error("Found webapp: " + warName);
|
2010-02-08 22:19:59 +00:00
|
|
|
// check for duplicates in $I2P
|
2010-02-26 16:58:01 +00:00
|
|
|
if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
|
2010-02-08 22:19:59 +00:00
|
|
|
log.error("Skipping duplicate webapp " + warName + " in plugin " + appName);
|
|
|
|
continue;
|
|
|
|
}
|
2011-12-24 00:48:30 +00:00
|
|
|
String enabled = wprops.getProperty(RouterConsoleRunner.PREFIX + warName + ENABLED);
|
2010-02-07 13:32:49 +00:00
|
|
|
if (! "false".equals(enabled)) {
|
2011-04-10 18:22:43 +00:00
|
|
|
if (log.shouldLog(Log.INFO))
|
|
|
|
log.info("Starting webapp: " + warName);
|
2010-02-07 13:32:49 +00:00
|
|
|
String path = new File(webappDir, fileNames[i]).getCanonicalPath();
|
|
|
|
WebAppStarter.startWebApp(ctx, server, warName, path);
|
2010-11-24 14:31:54 +00:00
|
|
|
pluginWars.get(appName).add(warName);
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
2010-02-07 17:13:44 +00:00
|
|
|
log.error("Error resolving '" + fileNames[i] + "' in '" + webappDir, ioe);
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-04-10 18:22:43 +00:00
|
|
|
} else {
|
|
|
|
log.error("No console web server to start plugins?");
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add translation jars in console/locale
|
2010-02-07 18:18:31 +00:00
|
|
|
// These will not override existing resource bundles since we are adding them
|
|
|
|
// later in the classpath.
|
|
|
|
File localeDir = new File(pluginDir, "console/locale");
|
|
|
|
if (localeDir.exists() && localeDir.isDirectory()) {
|
|
|
|
File[] files = localeDir.listFiles();
|
|
|
|
if (files != null) {
|
|
|
|
boolean added = false;
|
|
|
|
for (int i = 0; i < files.length; i++) {
|
|
|
|
File f = files[i];
|
|
|
|
if (f.getName().endsWith(".jar")) {
|
|
|
|
try {
|
|
|
|
addPath(f.toURI().toURL());
|
|
|
|
log.error("INFO: Adding translation plugin to classpath: " + f);
|
|
|
|
added = true;
|
|
|
|
} catch (Exception e) {
|
|
|
|
log.error("Plugin " + appName + " bad classpath element: " + f, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (added)
|
|
|
|
Translate.clearCache();
|
|
|
|
}
|
|
|
|
}
|
2010-02-07 13:32:49 +00:00
|
|
|
|
|
|
|
// add summary bar link
|
2010-02-08 23:28:09 +00:00
|
|
|
String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx));
|
2010-02-08 16:15:23 +00:00
|
|
|
if (name == null)
|
2010-02-08 23:28:09 +00:00
|
|
|
name = ConfigClientsHelper.stripHTML(props, "consoleLinkName");
|
|
|
|
String url = ConfigClientsHelper.stripHTML(props, "consoleLinkURL");
|
2010-04-05 13:22:16 +00:00
|
|
|
if (name != null && url != null && name.length() > 0 && url.length() > 0) {
|
|
|
|
String tip = ConfigClientsHelper.stripHTML(props, "consoleLinkTooltip_" + Messages.getLanguage(ctx));
|
|
|
|
if (tip == null)
|
|
|
|
tip = ConfigClientsHelper.stripHTML(props, "consoleLinkTooltip");
|
|
|
|
if (tip != null)
|
|
|
|
NavHelper.registerApp(name, url, tip);
|
|
|
|
else
|
|
|
|
NavHelper.registerApp(name, url);
|
|
|
|
}
|
2010-02-07 13:32:49 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-02-11 21:41:54 +00:00
|
|
|
/**
|
|
|
|
* @return true on success
|
|
|
|
* @throws just about anything, caller would be wise to catch Throwable
|
|
|
|
*/
|
2010-03-15 16:19:19 +00:00
|
|
|
static boolean stopPlugin(RouterContext ctx, String appName) throws Exception {
|
2010-02-08 20:37:49 +00:00
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2010-05-05 19:34:03 +00:00
|
|
|
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
2010-02-08 20:37:49 +00:00
|
|
|
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
|
|
|
log.error("Cannot stop nonexistent plugin: " + appName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop things in clients.config
|
|
|
|
File clientConfig = new File(pluginDir, "clients.config");
|
|
|
|
if (clientConfig.exists()) {
|
|
|
|
Properties props = new Properties();
|
|
|
|
DataHelper.loadProps(props, clientConfig);
|
|
|
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
2010-02-11 19:18:26 +00:00
|
|
|
runClientApps(ctx, pluginDir, clients, "stop");
|
2010-02-08 20:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// stop console webapps in console/webapps
|
2011-12-23 00:56:48 +00:00
|
|
|
//ContextHandlerCollection server = WebAppStarter.getConsoleServer();
|
|
|
|
//if (server != null) {
|
2010-11-24 14:31:54 +00:00
|
|
|
/*
|
2010-02-08 22:19:59 +00:00
|
|
|
File consoleDir = new File(pluginDir, "console");
|
|
|
|
Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
|
|
|
|
File webappDir = new File(consoleDir, "webapps");
|
|
|
|
String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
|
|
|
|
if (fileNames != null) {
|
|
|
|
for (int i = 0; i < fileNames.length; i++) {
|
|
|
|
String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
|
2010-02-26 16:58:01 +00:00
|
|
|
if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
|
2010-02-08 22:19:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
WebAppStarter.stopWebApp(server, warName);
|
|
|
|
}
|
|
|
|
}
|
2010-11-24 14:31:54 +00:00
|
|
|
*/
|
2010-11-27 13:52:57 +00:00
|
|
|
if(pluginWars.containsKey(appName)) {
|
|
|
|
Iterator <String> wars = pluginWars.get(appName).iterator();
|
|
|
|
while (wars.hasNext()) {
|
|
|
|
String warName = wars.next();
|
2011-12-23 00:56:48 +00:00
|
|
|
WebAppStarter.stopWebApp(warName);
|
2010-11-27 13:52:57 +00:00
|
|
|
}
|
|
|
|
pluginWars.get(appName).clear();
|
2010-11-24 14:31:54 +00:00
|
|
|
}
|
2011-12-23 00:56:48 +00:00
|
|
|
//}
|
2010-02-08 22:19:59 +00:00
|
|
|
|
2010-02-08 20:37:49 +00:00
|
|
|
// remove summary bar link
|
|
|
|
Properties props = pluginProperties(ctx, appName);
|
2010-02-08 23:28:09 +00:00
|
|
|
String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx));
|
2010-02-08 20:37:49 +00:00
|
|
|
if (name == null)
|
2010-02-08 23:28:09 +00:00
|
|
|
name = ConfigClientsHelper.stripHTML(props, "consoleLinkName");
|
2010-02-08 20:37:49 +00:00
|
|
|
if (name != null && name.length() > 0)
|
|
|
|
NavHelper.unregisterApp(name);
|
|
|
|
|
2010-03-29 21:20:48 +00:00
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("Stopping plugin: " + appName);
|
2010-02-08 20:37:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-02-26 16:58:01 +00:00
|
|
|
/** @return true on success - caller should call stopPlugin() first */
|
2010-03-15 16:19:19 +00:00
|
|
|
static boolean deletePlugin(RouterContext ctx, String appName) throws Exception {
|
2010-02-10 19:09:35 +00:00
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2010-05-05 19:34:03 +00:00
|
|
|
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
2010-02-10 19:09:35 +00:00
|
|
|
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
2010-02-26 16:58:01 +00:00
|
|
|
log.error("Cannot delete nonexistent plugin: " + appName);
|
2010-02-10 19:09:35 +00:00
|
|
|
return false;
|
|
|
|
}
|
2010-02-11 19:18:26 +00:00
|
|
|
// uninstall things in clients.config
|
|
|
|
File clientConfig = new File(pluginDir, "clients.config");
|
|
|
|
if (clientConfig.exists()) {
|
|
|
|
Properties props = new Properties();
|
|
|
|
DataHelper.loadProps(props, clientConfig);
|
|
|
|
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
|
|
|
runClientApps(ctx, pluginDir, clients, "uninstall");
|
|
|
|
}
|
2010-02-26 16:58:01 +00:00
|
|
|
|
|
|
|
// unregister themes, and switch to default if we are unregistering the current theme
|
|
|
|
File dir = new File(pluginDir, "console/themes");
|
|
|
|
File[] tfiles = dir.listFiles();
|
|
|
|
if (tfiles != null) {
|
|
|
|
String current = ctx.getProperty(CSSHelper.PROP_THEME_NAME);
|
2012-01-18 01:54:34 +00:00
|
|
|
Map<String, String> changes = new HashMap();
|
|
|
|
List<String> removes = new ArrayList();
|
2010-02-26 16:58:01 +00:00
|
|
|
for (int i = 0; i < tfiles.length; i++) {
|
|
|
|
String name = tfiles[i].getName();
|
2010-04-10 15:29:16 +00:00
|
|
|
if (tfiles[i].isDirectory() && (!Arrays.asList(STANDARD_THEMES).contains(tfiles[i]))) {
|
2012-01-18 01:54:34 +00:00
|
|
|
removes.add(ConfigUIHelper.PROP_THEME_PFX + name);
|
2010-02-26 16:58:01 +00:00
|
|
|
if (name.equals(current))
|
2012-01-18 01:54:34 +00:00
|
|
|
changes.put(CSSHelper.PROP_THEME_NAME, CSSHelper.DEFAULT_THEME);
|
2010-02-26 16:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
2012-01-18 01:54:34 +00:00
|
|
|
ctx.router().saveConfig(changes, removes);
|
2010-02-26 16:58:01 +00:00
|
|
|
}
|
|
|
|
|
2010-02-10 19:09:35 +00:00
|
|
|
FileUtil.rmdir(pluginDir, false);
|
|
|
|
Properties props = pluginProperties();
|
|
|
|
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
|
|
|
String name = (String)iter.next();
|
2012-01-15 16:59:33 +00:00
|
|
|
if (name.startsWith(PREFIX + appName + '.'))
|
2010-02-10 19:09:35 +00:00
|
|
|
iter.remove();
|
|
|
|
}
|
|
|
|
storePluginProperties(props);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-02-08 16:15:23 +00:00
|
|
|
/** plugin.config */
|
|
|
|
public static Properties pluginProperties(I2PAppContext ctx, String appName) {
|
2010-05-05 19:34:03 +00:00
|
|
|
File cfgFile = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
2010-02-08 16:15:23 +00:00
|
|
|
Properties rv = new Properties();
|
|
|
|
try {
|
|
|
|
DataHelper.loadProps(rv, cfgFile);
|
|
|
|
} catch (IOException ioe) {}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* plugins.config
|
|
|
|
* this auto-adds a propery for every dir in the plugin directory
|
|
|
|
*/
|
2010-02-07 13:32:49 +00:00
|
|
|
public static Properties pluginProperties() {
|
|
|
|
File dir = I2PAppContext.getGlobalContext().getConfigDir();
|
|
|
|
Properties rv = new Properties();
|
|
|
|
File cfgFile = new File(dir, "plugins.config");
|
|
|
|
|
|
|
|
try {
|
|
|
|
DataHelper.loadProps(rv, cfgFile);
|
|
|
|
} catch (IOException ioe) {}
|
|
|
|
|
2010-02-10 15:35:00 +00:00
|
|
|
List<String> names = getPlugins();
|
|
|
|
for (String name : names) {
|
|
|
|
String prop = PREFIX + name + ENABLED;
|
|
|
|
if (rv.getProperty(prop) == null)
|
|
|
|
rv.setProperty(prop, "true");
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-01-15 16:59:33 +00:00
|
|
|
/**
|
|
|
|
* Is the plugin enabled in plugins.config?
|
|
|
|
* Default true
|
|
|
|
*
|
|
|
|
* @since 0.8.13
|
|
|
|
*/
|
|
|
|
public static boolean isPluginEnabled(String appName) {
|
|
|
|
Properties props = pluginProperties();
|
|
|
|
String prop = PREFIX + appName + ENABLED;
|
2012-09-28 17:50:41 +00:00
|
|
|
return Boolean.parseBoolean(props.getProperty(prop, "true"));
|
2012-01-15 16:59:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable in plugins.config
|
|
|
|
*
|
|
|
|
* @since 0.8.13
|
|
|
|
*/
|
|
|
|
public static void disablePlugin(String appName) {
|
|
|
|
Properties props = pluginProperties();
|
|
|
|
String prop = PREFIX + appName + ENABLED;
|
2012-09-28 17:50:41 +00:00
|
|
|
if (Boolean.parseBoolean(props.getProperty(prop, "true"))) {
|
2012-01-15 16:59:33 +00:00
|
|
|
props.setProperty(prop, "false");
|
|
|
|
storePluginProperties(props);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-10 15:35:00 +00:00
|
|
|
/**
|
|
|
|
* all installed plugins whether enabled or not
|
|
|
|
*/
|
|
|
|
public static List<String> getPlugins() {
|
|
|
|
List<String> rv = new ArrayList();
|
2010-05-05 19:34:03 +00:00
|
|
|
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PluginUpdateHandler.PLUGIN_DIR);
|
2010-02-07 13:32:49 +00:00
|
|
|
File[] files = pluginDir.listFiles();
|
|
|
|
if (files == null)
|
|
|
|
return rv;
|
|
|
|
for (int i = 0; i < files.length; i++) {
|
2010-02-10 15:35:00 +00:00
|
|
|
if (files[i].isDirectory())
|
|
|
|
rv.add(files[i].getName());
|
|
|
|
}
|
2012-03-19 12:37:39 +00:00
|
|
|
Collections.sort(rv); // ensure the list is in sorted order.
|
2010-02-10 15:35:00 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The signing keys from all the plugins
|
|
|
|
* @return Map of key to keyname
|
|
|
|
* Last one wins if a dup (installer should prevent dups)
|
|
|
|
*/
|
|
|
|
public static Map<String, String> getPluginKeys(I2PAppContext ctx) {
|
|
|
|
Map<String, String> rv = new HashMap();
|
|
|
|
List<String> names = getPlugins();
|
|
|
|
for (String name : names) {
|
|
|
|
Properties props = pluginProperties(ctx, name);
|
|
|
|
String pubkey = props.getProperty("key");
|
2010-02-17 18:12:46 +00:00
|
|
|
String signer = props.getProperty("signer");
|
|
|
|
if (pubkey != null && signer != null && pubkey.length() == 172 && signer.length() > 0)
|
|
|
|
rv.put(pubkey, signer);
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2010-02-08 16:15:23 +00:00
|
|
|
/**
|
|
|
|
* plugins.config
|
|
|
|
*/
|
|
|
|
public static void storePluginProperties(Properties props) {
|
|
|
|
File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins.config");
|
|
|
|
try {
|
|
|
|
DataHelper.storeProps(props, cfgFile);
|
|
|
|
} catch (IOException ioe) {}
|
|
|
|
}
|
|
|
|
|
2010-03-15 16:19:19 +00:00
|
|
|
/**
|
|
|
|
* @param action "start" or "stop" or "uninstall"
|
|
|
|
* @throws just about anything if an app has a delay less than zero, caller would be wise to catch Throwable
|
|
|
|
* If no apps have a delay less than zero, it shouldn't throw anything
|
|
|
|
*/
|
|
|
|
private static void runClientApps(RouterContext ctx, File pluginDir, List<ClientAppConfig> apps, String action) throws Exception {
|
2010-02-07 13:32:49 +00:00
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
2010-04-16 03:58:48 +00:00
|
|
|
|
|
|
|
// initialize pluginThreadGroup and pluginJobs
|
|
|
|
String pluginName = pluginDir.getName();
|
|
|
|
if (!pluginThreadGroups.containsKey(pluginName))
|
|
|
|
pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName));
|
|
|
|
ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName);
|
|
|
|
if (action.equals("start"))
|
|
|
|
pluginJobs.put(pluginName, new ConcurrentHashSet<Job>());
|
|
|
|
|
2010-02-07 13:32:49 +00:00
|
|
|
for(ClientAppConfig app : apps) {
|
2010-02-11 19:18:26 +00:00
|
|
|
if (action.equals("start") && app.disabled)
|
2010-02-07 13:32:49 +00:00
|
|
|
continue;
|
2010-02-08 20:37:49 +00:00
|
|
|
String argVal[];
|
2010-02-11 19:18:26 +00:00
|
|
|
if (action.equals("start")) {
|
|
|
|
// start
|
2010-02-08 20:37:49 +00:00
|
|
|
argVal = LoadClientAppsJob.parseArgs(app.args);
|
|
|
|
} else {
|
2010-02-11 19:18:26 +00:00
|
|
|
String args;
|
|
|
|
if (action.equals("stop"))
|
|
|
|
args = app.stopargs;
|
|
|
|
else if (action.equals("uninstall"))
|
|
|
|
args = app.uninstallargs;
|
|
|
|
else
|
|
|
|
throw new IllegalArgumentException("bad action");
|
|
|
|
// args must be present
|
|
|
|
if (args == null || args.length() <= 0)
|
2010-02-08 20:37:49 +00:00
|
|
|
continue;
|
2010-02-11 19:18:26 +00:00
|
|
|
argVal = LoadClientAppsJob.parseArgs(args);
|
2010-02-08 20:37:49 +00:00
|
|
|
}
|
2010-02-07 13:32:49 +00:00
|
|
|
// do this after parsing so we don't need to worry about quoting
|
|
|
|
for (int i = 0; i < argVal.length; i++) {
|
|
|
|
if (argVal[i].indexOf("$") >= 0) {
|
|
|
|
argVal[i] = argVal[i].replace("$I2P", ctx.getBaseDir().getAbsolutePath());
|
|
|
|
argVal[i] = argVal[i].replace("$CONFIG", ctx.getConfigDir().getAbsolutePath());
|
|
|
|
argVal[i] = argVal[i].replace("$PLUGIN", pluginDir.getAbsolutePath());
|
|
|
|
}
|
|
|
|
}
|
2010-05-05 19:34:03 +00:00
|
|
|
|
|
|
|
ClassLoader cl = null;
|
2010-02-07 17:13:44 +00:00
|
|
|
if (app.classpath != null) {
|
|
|
|
String cp = new String(app.classpath);
|
|
|
|
if (cp.indexOf("$") >= 0) {
|
|
|
|
cp = cp.replace("$I2P", ctx.getBaseDir().getAbsolutePath());
|
|
|
|
cp = cp.replace("$CONFIG", ctx.getConfigDir().getAbsolutePath());
|
|
|
|
cp = cp.replace("$PLUGIN", pluginDir.getAbsolutePath());
|
|
|
|
}
|
2010-05-05 19:34:03 +00:00
|
|
|
|
|
|
|
// Old way - add for the whole JVM
|
|
|
|
//addToClasspath(cp, app.clientName, log);
|
|
|
|
|
|
|
|
// New way - add only for this client
|
|
|
|
// We cache the ClassLoader we start the client with, so
|
|
|
|
// we can reuse it for stopping and uninstalling.
|
|
|
|
// If we don't, the client won't be able to find its
|
|
|
|
// static members.
|
|
|
|
String clCacheKey = pluginName + app.className + app.args;
|
|
|
|
if (!action.equals("start"))
|
|
|
|
cl = _clCache.get(clCacheKey);
|
|
|
|
if (cl == null) {
|
|
|
|
URL[] urls = classpathToURLArray(cp, app.clientName, log);
|
|
|
|
if (urls != null) {
|
|
|
|
cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
|
|
|
|
if (action.equals("start"))
|
|
|
|
_clCache.put(clCacheKey, cl);
|
|
|
|
}
|
|
|
|
}
|
2010-02-07 17:13:44 +00:00
|
|
|
}
|
2010-03-15 16:19:19 +00:00
|
|
|
|
|
|
|
if (app.delay < 0 && action.equals("start")) {
|
|
|
|
// this will throw exceptions
|
2010-05-05 19:34:03 +00:00
|
|
|
LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log, cl);
|
2010-03-15 16:19:19 +00:00
|
|
|
} else if (app.delay == 0 || !action.equals("start")) {
|
|
|
|
// quick check, will throw ClassNotFoundException on error
|
2010-05-05 19:34:03 +00:00
|
|
|
LoadClientAppsJob.testClient(app.className, cl);
|
2010-02-07 13:32:49 +00:00
|
|
|
// run this guy now
|
2010-05-05 19:34:03 +00:00
|
|
|
LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log, pluginThreadGroup, cl);
|
2010-02-07 13:32:49 +00:00
|
|
|
} else {
|
2010-05-10 07:27:34 +00:00
|
|
|
// If there is some delay, there may be a really good reason for it.
|
|
|
|
// Loading a class would be one of them!
|
|
|
|
// So we do a quick check first, If it bombs out, we delay and try again.
|
|
|
|
// If it bombs after that, then we throw the ClassNotFoundException.
|
|
|
|
try {
|
|
|
|
// quick check
|
|
|
|
LoadClientAppsJob.testClient(app.className, cl);
|
|
|
|
} catch(ClassNotFoundException ex) {
|
|
|
|
// Try again 1 or 2 seconds later.
|
|
|
|
// This should be enough time. Although it is a lousy hack
|
|
|
|
// it should work for most cases.
|
|
|
|
// Perhaps it may be even better to delay a percentage
|
|
|
|
// if > 1, and reduce the delay time.
|
|
|
|
// Under normal circumstances there will be no delay at all.
|
|
|
|
if(app.delay > 1) {
|
|
|
|
Thread.sleep(2000);
|
|
|
|
} else {
|
|
|
|
Thread.sleep(1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// quick check, will throw ClassNotFoundException on error
|
|
|
|
LoadClientAppsJob.testClient(app.className, cl);
|
|
|
|
// wait before firing it up
|
|
|
|
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup, cl);
|
|
|
|
ctx.jobQueue().addJob(job);
|
|
|
|
pluginJobs.get(pluginName).add(job);
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-07 17:13:44 +00:00
|
|
|
|
2010-04-16 03:58:48 +00:00
|
|
|
public static boolean isPluginRunning(String pluginName, RouterContext ctx) {
|
|
|
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
|
|
|
|
|
|
|
boolean isJobRunning = false;
|
|
|
|
if (pluginJobs.containsKey(pluginName))
|
|
|
|
for (Job job: pluginJobs.get(pluginName))
|
|
|
|
if (ctx.jobQueue().isJobActive(job)) {
|
|
|
|
isJobRunning = true;
|
|
|
|
break;
|
|
|
|
}
|
2010-11-24 14:31:54 +00:00
|
|
|
boolean isWarRunning = false;
|
|
|
|
if(pluginWars.containsKey(pluginName)) {
|
|
|
|
Iterator <String> it = pluginWars.get(pluginName).iterator();
|
|
|
|
while(it.hasNext() && !isWarRunning) {
|
|
|
|
String warName = it.next();
|
|
|
|
if(WebAppStarter.isWebAppRunning(warName)) {
|
|
|
|
isWarRunning = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-04-16 03:58:48 +00:00
|
|
|
|
2010-04-23 16:28:14 +00:00
|
|
|
if (log.shouldLog(Log.DEBUG))
|
2010-11-24 14:31:54 +00:00
|
|
|
log.debug("plugin name = <" + pluginName + ">; threads running? " + isClientThreadRunning(pluginName) + "; webapp runing? " + isWarRunning + "; jobs running? " + isJobRunning);
|
|
|
|
return isClientThreadRunning(pluginName) || isWarRunning || isJobRunning;
|
|
|
|
//
|
|
|
|
//if (log.shouldLog(Log.DEBUG))
|
|
|
|
// log.debug("plugin name = <" + pluginName + ">; threads running? " + isClientThreadRunning(pluginName) + "; webapp runing? " + WebAppStarter.isWebAppRunning(pluginName) + "; jobs running? " + isJobRunning);
|
|
|
|
//return isClientThreadRunning(pluginName) || WebAppStarter.isWebAppRunning(pluginName) || isJobRunning;
|
|
|
|
//
|
2010-04-16 03:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns <code>true</code> if one or more client threads are running in a given plugin.
|
|
|
|
* @param pluginName
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private static boolean isClientThreadRunning(String pluginName) {
|
|
|
|
ThreadGroup group = pluginThreadGroups.get(pluginName);
|
|
|
|
if (group == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Thread[] activeThreads = new Thread[1];
|
|
|
|
group.enumerate(activeThreads);
|
|
|
|
return activeThreads[0] != null;
|
|
|
|
}
|
|
|
|
|
2010-02-07 17:13:44 +00:00
|
|
|
/**
|
|
|
|
* Perhaps there's an easy way to use Thread.setContextClassLoader()
|
|
|
|
* but I don't see how to make it magically get used for everything.
|
|
|
|
* So add this to the whole JVM's classpath.
|
|
|
|
*/
|
2010-05-05 19:34:03 +00:00
|
|
|
/******
|
2010-02-07 17:13:44 +00:00
|
|
|
private static void addToClasspath(String classpath, String clientName, Log log) {
|
|
|
|
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
|
|
|
while (tok.hasMoreTokens()) {
|
|
|
|
String elem = tok.nextToken().trim();
|
|
|
|
File f = new File(elem);
|
|
|
|
if (!f.isAbsolute()) {
|
|
|
|
log.error("Plugin client " + clientName + " classpath element is not absolute: " + f);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
addPath(f.toURI().toURL());
|
2010-03-29 21:20:48 +00:00
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("INFO: Adding plugin to classpath: " + f);
|
2010-02-07 17:13:44 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-05-05 19:34:03 +00:00
|
|
|
*****/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return null if no valid elements
|
|
|
|
*/
|
|
|
|
private static URL[] classpathToURLArray(String classpath, String clientName, Log log) {
|
|
|
|
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
|
|
|
List<URL> urls = new ArrayList();
|
|
|
|
while (tok.hasMoreTokens()) {
|
|
|
|
String elem = tok.nextToken().trim();
|
|
|
|
File f = new File(elem);
|
|
|
|
if (!f.isAbsolute()) {
|
|
|
|
log.error("Plugin client " + clientName + " classpath element is not absolute: " + f);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
urls.add(f.toURI().toURL());
|
|
|
|
if (log.shouldLog(Log.WARN))
|
|
|
|
log.warn("INFO: Adding plugin to classpath: " + f);
|
|
|
|
} catch (Exception e) {
|
|
|
|
log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (urls.isEmpty())
|
|
|
|
return null;
|
|
|
|
return urls.toArray(new URL[urls.size()]);
|
|
|
|
}
|
2010-02-07 17:13:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* http://jimlife.wordpress.com/2007/12/19/java-adding-new-classpath-at-runtime/
|
|
|
|
*/
|
2010-07-06 15:22:48 +00:00
|
|
|
private static void addPath(URL u) throws Exception {
|
2010-02-07 17:13:44 +00:00
|
|
|
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
|
|
|
|
Class urlClass = URLClassLoader.class;
|
|
|
|
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
|
|
|
|
method.setAccessible(true);
|
|
|
|
method.invoke(urlClassLoader, new Object[]{u});
|
|
|
|
}
|
2012-01-23 17:53:59 +00:00
|
|
|
|
|
|
|
/** translate a string */
|
|
|
|
private static String ngettext(String s, String p, int n, I2PAppContext ctx) {
|
|
|
|
return Messages.getString(n, s, p, ctx);
|
|
|
|
}
|
2010-02-07 13:32:49 +00:00
|
|
|
}
|