All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.i2p.router.startup.ClientAppConfig Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package net.i2p.router.startup;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.util.FileSuffixFilter;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.ObjectCounterUnsafe;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SystemVersion;


/**
 * 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.
 *
 * 
 *
 * 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 */ private final static long DEFAULT_STARTUP_DELAY = 2*60*1000; /** speed up i2ptunnel without rewriting clients.config */ private final static long I2PTUNNEL_STARTUP_DELAY = -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 CLIENT_CONFIG_DIR = "clients.config.d"; 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; /** @since 0.9.42 */ File configFile; 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; } /* * Only valid for the router's clients (not plugins). * Only valid after getClientApps(ctx) has been called. * @since 0.9.42 */ public synchronized static boolean isSplitConfig(I2PAppContext ctx) { File dir = new File(ctx.getConfigDir(), CLIENT_CONFIG_DIR); return dir.exists() && !configFile(ctx).exists(); } /* * This is the old config file. Only valid if not a split config. */ 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; } /* * This is the config dir. Only valid if a split config. * @since 0.9.48 */ public static File configDir(I2PAppContext ctx) { return new File(ctx.getConfigDir(), CLIENT_CONFIG_DIR); } /* * Go through the files, and return a List of ClientAppConfig structures * This is for the router. */ public synchronized static List getClientApps(RouterContext ctx) { File dir = new SecureDirectory(ctx.getConfigDir(), CLIENT_CONFIG_DIR); // clients.config List rv = new ArrayList(8); File cf = configFile(ctx); try { List cacs = getClientApps(cf); if (!cacs.isEmpty()) { if (!SystemVersion.isAndroid()) MigrateJetty.migrate(ctx, cacs); boolean ok = migrate(ctx, cacs, cf, dir); if (!ok) rv.addAll(cacs); } } catch (IOException ioe) { ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + cf, ioe); System.out.println("Error loading the client app properties from " + cf + ' ' + ioe); } // clients.config.d if (dir.isDirectory()) { File[] files = dir.listFiles(new FileSuffixFilter(".config")); if (files != null && files.length > 0) { // sort so the returned order is consistent Arrays.sort(files); for (File f : files) { if (!f.getName().endsWith(".config")) continue; if (!f.isFile()) continue; try { List cacs = getClientApps(f); if (!cacs.isEmpty()) { rv.addAll(cacs); } else { ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + f); System.out.println("Error loading the client app properties from " + f); } } catch (IOException ioe) { ctx.logManager().getLog(ClientAppConfig.class).error("Error loading the client app properties from " + f, ioe); System.out.println("Error loading the client app properties from " + f + ' ' + ioe); } } } } return rv; } /* * Go through the file, and return a List of ClientAppConfig structures * * @since 0.7.12 */ public synchronized static List getClientApps(File cfgFile) throws IOException { if (!cfgFile.isFile()) return new ArrayList(); Properties clientApps = new Properties(); DataHelper.loadProps(clientApps, cfgFile); List rv = getClientApps(clientApps); for (ClientAppConfig cac : rv) { cac.configFile = cfgFile; } return rv; } /* * Migrate apps from file to individual files in dir * * @return success * @since 0.9.42 */ private static boolean migrate(I2PAppContext ctx, List apps, File from, File dir) { // don't migrate Android if (SystemVersion.isAndroid()) return false; // don't migrate portable try { if (ctx.getConfigDir().getCanonicalPath().equals(ctx.getBaseDir().getCanonicalPath())) return false; } catch (IOException ioe) {} if (!dir.isDirectory() && !dir.mkdirs()) return false; boolean ok = true; for (int i = 0; i < apps.size(); i++) { ClientAppConfig cac = apps.get(i); String name = i + "-" + cac.className + "-clients.config"; if (i < 10) name = '0' + name; File f = new File(dir, name); cac.configFile = f; try { writeClientAppConfig(ctx, cac); } catch (IOException ioe) { ctx.logManager().getLog(ClientAppConfig.class).error("Error migrating the client app properties to " + f, ioe); System.out.println("Error migrating the client app properties to " + f + ' ' + ioe); cac.configFile = from; ok = false; } } if (ok) { if (!FileUtil.rename(from, new File(from.getAbsolutePath() + ".bak"))) from.delete(); } return ok; } /* * Go through the properties, and return a List of ClientAppConfig structures * * @since 0.7.12 */ private static List getClientApps(Properties clientApps) { List rv = new ArrayList(8); int i = 0; while (true) { ClientAppConfig cac = getClientApp(clientApps, PREFIX + i); if (cac == null) break; i++; rv.add(cac); } return rv; } /* * Go through the properties, and get a single ClientAppConfig structure * with this prefix * * @return null if none * @since 0.9.42 split out from above */ private static ClientAppConfig getClientApp(Properties clientApps, String prefix) { String className = clientApps.getProperty(prefix + ".main"); if (className == null) return null; String clientName = clientApps.getProperty(prefix + ".name"); String args = clientApps.getProperty(prefix + ".args"); String delayStr = clientApps.getProperty(prefix + ".delay"); String onBoot = clientApps.getProperty(prefix + ".onBoot"); String disabled = clientApps.getProperty(prefix + ".startOnLoad"); String classpath = clientApps.getProperty(prefix + ".classpath"); String stopargs = clientApps.getProperty(prefix + ".stopargs"); String uninstallargs = clientApps.getProperty(prefix + ".uninstallargs"); boolean dis = disabled != null && "false".equals(disabled); boolean onStartup = false; if (onBoot != null) onStartup = "true".equals(onBoot) || "yes".equals(onBoot); long delay; if (onStartup) { delay = 0; } else if (className.equals("net.i2p.i2ptunnel.TunnelControllerGroup")) { // speed up the start of i2ptunnel for everybody without rewriting clients.config delay = I2PTUNNEL_STARTUP_DELAY; } else { delay = DEFAULT_STARTUP_DELAY; if (delayStr != null) try { delay = 1000*Integer.parseInt(delayStr); } catch (NumberFormatException nfe) {} } return new ClientAppConfig(className, clientName, args, delay, dis, classpath, stopargs, uninstallargs); } /** * Classpath and stopargs not supported. * All other apps in the file will be deleted. * Do not use if multiple apps in a single file - use writeClientAppConfig(ctx, apps). * If app.configFile is null, a new file will be created and assigned. * * @since 0.9.42 */ public synchronized static void writeClientAppConfig(I2PAppContext ctx, ClientAppConfig app) throws IOException { if (app.configFile == null) { File dir = new SecureDirectory(ctx.getConfigDir(), CLIENT_CONFIG_DIR); if (!dir.isDirectory() && !dir.mkdirs()) throw new IOException("Can't create " + dir); int i = 0; String[] files = dir.list(); if (files != null) i = files.length; File f; do { String name = i + "-" + app.className + "-clients.config"; if (i < 10) name = '0' + name; f = new File(dir, name); i++; } while (f.exists()); app.configFile = f; } writeClientAppConfig(Collections.singletonList(app), app.configFile); } /** * Classpath and stopargs not supported. * All other apps in the files will be deleted. * Do not add apps with this method - use writeClientAppConfig(ctx, app). * Do not delete apps with this method - use deleteClientAppConfig(). * * @since 0.9.42 split out from above */ public synchronized static void writeClientAppConfig(I2PAppContext ctx, List apps) throws IOException { // Gather the set of config files ObjectCounterUnsafe counter = new ObjectCounterUnsafe(); for (ClientAppConfig cac : apps) { File f = cac.configFile; if (f == null) throw new IllegalArgumentException("No file for " + cac.className); counter.increment(f); } IOException e = null; // Write the config files Set files = counter.objects(); // For each file, write all the configs for that file for (File f : files) { // Gather configs for this file List cacs = new ArrayList(8); for (ClientAppConfig cac : apps) { if (cac.configFile.equals(f)) cacs.add(cac); } try { writeClientAppConfig(cacs, f); } catch (IOException ioe) { if (e == null) e = ioe; } } if (e != null) throw e; } /** * All to a single file, apps.configFile ignored * * @throws IllegalArgumentException if null cfgFile * @since 0.9.42 split out from above */ private static void writeClientAppConfig(List apps, File cfgFile) throws IOException { if (cfgFile == null) throw new IllegalArgumentException("No file"); Properties props = new OrderedProperties(); for(int i = 0; i < apps.size(); i++) { ClientAppConfig app = apps.get(i); String pfx = PREFIX + i; props.setProperty(pfx + ".main", app.className); props.setProperty(pfx + ".name", app.clientName); if (app.args != null) props.setProperty(pfx + ".args", app.args); props.setProperty(pfx + ".delay", Long.toString(app.delay / 1000)); props.setProperty(pfx + ".startOnLoad", Boolean.toString(!app.disabled)); } DataHelper.storeProps(props, cfgFile); } /** * This works for both split and non-split config. * If this is the only config in the file, the file will be deleted; * otherwise the file will be saved with the remaining configs. * * @return success * @throws IllegalArgumentException if cac has a null configfile * @since 0.9.42 */ public synchronized static boolean deleteClientAppConfig(ClientAppConfig cac) throws IOException { File f = cac.configFile; if (f == null) throw new IllegalArgumentException("No file for " + cac.className); List cacs = getClientApps(f); if (cacs.remove(cac)) { if (cacs.isEmpty()) return f.delete(); writeClientAppConfig(cacs, f); return true; } return false; } /** * @since 0.9.42 */ @Override public int hashCode() { return DataHelper.hashCode(className); } /** * Matches on class, args, and name only * @since 0.9.42 */ @Override public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; if (o instanceof ClientAppConfig) { ClientAppConfig cac = (ClientAppConfig) o; return DataHelper.eq(className, cac.className) && DataHelper.eq(clientName, cac.clientName) && DataHelper.eq(args, cac.args); } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy