diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 9879e02c96..c1463000bc 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -177,6 +177,8 @@ public class ConfigClientsHelper extends HelperBase { 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); + if (val.equals(PluginStarter.DELETED)) + continue; Properties appProps = PluginStarter.pluginProperties(_context, app); if (appProps.isEmpty()) continue; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index ea372ec5c8..baad6df997 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -49,8 +49,11 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; */ public class PluginStarter implements Runnable { protected RouterContext _context; + private static final String CONFIG_FILE = "plugins.config"; public static final String PREFIX = "plugin."; + // false, true, or deleted public static final String ENABLED = ".startOnLoad"; + public static final String DELETED = "deleted"; public static final String PLUGIN_DIR = "plugins"; private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns", "susimail", "addressbook", "routerconsole" }; @@ -71,6 +74,7 @@ public class PluginStarter implements Runnable { } public void run() { + deferredDeletePlugins(_context); if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") && !NewsHelper.isUpdateInProgress()) { String prev = _context.getProperty("router.previousVersion"); @@ -183,10 +187,10 @@ public class PluginStarter implements Runnable { static void startPlugins(RouterContext ctx) { Log log = ctx.logManager().getLog(PluginStarter.class); Properties props = pluginProperties(); - for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); + for (Map.Entry e : props.entrySet()) { + String name = (String)e.getKey(); if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) { - if (Boolean.parseBoolean(props.getProperty(name))) { + if (Boolean.parseBoolean((String) e.getValue())) { String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED)); // plugins could have been started after update if (isPluginRunning(app, ctx)) @@ -194,14 +198,50 @@ public class PluginStarter implements Runnable { try { if (!startPlugin(ctx, app)) log.error("Failed to start plugin: " + app); - } catch (Throwable e) { - log.error("Failed to start plugin: " + app, e); + } catch (Throwable t) { + log.error("Failed to start plugin: " + app, t); } } } } } + /** + * Deferred deletion of plugins that we failed to delete before. + * + * @since 0.9.13 + */ + private static void deferredDeletePlugins(RouterContext ctx) { + Log log = ctx.logManager().getLog(PluginStarter.class); + boolean changed = false; + Properties props = pluginProperties(); + for (Iterator> iter = props.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry e = iter.next(); + String name = (String)e.getKey(); + if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) { + // deferred deletion of a plugin + if (e.getValue().equals(DELETED)) { + String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED)); + // shouldn't happen, this is run early + if (isPluginRunning(app, ctx)) + continue; + File pluginDir = new File(ctx.getConfigDir(), PLUGIN_DIR + '/' + app); + boolean deleted = FileUtil.rmdir(pluginDir, false); + if (deleted) { + log.logAlways(Log.WARN, "Deferred deletion of " + pluginDir + " successful"); + iter.remove(); + changed = true; + } else { + if (log.shouldLog(Log.WARN)) + log.warn("Deferred deletion of " + pluginDir + " failed"); + } + } + } + } + if (changed) + storePluginProperties(props); + } + /** * @return true on success * @throws just about anything, caller would be wise to catch Throwable @@ -473,13 +513,19 @@ public class PluginStarter implements Runnable { ctx.router().saveConfig(changes, removes); } - FileUtil.rmdir(pluginDir, false); + 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; } @@ -496,18 +542,18 @@ public class PluginStarter implements Runnable { /** * plugins.config - * this auto-adds a propery for every dir in the plugin directory + * this auto-adds a property 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"); + File cfgFile = new File(dir, CONFIG_FILE); try { DataHelper.loadProps(rv, cfgFile); } catch (IOException ioe) {} - List names = getPlugins(); + List names = getAllPlugins(); for (String name : names) { String prop = PREFIX + name + ENABLED; if (rv.getProperty(prop) == null) @@ -543,9 +589,29 @@ public class PluginStarter implements Runnable { } /** - * all installed plugins whether enabled or not + * all installed plugins whether enabled or not, + * but does NOT include plugins marked as deleted. + * @return non-null, sorted, modifiable */ public static List getPlugins() { + List rv = getAllPlugins(); + Properties props = pluginProperties(); + for (Iterator iter = rv.iterator(); iter.hasNext(); ) { + String app = iter.next(); + if (DELETED.equals(props.getProperty(PREFIX + app + ENABLED))) + iter.remove(); + } + Collections.sort(rv); // ensure the list is in sorted order. + return rv; + } + + /** + * all installed plugins whether enabled or not, + * DOES include plugins marked as deleted. + * @return non-null, unsorted, modifiable + * @since 0.9.13 + */ + private static List getAllPlugins() { List rv = new ArrayList(); File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PLUGIN_DIR); File[] files = pluginDir.listFiles(); @@ -555,7 +621,6 @@ public class PluginStarter implements Runnable { if (files[i].isDirectory()) rv.add(files[i].getName()); } - Collections.sort(rv); // ensure the list is in sorted order. return rv; } @@ -581,7 +646,7 @@ public class PluginStarter implements Runnable { * plugins.config */ public static void storePluginProperties(Properties props) { - File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins.config"); + File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), CONFIG_FILE); try { DataHelper.storeProps(props, cfgFile); } catch (IOException ioe) {} diff --git a/history.txt b/history.txt index 15456818e4..d65405e6ef 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,17 @@ +2014-04-30 zzz + * Plugins: Retry deletion at restart if it fails (ticket #1257) + +2014-04-29 zzz + * SusiMail: + - Add print css + - Hide header and footer in mobile css + - Fix 'from' address in compose + +2014-04-28 zzz + * UDP: + - Locking fixes on peer testing + - Slow down peer test frequency, esp. when firewalled + 2014-04-27 zzz * NTCP: Remove published NTCP address if SSU becomes firewalled, to fix the "Firewalled with NTCP enabled" message @@ -6,7 +20,7 @@ * SusiMail: - Add locking for disk cache - Remove cancel button from login page - - New configuration page + - New configuration page (ticket #1158) - Move set page form to configuration page - Theme and js enhancements * UDP: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 7f4edb9752..31c35f8c3e 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 13; + public final static long BUILD = 14; /** for example "-test" */ public final static String EXTRA = "";