/** * @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 { Log log = ctx.logManager().getLog(PluginStarter.class); // initialize pluginThreadGroup and _pendingPluginClients String pluginName = pluginDir.getName(); if (!pluginThreadGroups.containsKey(pluginName)) pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName)); ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName); if (action.equals("start")) _pendingPluginClients.put(pluginName, new ConcurrentHashSet<SimpleTimer2.TimedEvent>()); for (ClientAppConfig app : apps) { // If the client is a running ClientApp that we want to stop, // bypass all the logic below. if (action.equals("stop")) { String[] argVal = LoadClientAppsJob.parseArgs(app.args); // We must do all the substitution just as when started, so the // argument array comparison in getClientApp() works. // 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()); } } ClientApp ca = ctx.routerAppManager().getClientApp(app.className, argVal); if (ca != null) { // even if (ca.getState() != ClientAppState.RUNNING), we do this, we don't want to fall // thru try { ca.shutdown(LoadClientAppsJob.parseArgs(app.stopargs)); } catch (Throwable t) { throw new Exception(t); } continue; } } if (action.equals("start") && app.disabled) continue; String argVal[]; if (action.equals("start")) { // start argVal = LoadClientAppsJob.parseArgs(app.args); } else { 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) continue; argVal = LoadClientAppsJob.parseArgs(args); } // 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()); } } ClassLoader cl = null; if (app.classpath != null) { String cp = 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()); } // 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); } } } if (app.delay < 0 && action.equals("start")) { // this will throw exceptions LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log, cl); } else if (app.delay == 0 || !action.equals("start")) { // quick check, will throw ClassNotFoundException on error LoadClientAppsJob.testClient(app.className, cl); // run this guy now LoadClientAppsJob.runClient( app.className, app.clientName, argVal, ctx, log, pluginThreadGroup, cl); } else { // 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. try { if (app.delay > 1) { Thread.sleep(2000); } else { Thread.sleep(1000); } } catch (InterruptedException ie) { } // quick check, will throw ClassNotFoundException on error LoadClientAppsJob.testClient(app.className, cl); } // wait before firing it up SimpleTimer2.TimedEvent evt = new TrackedDelayedClient( pluginName, ctx.simpleTimer2(), ctx, app.className, app.clientName, argVal, pluginThreadGroup, cl); evt.schedule(app.delay); } } }
/** * threaded * * @since 0.8.13 */ static void updateAll(RouterContext ctx) { Thread t = new I2PAppThread(new PluginUpdater(ctx), "PluginUpdater", true); t.start(); }
/** * inline * * @since 0.8.13 */ private static void updateAll(RouterContext ctx, boolean delay) { List<String> plugins = getPlugins(); Map<String, String> toUpdate = new HashMap<String, String>(); 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; ConsoleUpdateManager mgr = UpdateHandler.updateManager(ctx); if (mgr == null) return; if (mgr.isUpdateInProgress()) return; if (delay) { // wait for proxy mgr.update(TYPE_DUMMY, 3 * 60 * 1000); mgr.notifyProgress(null, Messages.getString("Checking for plugin updates", ctx)); int loop = 0; do { try { Thread.sleep(5 * 1000); } catch (InterruptedException ie) { } if (loop++ > 40) break; } while (mgr.isUpdateInProgress(TYPE_DUMMY)); } String proxyHost = ctx.getProperty( ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST); int proxyPort = ConfigUpdateHandler.proxyPort(ctx); if (proxyPort == ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT && proxyHost.equals(ConfigUpdateHandler.DEFAULT_PROXY_HOST) && ctx.portMapper().getPort(PortMapper.SVC_HTTP_PROXY) < 0) { mgr.notifyComplete( null, Messages.getString("Plugin update check failed", ctx) + " - " + Messages.getString("HTTP client proxy tunnel must be running", ctx)); return; } Log log = ctx.logManager().getLog(PluginStarter.class); int updated = 0; for (Map.Entry<String, String> entry : toUpdate.entrySet()) { String appName = entry.getKey(); if (log.shouldLog(Log.WARN)) log.warn("Checking for update plugin: " + appName); // blocking if (mgr.checkAvailable(PLUGIN, appName, 60 * 1000) == null) { if (log.shouldLog(Log.WARN)) log.warn("No update available for plugin: " + appName); continue; } if (log.shouldLog(Log.WARN)) log.warn("Updating plugin: " + appName); // non-blocking mgr.update(PLUGIN, appName, 30 * 60 * 1000); int loop = 0; do { // only wait for 4 minutes, then we will // keep going try { Thread.sleep(5 * 1000); } catch (InterruptedException ie) { } if (loop++ > 48) break; } while (mgr.isUpdateInProgress(PLUGIN, appName)); if (mgr.getUpdateAvailable(PLUGIN, appName) != null) updated++; } if (updated > 0) mgr.notifyComplete(null, ngettext("1 plugin updated", "{0} plugins updated", updated, ctx)); else mgr.notifyComplete(null, Messages.getString("Plugin update check complete", ctx)); }