package net.i2p.router.startup; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; import net.i2p.util.SecureFileOutputStream; /** * Contains a really simple ClientApp "structure" and some static methods * so they can be used both by LoadClientAppsJob and by the configuration * page in the router console. * * <pre> * * 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. * * </pre> */ public class ClientAppConfig { /** wait 2 minutes before starting up client apps */ private final static long DEFAULT_STARTUP_DELAY = 2*60*1000; /** speed up i2ptunnel without rewriting clients.config */ private final static long I2PTUNNEL_STARTUP_DELAY = 35*1000; private static final String PROP_CLIENT_CONFIG_FILENAME = "router.clientConfigFile"; private static final String DEFAULT_CLIENT_CONFIG_FILENAME = "clients.config"; private static final String PREFIX = "clientApp."; // let's keep this really simple // Following 4 may be edited in router console public String className; public String clientName; public String args; public boolean disabled; public final long delay; /** @since 0.7.12 */ public final String classpath; /** @since 0.7.12 */ public final String stopargs; /** @since 0.7.12 */ public final String uninstallargs; public ClientAppConfig(String cl, String client, String a, long d, boolean dis) { this(cl, client, a, d, dis, null, null, null); } /** @since 0.7.12 */ public ClientAppConfig(String cl, String client, String a, long d, boolean dis, String cp, String sa, String ua) { className = cl; clientName = client; args = a; delay = d; disabled = dis; classpath = cp; stopargs = sa; uninstallargs = ua; } public static File configFile(I2PAppContext ctx) { String clientConfigFile = ctx.getProperty(PROP_CLIENT_CONFIG_FILENAME, DEFAULT_CLIENT_CONFIG_FILENAME); File cfgFile = new File(clientConfigFile); if (!cfgFile.isAbsolute()) cfgFile = new File(ctx.getConfigDir(), clientConfigFile); return cfgFile; } private static Properties getClientAppProps(RouterContext ctx) { Properties rv = new Properties(); File cfgFile = configFile(ctx); // fall back to use router.config's clientApp.* lines if (!cfgFile.exists()) { System.out.println("Warning - No client config file " + cfgFile.getAbsolutePath()); rv.putAll(ctx.router().getConfigMap()); return rv; } try { DataHelper.loadProps(rv, cfgFile); } catch (IOException ioe) { System.out.println("Error loading the client app properties from " + cfgFile.getAbsolutePath() + ' ' + ioe); } return rv; } /* * Go through the properties, and return a List of ClientAppConfig structures * This is for the router. */ public static List<ClientAppConfig> getClientApps(RouterContext ctx) { Properties clientApps = getClientAppProps(ctx); List<ClientAppConfig> rv = getClientApps(clientApps); MigrateJetty.migrate(ctx, rv); return rv; } /* * Go through the properties, and return a List of ClientAppConfig structures * This is for plugins. * * @since 0.7.12 */ public static List<ClientAppConfig> getClientApps(File cfgFile) { Properties clientApps = new Properties(); try { DataHelper.loadProps(clientApps, cfgFile); } catch (IOException ioe) { return Collections.emptyList(); } return getClientApps(clientApps); } /* * Go through the properties, and return a List of ClientAppConfig structures * * @since 0.7.12 */ private static List<ClientAppConfig> getClientApps(Properties clientApps) { List<ClientAppConfig> rv = new ArrayList<ClientAppConfig>(8); int i = 0; while (true) { String className = clientApps.getProperty(PREFIX + i + ".main"); if (className == null) break; String clientName = clientApps.getProperty(PREFIX + i + ".name"); String args = clientApps.getProperty(PREFIX + i + ".args"); String delayStr = clientApps.getProperty(PREFIX + i + ".delay"); String onBoot = clientApps.getProperty(PREFIX + i + ".onBoot"); String disabled = clientApps.getProperty(PREFIX + i + ".startOnLoad"); String classpath = clientApps.getProperty(PREFIX + i + ".classpath"); String stopargs = clientApps.getProperty(PREFIX + i + ".stopargs"); String uninstallargs = clientApps.getProperty(PREFIX + i + ".uninstallargs"); i++; boolean dis = disabled != null && "false".equals(disabled); boolean onStartup = false; if (onBoot != null) onStartup = "true".equals(onBoot) || "yes".equals(onBoot); // speed up the start of i2ptunnel for everybody without rewriting clients.config long delay = onStartup ? 0 : (className.equals("net.i2p.i2ptunnel.TunnelControllerGroup") ? I2PTUNNEL_STARTUP_DELAY : DEFAULT_STARTUP_DELAY); if (delayStr != null && !onStartup) try { delay = 1000*Integer.parseInt(delayStr); } catch (NumberFormatException nfe) {} rv.add(new ClientAppConfig(className, clientName, args, delay, dis, classpath, stopargs, uninstallargs)); } return rv; } /** classpath and stopargs not supported */ public static void writeClientAppConfig(RouterContext ctx, List<ClientAppConfig> apps) { File cfgFile = configFile(ctx); FileOutputStream fos = null; try { fos = new SecureFileOutputStream(cfgFile); StringBuilder buf = new StringBuilder(2048); for(int i = 0; i < apps.size(); i++) { ClientAppConfig app = apps.get(i); buf.append(PREFIX).append(i).append(".main=").append(app.className).append("\n"); buf.append(PREFIX).append(i).append(".name=").append(app.clientName).append("\n"); if (app.args != null) buf.append(PREFIX).append(i).append(".args=").append(app.args).append("\n"); buf.append(PREFIX).append(i).append(".delay=").append(app.delay / 1000).append("\n"); buf.append(PREFIX).append(i).append(".startOnLoad=").append(!app.disabled).append("\n"); } fos.write(buf.toString().getBytes("UTF-8")); } catch (IOException ioe) { } finally { if (fos != null) try { fos.close(); } catch (IOException ioe) {} } } }