forked from I2P_Developers/i2p.i2p
start of a plugin starter
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -13,7 +12,6 @@ import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.mortbay.http.HttpListener;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
/**
|
||||
@@ -180,16 +178,14 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
addFormNotice(_("WebApp configuration saved successfully - restart required to take effect."));
|
||||
}
|
||||
|
||||
// Big hack for the moment, not using properties for directory and port
|
||||
// Go through all the Jetty servers, find the one serving port 7657,
|
||||
// requested and add the .war to that one
|
||||
/**
|
||||
* Big hack for the moment, not using properties for directory and port
|
||||
* Go through all the Jetty servers, find the one serving port 7657,
|
||||
* requested and add the .war to that one
|
||||
*/
|
||||
private void startWebApp(String app) {
|
||||
Collection c = Server.getHttpServers();
|
||||
for (int i = 0; i < c.size(); i++) {
|
||||
Server s = (Server) c.toArray()[i];
|
||||
HttpListener[] hl = s.getListeners();
|
||||
for (int j = 0; j < hl.length; j++) {
|
||||
if (hl[j].getPort() == 7657) {
|
||||
Server s = PluginStarter.getConsoleServer();
|
||||
if (s != null) {
|
||||
try {
|
||||
File path = new File(_context.getBaseDir(), "webapps");
|
||||
path = new File(path, app + ".war");
|
||||
@@ -199,8 +195,6 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
addFormError(_("Failed to start") + ' ' + _(app) + " " + ioe + '.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
addFormError(_("Failed to find server."));
|
||||
}
|
||||
|
@@ -68,6 +68,25 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getForm3() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("Plugin") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Start Now") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
|
||||
Properties props = PluginStarter.pluginProperties();
|
||||
Set<String> keys = new TreeSet(props.keySet());
|
||||
for (Iterator<String> iter = keys.iterator(); iter.hasNext(); ) {
|
||||
String name = iter.next();
|
||||
if (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)) {
|
||||
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
||||
String val = props.getProperty(name);
|
||||
renderForm(buf, app, app, !"addressbook".equals(app),
|
||||
"true".equals(val), false, app, false, false);
|
||||
}
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** ro trumps edit and showEditButton */
|
||||
private void renderForm(StringBuilder buf, String index, String name, boolean urlify,
|
||||
boolean enabled, boolean ro, String desc, boolean edit, boolean showEditButton) {
|
||||
|
@@ -0,0 +1,169 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.mortbay.http.HttpListener;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
|
||||
/**
|
||||
* Start plugins that are already installed
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class PluginStarter implements Runnable {
|
||||
private RouterContext _context;
|
||||
static final String PREFIX = "plugin.";
|
||||
static final String ENABLED = ".startOnLoad";
|
||||
|
||||
public PluginStarter(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
startPlugins(_context);
|
||||
}
|
||||
|
||||
static void startPlugins(RouterContext ctx) {
|
||||
Properties props = pluginProperties();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)) {
|
||||
if (Boolean.valueOf(props.getProperty(name)).booleanValue()) {
|
||||
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
||||
try {
|
||||
if (!startPlugin(ctx, app))
|
||||
System.err.println("Failed to start plugin: " + app);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to start plugin: " + app + ' ' + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return true on success */
|
||||
static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
System.err.println("Cannot start nonexistent plugin: " + appName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// load and start 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);
|
||||
}
|
||||
|
||||
// start console webapps in console/webapps
|
||||
Server server = getConsoleServer();
|
||||
if (server != null) {
|
||||
File consoleDir = new File(pluginDir, "console");
|
||||
Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
|
||||
File webappDir = new File(pluginDir, "webapps");
|
||||
String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
|
||||
if (fileNames != null) {
|
||||
for (int i = 0; i < fileNames.length; i++) {
|
||||
try {
|
||||
String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
|
||||
// check for duplicates in $I2P ?
|
||||
String enabled = props.getProperty(PREFIX + warName + ENABLED);
|
||||
if (! "false".equals(enabled)) {
|
||||
String path = new File(webappDir, fileNames[i]).getCanonicalPath();
|
||||
WebAppStarter.startWebApp(ctx, server, warName, path);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error resolving '" + fileNames[i] + "' in '" + webappDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add translation jars in console/locale
|
||||
|
||||
// add themes in console/themes
|
||||
|
||||
// add summary bar link
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** this auto-adds a propery for every dir in the plugin directory */
|
||||
public static Properties pluginProperties() {
|
||||
File dir = I2PAppContext.getGlobalContext().getConfigDir();
|
||||
Properties rv = new Properties();
|
||||
File cfgFile = new File(dir, "plugins.config");
|
||||
|
||||
try {
|
||||
DataHelper.loadProps(rv, cfgFile);
|
||||
} catch (IOException ioe) {}
|
||||
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getAppDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File[] files = pluginDir.listFiles();
|
||||
if (files == null)
|
||||
return rv;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
String name = files[i].getName();
|
||||
String prop = PREFIX + name + ENABLED;
|
||||
if (files[i].isDirectory() && rv.getProperty(prop) == null)
|
||||
rv.setProperty(prop, "true");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** see comments in ConfigClientsHandler */
|
||||
static Server getConsoleServer() {
|
||||
Collection c = Server.getHttpServers();
|
||||
for (int i = 0; i < c.size(); i++) {
|
||||
Server s = (Server) c.toArray()[i];
|
||||
HttpListener[] hl = s.getListeners();
|
||||
for (int j = 0; j < hl.length; j++) {
|
||||
if (hl[j].getPort() == 7657)
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void runClientApps(RouterContext ctx, File pluginDir, List<ClientAppConfig> apps) {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
for(ClientAppConfig app : apps) {
|
||||
if (app.disabled)
|
||||
continue;
|
||||
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());
|
||||
}
|
||||
}
|
||||
if (app.delay == 0) {
|
||||
// run this guy now
|
||||
LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log);
|
||||
} else {
|
||||
// wait before firing it up
|
||||
ctx.jobQueue().addJob(new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,13 +20,13 @@ import net.i2p.util.VersionComparator;
|
||||
/**
|
||||
* Download and install a plugin.
|
||||
* A plugin is a standard .sud file with a 40-byte signature,
|
||||
* a 16-byte version (which is ignored), and a .zip file.
|
||||
* a 16-byte version, and a .zip file.
|
||||
* Unlike for router updates, we need not have the public key
|
||||
* for the signature in advance.
|
||||
*
|
||||
* The zip file must have a standard directory layout, with
|
||||
* a install.properties file at the top level.
|
||||
* The properties file contains properties for the package name, version,
|
||||
* a plugin.config file at the top level.
|
||||
* The config file contains properties for the package name, version,
|
||||
* signing public key, and other settings.
|
||||
* The zip file will typically contain a webapps/ or lib/ dir,
|
||||
* and a webapps.config and/or clients.config file.
|
||||
@@ -159,7 +159,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
updateStatus("<b>" + _("Plugin from {0} is corrupt", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
File installProps = new File(tempDir, "install.properties");
|
||||
File installProps = new File(tempDir, "plugin.config");
|
||||
Properties props = new OrderedProperties();
|
||||
try {
|
||||
DataHelper.loadProps(props, installProps);
|
||||
@@ -220,6 +220,8 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo compare sud version with property version
|
||||
|
||||
String minVersion = props.getProperty("min-i2p-version");
|
||||
if (minVersion != null &&
|
||||
(new VersionComparator()).compare(CoreVersion.VERSION, minVersion) < 0) {
|
||||
@@ -236,17 +238,16 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isUpdate = Boolean.valueOf(props.getProperty("update")).booleanValue();
|
||||
File destDir = new File(appDir, appName);
|
||||
if (destDir.exists()) {
|
||||
if (!isUpdate) {
|
||||
if (Boolean.valueOf(props.getProperty("install-only")).booleanValue()) {
|
||||
to.delete();
|
||||
updateStatus("<b>" + _("Downloaded plugin is not for upgrading but the plugin is already installed", url) + "</b>");
|
||||
return;
|
||||
}
|
||||
|
||||
// compare previous version
|
||||
File oldPropFile = new File(destDir, "install.properties");
|
||||
File oldPropFile = new File(destDir, "plugin.config");
|
||||
Properties oldProps = new OrderedProperties();
|
||||
try {
|
||||
DataHelper.loadProps(oldProps, oldPropFile);
|
||||
@@ -289,7 +290,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
// check if it is running now and stop it?
|
||||
|
||||
} else {
|
||||
if (isUpdate) {
|
||||
if (Boolean.valueOf(props.getProperty("update-only")).booleanValue()) {
|
||||
to.delete();
|
||||
updateStatus("<b>" + _("Plugin is for upgrades only, but the plugin is not installed", url) + "</b>");
|
||||
return;
|
||||
@@ -309,9 +310,22 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
}
|
||||
|
||||
to.delete();
|
||||
updateStatus("<b>" + _("Plugin successfully installed in {0}", destDir.getAbsolutePath()) + "</b>");
|
||||
|
||||
// start everything
|
||||
if (Boolean.valueOf(props.getProperty("dont-start-at-install")).booleanValue()) {
|
||||
if (Boolean.valueOf(props.getProperty("router-restart-required")).booleanValue())
|
||||
updateStatus("<b>" + _("Plugin {0} successfully installed, router restart required", appName) + "</b>");
|
||||
else
|
||||
updateStatus("<b>" + _("Plugin {0} successfully installed", appName) + "</b>");
|
||||
} else {
|
||||
// start everything
|
||||
try {
|
||||
if (PluginStarter.startPlugin(_context, appName))
|
||||
updateStatus("<b>" + _("Plugin {0} started", appName) + "</b>");
|
||||
else
|
||||
updateStatus("<b>" + _("Failed to start plugin {0}, check logs", appName) + "</b>");
|
||||
} catch (Exception e) {
|
||||
updateStatus("<b>" + _("Failed to start plugin {0}:", appName) + ' ' + e + "</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -181,13 +181,17 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
|
||||
Thread t = new I2PAppThread(fetcher, "NewsFetcher");
|
||||
t.setDaemon(true);
|
||||
Thread t = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||
t.start();
|
||||
|
||||
Thread st = new I2PAppThread(new StatSummarizer(), "StatSummarizer");
|
||||
st.setDaemon(true);
|
||||
st.start();
|
||||
t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
|
||||
t.start();
|
||||
|
||||
List<RouterContext> contexts = RouterContext.listContexts();
|
||||
if (contexts != null) {
|
||||
t = new I2PAppThread(new PluginStarter(contexts.get(0)), "PluginStarter", true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
static void initialize(WebApplicationContext context) {
|
||||
@@ -206,10 +210,10 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
|
||||
static String getPassword() {
|
||||
List contexts = RouterContext.listContexts();
|
||||
List<RouterContext> contexts = RouterContext.listContexts();
|
||||
if (contexts != null) {
|
||||
for (int i = 0; i < contexts.size(); i++) {
|
||||
RouterContext ctx = (RouterContext)contexts.get(i);
|
||||
RouterContext ctx = contexts.get(i);
|
||||
String password = ctx.getProperty("consolePassword");
|
||||
if (password != null) {
|
||||
password = password.trim();
|
||||
@@ -267,11 +271,12 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private static class WarFilenameFilter implements FilenameFilter {
|
||||
static class WarFilenameFilter implements FilenameFilter {
|
||||
private static final WarFilenameFilter _filter = new WarFilenameFilter();
|
||||
public static WarFilenameFilter instance() { return _filter; }
|
||||
public boolean accept(File dir, String name) {
|
||||
return (name != null) && (name.endsWith(".war") && !name.equals(ROUTERCONSOLE + ".war"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user