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) {}
}
}
}