From 16a14d4ebda736f7f53bd1edcec2bc45c62afaa1 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 15 Mar 2010 16:19:19 +0000 Subject: [PATCH] * Clients: - Negative delay means run immediately and inline - Add methods to test class and run inline, to propagate errors to the console - Add javadoc for clients.config format - Use new methods for plugins --- .../src/net/i2p/router/web/PluginStarter.java | 22 +++++++--- .../i2p/router/startup/ClientAppConfig.java | 43 +++++++++++++++++++ .../i2p/router/startup/LoadClientAppsJob.java | 34 ++++++++++++++- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index 030f7670b1..1e14b8917d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -180,7 +180,7 @@ public class PluginStarter implements Runnable { * @return true on success * @throws just about anything, caller would be wise to catch Throwable */ - static boolean stopPlugin(RouterContext ctx, String appName) throws IOException { + static boolean stopPlugin(RouterContext ctx, String appName) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName); if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) { @@ -228,7 +228,7 @@ public class PluginStarter implements Runnable { } /** @return true on success - caller should call stopPlugin() first */ - static boolean deletePlugin(RouterContext ctx, String appName) throws IOException { + static boolean deletePlugin(RouterContext ctx, String appName) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName); if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) { @@ -348,8 +348,12 @@ public class PluginStarter implements Runnable { } catch (IOException ioe) {} } - /** @param action "start" or "stop" or "uninstall" */ - private static void runClientApps(RouterContext ctx, File pluginDir, List apps, String action) { + /** + * @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 apps, String action) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); for(ClientAppConfig app : apps) { if (action.equals("start") && app.disabled) @@ -388,10 +392,18 @@ public class PluginStarter implements Runnable { } addToClasspath(cp, app.clientName, log); } - if (app.delay == 0 || !action.equals("start")) { + + if (app.delay < 0 && action.equals("start")) { + // this will throw exceptions + LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log); + } else if (app.delay == 0 || !action.equals("start")) { + // quick check, will throw ClassNotFoundException on error + LoadClientAppsJob.testClient(app.className); // run this guy now LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log); } else { + // quick check, will throw ClassNotFoundException on error + LoadClientAppsJob.testClient(app.className); // wait before firing it up ctx.jobQueue().addJob(new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay)); } diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java index 4c5125bc2c..3e1f19f71d 100644 --- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java +++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java @@ -18,6 +18,49 @@ import net.i2p.router.RouterContext; * so they can be used both by LoadClientAppsJob and by the configuration * page in the router console. * + *
+ *
+ * clients.config format:
+ *
+ * Lines are of the form clientApp.x.prop=val, where x is the app number.
+ * App numbers MUST start with 0 and be consecutive.
+ *
+ * Properties are as follows:
+ *	main: Full class name. Required. The main() method in this
+ *	      class will be run.
+ *	name: Name to be displayed on console.
+ *	args: Arguments to the main class, separated by spaces or tabs.
+ *	      Arguments containing spaces or tabs may be quoted with ' or "
+ *	delay: Seconds before starting, default 120
+ *	onBoot: {true|false}, default false, forces a delay of 0,
+ *	        overrides delay setting
+ *	startOnLoad: {true|false} Is the client to be run at all?
+ *                    Default true
+ *
+ * The following additional properties are used only by plugins:
+ *	stopargs: Arguments to stop the client.
+ *	uninstallargs: Arguments to stop the client.
+ *	classpath: Additional classpath elements for the client,
+ *	           separated by commas.
+ *
+ * The following substitutions are made in the args, stopargs,
+ * uninstallargs, and classpath lines, for plugins only:
+ *	$I2P: The base I2P install directory
+ *	$CONFIG: The user's configuration directory (e.g. ~/.i2p)
+ *	$PLUGIN: This plugin's directory (e.g. ~/.i2p/plugins/foo)
+ *
+ * All properties except "main" are optional.
+ * Lines starting with "#" are comments.
+ *
+ * If the delay is less than zero, the client is run immediately,
+ * in the same thread, so that exceptions may be propagated to the console.
+ * In this case, the client should either throw an exception, return quickly,
+ * or spawn its own thread.
+ * If the delay is greater than or equal to zero, it will be run
+ * in a new thread, and exceptions will be logged but not propagated
+ * to the console.
+ *
+ * 
*/ public class ClientAppConfig { /** wait 2 minutes before starting up client apps */ diff --git a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java index 9be0885ae6..1f68c147de 100644 --- a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java +++ b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java @@ -11,7 +11,7 @@ import net.i2p.util.I2PThread; import net.i2p.util.Log; /** - * Run any client applications specified in the router.config. If any clientApp + * Run any client applications specified in clients.config. If any clientApp * contains the config property ".onBoot=true" it'll be launched immediately, otherwise * it'll get queued up for starting 2 minutes later. * @@ -40,7 +40,7 @@ public class LoadClientAppsJob extends JobImpl { if (app.disabled) continue; String argVal[] = parseArgs(app.args); - if (app.delay == 0) { + if (app.delay <= 0) { // run this guy now runClient(app.className, app.clientName, argVal, _log); } else { @@ -118,6 +118,36 @@ public class LoadClientAppsJob extends JobImpl { return rv; } + /** + * Use to test if the class is present, + * to propagate an error back to the user, + * since runClient() runs in a separate thread. + * + * @since 0.7.13 + */ + public static void testClient(String className) throws ClassNotFoundException { + Class.forName(className); + } + + /** + * Run client in this thread. + * + * @throws just about anything, caller would be wise to catch Throwable + * @since 0.7.13 + */ + public static void runClientInline(String className, String clientName, String args[], Log log) throws Exception { + if (log.shouldLog(Log.INFO)) + log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args)); + if (args == null) + args = new String[0]; + Class cls = Class.forName(className); + Method method = cls.getMethod("main", new Class[] { String[].class }); + method.invoke(cls, new Object[] { args }); + } + + /** + * Run client in a new thread. + */ public static void runClient(String className, String clientName, String args[], Log log) { if (log.shouldLog(Log.INFO)) log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args));