forked from I2P_Developers/i2p.i2p
* Plugins: Retry deletion at restart if it fails (ticket #1257)
This commit is contained in:
@ -177,6 +177,8 @@ public class ConfigClientsHelper extends HelperBase {
|
|||||||
if (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)) {
|
if (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)) {
|
||||||
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
||||||
String val = props.getProperty(name);
|
String val = props.getProperty(name);
|
||||||
|
if (val.equals(PluginStarter.DELETED))
|
||||||
|
continue;
|
||||||
Properties appProps = PluginStarter.pluginProperties(_context, app);
|
Properties appProps = PluginStarter.pluginProperties(_context, app);
|
||||||
if (appProps.isEmpty())
|
if (appProps.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
@ -49,8 +49,11 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
|||||||
*/
|
*/
|
||||||
public class PluginStarter implements Runnable {
|
public class PluginStarter implements Runnable {
|
||||||
protected RouterContext _context;
|
protected RouterContext _context;
|
||||||
|
private static final String CONFIG_FILE = "plugins.config";
|
||||||
public static final String PREFIX = "plugin.";
|
public static final String PREFIX = "plugin.";
|
||||||
|
// false, true, or deleted
|
||||||
public static final String ENABLED = ".startOnLoad";
|
public static final String ENABLED = ".startOnLoad";
|
||||||
|
public static final String DELETED = "deleted";
|
||||||
public static final String PLUGIN_DIR = "plugins";
|
public static final String PLUGIN_DIR = "plugins";
|
||||||
private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
|
private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
|
||||||
"susimail", "addressbook", "routerconsole" };
|
"susimail", "addressbook", "routerconsole" };
|
||||||
@ -71,6 +74,7 @@ public class PluginStarter implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
|
deferredDeletePlugins(_context);
|
||||||
if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") &&
|
if (_context.getBooleanPropertyDefaultTrue("plugins.autoUpdate") &&
|
||||||
!NewsHelper.isUpdateInProgress()) {
|
!NewsHelper.isUpdateInProgress()) {
|
||||||
String prev = _context.getProperty("router.previousVersion");
|
String prev = _context.getProperty("router.previousVersion");
|
||||||
@ -183,10 +187,10 @@ public class PluginStarter implements Runnable {
|
|||||||
static void startPlugins(RouterContext ctx) {
|
static void startPlugins(RouterContext ctx) {
|
||||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||||
Properties props = pluginProperties();
|
Properties props = pluginProperties();
|
||||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
for (Map.Entry<Object, Object> e : props.entrySet()) {
|
||||||
String name = (String)iter.next();
|
String name = (String)e.getKey();
|
||||||
if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) {
|
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));
|
String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED));
|
||||||
// plugins could have been started after update
|
// plugins could have been started after update
|
||||||
if (isPluginRunning(app, ctx))
|
if (isPluginRunning(app, ctx))
|
||||||
@ -194,14 +198,50 @@ public class PluginStarter implements Runnable {
|
|||||||
try {
|
try {
|
||||||
if (!startPlugin(ctx, app))
|
if (!startPlugin(ctx, app))
|
||||||
log.error("Failed to start plugin: " + app);
|
log.error("Failed to start plugin: " + app);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable t) {
|
||||||
log.error("Failed to start plugin: " + app, e);
|
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<Map.Entry<Object, Object>> iter = props.entrySet().iterator(); iter.hasNext(); ) {
|
||||||
|
Map.Entry<Object, Object> 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
|
* @return true on success
|
||||||
* @throws just about anything, caller would be wise to catch Throwable
|
* @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);
|
ctx.router().saveConfig(changes, removes);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtil.rmdir(pluginDir, false);
|
boolean deleted = FileUtil.rmdir(pluginDir, false);
|
||||||
Properties props = pluginProperties();
|
Properties props = pluginProperties();
|
||||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||||
String name = (String)iter.next();
|
String name = (String)iter.next();
|
||||||
if (name.startsWith(PREFIX + appName + '.'))
|
if (name.startsWith(PREFIX + appName + '.'))
|
||||||
iter.remove();
|
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);
|
storePluginProperties(props);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -496,18 +542,18 @@ public class PluginStarter implements Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* plugins.config
|
* 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() {
|
public static Properties pluginProperties() {
|
||||||
File dir = I2PAppContext.getGlobalContext().getConfigDir();
|
File dir = I2PAppContext.getGlobalContext().getConfigDir();
|
||||||
Properties rv = new Properties();
|
Properties rv = new Properties();
|
||||||
File cfgFile = new File(dir, "plugins.config");
|
File cfgFile = new File(dir, CONFIG_FILE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DataHelper.loadProps(rv, cfgFile);
|
DataHelper.loadProps(rv, cfgFile);
|
||||||
} catch (IOException ioe) {}
|
} catch (IOException ioe) {}
|
||||||
|
|
||||||
List<String> names = getPlugins();
|
List<String> names = getAllPlugins();
|
||||||
for (String name : names) {
|
for (String name : names) {
|
||||||
String prop = PREFIX + name + ENABLED;
|
String prop = PREFIX + name + ENABLED;
|
||||||
if (rv.getProperty(prop) == null)
|
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<String> getPlugins() {
|
public static List<String> getPlugins() {
|
||||||
|
List<String> rv = getAllPlugins();
|
||||||
|
Properties props = pluginProperties();
|
||||||
|
for (Iterator<String> 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<String> getAllPlugins() {
|
||||||
List<String> rv = new ArrayList<String>();
|
List<String> rv = new ArrayList<String>();
|
||||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PLUGIN_DIR);
|
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PLUGIN_DIR);
|
||||||
File[] files = pluginDir.listFiles();
|
File[] files = pluginDir.listFiles();
|
||||||
@ -555,7 +621,6 @@ public class PluginStarter implements Runnable {
|
|||||||
if (files[i].isDirectory())
|
if (files[i].isDirectory())
|
||||||
rv.add(files[i].getName());
|
rv.add(files[i].getName());
|
||||||
}
|
}
|
||||||
Collections.sort(rv); // ensure the list is in sorted order.
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,7 +646,7 @@ public class PluginStarter implements Runnable {
|
|||||||
* plugins.config
|
* plugins.config
|
||||||
*/
|
*/
|
||||||
public static void storePluginProperties(Properties props) {
|
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 {
|
try {
|
||||||
DataHelper.storeProps(props, cfgFile);
|
DataHelper.storeProps(props, cfgFile);
|
||||||
} catch (IOException ioe) {}
|
} catch (IOException ioe) {}
|
||||||
|
16
history.txt
16
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
|
2014-04-27 zzz
|
||||||
* NTCP: Remove published NTCP address if SSU becomes firewalled,
|
* NTCP: Remove published NTCP address if SSU becomes firewalled,
|
||||||
to fix the "Firewalled with NTCP enabled" message
|
to fix the "Firewalled with NTCP enabled" message
|
||||||
@ -6,7 +20,7 @@
|
|||||||
* SusiMail:
|
* SusiMail:
|
||||||
- Add locking for disk cache
|
- Add locking for disk cache
|
||||||
- Remove cancel button from login page
|
- Remove cancel button from login page
|
||||||
- New configuration page
|
- New configuration page (ticket #1158)
|
||||||
- Move set page form to configuration page
|
- Move set page form to configuration page
|
||||||
- Theme and js enhancements
|
- Theme and js enhancements
|
||||||
* UDP:
|
* UDP:
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 13;
|
public final static long BUILD = 14;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
Reference in New Issue
Block a user