Search in sources :

Example 1 with ClientAppConfig

use of net.i2p.router.startup.ClientAppConfig in project i2p.i2p by i2p.

the class PluginStarter method deletePlugin.

/**
 *  @return true on success - caller should call stopPlugin() first
 *  @since public since 0.9.33, was package private
 */
public static boolean deletePlugin(RouterContext ctx, String appName) throws Exception {
    Log log = ctx.logManager().getLog(PluginStarter.class);
    File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName);
    if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
        log.error("Cannot delete nonexistent plugin: " + appName);
        return false;
    }
    // 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");
    }
    // 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);
        Map<String, String> changes = new HashMap<String, String>();
        List<String> removes = new ArrayList<String>();
        for (int i = 0; i < tfiles.length; i++) {
            String name = tfiles[i].getName();
            if (tfiles[i].isDirectory() && (!Arrays.asList(STANDARD_THEMES).contains(tfiles[i]))) {
                removes.add(CSSHelper.PROP_THEME_PFX + name);
                if (name.equals(current))
                    changes.put(CSSHelper.PROP_THEME_NAME, CSSHelper.DEFAULT_THEME);
            }
        }
        ctx.router().saveConfig(changes, removes);
    }
    boolean deleted = FileUtil.rmdir(pluginDir, false);
    Properties props = pluginProperties();
    for (Iterator<?> iter = props.keySet().iterator(); iter.hasNext(); ) {
        String name = (String) iter.next();
        if (name.startsWith(PREFIX + appName + '.'))
            iter.remove();
    }
    if (!deleted) {
        // This happens on Windows when there are plugin jars in classpath
        // Mark it as deleted, we will try again after restart
        log.logAlways(Log.WARN, "Deletion of " + pluginDir + " failed, will try again at restart");
        props.setProperty(PREFIX + appName + ENABLED, DELETED);
    }
    storePluginProperties(props);
    return true;
}
Also used : Log(net.i2p.util.Log) HashMap(java.util.HashMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) ArrayList(java.util.ArrayList) Properties(java.util.Properties) ClientAppConfig(net.i2p.router.startup.ClientAppConfig) File(java.io.File)

Example 2 with ClientAppConfig

use of net.i2p.router.startup.ClientAppConfig in project i2p.i2p by i2p.

the class PluginStarter method startPlugin.

/**
 *  @return true on success
 *  @throws Exception just about anything, caller would be wise to catch Throwable
 */
@SuppressWarnings("deprecation")
public static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
    Log log = ctx.logManager().getLog(PluginStarter.class);
    File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName);
    String iconfile = null;
    if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
        log.error("Cannot start nonexistent plugin: " + appName);
        disablePlugin(appName);
        return false;
    }
    // Do we need to extract an update?
    File pluginUpdate = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + appName + "/app.xpi2p.zip");
    if (pluginUpdate.exists()) {
        // Compare the start time of the router with the plugin.
        if (ctx.router().getWhenStarted() > pluginUpdate.lastModified()) {
            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.
    }
    Properties props = pluginProperties(ctx, appName);
    // For the following, we use the exact same translated strings as in PluginUpdateRunner
    // to avoid duplication
    String minVersion = stripHTML(props, "min-i2p-version");
    if (minVersion != null && VersionComparator.comp(CoreVersion.VERSION, minVersion) < 0) {
        String foo = "Plugin " + appName + " requires I2P version " + minVersion + " or higher";
        log.error(foo);
        disablePlugin(appName);
        foo = gettext("This plugin requires I2P version {0} or higher", minVersion, ctx);
        throw new Exception(foo);
    }
    minVersion = stripHTML(props, "min-java-version");
    if (minVersion != null && VersionComparator.comp(System.getProperty("java.version"), minVersion) < 0) {
        String foo = "Plugin " + appName + " requires Java version " + minVersion + " or higher";
        log.error(foo);
        disablePlugin(appName);
        foo = gettext("This plugin requires Java version {0} or higher", minVersion, ctx);
        throw new Exception(foo);
    }
    String jVersion = RouterConsoleRunner.jettyVersion();
    minVersion = stripHTML(props, "min-jetty-version");
    if (minVersion != null && VersionComparator.comp(minVersion, jVersion) > 0) {
        String foo = "Plugin " + appName + " requires Jetty version " + minVersion + " or higher";
        log.error(foo);
        disablePlugin(appName);
        foo = gettext("Plugin requires Jetty version {0} or higher", minVersion, ctx);
        throw new Exception(foo);
    }
    String blacklistVersion = jetty9Blacklist.get(appName);
    String curVersion = stripHTML(props, "version");
    if (blacklistVersion != null && VersionComparator.comp(curVersion, blacklistVersion) <= 0) {
        String foo = "Plugin " + appName + " requires Jetty version 8.9999 or lower";
        log.error(foo);
        disablePlugin(appName);
        foo = gettext("Plugin requires Jetty version {0} or lower", "8.9999", ctx);
        throw new Exception(foo);
    }
    String maxVersion = stripHTML(props, "max-jetty-version");
    if (maxVersion != null && VersionComparator.comp(maxVersion, jVersion) < 0) {
        String foo = "Plugin " + appName + " requires Jetty version " + maxVersion + " or lower";
        log.error(foo);
        disablePlugin(appName);
        foo = gettext("Plugin requires Jetty version {0} or lower", maxVersion, ctx);
        throw new Exception(foo);
    }
    if (log.shouldLog(Log.INFO))
        log.info("Starting plugin: " + appName);
    // 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]))) {
                // deprecated
                ctx.router().setConfigSetting(CSSHelper.PROP_THEME_PFX + name, tfiles[i].getAbsolutePath());
            // we don't need to save
            }
        }
    }
    // handle console icons for plugins without web-resources through prop icon-code
    String fullprop = props.getProperty("icon-code");
    if (fullprop != null && fullprop.length() > 1) {
        byte[] decoded = Base64.decode(fullprop);
        if (decoded != null) {
            NavHelper.setBinary(appName, decoded);
            iconfile = "/Plugins/pluginicon?plugin=" + appName;
        } else {
            iconfile = "/themes/console/images/plugin.png";
        }
    }
    // load and start things in clients.config
    File clientConfig = new File(pluginDir, "clients.config");
    if (clientConfig.exists()) {
        Properties cprops = new Properties();
        DataHelper.loadProps(cprops, clientConfig);
        List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
        runClientApps(ctx, pluginDir, clients, "start");
    }
    // start console webapps in console/webapps
    ContextHandlerCollection server = WebAppStarter.getConsoleServer();
    if (server != null) {
        File consoleDir = new File(pluginDir, "console");
        Properties wprops = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
        File webappDir = new File(consoleDir, "webapps");
        File[] files = webappDir.listFiles(RouterConsoleRunner.WAR_FILTER);
        if (files != null) {
            if (!pluginWars.containsKey(appName))
                pluginWars.put(appName, new ConcurrentHashSet<String>());
            for (int i = 0; i < files.length; i++) {
                try {
                    String warName = files[i].getName();
                    warName = warName.substring(0, warName.lastIndexOf(".war"));
                    // check for duplicates in $I2P
                    if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
                        log.error("Skipping duplicate webapp " + warName + " in plugin " + appName);
                        continue;
                    }
                    String enabled = wprops.getProperty(RouterConsoleRunner.PREFIX + warName + ENABLED);
                    if (!"false".equals(enabled)) {
                        if (log.shouldLog(Log.INFO))
                            log.info("Starting webapp: " + warName);
                        String path = files[i].getCanonicalPath();
                        WebAppStarter.startWebApp(ctx, server, warName, path);
                        pluginWars.get(appName).add(warName);
                    }
                } catch (IOException ioe) {
                    log.error("Error resolving '" + files[i] + "' in '" + webappDir, ioe);
                }
            }
            // Check for iconfile in plugin.properties
            String icfile = stripHTML(props, "console-icon");
            if (icfile != null && !icfile.contains("..")) {
                StringBuilder buf = new StringBuilder(32);
                buf.append('/').append(appName);
                if (!icfile.startsWith("/"))
                    buf.append('/');
                buf.append(icfile);
                iconfile = buf.toString();
            }
        }
    } else {
        log.error("No console web server to start plugins?");
    }
    // add translation jars in console/locale
    // 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(new FileSuffixFilter(".jar"));
        if (files != null) {
            boolean added = false;
            for (int i = 0; i < files.length; i++) {
                File f = files[i];
                try {
                    addPath(f.toURI().toURL());
                    log.info("INFO: Adding translation plugin to classpath: " + f);
                    added = true;
                } catch (ClassCastException e) {
                    log.logAlways(Log.WARN, "Java version: " + System.getProperty("java.version") + " does not support adding classpath element: " + f + " for plugin " + appName);
                } catch (RuntimeException e) {
                    log.error("Plugin " + appName + " bad classpath element: " + f, e);
                }
            }
            if (added)
                Translate.clearCache();
        }
    }
    // add summary bar link
    String name = stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx));
    if (name == null)
        name = stripHTML(props, "consoleLinkName");
    String url = stripHTML(props, "consoleLinkURL");
    if (name != null && url != null && name.length() > 0 && url.length() > 0) {
        String tip = stripHTML(props, "consoleLinkTooltip_" + Messages.getLanguage(ctx));
        if (tip == null)
            tip = stripHTML(props, "consoleLinkTooltip");
        NavHelper.registerApp(name, url, tip, iconfile);
    }
    return true;
}
Also used : Log(net.i2p.util.Log) ContextHandlerCollection(org.eclipse.jetty.server.handler.ContextHandlerCollection) IOException(java.io.IOException) Properties(java.util.Properties) IOException(java.io.IOException) ConcurrentHashSet(net.i2p.util.ConcurrentHashSet) ClientAppConfig(net.i2p.router.startup.ClientAppConfig) FileSuffixFilter(net.i2p.util.FileSuffixFilter) File(java.io.File)

Example 3 with ClientAppConfig

use of net.i2p.router.startup.ClientAppConfig in project i2p.i2p by i2p.

the class PluginStarter method runClientApps.

/**
 *  @param action "start" or "stop" or "uninstall"
 *  @throws Exception 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) {
        // bypass all the logic below.
        if (action.equals("stop")) {
            String[] argVal = LoadClientAppsJob.parseArgs(app.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());
                }
            }
            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 it bombs after that, then we throw the ClassNotFoundException.
            try {
                // quick check
                LoadClientAppsJob.testClient(app.className, cl);
            } catch (ClassNotFoundException ex) {
                // 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);
        }
    }
}
Also used : Log(net.i2p.util.Log) ClientApp(net.i2p.app.ClientApp) IOException(java.io.IOException) URL(java.net.URL) ConcurrentHashSet(net.i2p.util.ConcurrentHashSet) URLClassLoader(java.net.URLClassLoader) ClientAppConfig(net.i2p.router.startup.ClientAppConfig) URLClassLoader(java.net.URLClassLoader) SimpleTimer2(net.i2p.util.SimpleTimer2)

Example 4 with ClientAppConfig

use of net.i2p.router.startup.ClientAppConfig in project i2p.i2p by i2p.

the class ConfigClientsHandler method startClient.

private void startClient(int i) {
    List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
    if (i >= clients.size()) {
        addFormError(_t("Bad client index."));
        return;
    }
    ClientAppConfig ca = clients.get(i);
    LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), _context, _log);
    addFormNotice(_t("Client {0} started", ca.clientName));
    // Give a chance for status to update
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ie) {
    }
}
Also used : ClientAppConfig(net.i2p.router.startup.ClientAppConfig)

Example 5 with ClientAppConfig

use of net.i2p.router.startup.ClientAppConfig in project i2p.i2p by i2p.

the class ConfigClientsHandler method stopClient.

/**
 *  @since Implemented in 0.9.6 using ClientAppManager
 */
private void stopClient(int i) {
    List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
    if (i >= clients.size()) {
        addFormError(_t("Bad client index."));
        return;
    }
    ClientAppConfig ca = clients.get(i);
    ClientApp clientApp = _context.routerAppManager().getClientApp(ca.className, LoadClientAppsJob.parseArgs(ca.args));
    if (clientApp != null && clientApp.getState() == ClientAppState.RUNNING) {
        try {
            // todo parseArgs(ca.stopArgs) ?
            clientApp.shutdown(null);
            addFormNotice(_t("Client {0} stopped", ca.clientName));
            // Give a chance for status to update
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
            }
        } catch (Throwable t) {
            addFormError("Cannot stop client " + ca.className + ": " + t);
            _log.error("Error stopping client " + ca.className, t);
        }
    } else {
        addFormError("Cannot stop client " + i + ": " + ca.className);
    }
}
Also used : ClientApp(net.i2p.app.ClientApp) ClientAppConfig(net.i2p.router.startup.ClientAppConfig)

Aggregations

ClientAppConfig (net.i2p.router.startup.ClientAppConfig)10 Log (net.i2p.util.Log)4 File (java.io.File)3 Properties (java.util.Properties)3 ClientApp (net.i2p.app.ClientApp)3 IOException (java.io.IOException)2 ArrayList (java.util.ArrayList)2 ConcurrentHashSet (net.i2p.util.ConcurrentHashSet)2 URL (java.net.URL)1 URLClassLoader (java.net.URLClassLoader)1 HashMap (java.util.HashMap)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1 UrlLauncher (net.i2p.apps.systray.UrlLauncher)1 RouterConsoleRunner (net.i2p.router.web.RouterConsoleRunner)1 FileSuffixFilter (net.i2p.util.FileSuffixFilter)1 SimpleTimer2 (net.i2p.util.SimpleTimer2)1 ContextHandlerCollection (org.eclipse.jetty.server.handler.ContextHandlerCollection)1