/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.pluginmanager;
import static java.util.concurrent.TimeUnit.MINUTES;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarException;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipException;
import org.tanukisoftware.wrapper.WrapperManager;
import freenet.client.HighLevelSimpleClient;
import freenet.clients.fcp.ClientPut;
import freenet.clients.http.QueueToadlet;
import freenet.clients.http.PageMaker.THEME;
import freenet.clients.http.Toadlet;
import freenet.config.Config;
import freenet.config.InvalidConfigValueException;
import freenet.config.NodeNeedRestartException;
import freenet.config.SubConfig;
import freenet.crypt.SHA256;
import freenet.keys.FreenetURI;
import freenet.l10n.NodeL10n;
import freenet.l10n.BaseL10n.LANGUAGE;
import freenet.node.Node;
import freenet.node.NodeClientCore;
import freenet.node.RequestClient;
import freenet.node.RequestStarter;
import freenet.node.useralerts.AbstractUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.pluginmanager.OfficialPlugins.OfficialPluginDescription;
import freenet.support.HTMLNode;
import freenet.support.HexUtil;
import freenet.support.JarClassLoader;
import freenet.support.Logger;
import freenet.support.SerialExecutor;
import freenet.support.Ticker;
import freenet.support.Logger.LogLevel;
import freenet.support.api.BooleanCallback;
import freenet.support.api.HTTPRequest;
import freenet.support.api.StringArrCallback;
import freenet.support.io.Closer;
import freenet.support.io.FileUtil;
import freenet.support.io.NativeThread;
public class PluginManager {
/*
*
* TODO: Synchronize
* TODO: Synchronize
* TODO: Synchronize
* TODO: Synchronize
* TODO: Synchronize
*
*/
private final HashMap<String, FredPlugin> toadletList;
/* All currently starting plugins. */
private final OfficialPlugins officialPlugins = new OfficialPlugins();
private final Set<PluginProgress> startingPlugins = new HashSet<PluginProgress>();
private final ArrayList<PluginInfoWrapper> pluginWrappers;
private final HashMap<String, PluginLoadFailedUserAlert> pluginsFailedLoad;
final Node node;
private final NodeClientCore core;
SubConfig pmconfig;
private boolean logMINOR;
private boolean logDEBUG;
private final HighLevelSimpleClient client;
private static PluginManager selfinstance = null;
private THEME fproxyTheme;
private final SerialExecutor executor;
private boolean alwaysLoadOfficialPluginsFromCentralServer = false;
static final short PRIO = RequestStarter.INTERACTIVE_PRIORITY_CLASS;
public PluginManager(Node node, int lastVersion) {
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
// config
toadletList = new HashMap<String, FredPlugin>();
pluginWrappers = new ArrayList<PluginInfoWrapper>();
pluginsFailedLoad = new HashMap<String, PluginLoadFailedUserAlert>();
this.node = node;
this.core = node.clientCore;
if(logMINOR)
Logger.minor(this, "Starting Plugin Manager");
if(logDEBUG)
Logger.debug(this, "Initialize Plugin Manager config");
client = core.makeClient(PRIO, true, false);
// callback executor
executor = new SerialExecutor(NativeThread.NORM_PRIORITY);
executor.start(node.executor, "PM callback executor");
pmconfig = new SubConfig("pluginmanager", node.config);
// pmconfig.register("configfile", "fplugins.ini", 9, true, true, "PluginConfig.configFile", "PluginConfig.configFileLong",
// new StringCallback() {
// public String get() {
// return configFile;
// }
//
// public void set(String val) throws InvalidConfigValueException {
// configFile = val;
// }
// });
// configFile = pmconfig.getString("configfile");
// pmconfig.register("installdir", "fplugins", 9, true, true, "PluginConfig.installDir", "PluginConfig.installDirLong",
// new StringCallback() {
// public String get() {
// return installDir;
// //return getConfigLoadString();
// }
//
// public void set(String val) throws InvalidConfigValueException {
// installDir = val;
// //if(storeDir.equals(new File(val))) return;
// // FIXME
// //throw new InvalidConfigValueException(NodeL10n.getBase().getString("PluginManager.cannotSetOnceLoaded"));
// }
// });
// installDir = pmconfig.getString("installdir");
// Start plugins in the config
pmconfig.register("loadplugin", null, 0, true, false, "PluginManager.loadedOnStartup", "PluginManager.loadedOnStartupLong",
new StringArrCallback() {
@Override
public String[] get() {
return getConfigLoadString();
}
@Override
public void set(String[] val) throws InvalidConfigValueException {
//if(storeDir.equals(new File(val))) return;
// FIXME
throw new InvalidConfigValueException(NodeL10n.getBase().getString("PluginManager.cannotSetOnceLoaded"));
}
@Override
public boolean isReadOnly() {
return true;
}
});
toStart = pmconfig.getStringArr("loadplugin");
if(lastVersion < 1237 && contains(toStart, "XMLLibrarian") && !contains(toStart, "Library")) {
toStart = Arrays.copyOf(toStart, toStart.length+1);
toStart[toStart.length-1] = "Library";
System.err.println("Loading Library plugin, replaces XMLLibrarian, when upgrading from pre-1237");
}
if(contains(toStart, "KeyExplorer")) {
for(int i=0;i<toStart.length;i++) {
if("KeyExplorer".equals(toStart[i]))
toStart[i] = "KeyUtils";
}
System.err.println("KeyExplorer plugin renamed to KeyUtils");
}
// This should default to false. Even though we use SSL, a wiretapper may be able to tell which
// plugin is being loaded, and correlate that with identity creation; plus of course they can see
// that somebody is using Freenet.
pmconfig.register("alwaysLoadOfficialPluginsFromCentralServer", false, 0, false, false, "PluginManager.alwaysLoadPluginsFromCentralServer", "PluginManager.alwaysLoadPluginsFromCentralServerLong", new BooleanCallback() {
@Override
public Boolean get() {
return alwaysLoadOfficialPluginsFromCentralServer;
}
@Override
public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException {
alwaysLoadOfficialPluginsFromCentralServer = val;
}
});
alwaysLoadOfficialPluginsFromCentralServer = pmconfig.getBoolean("alwaysLoadOfficialPluginsFromCentralServer");
if(node.lastVersion <= 1437)
// Overwrite this setting, since it will have been set by the old callback and then written as it's not default.
// FIXME remove back compatibility code.
alwaysLoadOfficialPluginsFromCentralServer = false;
pmconfig.finishedInitialization();
fproxyTheme = THEME.themeFromName(node.config.get("fproxy").getString("css"));
selfinstance = this;
}
private boolean contains(String[] array, String string) {
for(String s : array)
if(string.equals(s)) return true;
return false;
}
private boolean started;
private boolean stopping;
private String[] toStart;
public void start(Config config) {
if(toStart != null)
for(final String name : toStart) {
core.getExecutor().execute(new Runnable() {
@Override
public void run() {
startPluginAuto(name, false);
}
});
}
synchronized(pluginWrappers) {
started = true;
toStart = null;
}
}
public void stop(long maxWaitTime) {
// Stop loading plugins.
ArrayList<PluginProgress> matches = new ArrayList<PluginProgress>();
synchronized(this) {
stopping = true;
for(Iterator<PluginProgress> i = startingPlugins.iterator();i.hasNext();) {
PluginProgress progress = i.next();
matches.add(progress);
i.remove();
}
}
for(PluginProgress progress : matches) {
progress.kill();
}
// Stop already loaded plugins.
ArrayList<PluginInfoWrapper> wrappers;
synchronized(pluginWrappers) {
wrappers = new ArrayList<PluginInfoWrapper>(pluginWrappers);
}
for(PluginInfoWrapper pi : wrappers)
pi.startShutdownPlugin(this, false);
long now = System.currentTimeMillis();
long deadline = now + maxWaitTime;
while(true) {
int delta = (int) (deadline - now);
if(delta <= 0) {
String list = pluginList(wrappers);
Logger.error(this, "Plugins still shutting down at timeout:\n"+list);
System.err.println("Plugins still shutting down at timeout:\n"+list);
} else {
for(Iterator<PluginInfoWrapper> it = wrappers.listIterator();it.hasNext();) {
PluginInfoWrapper pi = it.next();
System.out.println("Waiting for plugin to finish shutting down: "+pi.getFilename());
if(pi.finishShutdownPlugin(this, delta, false)) {
it.remove();
}
}
if(wrappers.isEmpty()) {
Logger.normal(this, "All plugins unloaded");
System.out.println("All plugins unloaded");
return;
}
String list = pluginList(wrappers);
Logger.error(this, "Plugins still shutting down:\n"+list);
System.err.println("Plugins still shutting down:\n"+list);
}
}
}
private static String pluginList(ArrayList<PluginInfoWrapper> wrappers) {
StringBuffer sb = new StringBuffer();
for(PluginInfoWrapper pi : wrappers) {
sb.append(pi.getFilename());
sb.append('\n');
}
return sb.toString();
}
private String[] getConfigLoadString() {
ArrayList<String> v = new ArrayList<String>();
synchronized(pluginWrappers) {
if(!started) return toStart;
for(PluginInfoWrapper pi : pluginWrappers) {
v.add(pi.getFilename());
}
for(String s : pluginsFailedLoad.keySet()) {
v.add(s);
}
}
return v.toArray(new String[v.size()]);
}
/**
* Returns a set of all currently starting plugins.
*
* @return All currently starting plugins
*/
public Set<PluginProgress> getStartingPlugins() {
synchronized(startingPlugins) {
return new HashSet<PluginProgress>(startingPlugins);
}
}
// try to guess around...
public PluginInfoWrapper startPluginAuto(final String pluginname, boolean store) {
OfficialPluginDescription desc;
if((desc = isOfficialPlugin(pluginname)) != null) {
return startPluginOfficial(pluginname, store, desc, false, false);
}
try {
new FreenetURI(pluginname); // test for MalformedURLException
return startPluginFreenet(pluginname, store);
} catch(MalformedURLException e) {
// not a freenet key
}
File[] roots = File.listRoots();
for(File f : roots) {
if(pluginname.startsWith(f.getName()) && new File(pluginname).exists()) {
return startPluginFile(pluginname, store);
}
}
return startPluginURL(pluginname, store);
}
public PluginInfoWrapper startPluginOfficial(final String pluginname, boolean store, boolean force, boolean forceHTTPS) {
return startPluginOfficial(pluginname, store, officialPlugins.get(pluginname), force, forceHTTPS);
}
public PluginInfoWrapper startPluginOfficial(final String pluginname, boolean store, OfficialPluginDescription desc, boolean force, boolean forceHTTPS) {
if((alwaysLoadOfficialPluginsFromCentralServer && !force)|| force && forceHTTPS) {
return realStartPlugin(new PluginDownLoaderOfficialHTTPS(), pluginname, store, false);
} else {
return realStartPlugin(new PluginDownLoaderOfficialFreenet(client, node, false), pluginname, store, false);
}
}
public PluginInfoWrapper startPluginFile(final String filename, boolean store) {
return realStartPlugin(new PluginDownLoaderFile(), filename, store, false);
}
public PluginInfoWrapper startPluginURL(final String filename, boolean store) {
return realStartPlugin(new PluginDownLoaderURL(), filename, store, false);
}
public PluginInfoWrapper startPluginFreenet(final String filename, boolean store) {
return realStartPlugin(new PluginDownLoaderFreenet(client, node, false), filename, store, false);
}
private PluginInfoWrapper realStartPlugin(final PluginDownLoader<?> pdl, final String filename, final boolean store, boolean ignoreOld) {
if(filename.trim().length() == 0)
return null;
final PluginProgress pluginProgress = new PluginProgress(filename, pdl);
synchronized(startingPlugins) {
startingPlugins.add(pluginProgress);
}
Logger.normal(this, "Loading plugin: " + filename);
FredPlugin plug;
PluginInfoWrapper pi = null;
try {
plug = loadPlugin(pdl, filename, pluginProgress, ignoreOld);
if (plug == null)
return null; // Already loaded
pluginProgress.setProgress(PluginProgress.PROGRESS_STATE.STARTING);
pi = new PluginInfoWrapper(node, plug, filename, pdl.isOfficialPluginLoader());
PluginHandler.startPlugin(PluginManager.this, pi);
synchronized (pluginWrappers) {
pluginWrappers.add(pi);
pluginsFailedLoad.remove(filename);
}
Logger.normal(this, "Plugin loaded: " + filename);
} catch (PluginNotFoundException e) {
Logger.normal(this, "Loading plugin failed (" + filename + ')', e);
boolean stillTrying = false;
if(pdl instanceof PluginDownLoaderOfficialFreenet) {
PluginDownLoaderOfficialFreenet downloader = (PluginDownLoaderOfficialFreenet) pdl;
if(!(downloader.fatalFailure() || downloader.desperate || twoCopiesInStartingPlugins(filename))) {
// Retry forever...
final PluginDownLoaderOfficialFreenet retry =
new PluginDownLoaderOfficialFreenet(client, node, true);
stillTrying = true;
node.getTicker().queueTimedJob(new Runnable() {
@Override
public void run() {
realStartPlugin(retry, filename, store, true);
}
}, 0);
}
} else if(pdl instanceof PluginDownLoaderFreenet) {
PluginDownLoaderFreenet downloader = (PluginDownLoaderFreenet) pdl;
if(!(downloader.fatalFailure() || downloader.desperate || twoCopiesInStartingPlugins(filename))) {
// Retry forever...
final PluginDownLoaderFreenet retry =
new PluginDownLoaderFreenet(client, node, true);
stillTrying = true;
node.getTicker().queueTimedJob(new Runnable() {
@Override
public void run() {
realStartPlugin(retry, filename, store, true);
}
}, 0);
}
}
PluginLoadFailedUserAlert newAlert =
new PluginLoadFailedUserAlert(filename,
pdl instanceof PluginDownLoaderOfficialHTTPS || pdl instanceof PluginDownLoaderOfficialFreenet, pdl instanceof PluginDownLoaderOfficialFreenet, stillTrying, e);
PluginLoadFailedUserAlert oldAlert = null;
synchronized (pluginWrappers) {
oldAlert = pluginsFailedLoad.put(filename, newAlert);
}
core.alerts.register(newAlert);
core.alerts.unregister(oldAlert);
} catch (UnsupportedClassVersionError e) {
Logger.error(this, "Could not load plugin " + filename + " : " + e,
e);
System.err.println("Could not load plugin " + filename + " : " + e);
e.printStackTrace();
System.err.println("Plugin " + filename + " appears to require a later JVM");
Logger.error(this, "Plugin " + filename + " appears to require a later JVM");
PluginLoadFailedUserAlert newAlert =
new PluginLoadFailedUserAlert(filename, pdl instanceof PluginDownLoaderOfficialHTTPS || pdl instanceof PluginDownLoaderOfficialFreenet, pdl instanceof PluginDownLoaderOfficialFreenet, false, l10n("pluginReqNewerJVMTitle", "name", filename));
PluginLoadFailedUserAlert oldAlert = null;
synchronized (pluginWrappers) {
oldAlert = pluginsFailedLoad.put(filename, newAlert);
}
core.alerts.register(newAlert);
core.alerts.unregister(oldAlert);
} catch (Throwable e) {
Logger.error(this, "Could not load plugin " + filename + " : " + e, e);
System.err.println("Could not load plugin " + filename + " : " + e);
e.printStackTrace();
System.err.println("Plugin "+filename+" is broken, but we want to retry after next startup");
Logger.error(this, "Plugin "+filename+" is broken, but we want to retry after next startup");
PluginLoadFailedUserAlert newAlert =
new PluginLoadFailedUserAlert(filename, pdl instanceof PluginDownLoaderOfficialHTTPS || pdl instanceof PluginDownLoaderOfficialFreenet, pdl instanceof PluginDownLoaderOfficialFreenet, false, e);
PluginLoadFailedUserAlert oldAlert = null;
synchronized (pluginWrappers) {
oldAlert = pluginsFailedLoad.put(filename, newAlert);
}
core.alerts.register(newAlert);
core.alerts.unregister(oldAlert);
} finally {
synchronized (startingPlugins) {
startingPlugins.remove(pluginProgress);
}
}
/* try not to destroy the config. */
synchronized(this) {
if (store)
core.storeConfig();
}
if(pi != null)
node.nodeUpdater.startPluginUpdater(filename);
return pi;
}
private synchronized boolean twoCopiesInStartingPlugins(String filename) {
int count = 0;
for(PluginProgress progress : startingPlugins) {
if(filename.equals(progress.name)) {
count++;
if(count == 2) return true;
}
}
return false;
}
class PluginLoadFailedUserAlert extends AbstractUserAlert {
final String filename;
final String message;
final boolean official;
final boolean officialFromFreenet;
final boolean stillTryingOverFreenet;
public PluginLoadFailedUserAlert(String filename, boolean official, boolean officialFromFreenet, boolean stillTryingOverFreenet, String message) {
this.filename = filename;
this.official = official;
this.message = message;
this.officialFromFreenet = officialFromFreenet;
this.stillTryingOverFreenet = stillTryingOverFreenet;
}
public PluginLoadFailedUserAlert(String filename, boolean official, boolean officialFromFreenet, boolean stillTryingOverFreenet, Throwable e) {
this.filename = filename;
this.official = official;
this.stillTryingOverFreenet = stillTryingOverFreenet;
String msg;
if(e instanceof PluginNotFoundException)
msg = e.getMessage();
else
// If it's something wierd, we need to know what it is.
msg = e.getClass() + ": " + e.getMessage();
if(msg == null) msg = e.toString();
this.message = msg;
this.officialFromFreenet = officialFromFreenet;
}
@Override
public String dismissButtonText() {
return l10n("deleteFailedPluginButton");
}
@Override
public void onDismiss() {
synchronized(pluginWrappers) {
pluginsFailedLoad.remove(filename);
}
node.executor.execute(new Runnable() {
@Override
public void run() {
cancelRunningLoads(filename, null);
}
});
}
@Override
public String anchor() {
return "pluginfailed:"+filename;
}
@Override
public HTMLNode getHTMLText() {
HTMLNode div = new HTMLNode("div");
HTMLNode p = div.addChild("p");
p.addChild("#", l10n("pluginLoadingFailedWithMessage", new String[] { "name", "message" }, new String[] { filename, message }));
if(stillTryingOverFreenet) {
div.addChild("p", l10n("pluginLoadingFailedStillTryingOverFreenet"));
}
if(official) {
p = div.addChild("p");
if(officialFromFreenet)
p.addChild("#", l10n("officialPluginLoadFailedSuggestTryAgainFreenet"));
else
p.addChild("#", l10n("officialPluginLoadFailedSuggestTryAgainHTTPS"));
HTMLNode reloadForm = div.addChild("form", new String[] { "action", "method" }, new String[] { "/plugins/", "post" });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", node.clientCore.formPassword });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "plugin-name", filename });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "pluginSource", "https" });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "submit-official", l10n("officialPluginLoadFailedTryAgain") });
if(!stillTryingOverFreenet) {
reloadForm = div.addChild("form", new String[] { "action", "method" }, new String[] { "/plugins/", "post" });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", node.clientCore.formPassword });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "plugin-name", filename });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "pluginSource", "freenet" });
reloadForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "submit-official", l10n("officialPluginLoadFailedTryAgainFreenet") });
}
}
return div;
}
@Override
public short getPriorityClass() {
return UserAlert.ERROR;
}
@Override
public String getShortText() {
return l10n("pluginLoadingFailedShort", "name", filename);
}
@Override
public String getText() {
return l10n("pluginLoadingFailedWithMessage", new String[] { "name", "message" }, new String[] { filename, message });
}
@Override
public String getTitle() {
return l10n("pluginLoadingFailedTitle");
}
@Override
public boolean isEventNotification() {
return false;
}
@Override
public boolean isValid() {
boolean success;
synchronized(pluginWrappers) {
success = pluginsFailedLoad.containsKey(filename);
}
if(!success) {
core.alerts.unregister(this);
}
return success;
}
@Override
public void isValid(boolean validity) {
}
@Override
public boolean shouldUnregisterOnDismiss() {
return true;
}
@Override
public boolean userCanDismiss() {
return true;
}
}
void register(PluginInfoWrapper pi) {
FredPlugin plug = pi.getPlugin();
// handles FProxy? If so, register
if(pi.isPproxyPlugin())
registerToadlet(plug);
if(pi.isConfigurablePlugin()) {
// Registering the toadlet with atFront=false means that
// the node's ConfigToadlet will clobber the plugin's
// ConfigToadlet and the page will not be visible. So it
// must be registered with atFront=true. This means that
// malicious plugins could try to hijack node config
// pages, to ill effect. Let's avoid that.
boolean pluginIsTryingToHijackNodeConfig = false;
for(SubConfig subconfig : node.config.getConfigs()) {
if(pi.getPluginClassName().equals(subconfig.getPrefix())) {
pluginIsTryingToHijackNodeConfig = true;
break;
}
}
if(pluginIsTryingToHijackNodeConfig) {
Logger.warning(this, "The plugin loaded from "+pi.getFilename()+" is attempting to hijack a node configuration page; refusing to register its ConfigToadlet");
} else {
Toadlet toadlet = pi.getConfigToadlet();
core.getToadletContainer().register(toadlet, "FProxyToadlet.categoryConfig", toadlet.path(), true, "ConfigToadlet."+pi.getPluginClassName()+".label", "ConfigToadlet."+pi.getPluginClassName()+".tooltip", true, null, (FredPluginL10n)pi.getPlugin());
}
}
if(pi.isIPDetectorPlugin())
node.ipDetector.registerIPDetectorPlugin((FredPluginIPDetector) plug);
if(pi.isPortForwardPlugin())
node.ipDetector.registerPortForwardPlugin((FredPluginPortForward) plug);
if(pi.isBandwidthIndicator())
node.ipDetector.registerBandwidthIndicatorPlugin((FredPluginBandwidthIndicator) plug);
}
public void cancelRunningLoads(String filename, PluginProgress exceptFor) {
Logger.normal(this, "Cancelling loads for plugin "+filename);
ArrayList<PluginProgress> matches = null;
synchronized(this) {
for(Iterator<PluginProgress> i = startingPlugins.iterator();i.hasNext();) {
PluginProgress progress = i.next();
if(progress == exceptFor) continue;
if(!filename.equals(progress.name)) continue;
if(matches == null) matches = new ArrayList<PluginProgress>();
matches.add(progress);
i.remove();
}
}
if(matches == null) return;
for(PluginProgress progress : matches) {
progress.kill();
}
}
/**
* Returns the translation of the given key, prefixed by the short name of
* the current class.
*
* @param key
* The key to fetch
* @return The translation
*/
static String l10n(String key) {
return NodeL10n.getBase().getString("PluginManager." + key);
}
private static String l10n(String key, String pattern, String value) {
return NodeL10n.getBase().getString("PluginManager." + key, pattern, value);
}
/**
* Returns the translation of the given key, replacing each occurence of
* <code>${<em>pattern</em>}</code> with <code>value</code>.
*
* @param key
* The key to fetch
* @param patterns
* The patterns to replace
* @param values
* The values to substitute
* @return The translation
*/
private String l10n(String key, String[] patterns, String[] values) {
return NodeL10n.getBase().getString("PluginManager." + key, patterns, values);
}
private void registerToadlet(FredPlugin pl) {
//toadletList.put(e.getStackTrace()[1].getClass().toString(), pl);
synchronized(toadletList) {
toadletList.put(pl.getClass().getName(), pl);
}
Logger.normal(this, "Added HTTP handler for /plugins/" + pl.getClass().getName() + '/');
}
/**
* Remove a plugin from the plugin list.
*/
public void removePlugin(PluginInfoWrapper pi) {
synchronized(pluginWrappers) {
if((!stopping) && !pluginWrappers.remove(pi))
return;
}
core.storeConfig();
}
/**
* Removes the cached copy of the given plugin from the plugins/ directory.
*
* @param pluginSpecification
* The plugin specification
*/
public void removeCachedCopy(String pluginSpecification) {
if(pluginSpecification == null) {
// Will be null if the file for a given plugin can't be found, eg. if it has already been
// removed. Ignore it since the file isn't there anyway
Logger.warning(this, "Can't remove null from cache. Ignoring");
return;
}
int lastSlash = pluginSpecification.lastIndexOf('/');
String pluginFilename;
if(lastSlash == -1)
/* Windows, maybe? */
lastSlash = pluginSpecification.lastIndexOf('\\');
File pluginDirectory = node.getPluginDir();
if(lastSlash == -1) {
/* it's an official plugin or filename without path */
if (pluginSpecification.toLowerCase().endsWith(".jar"))
pluginFilename = pluginSpecification;
else
pluginFilename = pluginSpecification + ".jar";
} else
pluginFilename = pluginSpecification.substring(lastSlash + 1);
if(logDEBUG)
Logger.minor(this, "Delete plugin - plugname: " + pluginSpecification + " filename: " + pluginFilename, new Exception("debug"));
File[] cachedFiles = getPreviousInstances(pluginDirectory, pluginFilename);
for (File cachedFile : cachedFiles) {
if (!cachedFile.delete())
if(logMINOR) Logger.minor(this, "Can't delete file " + cachedFile);
}
}
public void unregisterPluginToadlet(PluginInfoWrapper pi) {
synchronized(toadletList) {
try {
toadletList.remove(pi.getPluginClassName());
Logger.normal(this, "Removed HTTP handler for /plugins/" +
pi.getPluginClassName() + '/', new Exception("debug"));
} catch(Throwable ex) {
Logger.error(this, "removing Plugin", ex);
}
}
}
public void addToadletSymlinks(PluginInfoWrapper pi) {
synchronized(toadletList) {
try {
String targets[] = pi.getPluginToadletSymlinks();
if(targets == null)
return;
for(String target: targets) {
toadletList.remove(target);
Logger.normal(this, "Removed HTTP symlink: " + target +
" => /plugins/" + pi.getPluginClassName() + '/');
}
} catch(Throwable ex) {
Logger.error(this, "removing Toadlet-link", ex);
}
}
}
public void removeToadletSymlinks(PluginInfoWrapper pi) {
synchronized(toadletList) {
String rm = null;
try {
String targets[] = pi.getPluginToadletSymlinks();
if(targets == null)
return;
for(String target: targets) {
rm = target;
toadletList.remove(target);
pi.removePluginToadletSymlink(target);
Logger.normal(this, "Removed HTTP symlink: " + target +
" => /plugins/" + pi.getPluginClassName() + '/');
}
} catch(Throwable ex) {
Logger.error(this, "removing Toadlet-link: " + rm, ex);
}
}
}
public String dumpPlugins() {
StringBuilder out = new StringBuilder();
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
out.append(pi.toString());
out.append('\n');
}
}
return out.toString();
}
public Set<PluginInfoWrapper> getPlugins() {
TreeSet<PluginInfoWrapper> out = new TreeSet<PluginInfoWrapper>();
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
out.add(pi);
}
}
return out;
}
/**
* look for PluginInfo for a Plugin with given classname
* @param plugname
* @return the PluginInfo or null if not found
*/
public PluginInfoWrapper getPluginInfo(String plugname) {
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
if(pi.getPluginClassName().equals(plugname) || pi.getFilename().equals(plugname))
return pi;
}
}
return null;
}
/**
* look for a FCPPlugin with given classname
* @param plugname
* @return the plugin or null if not found
*/
public FredPluginFCP getFCPPlugin(String plugname) {
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
if(pi.isFCPPlugin() && pi.getPluginClassName().equals(plugname) && !pi.isStopping())
return (FredPluginFCP) pi.plug;
}
}
return null;
}
/**
* look for a Plugin with given classname
* @param plugname
* @return the true if not found
*/
public boolean isPluginLoaded(String plugname) {
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
if(pi.getPluginClassName().equals(plugname) || pi.getFilename().equals(plugname))
return true;
}
}
return false;
}
/**
* @param plugname The plugin filename e.g. "Library" for an official plugin.
* @return the true if not found
*/
public boolean isPluginLoadedOrLoadingOrWantLoad(String plugname) {
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size(); i++) {
PluginInfoWrapper pi = pluginWrappers.get(i);
if(pi.getFilename().equals(plugname))
return true;
}
}
if(pluginsFailedLoad.containsKey(plugname)) return true;
for(PluginProgress prog : startingPlugins)
if(prog.name.equals(plugname)) return true;
return false;
}
public String handleHTTPGet(String plugin, HTTPRequest request) throws PluginHTTPException {
FredPlugin handler = null;
synchronized(toadletList) {
handler = toadletList.get(plugin);
}
if (!(handler instanceof FredPluginHTTP)) {
throw new NotFoundPluginHTTPException("Plugin not loaded!", "/plugins");
}
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader pluginClassLoader = handler.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(pluginClassLoader);
try {
return ((FredPluginHTTP) handler).handleHTTPGet(request);
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
}
public String handleHTTPPost(String plugin, HTTPRequest request) throws PluginHTTPException {
FredPlugin handler = null;
synchronized(toadletList) {
handler = toadletList.get(plugin);
}
if (handler == null)
throw new NotFoundPluginHTTPException("Plugin '"+plugin+"' not found!", "/plugins");
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader pluginClassLoader = handler.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(pluginClassLoader);
try {
if(handler instanceof FredPluginHTTP)
return ((FredPluginHTTP) handler).handleHTTPPost(request);
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
throw new NotFoundPluginHTTPException("Plugin '"+plugin+"' not found!", "/plugins");
}
public void killPlugin(String name, long maxWaitTime, boolean reloading) {
PluginInfoWrapper pi = null;
boolean found = false;
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size() && !found; i++) {
pi = pluginWrappers.get(i);
if(pi.getThreadName().equals(name)) {
found = true;
break;
}
}
}
if(found)
pi.stopPlugin(this, maxWaitTime, reloading);
}
public void killPluginByFilename(String name, long maxWaitTime, boolean reloading) {
PluginInfoWrapper pi = null;
boolean found = false;
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size() && !found; i++) {
pi = pluginWrappers.get(i);
if(pi.getFilename().equals(name)) {
found = true;
break;
}
}
}
if(found)
pi.stopPlugin(this, maxWaitTime, reloading);
}
public void killPluginByClass(String name, long maxWaitTime) {
PluginInfoWrapper pi = null;
boolean found = false;
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size() && !found; i++) {
pi = pluginWrappers.get(i);
if(pi.getPluginClassName().equals(name)) {
found = true;
break;
}
}
}
if(found)
pi.stopPlugin(this, maxWaitTime, false);
}
public void killPlugin(FredPlugin plugin, long maxWaitTime) {
PluginInfoWrapper pi = null;
boolean found = false;
synchronized(pluginWrappers) {
for(int i = 0; i < pluginWrappers.size() && !found; i++) {
pi = pluginWrappers.get(i);
if(pi.plug == plugin)
found = true;
}
}
if(found)
pi.stopPlugin(this, maxWaitTime, false);
}
public OfficialPluginDescription getOfficialPlugin(String name) {
return officialPlugins.get(name);
}
public Collection<OfficialPluginDescription> getOfficialPlugins() {
return officialPlugins.getAll();
}
/**
* Returns a list of the names of all available official plugins. Right now
* this list is hardcoded but in future we could retrieve this list from emu
* or from freenet itself.
*
* @return A list of all available plugin names
*/
public List<OfficialPluginDescription> findAvailablePlugins() {
List<OfficialPluginDescription> availablePlugins = new ArrayList<OfficialPluginDescription>();
availablePlugins.addAll(officialPlugins.getAll());
return availablePlugins;
}
public OfficialPluginDescription isOfficialPlugin(String name) {
if((name == null) || (name.trim().length() == 0))
return null;
List<OfficialPluginDescription> availablePlugins = findAvailablePlugins();
for(OfficialPluginDescription desc : availablePlugins) {
if(desc.name.equals(name))
return desc;
}
return null;
}
/** Separate lock for plugin loading. Don't use (this) as we also use that for
* writing the config file, and because we do a lot inside the lock below; it
* must not be taken in any other circumstance. */
private final Object pluginLoadSyncObject = new Object();
/** All plugin updates are on a single request client. */
public final RequestClient singleUpdaterRequestClient = new RequestClient() {
@Override
public boolean persistent() {
return false;
}
@Override
public boolean realTimeFlag() {
return false;
}
};
public File getPluginFilename(String pluginName) {
File pluginDirectory = node.getPluginDir();
if((pluginDirectory.exists() && !pluginDirectory.isDirectory()) || (!pluginDirectory.exists() && !pluginDirectory.mkdirs()))
return null;
return new File(pluginDirectory, pluginName + ".jar");
}
/**
* Tries to load a plugin from the given name. If the name only contains the
* name of a plugin it is loaded from the plugin directory, if found,
* otherwise it's loaded from the project server. If the name contains a
* complete url and the short file already exists in the plugin directory
* it's loaded from the plugin directory, otherwise it's retrieved from the
* remote server.
* @param pdl
*
* @param name
* The specification of the plugin
* @param ignoreOld If true, ignore any old cached copies of the plugin,
* and download a new version anyway. This is especially important on Windows,
* where we will not usually be able to delete the file after determining
* that it is too old.
* @return An instanciated object of the plugin
* @throws PluginNotFoundException
* If anything goes wrong.
*/
private FredPlugin loadPlugin(PluginDownLoader<?> pdl, String name, PluginProgress progress, boolean ignoreOld) throws PluginNotFoundException {
pdl.setSource(name);
/* check for plugin directory. */
File pluginDirectory = node.getPluginDir();
if((pluginDirectory.exists() && !pluginDirectory.isDirectory()) || (!pluginDirectory.exists() && !pluginDirectory.mkdirs())) {
Logger.error(this, "could not create plugin directory");
throw new PluginNotFoundException("could not create plugin directory");
}
/* get plugin filename. */
String filename = pdl.getPluginName(name);
boolean pluginIsLocal = pdl instanceof PluginDownLoaderFile;
File pluginFile = new File(pluginDirectory, filename + "-" + System.currentTimeMillis());
/* check for previous instances and delete them. */
File[] filesInPluginDirectory = getPreviousInstances(pluginDirectory, filename);
boolean first = true;
for (File cachedFile : filesInPluginDirectory) {
if (first && !pluginIsLocal && !ignoreOld) {
first = false;
pluginFile = new File(pluginDirectory, cachedFile.getName());
continue;
}
first = false;
cachedFile.delete();
}
boolean downloaded = false;
/* check if file needs to be downloaded. */
if(logMINOR)
Logger.minor(this, "plugin file " + pluginFile.getAbsolutePath() + " exists: " + pluginFile.exists()+" downloader "+pdl+" name "+name);
int RETRIES = 5;
for(int i = 0; i < RETRIES; i++) {
if(!pluginFile.exists() || pluginFile.length() == 0)
try {
downloaded = true;
System.err.println("Downloading plugin "+name);
WrapperManager.signalStarting((int) MINUTES.toMillis(5));
File tempPluginFile = null;
OutputStream pluginOutputStream = null;
InputStream pluginInputStream = null;
try {
tempPluginFile = File.createTempFile("plugin-", ".jar", pluginDirectory);
tempPluginFile.deleteOnExit();
pluginOutputStream = new FileOutputStream(tempPluginFile);
pluginInputStream = pdl.getInputStream(progress);
byte[] buffer = new byte[1024];
int read;
while((read = pluginInputStream.read(buffer)) != -1) {
pluginOutputStream.write(buffer, 0, read);
}
pluginOutputStream.close();
if(tempPluginFile.length() == 0)
throw new PluginNotFoundException("downloaded zero length file");
if(!FileUtil.renameTo(tempPluginFile, pluginFile)) {
Logger.error(this, "could not rename temp file to plugin file");
throw new PluginNotFoundException("could not rename temp file to plugin file");
}
// try strongest first
String testsum = null;
String digest = pdl.getSHA256sum();
if(digest == null) {
digest = pdl.getSHA1sum();
} else {
testsum = getFileDigest(pluginFile, "SHA-256");
}
if(digest != null && testsum == null) {
testsum = getFileDigest(pluginFile, "SHA-1");
}
if(digest != null) {
if(!(digest.equalsIgnoreCase(testsum))) {
Logger.error(this, "Checksum verification failed, should be " + digest + " but was " + testsum);
throw new PluginNotFoundException("Checksum verification failed, should be " + digest + " but was " + testsum);
}
}
} catch(IOException ioe1) {
Logger.error(this, "could not load plugin", ioe1);
if(tempPluginFile != null)
tempPluginFile.delete();
throw new PluginNotFoundException("could not load plugin: " + ioe1.getMessage(), ioe1);
} finally {
Closer.close(pluginOutputStream);
Closer.close(pluginInputStream);
}
} catch(PluginNotFoundException e) {
if(i < RETRIES - 1) {
Logger.normal(this, "Failed to load plugin: " + e, e);
continue;
} else
throw e;
}
cancelRunningLoads(name, progress);
// we do quite a lot inside the lock, use a dedicated one
synchronized (pluginLoadSyncObject) {
/* now get the manifest file. */
JarFile pluginJarFile = null;
String pluginMainClassName = null;
try {
pluginJarFile = new JarFile(pluginFile);
Manifest manifest = pluginJarFile.getManifest();
if(manifest == null) {
Logger.error(this, "could not load manifest from plugin file");
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("could not load manifest from plugin file");
}
Attributes mainAttributes = manifest.getMainAttributes();
if(mainAttributes == null) {
Logger.error(this, "manifest does not contain attributes");
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("manifest does not contain attributes");
}
pluginMainClassName = mainAttributes.getValue("Plugin-Main-Class");
if(pluginMainClassName == null) {
Logger.error(this, "manifest does not contain a Plugin-Main-Class attribute");
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("manifest does not contain a Plugin-Main-Class attribute");
}
if(this.isPluginLoaded(pluginMainClassName)) {
Logger.error(this, "Plugin already loaded: "+filename);
return null;
}
} catch(JarException je1) {
Logger.error(this, "could not process jar file", je1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("could not process jar file", je1);
} catch(ZipException ze1) {
Logger.error(this, "could not process jar file", ze1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("could not process jar file", ze1);
} catch(IOException ioe1) {
Logger.error(this, "error processing jar file", ioe1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("error procesesing jar file", ioe1);
} finally {
Closer.close(pluginJarFile);
}
try {
JarClassLoader jarClassLoader = new JarClassLoader(pluginFile);
Class<?> pluginMainClass = jarClassLoader.loadClass(pluginMainClassName);
Object object = pluginMainClass.newInstance();
if(!(object instanceof FredPlugin)) {
Logger.error(this, "plugin main class is not a plugin");
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("plugin main class is not a plugin");
}
if(pdl instanceof PluginDownLoaderOfficialHTTPS ||
pdl instanceof PluginDownLoaderOfficialFreenet) {
System.err.println("Loading official plugin "+name);
// Check the version after loading it!
// FIXME IMPORTANT Build the version into the manifest. This is actually pretty easy and just involves changing build.xml.
// We already do similar things elsewhere.
// Ugh, this is just as messy ... ideas???? Maybe we need to have OS
// detection and use grep/sed on unix and find on windows???
OfficialPluginDescription desc = officialPlugins.get(name);
long minVer = desc.minimumVersion;
long ver = -1;
if(minVer != -1) {
if(object instanceof FredPluginRealVersioned) {
ver = ((FredPluginRealVersioned)object).getRealVersion();
}
}
// FIXME l10n the PluginNotFoundException errors.
if(ver < minVer) {
System.err.println("Failed to load plugin "+name+" : TOO OLD: need at least version "+minVer+" but is "+ver);
Logger.error(this, "Failed to load plugin "+name+" : TOO OLD: need at least version "+minVer+" but is "+ver);
// At this point, the constructor has run, so it's theoretically possible that the plugin has e.g. created some threads.
// However, it has not been able to use any of the node's services, because we haven't passed it the PluginRespirator.
// So there is no need to call runPlugin and terminate().
// And it doesn't matter all that much if the shutdown fails - we won't be able to delete the file on Windows anyway, we're relying on the ignoreOld logic.
// Plus, this will not cause a leak of more than one fd per plugin, even when it has started threads.
try {
jarClassLoader.close();
} catch (Throwable t) {
Logger.error(this, "Failed to close jar classloader for plugin: "+t, t);
}
pluginFile.delete();
if(!downloaded) continue;
throw new PluginTooOldException("plugin too old: need at least version "+minVer+" but is "+ver);
}
}
if(object instanceof FredPluginL10n) {
((FredPluginL10n)object).setLanguage(NodeL10n.getBase().getSelectedLanguage());
}
if(object instanceof FredPluginBaseL10n) {
((FredPluginBaseL10n)object).setLanguage(NodeL10n.getBase().getSelectedLanguage());
}
if(object instanceof FredPluginThemed) {
((FredPluginThemed)object).setTheme(fproxyTheme);
}
return (FredPlugin) object;
} catch(IOException ioe1) {
Logger.error(this, "could not load plugin", ioe1);
pluginFile.delete();
throw new PluginNotFoundException("could not load plugin", ioe1);
} catch(ClassNotFoundException cnfe1) {
Logger.error(this, "could not find plugin class", cnfe1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException(
"could not find plugin class: \"" + cnfe1.getMessage() + "\"", cnfe1);
} catch(InstantiationException ie1) {
Logger.error(this, "could not instantiate plugin", ie1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("could not instantiate plugin", ie1);
} catch(IllegalAccessException iae1) {
Logger.error(this, "could not access plugin main class", iae1);
pluginFile.delete();
throw new PluginNotFoundException("could not access plugin main class", iae1);
} catch(NoClassDefFoundError ncdfe1) {
Logger.error(this, "could not find class def, may a missing lib?", ncdfe1);
pluginFile.delete();
if(!downloaded) continue;
throw new PluginNotFoundException("could not find class def, may a missing lib?", ncdfe1);
} catch(Throwable t) {
Logger.error(this, "unexpected error while plugin loading", t);
pluginFile.delete();
throw new PluginNotFoundException("unexpected error while plugin loading " + t, t);
}
}
}
return null;
}
/**
* This returns all existing instances of cached JAR files that start with
* the given filename followed by a dash (“-”), sorted numerically by the
* appendix, largest (i.e. newest) first.
*
* @param pluginDirectory
* The plugin cache directory
* @param filename
* The name of the JAR file
* @return All cached instances
*/
private File[] getPreviousInstances(File pluginDirectory, final String filename) {
File[] cachedFiles = pluginDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile() && pathname.getName().startsWith(filename);
}
});
Arrays.sort(cachedFiles, new Comparator<File>() {
@Override
public int compare(File file1, File file2) {
return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, extractTimestamp(file2.getName()) - extractTimestamp(file1.getName())));
}
private long extractTimestamp(String filename) {
int lastIndexOfDash = filename.lastIndexOf(".jar-");
if (lastIndexOfDash == -1) {
return 0;
}
try {
return Long.parseLong(filename.substring(lastIndexOfDash + 5));
} catch (NumberFormatException nfe1) {
return 0;
}
}
});
return cachedFiles;
}
private String getFileDigest(File file, String digest) throws PluginNotFoundException {
final int BUFFERSIZE = 4096;
MessageDigest hash = null;
FileInputStream fis = null;
BufferedInputStream bis = null;
boolean wasFromDigest256Pool = false;
String result;
try {
if ("SHA-256".equals(digest)) {
hash = SHA256.getMessageDigest(); // grab digest from pool
wasFromDigest256Pool = true;
} else {
hash = MessageDigest.getInstance(digest);
}
// We compute the hash
// http://java.sun.com/developer/TechTips/1998/tt0915.html#tip2
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
int len = 0;
byte[] buffer = new byte[BUFFERSIZE];
while((len = bis.read(buffer)) > -1) {
hash.update(buffer, 0, len);
}
result = HexUtil.bytesToHex(hash.digest());
if (wasFromDigest256Pool)
SHA256.returnMessageDigest(hash);
} catch(Exception e) {
throw new PluginNotFoundException("Error while computing hash '"+digest+"' of the downloaded plugin: " + e, e);
} finally {
Closer.close(bis);
Closer.close(fis);
}
return result;
}
Ticker getTicker() {
return node.getTicker();
}
/**
* Tracks the progress of loading and starting a plugin.
*
* @author David ‘Bombe’ Roden <bombe@freenetproject.org>
* @version $Id$
*/
public static class PluginProgress {
enum PROGRESS_STATE {
DOWNLOADING,
STARTING
}
/** The starting time. */
private long startingTime = System.currentTimeMillis();
/** The current state. */
private PROGRESS_STATE pluginProgress;
/** The name by which the plugin is loaded. */
private String name;
/** Total. Might be bytes, might be blocks. */
private int total;
/** Minimum for success */
private int minSuccessful;
/** Current value. Same units as total. */
private int current;
private boolean finalisedTotal;
private int failed;
private int fatallyFailed;
private final PluginDownLoader<?> loader;
/**
* Creates a new progress tracker for a plugin that is loaded by the
* given name.
*
* @param name
* The name by which the plugin is loaded
* @param pdl
*/
PluginProgress(String name, PluginDownLoader<?> pdl) {
this.name = name;
pluginProgress = PROGRESS_STATE.DOWNLOADING;
loader = pdl;
}
public void kill() {
loader.tryCancel();
}
/**
* Returns the number of milliseconds this plugin is already being
* loaded.
*
* @return The time this plugin is already being loaded (in
* milliseconds)
*/
public long getTime() {
return System.currentTimeMillis() - startingTime;
}
/**
* Returns the name by which the plugin is loaded.
*
* @return The name by which the plugin is loaded
*/
public String getName() {
return name;
}
/**
* Returns the current state of the plugin start procedure.
*
* @return The current state of the plugin
*/
public PROGRESS_STATE getProgress() {
return pluginProgress;
}
/**
* Sets the current state of the plugin start procedure
*
* @param pluginProgress
* The current state
*/
void setProgress(PROGRESS_STATE state) {
this.pluginProgress = state;
}
/**
* If this object is one of the constants {@link #DOWNLOADING} or
* {@link #STARTING}, the name of those constants will be returned,
* otherwise a textual representation of the plugin progress is
* returned.
*
* @return The name of a constant, or the plugin progress
*/
@Override
public String toString() {
return "PluginProgress[name=" + name + ",startingTime=" + startingTime + ",progress=" + pluginProgress + "]";
}
public String toLocalisedString() {
if(pluginProgress == PROGRESS_STATE.DOWNLOADING && total > 0)
return NodeL10n.getBase().getString("PproxyToadlet.startingPluginStatus.downloading") + " : "+((current*100.0) / total)+"%";
else if(pluginProgress == PROGRESS_STATE.DOWNLOADING)
return NodeL10n.getBase().getString("PproxyToadlet.startingPluginStatus.downloading");
else if(pluginProgress == PROGRESS_STATE.STARTING)
return NodeL10n.getBase().getString("PproxyToadlet.startingPluginStatus.starting");
else
return toString();
}
public HTMLNode toLocalisedHTML() {
if(pluginProgress == PROGRESS_STATE.DOWNLOADING && total > 0) {
return QueueToadlet.createProgressCell(false, true, ClientPut.COMPRESS_STATE.WORKING, current, failed, fatallyFailed, minSuccessful, total, finalisedTotal, false);
} else if(pluginProgress == PROGRESS_STATE.DOWNLOADING)
return new HTMLNode("td", NodeL10n.getBase().getString("PproxyToadlet.startingPluginStatus.downloading"));
else if(pluginProgress == PROGRESS_STATE.STARTING)
return new HTMLNode("td", NodeL10n.getBase().getString("PproxyToadlet.startingPluginStatus.starting"));
else
return new HTMLNode("td", toString());
}
public void setDownloadProgress(int minSuccess, int current, int total, int failed, int fatallyFailed, boolean finalised) {
this.pluginProgress = PROGRESS_STATE.DOWNLOADING;
this.total = total;
this.current = current;
this.minSuccessful = minSuccess;
this.failed = failed;
this.fatallyFailed = fatallyFailed;
this.finalisedTotal = finalised;
}
public void setDownloading() {
this.pluginProgress = PROGRESS_STATE.DOWNLOADING;
}
public boolean isOfficialPlugin() {
return loader.isOfficialPluginLoader();
}
public String getLocalisedPluginName() {
String pluginName = getName();
if(isOfficialPlugin()) {
return getOfficialPluginLocalisedName(pluginName);
} else return pluginName;
}
}
static String getOfficialPluginLocalisedName(String pluginName) {
return l10n("pluginName."+pluginName);
}
public void setFProxyTheme(final THEME cssName) {
//if (fproxyTheme.equals(cssName)) return;
fproxyTheme = cssName;
synchronized(pluginWrappers) {
for(PluginInfoWrapper pi: pluginWrappers) {
pi.pr.getPageMaker().setTheme(cssName);
if(pi.isThemedPlugin()) {
final FredPluginThemed plug = (FredPluginThemed)(pi.plug);
executor.execute(new Runnable() {
@Override
public void run() {
try {
plug.setTheme(cssName);
} catch (Throwable t) {
Logger.error(this, "Cought Trowable in Callback", t);
}
}}, "Callback");
}
}
}
}
public static void setLanguage(LANGUAGE lang) {
if (selfinstance == null) return;
selfinstance.setPluginLanguage(lang);
}
private void setPluginLanguage(final LANGUAGE lang) {
synchronized(pluginWrappers) {
for(PluginInfoWrapper pi: pluginWrappers) {
if(pi.isL10nPlugin()) {
final FredPluginL10n plug = (FredPluginL10n)(pi.plug);
executor.execute(new Runnable() {
@Override
public void run() {
try {
plug.setLanguage(lang);
} catch (Throwable t) {
Logger.error(this, "Cought Trowable in Callback", t);
}
}}, "Callback");
} else if(pi.isBaseL10nPlugin()) {
final FredPluginBaseL10n plug = (FredPluginBaseL10n)(pi.plug);
executor.execute(new Runnable() {
@Override
public void run() {
try {
plug.setLanguage(lang);
} catch (Throwable t) {
Logger.error(this, "Cought Trowable in Callback", t);
}
}}, "Callback");
}
}
}
}
public THEME getFProxyTheme() {
return fproxyTheme;
}
public boolean loadOfficialPluginsFromWeb() {
return alwaysLoadOfficialPluginsFromCentralServer;
}
public void unregisterPlugin(PluginInfoWrapper wrapper, FredPlugin plug, boolean reloading) {
unregisterPluginToadlet(wrapper);
if(wrapper.isConfigurablePlugin()) {
core.getToadletContainer().unregister(wrapper.getConfigToadlet());
}
if(wrapper.isIPDetectorPlugin())
node.ipDetector.unregisterIPDetectorPlugin((FredPluginIPDetector)plug);
if(wrapper.isPortForwardPlugin())
node.ipDetector.unregisterPortForwardPlugin((FredPluginPortForward)plug);
if(wrapper.isBandwidthIndicator())
node.ipDetector.unregisterBandwidthIndicatorPlugin((FredPluginBandwidthIndicator)plug);
if(!reloading)
node.nodeUpdater.stopPluginUpdater(wrapper.getFilename());
}
}