[freenet-dev] [freenet-cvs] r15624 - in trunk/freenet/src/freenet: clients/http l10n node pluginmanager support support/io

Matthew Toseland toad at amphibian.dyndns.org
Tue Nov 13 23:49:43 UTC 2007


PluginProgress:
- toString() is fed into l10n somewhere. It's not always something which *can* 
be l10n'ed (it may be PluginProgress[name=...). You should deal with this.
- The constants should really be of a different type to the containing class.

Other than that, good stuff! I wonder what the purpose of the new 
JarClassLoader class is?

On Sunday 28 October 2007 18:44, you wrote:
> Author: bombe
> Date: 2007-10-28 18:44:08 +0000 (Sun, 28 Oct 2007)
> New Revision: 15624
> 
> Added:
>    trunk/freenet/src/freenet/support/JarClassLoader.java
>    trunk/freenet/src/freenet/support/io/Closer.java
>    trunk/freenet/src/freenet/support/io/StreamCopier.java
> Modified:
>    trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
>    trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
>    trunk/freenet/src/freenet/node/TextModeClientInterface.java
>    trunk/freenet/src/freenet/pluginmanager/PluginHandler.java
>    trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
>    trunk/freenet/src/freenet/pluginmanager/PluginManager.java
> Log:
> fix #1815, start plugins in background thread
> improve plugin loading interface
> save downloaded plugins
> 
> Modified: trunk/freenet/src/freenet/clients/http/PproxyToadlet.java
> ===================================================================
> --- trunk/freenet/src/freenet/clients/http/PproxyToadlet.java	2007-10-28 
18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/clients/http/PproxyToadlet.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -9,6 +9,7 @@
>  import java.net.URL;
>  import java.util.Date;
>  import java.util.Iterator;
> +import java.util.Set;
>  
>  import freenet.client.HighLevelSimpleClient;
>  import freenet.l10n.L10n;
> @@ -21,9 +22,11 @@
>  import freenet.pluginmanager.PluginInfoWrapper;
>  import freenet.pluginmanager.PluginManager;
>  import freenet.pluginmanager.RedirectPluginHTTPException;
> +import freenet.pluginmanager.PluginManager.PluginProgress;
>  import freenet.support.HTMLNode;
>  import freenet.support.Logger;
>  import freenet.support.MultiValueTable;
> +import freenet.support.TimeUtil;
>  import freenet.support.api.HTTPRequest;
>  import freenet.support.io.FileUtil;
>  
> @@ -118,104 +121,28 @@
>  		}
>  		else
>  		{
> +			final boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
> +			final boolean logNORMAL = Logger.shouldLog(Logger.NORMAL, this);
>  
> -			if (request.isPartSet("load")) {
> -				String filename = request.getPartAsString("load", 
MAX_PLUGIN_NAME_LENGTH);
> -				final boolean logMINOR = Logger.shouldLog(Logger.MINOR, this);
> -				boolean downloaded = false;
> -
> -				if(logMINOR) Logger.minor(this, "Loading "+filename);
> -				if (filename.endsWith("#")) {
> -					for (int tries = 0; (tries <= 5) && (downloaded == false); tries++) {
> -						if (filename.indexOf('@') > -1) {
> -							Logger
> -							.error(this,
> -							"We don't allow downloads from anywhere else but our server");
> -							sendErrorPage(ctx, 403, l10n("Error"), 
l10n("downloadNotAllowedFromRemoteServer"));
> -							return;
> -						}
> -						String pluginname = filename.substring(0,
> -								filename.length() - 1);
> -						filename = null;
> -
> -						URL url;
> -						InputStream is = null;
> -
> -						try {
> -							url = new URL(
> -									"http://downloads.freenetproject.org/alpha/plugins/"
> -									+ pluginname + ".jar.url");
> -							if (logMINOR)
> -								Logger.minor(this, "Downloading " + url);
> -							is = url.openStream();
> -
> -							File pluginsDirectory = new File("plugins");
> -							if (!pluginsDirectory.exists()) {
> -								Logger
> -								.normal(this,
> -								"The plugin directory hasn't been found, let's create it");
> -								if (!pluginsDirectory.mkdir()) {
> -									sendErrorPage(ctx, 500, l10n("Error"), 
l10n("pluginDirectoryNotCreated"));
> -									return;
> -								}
> -							}
> -
> -							File finalFile = new File("plugins/" + pluginname
> -									+ ".jar");
> -							if (!FileUtil.writeTo(is, finalFile))
> -								Logger.error(this,
> -										"Failed to rename the temporary file into "
> -										+ finalFile);
> -
> -							filename = "*@file://"
> -								+ FileUtil.getCanonicalFile(finalFile);
> -							if (logMINOR)
> -								Logger.minor(this, "Rewritten to " + filename);
> -							downloaded = true;
> -						} catch (MalformedURLException mue) {
> -							Logger.error(this,
> -									"MalformedURLException has occured : " + mue,
> -									mue);
> -							sendErrorPage(ctx, l10n("Error"), l10n("pluginNotDownloaded"), mue);
> -							return;
> -						} catch (FileNotFoundException e) {
> -							Logger.error(this,
> -									"FileNotFoundException has occured : " + e, e);
> -							sendErrorPage(ctx, l10n("Error"), l10n("pluginNotDownloaded"), e);
> -							return;
> -						} catch (IOException ioe) {
> -							System.out.println("Caught :" + ioe.getMessage());
> -							ioe.printStackTrace();
> -							sendErrorPage(ctx, l10n("Error"), l10n("pluginNotDownloaded"), ioe);
> -							return;
> -						} finally {
> -							try {
> -								if (is != null)
> -									is.close();
> -							} catch (IOException ioe) {
> -							}
> -						}
> -					}
> -					if (filename == null) {
> -						sendErrorPage(ctx, 500, l10n("Error"), l10n("pluginNotDownloaded"));
> -						return;
> -					}
> -					else if(!downloaded) {
> -						Logger.error(this, "Can't load the given plugin; giving up");
> -						sendErrorPage(ctx, 500, l10n("Error"), l10n("pluginNotDownloaded"));
> -						return;
> -					}
> +			if (request.isPartSet("submit-official") || 
request.isPartSet("submit-other")) {
> +				String pluginName = null;
> +				boolean refresh = request.isPartSet("refresh-on-startup");
> +				if (request.isPartSet("submit-official")) {
> +					pluginName = request.getPartAsString("plugin-name", 40);
> +				} else {
> +					pluginName = request.getPartAsString("plugin-url", 200);
>  				}
> -
> -				pm.startPlugin(filename, true);
> +				pm.startPlugin(pluginName, refresh, true);
>  				headers.put("Location", ".");
>  				ctx.sendReplyHeaders(302, "Found", headers, null, 0);
>  				return;
> -			}if (request.isPartSet("cancel")){
> +			}
> +			if (request.isPartSet("cancel")){
>  				headers.put("Location", "/plugins/");
>  				ctx.sendReplyHeaders(302, "Found", headers, null, 0);
>  				return;
> -			}if (request.getPartAsString("unloadconfirm", 
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
> +			}
> +			if (request.getPartAsString("unloadconfirm", 
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
>  				pm.killPlugin(request.getPartAsString("unloadconfirm", 
MAX_PLUGIN_NAME_LENGTH), MAX_THREADED_UNLOAD_WAIT_TIME);
>  				HTMLNode pageNode = ctx.getPageMaker().getPageNode(l10n("plugins"), 
ctx);
>  				HTMLNode contentNode = ctx.getPageMaker().getContentNode(pageNode);
> @@ -243,11 +170,13 @@
>  				return;
>  			}else if (request.getPartAsString("reload", 
MAX_PLUGIN_NAME_LENGTH).length() > 0) {
>  				String fn = null;
> +				boolean refresh = false;
>  				Iterator it = pm.getPlugins().iterator();
>  				while (it.hasNext()) {
>  					PluginInfoWrapper pi = (PluginInfoWrapper) it.next();
>  					if (pi.getThreadName().equals(request.getPartAsString("reload", 
MAX_PLUGIN_NAME_LENGTH))) {
>  						fn = pi.getFilename();
> +						refresh = pi.isAutoRefresh();
>  						break;
>  					}
>  				}
> @@ -257,7 +186,7 @@
>  							L10n.getString("PluginToadlet.pluginNotFoundReload"));
>  				} else {
>  					pm.killPlugin(request.getPartAsString("reload", 
MAX_PLUGIN_NAME_LENGTH), MAX_THREADED_UNLOAD_WAIT_TIME);
> -					pm.startPlugin(fn, true);
> +					pm.startPlugin(fn, refresh, true);
>  
>  					headers.put("Location", ".");
>  					ctx.sendReplyHeaders(302, "Found", headers, null, 0);
> @@ -297,7 +226,18 @@
>  			Logger.minor(this, "Pproxy fetching "+path);
>  		try {
>  			if (path.equals("")) {
> -				this.showPluginList(ctx, request, pm);
> +				if (!ctx.isAllowedFullAccess()) {
> +					super.sendErrorPage(ctx, 403, "Unauthorized", 
L10n.getString("Toadlet.unauthorized"));
> +					return;
> +				}
> +
> +				HTMLNode pageNode = 
ctx.getPageMaker().getPageNode(l10n("pluginsWithNodeName", "name", 
core.getMyName()), ctx);
> +				HTMLNode contentNode = ctx.getPageMaker().getContentNode(pageNode);
> +
> +				this.showStartingPlugins(ctx, request, pm, contentNode);
> +				this.showPluginList(ctx, request, pm, contentNode);
> +
> +				writeHTMLReply(ctx, 200, "OK", pageNode.generate());
>  			} else {
>  				// split path into plugin class name and 'data' path for plugin
>  				int to = path.indexOf("/");
> @@ -336,16 +276,44 @@
>  		}
>  	}
>  
> -	private void showPluginList(ToadletContext ctx, HTTPRequest request, 
PluginManager pm) throws ToadletContextClosedException, IOException {
> -		if(!ctx.isAllowedFullAccess()) {
> -			super.sendErrorPage(ctx, 403, "Unauthorized", 
L10n.getString("Toadlet.unauthorized"));
> -			return;
> +	/**
> +	 * Shows a list of all currently loading plugins.
> +	 * 
> +	 * @param toadletContext
> +	 *            The toadlet context
> +	 * @param request
> +	 *            The HTTP request
> +	 * @param pluginManager
> +	 *            The plugin manager
> +	 * @throws ToadletContextClosedException
> +	 *             if the toadlet context is closed
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	private void showStartingPlugins(ToadletContext toadletContext, 
HTTPRequest request, PluginManager pluginManager, HTMLNode contentNode) 
throws ToadletContextClosedException, IOException {
> +		Set/*<PluginProgress>*/ startingPlugins = 
pluginManager.getStartingPlugins();
> +		if (!startingPlugins.isEmpty()) {
> +			HTMLNode startingPluginsBox = 
contentNode.addChild("div", "class", "infobox infobox-normal");
> +			startingPluginsBox.addChild("div", "class", "infobox-header", 
l10n("startingPluginsTitle"));
> +			HTMLNode startingPluginsContent = 
startingPluginsBox.addChild("div", "class", "infobox-content");
> +			HTMLNode startingPluginsTable = 
startingPluginsContent.addChild("table");
> +			HTMLNode startingPluginsHeader = startingPluginsTable.addChild("tr");
> +			startingPluginsHeader.addChild("th", l10n("startingPluginName"));
> +			startingPluginsHeader.addChild("th", l10n("startingPluginStatus"));
> +			startingPluginsHeader.addChild("th", l10n("startingPluginTime"));
> +			Iterator/*<PluginProgress>*/ startingPluginsIterator = 
startingPlugins.iterator();
> +			while (startingPluginsIterator.hasNext()) {
> +				PluginProgress pluginProgress = (PluginProgress) 
startingPluginsIterator.next();
> +				HTMLNode startingPluginsRow = startingPluginsTable.addChild("tr");
> +				startingPluginsRow.addChild("td", pluginProgress.getName());
> +				startingPluginsRow.addChild("td", l10n("startingPluginStatus." + 
pluginProgress.getProgress().toString()));
> +				startingPluginsRow.addChild("td", "aligh", "right", 
TimeUtil.formatTime(pluginProgress.getTime()));
> +			}
>  		}
> +	}
>  
> +	private void showPluginList(ToadletContext ctx, HTTPRequest request, 
PluginManager pm, HTMLNode contentNode) throws ToadletContextClosedException, 
IOException {
>  		if (!request.hasParameters()) {
> -			HTMLNode pageNode = 
ctx.getPageMaker().getPageNode(l10n("pluginsWithNodeName", "name", 
core.getMyName()), ctx);
> -			HTMLNode contentNode = ctx.getPageMaker().getContentNode(pageNode);
> -
>  			HTMLNode infobox = contentNode.addChild("div", "class", "infobox 
infobox-normal");
>  			infobox.addChild("div", "class", "infobox-header", 
L10n.getString("PluginToadlet.pluginListTitle"));
>  			HTMLNode infoboxContent = 
infobox.addChild("div", "class", "infobox-content");
> @@ -386,12 +354,27 @@
>  				}
>  			}
>  
> -			HTMLNode addForm = 
ctx.addFormChild(infoboxContent, ".", "addPluginForm");
> -			HTMLNode loadDiv = addForm.addChild("div");
> -			loadDiv.addChild("#", (l10n("loadPluginLabel") + ' '));
> -			loadDiv.addChild("input", new String[] { "type", "name", "size" }, new 
String[] { "text", "load", "40" });
> -			loadDiv.addChild("input", new String[] { "type", "value" }, new String[] 
{ "submit", l10n("Load") });
> -			writeHTMLReply(ctx, 200, "OK", pageNode.generate());
> +			/* box for "official" plugins. */
> +			HTMLNode addOfficialPluginBox = 
contentNode.addChild("div", "class", "infobox infobox-normal");
> +			addOfficialPluginBox.addChild("div", "class", "infobox-header", 
l10n("loadOfficialPlugin"));
> +			HTMLNode addOfficialPluginContent = 
addOfficialPluginBox.addChild("div", "class", "infobox-content");
> +			HTMLNode addOfficialForm = 
ctx.addFormChild(addOfficialPluginContent, ".", "addOfficialPluginForm");
> +			addOfficialForm.addChild("div", l10n("loadOfficialPluginText"));
> +			addOfficialForm.addChild("#", (l10n("loadOfficialPluginLabel") + ": "));
> +			addOfficialForm.addChild("input", new String[] 
{ "type", "name", "size" }, new String[] { "text", "plugin-name", "40" });
> +			addOfficialForm.addChild("input", new String[] 
{ "type", "name", "value", "checked" }, new String[] 
{ "checkbox", "refresh-on-startup", "tue", "checked" }, 
l10n("refreshOnStartup"));
> +			addOfficialForm.addChild("input", new String[] 
{ "type", "name", "value" }, new String[] { "submit", "submit-official", 
l10n("Load") });
> +
> +			/* box for unofficial plugins. */
> +			HTMLNode addOtherPluginBox = 
contentNode.addChild("div", "class", "infobox infobox-normal");
> +			addOtherPluginBox.addChild("div", "class", "infobox-header", 
l10n("loadOtherPlugin"));
> +			HTMLNode addOtherPluginContent = 
addOtherPluginBox.addChild("div", "class", "infobox-content");
> +			HTMLNode addOtherForm = 
ctx.addFormChild(addOtherPluginContent, ".", "addOtherPluginForm");
> +			addOtherForm.addChild("div", l10n("loadOtherPluginText"));
> +			addOtherForm.addChild("#", (l10n("loadOtherURLLabel") + ": "));
> +			addOtherForm.addChild("input", new String[] { "type", "name", "size" }, 
new String[] { "text", "plugin-url", "80" });
> +			addOtherForm.addChild("input", new String[] { "type", "name", "value" }, 
new String[] { "checkbox", "refresh-on-startup", "true" }, 
l10n("refreshOnStartup"));
> +			addOtherForm.addChild("input", new String[] { "type", "name", "value" }, 
new String[] { "submit", "submit-other", l10n("Load") });
>  		} 
>  	}
>  }
> 
> Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
> ===================================================================
> --- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties	2007-10-28 
18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -684,6 +684,9 @@
>  PluginManager.loadedPluginsLong=A list of plugins that are started when the 
node starts
>  PluginManager.pluginReqNewerJVM=The plugin ${name} seems to require a later 
JVM. Please install at least Sun java 1.5, or remove the plugin.
>  PluginManager.pluginReqNewerJVMTitle=Later JVM required by plugin ${name}.
> +PluginManager.pluginLoadingFailedTitle=Could not load plugin!
> +PluginManager.pluginLoadingFailed=The plugin specified by ${name} could not 
be loaded
> +PluginManager.pluginLoadingFailedWithMessage=The plugin specified by 
${name} could not be loaded: ${message}
>  PluginToadlet.addPluginTitle=Add a plugin
>  PluginToadlet.failedToLoadPlugin=Failed to load plugin.
>  PluginToadlet.failedToLoadPluginCheckClass=The plugin you requested could 
not be loaded. Please verify the name of the plugin's class and the URL, if 
you gave one.
> @@ -706,7 +709,12 @@
>  PproxyToadlet.Error=Error
>  PproxyToadlet.internalIDTitle=Internal ID
>  PproxyToadlet.Load=Load
> -PproxyToadlet.loadPluginLabel=Load Plugin:
> +PproxyToadlet.loadOfficialPlugin=Add an Official Plugin
> +PproxyToadlet.loadOfficialPluginText=These plugins are hosted on servers of 
The Freenet Project. We believe that these plugins are free of privacy leaks 
though we will not guarantee it. Possible plugin names are: Echo, Freemail, 
HelloWorld (yes, really), JSTUN, Librarian, MDNSDiscovery, SNMP, TestGallery, 
UPnP, XMLLibrarian, XMLSpider. Plugin names are case-sensitive.
> +PproxyToadlet.loadOfficialPluginLabel=Load Official Plugin
> +PproxyToadlet.loadOtherPlugin=Add an Unofficial Plugin
> +PproxyToadlet.loadOtherPluginText=Here you can enter the URL of a plugin 
you want to load. Other plugins than the ones listed above are not even 
remotely supported or checked for privacy leaks by us, so if you load a 
remote plugin here, you are basically on your own.
> +PproxyToadlet.loadOtherURLLabel=Plugin URL
>  PproxyToadlet.noPlugins=No plugins loaded
>  PproxyToadlet.pluginNotFoundReload=The specified plugin could not be 
located in order to reload it.
>  PproxyToadlet.pluginNotFoundReloadTitle=Plugin Not Found (reloading)
> @@ -714,9 +722,16 @@
>  PproxyToadlet.pluginUnloadedWithName=The plugin ${name} has been unloaded.
>  PproxyToadlet.plugins=Plugins
>  PproxyToadlet.pluginsWithNodeName=Plugins of ${name}
> +PproxyToadlet.refreshOnStartup=Reload from server on startup
>  PproxyToadlet.reload=Reload
>  PproxyToadlet.returnToPluginPage=Return to plugin page
>  PproxyToadlet.startedAtTitle=Started at
> +PproxyToadlet.startingPluginsTitle=Starting Plugins
> +PproxyToadlet.startingPluginName=Plugin name
> +PproxyToadlet.startingPluginStatus=Current status
> +PproxyToadlet.startingPluginStatus.downloading=downloading
> +PproxyToadlet.startingPluginStatus.starting=starting
> +PproxyToadlet.startingPluginTime=Time spent
>  PproxyToadlet.pluginDirectoryNotCreated=The plugin directory could not be 
created.
>  PproxyToadlet.pluginNotDownloaded=The plugin could not be downloaded.
>  PproxyToadlet.pluginStopping=Plugin Stopping
> 
> Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
> ===================================================================
> --- trunk/freenet/src/freenet/node/TextModeClientInterface.java	2007-10-28 
18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/node/TextModeClientInterface.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -878,22 +878,19 @@
>          	probeAll();
>          } else if(uline.startsWith("PLUGLOAD:")) {
>          	if (line.substring("PLUGLOAD:".length()).trim().equals("?")) {
> -        		outsb.append("  PLUGLOAD: pkg.Class                  - Load 
plugin from current classpath");        		
> -        		outsb.append("  PLUGLOAD: pkg.Class at file:<filename>  - Load 
plugin from file");
> -        		outsb.append("  PLUGLOAD: pkg.Class at http://...       - Load 
plugin from online file");
> -        		outsb.append("  PLUGLOAD:         *@...              - Load 
plugin from manifest in given jarfile");
> +        		outsb.append("  PLUGLOAD: pluginName         - Load official 
plugin from freenetproject.org");
> +        		outsb.append("  PLUGLOAD: file://<filename>  - Load plugin from 
file");
> +        		outsb.append("  PLUGLOAD: http://...         - Load plugin from 
online file");
>          		outsb.append("");
> -        		outsb.append("If the filename/url ends with \".url\", it" +
> -        				" is treated as a link, meaning that the first line is" +
> -        				" the accual URL. Else it is loaded as classpath and" +
> -        				" the class it loaded from it (meaning the file could" +
> -        				" be either a jar-file or a class-file).");
> -        		outsb.append("");
> -        		outsb.append("  PLUGLOAD: pkg.Class*  - Load newest version of 
plugin from http://downloads.freenetproject.org/alpha/plugins/");        		
> -        		outsb.append("");
> -        		
> -        	} else
> -        		
n.pluginManager.startPlugin(line.substring("PLUGLOAD:".length()).trim(), 
true);
> +        		outsb.append("If you append as asterisk (\"*\") to the name or 
URL, the plugin will be reloaded from the remote server on startup.");
> +        	} else {
> +        		String name = line.substring("PLUGLOAD:".length()).trim();
> +        		boolean refresh = name.endsWith("*");
> +        		if (refresh) {
> +        			name = name.substring(0, name.length() - 1);
> +        		}
> +        		n.pluginManager.startPlugin(name, refresh, true);
> +        	}
>              //outsb.append("PLUGLOAD: <pkg.classname>[(@<URI to 
jarfile.jar>|<<URI to file containing real URI>|* (will load from freenets 
pluginpool))] - Load plugin.");
>          } else if(uline.startsWith("PLUGLIST")) {
>          	outsb.append(n.pluginManager.dumpPlugins());
> 
> Modified: trunk/freenet/src/freenet/pluginmanager/PluginHandler.java
> ===================================================================
> --- trunk/freenet/src/freenet/pluginmanager/PluginHandler.java	2007-10-28 
18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/pluginmanager/PluginHandler.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -19,8 +19,8 @@
>  	 * 
>  	 * @param plug
>  	 */
> -	public static PluginInfoWrapper startPlugin(PluginManager pm, String 
filename, FredPlugin plug, PluginRespirator pr) {
> -		final PluginInfoWrapper pi = new PluginInfoWrapper(plug, filename);
> +	public static PluginInfoWrapper startPlugin(PluginManager pm, String 
filename, FredPlugin plug, PluginRespirator pr, boolean refresh) {
> +		final PluginInfoWrapper pi = new PluginInfoWrapper(plug, filename, 
refresh);
>  		final PluginStarter ps = new PluginStarter(pr, pi);
>  		ps.setPlugin(pm, plug);
>  		// We must start the plugin *after startup has finished*
> 
> Modified: trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java
> ===================================================================
> --- trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java	
2007-10-28 18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/pluginmanager/PluginInfoWrapper.java	
2007-10-28 18:44:08 UTC (rev 15624)
> @@ -19,13 +19,14 @@
>  	private boolean isThreadlessPlugin;
>  	private boolean isIPDetectorPlugin;
>  	private boolean isPortForwardPlugin;
> +	private boolean autoRefresh;
>  	private String filename;
>  	private HashSet toadletLinks=new HashSet();
>  	private boolean stopping = false;
>  	private boolean unregistered = false;
>  	//public String 
>  	
> -	public PluginInfoWrapper(FredPlugin plug, String filename) {
> +	public PluginInfoWrapper(FredPlugin plug, String filename, boolean 
autoRefresh) {
>  		this.plug = plug;
>  		if (fedPluginThread) return;
>  		className = plug.getClass().toString();
> @@ -37,8 +38,20 @@
>  		isThreadlessPlugin = (plug instanceof FredPluginThreadless);
>  		isIPDetectorPlugin = (plug instanceof FredPluginIPDetector);
>  		isPortForwardPlugin = (plug instanceof FredPluginPortForward);
> +		this.autoRefresh = autoRefresh;
>  	}
> -	
> +
> +	/**
> +	 * Returns whether this plugin should be refreshed from the server on
> +	 * startup.
> +	 * 
> +	 * @return <code>true</code> if the plugin should be refresh on startup,
> +	 *         <code>false</code> otherwise
> +	 */
> +	public boolean isAutoRefresh() {
> +		return autoRefresh;
> +	}
> +
>  	void setThread(Thread ps) {
>  		if(thread != null)
>  			throw new IllegalStateException("Already set a thread");
> 
> Modified: trunk/freenet/src/freenet/pluginmanager/PluginManager.java
> ===================================================================
> --- trunk/freenet/src/freenet/pluginmanager/PluginManager.java	2007-10-28 
18:33:01 UTC (rev 15623)
> +++ trunk/freenet/src/freenet/pluginmanager/PluginManager.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -4,10 +4,14 @@
>  package freenet.pluginmanager;
>  
>  import java.io.BufferedReader;
> +import java.io.File;
> +import java.io.FileOutputStream;
>  import java.io.IOException;
>  import java.io.InputStream;
>  import java.io.InputStreamReader;
> +import java.io.OutputStream;
>  import java.net.JarURLConnection;
> +import java.net.MalformedURLException;
>  import java.net.URI;
>  import java.net.URL;
>  import java.net.URLClassLoader;
> @@ -17,7 +21,12 @@
>  import java.util.Iterator;
>  import java.util.Set;
>  import java.util.Vector;
> +import java.util.jar.Attributes;
> +import java.util.jar.JarEntry;
> +import java.util.jar.JarException;
>  import java.util.jar.JarFile;
> +import java.util.jar.Manifest;
> +import java.util.zip.ZipException;
>  
>  import freenet.config.InvalidConfigValueException;
>  import freenet.config.SubConfig;
> @@ -27,10 +36,13 @@
>  import freenet.node.Ticker;
>  import freenet.node.useralerts.SimpleUserAlert;
>  import freenet.node.useralerts.UserAlert;
> +import freenet.support.JarClassLoader;
>  import freenet.support.Logger;
>  import freenet.support.URIPreEncoder;
>  import freenet.support.api.HTTPRequest;
>  import freenet.support.api.StringArrCallback;
> +import freenet.support.io.Closer;
> +import freenet.support.io.StreamCopier;
>  
>  public class PluginManager {
>  
> @@ -45,7 +57,11 @@
>  	 */
>  
>  	private final HashMap toadletList;
> -	private final Vector/*<PluginInfoWrapper>*/ pluginWrappers;
> +
> +	/* All currently starting plugins. */
> +	private final Set/* <PluginProgress> */startingPlugins = new HashSet/* 
<PluginProgress> */();
> +
> +	private final Vector/* <PluginInfoWrapper> */pluginWrappers;
>  	private PluginRespirator pluginRespirator = null;
>  	final Node node;
>  	private final NodeClientCore core;
> @@ -67,6 +83,7 @@
>  			public String[] get() {
>  				return getConfigLoadString();
>  			}
> +
>  			public void set(String[] val) throws InvalidConfigValueException {
>  				//if(storeDir.equals(new File(val))) return;
>  				// FIXME
> @@ -76,9 +93,13 @@
>  
>  		String fns[] = pmconfig.getStringArr("loadplugin");
>  		if (fns != null) {
> -			for (int i = 0 ; i < fns.length ; i++) {
> -				//System.err.println("Load: " + StringArrOption.decode(fns[i]));
> -				startPlugin(fns[i], false);
> +			for (int i = 0; i < fns.length; i++) {
> +				String name = fns[i];
> +				boolean refresh = name.endsWith("*");
> +				if (refresh) {
> +					name = name.substring(0, name.length() - 1);
> +				}
> +				startPlugin(name, refresh, false);
>  			}
>  		}
>  
> @@ -98,8 +119,10 @@
>  
>  			Vector v = new Vector();
>  
> -			while(it.hasNext())
> -				v.add(((PluginInfoWrapper)it.next()).getFilename());
> +			while (it.hasNext()) {
> +				PluginInfoWrapper pluginInfoWrapper = (PluginInfoWrapper) it.next();
> +				v.add(pluginInfoWrapper.getFilename() + 
(pluginInfoWrapper.isAutoRefresh() ? "*" : ""));
> +			}
>  
>  			return (String[]) v.toArray(new String[v.size()]);
>  		}catch (NullPointerException e){
> @@ -108,35 +131,63 @@
>  		}
>  	}
>  
> -	public void startPlugin(String filename, boolean store) {
> +	/**
> +	 * Returns a set of all currently starting plugins.
> +	 * 
> +	 * @return All currently starting plugins
> +	 */
> +	public Set/* <PluginProgess> */getStartingPlugins() {
> +		synchronized (startingPlugins) {
> +			return new HashSet/* <PluginProgress> */(startingPlugins);
> +		}
> +	}
> +
> +	public void startPlugin(final String filename, final boolean refresh, 
final boolean store) {
>  		if (filename.trim().length() == 0)
>  			return;
> -		Logger.normal(this, "Loading plugin: " + filename);
> -		FredPlugin plug;
> -		try {
> -			plug = LoadPlugin(filename);
> -			PluginInfoWrapper pi = PluginHandler.startPlugin(this, filename, plug, 
pluginRespirator);
> -			synchronized (pluginWrappers) {
> -				pluginWrappers.add(pi);
> +		final PluginProgress pluginProgress = new PluginProgress(filename);
> +		synchronized (startingPlugins) {
> +			startingPlugins.add(pluginProgress);
> +		}
> +		node.executor.execute(new Runnable() {
> +
> +			public void run() {
> +				Logger.normal(this, "Loading plugin: " + filename);
> +				FredPlugin plug;
> +				try {
> +					plug = loadPlugin(filename, refresh);
> +					pluginProgress.setProgress(PluginProgress.STARTING);
> +					PluginInfoWrapper pi = PluginHandler.startPlugin(PluginManager.this, 
filename, plug, pluginRespirator, refresh);
> +					synchronized (pluginWrappers) {
> +						pluginWrappers.add(pi);
> +					}
> +					Logger.normal(this, "Plugin loaded: " + filename);
> +				} catch (PluginNotFoundException e) {
> +					Logger.normal(this, "Loading plugin failed (" + filename + ')', e);
> +					String message = e.getMessage();
> +					core.alerts.register(new SimpleUserAlert(true, 
l10n("pluginLoadingFailedTitle"), l10n("pluginLoadingFailedWithMessage", new 
String[] { "name", "message" }, new String[] { filename, message }), 
UserAlert.ERROR));
> +				} catch (UnsupportedClassVersionError e) {
> +					Logger.error(this, "Could not load plugin " + filename + " : " + e, 
e);
> +					System.err.println("Could not load plugin " + filename + " : " + e);
> +					e.printStackTrace();
> +					String jvmVersion = System.getProperty("java.vm.version");
> +					if (jvmVersion.startsWith("1.4.") || jvmVersion.equals("1.4")) {
> +						System.err.println("Plugin " + filename + " appears to require a 
later JVM");
> +						Logger.error(this, "Plugin " + filename + " appears to require a 
later JVM");
> +						core.alerts.register(new SimpleUserAlert(true, 
l10n("pluginReqNewerJVMTitle", "name", filename), 
l10n("pluginReqNewerJVM", "name", filename), UserAlert.ERROR));
> +					}
> +				} finally {
> +					synchronized (startingPlugins) {
> +						startingPlugins.remove(pluginProgress);
> +					}
> +				}
> +				/* try not to destroy the config. */
> +				synchronized (this) {
> +					if (store)
> +						core.storeConfig();
> +				}
>  			}
> -			Logger.normal(this, "Plugin loaded: " + filename);
> -		} catch (PluginNotFoundException e) {
> -			Logger.normal(this, "Loading plugin failed (" + filename + ')', e);
> -		} catch (UnsupportedClassVersionError e) {
> -			Logger.error(this, "Could not load plugin "+filename+" : "+e, e);
> -			System.err.println("Could not load plugin "+filename+" : "+e);
> -			e.printStackTrace();
> -			String jvmVersion = System.getProperty("java.vm.version");
> -			if(jvmVersion.startsWith("1.4.") || jvmVersion.equals("1.4")) {
> -				System.err.println("Plugin "+filename+" appears to require a later 
JVM");
> -				Logger.error(this, "Plugin "+filename+" appears to require a later 
JVM");
> -				core.alerts.register(new SimpleUserAlert(true, 
> -						l10n("pluginReqNewerJVMTitle", "name", filename),
> -						l10n("pluginReqNewerJVM", "name", filename),
> -						UserAlert.ERROR));
> -			}
> -		}
> -		if(store) core.storeConfig();
> +		}, "Plugin Starter");
>  	}
>  
>  	void register(FredPlugin plug, PluginInfoWrapper pi) {
> @@ -153,10 +204,38 @@
>  		}
>  	}
>  
> +	/**
> +	 * 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
> +	 */
> +	private String l10n(String key) {
> +		return L10n.getString("PluginManager." + key);
> +	}
> +
>  	private String l10n(String key, String pattern, String value) {
>  		return L10n.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 L10n.getString("PluginManager." + key, patterns, values);
> +	}
> +
>  	private void registerToadlet(FredPlugin pl){
>  		//toadletList.put(e.getStackTrace()[1].getClass().toString(), pl);
>  		synchronized (toadletList) {
> @@ -313,180 +392,253 @@
>  	}
>  
>  	/**
> -	 * Method to load a plugin from the given path and return is as an object.
> -	 * Will accept filename to be of one of the following forms:
> -	 * "classname" to load a class from the current classpath
> -	 * "classame at file:/path/to/jarfile.jar" to load class from an other 
jarfile.
> -	 *
> -	 * @param filename 	The filename to load from
> -	 * @return			An instanciated object of the plugin
> -	 * @throws PluginNotFoundException	If anything goes wrong.
> +	 * Tries to load a plugin from the given name. If the name only contains 
the
> +	 * name of a plugin and the plugin should not be refreshed on startup it 
is
> +	 * loaded from the plugin directory, if found, otherwise it's refresh from
> +	 * the project server. If the name contains a complete url and the short
> +	 * file already exists in the plugin directory and the plugin should not 
be
> +	 * refreshed, it's loaded from the plugin directory, otherwise it's
> +	 * retrieved from the remote server.
> +	 * 
> +	 * @param name
> +	 *            The specification of the plugin
> +	 * @param refresh
> +	 *            Whether the file should be refreshed on startup
> +	 * @return An instanciated object of the plugin
> +	 * @throws PluginNotFoundException
> +	 *             If anything goes wrong.
>  	 */
> -	private FredPlugin LoadPlugin(String origFilename)
> -	throws PluginNotFoundException {
> -		logMINOR = Logger.shouldLog(Logger.MINOR, this);
> -		Class cls = null;
> -		for (int tries = 0; (tries <= 5) && (cls == null); tries++) {
> -			String filename = origFilename;
> -			if (filename.endsWith("*")) {
> -				filename = "*@http://downloads.freenetproject.org/alpha/plugins/"
> -						+ filename.substring(filename.lastIndexOf(".") + 1,
> -								filename.length() - 1) + ".jar.url";
> -				if (logMINOR)
> -					Logger.minor(this, "Rewritten to " + filename);
> +	private FredPlugin loadPlugin(String name, boolean refresh) throws 
PluginNotFoundException {
> +		/* check if name contains a URL. */
> +		URL pluginUrl = null;
> +		try {
> +			pluginUrl = new URL(name);
> +		} catch (MalformedURLException mue1) {
> +		}
> +		if (pluginUrl == null) {
> +			try {
> +				pluginUrl = new 
URL("http://downloads.freenetproject.org/alpha/plugins/" + name 
+ ".jar.url");
> +			} catch (MalformedURLException mue1) {
> +				Logger.error(this, "could not build plugin url for " + name, mue1);
> +				throw new PluginNotFoundException("could not build plugin url for " + 
name, mue1);
>  			}
> +		}
> +
> +		/* check for plugin directory. */
> +		File pluginDirectory = new File("plugins");
> +		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 completeFilename = pluginUrl.getPath();
> +		String filename = 
completeFilename.substring(completeFilename.lastIndexOf('/') + 1);
> +		File pluginFile = new File(pluginDirectory, filename);
> +
> +		/* check if file needs to be downloaded. */
> +		if (logMINOR) {
> +			Logger.minor(this, "plugin file " + pluginFile.getAbsolutePath() + " 
exists: " + pluginFile.exists());
> +		}
> +		if (refresh || !pluginFile.exists()) {
> +			File tempPluginFile = null;
> +			OutputStream pluginOutputStream = null;
> +			URLConnection urlConnection = null;
> +			InputStream pluginInputStream = null;
>  			try {
> -				BufferedReader in = null;
> -				InputStream is = null;
> -				if ((filename.indexOf("@") >= 0)) {
> -					boolean assumeURLRedirect = true;
> -					// Open from external file
> -					try {
> -						String realURL = null;
> -						String realClass = null;
> +				tempPluginFile = File.createTempFile("plugin-", ".jar", 
pluginDirectory);
> +				pluginOutputStream = new FileOutputStream(tempPluginFile);
> +				urlConnection = pluginUrl.openConnection();
> +				urlConnection.setUseCaches(false);
> +				urlConnection.setReadTimeout(0);
> +				urlConnection.setAllowUserInteraction(false);
> +				urlConnection.setConnectTimeout(180 * 1000);
> +				urlConnection.connect();
> +				pluginInputStream = urlConnection.getInputStream();
> +				byte[] buffer = new byte[1024];
> +				int read;
> +				while ((read = pluginInputStream.read(buffer)) != -1) {
> +					System.out.println("read " + read + " bytes");
> +					pluginOutputStream.write(buffer, 0, read);
> +				}
> +			} 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);
> +			}
> +			/* move temp jar to final jar. */
> +			if (pluginFile.exists()) {
> +				if (!pluginFile.delete()) {
> +					Logger.error(this, "could not remove old plugin file");
> +					throw new PluginNotFoundException("could not remove old plugin file");
> +				}
> +			}
> +			if (!tempPluginFile.renameTo(pluginFile)) {
> +				Logger.error(this, "could not rename temp file to plugin file");
> +				throw new PluginNotFoundException("could not rename temp file to plugin 
file");
> +			}
> +		}
>  
> -						// Load the jar-file
> -						String[] parts = filename.split("@");
> -						if (parts.length != 2) {
> -							throw new PluginNotFoundException(
> -							"Could not split at \"@\".");
> -						}
> -						realClass = parts[0];
> -						realURL = parts[1];
> -						if (logMINOR)
> -							Logger.minor(this, "Class: " + realClass + " URL: "
> -									+ realURL);
> +		/* 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");
> +				throw new PluginNotFoundException("could not load manifest from plugin 
file");
> +			}
> +			Attributes pluginMainClassAttributes = manifest.getMainAttributes();
> +			if (pluginMainClassAttributes == null) {
> +				Logger.error(this, "manifest does not contain Plugin-Main-Class 
attribute");
> +				throw new PluginNotFoundException("manifest does not contain 
Plugin-Main-Class attribute");
> +			}
> +			pluginMainClassName = 
pluginMainClassAttributes.getValue("Plugin-Main-Class");
> +		} catch (JarException je1) {
> +			Logger.error(this, "could not process jar file", je1);
> +			throw new PluginNotFoundException("could not process jar file", je1);
> +		} catch (ZipException ze1) {
> +			Logger.error(this, "could not process jar file", ze1);
> +			throw new PluginNotFoundException("could not process jar file", ze1);
> +		} catch (IOException ioe1) {
> +			Logger.error(this, "error processing jar file", ioe1);
> +			throw new PluginNotFoundException("error procesesing jar file", ioe1);
> +		} finally {
> +			Closer.close(pluginJarFile);
> +		}
>  
> -						if (filename.endsWith(".url")) {
> -							if (!assumeURLRedirect) {
> -								// Load the txt-file
> -								URL url = new URL(parts[1]);
> -								URLConnection uc = url.openConnection();
> -								in = new BufferedReader(new InputStreamReader(
> -										uc.getInputStream()));
> +		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");
> +				throw new PluginNotFoundException("plugin main class is not a plugin");
> +			}
> +			return (FredPlugin) object;
> +		} catch (IOException ioe1) {
> +			Logger.error(this, "could not load plugin", ioe1);
> +			throw new PluginNotFoundException("could not load plugin", ioe1);
> +		} catch (ClassNotFoundException cnfe1) {
> +			Logger.error(this, "could not find plugin class", cnfe1);
> +			throw new PluginNotFoundException("could not find plugin class", cnfe1);
> +		} catch (InstantiationException ie1) {
> +			Logger.error(this, "could not instantiate plugin", ie1);
> +			throw new PluginNotFoundException("could not instantiate plugin", ie1);
> +		} catch (IllegalAccessException iae1) {
> +			Logger.error(this, "could not access plugin main class", iae1);
> +			throw new PluginNotFoundException("could not access plugin main class", 
iae1);
> +		}
> +	}
>  
> -								realURL = in.readLine();
> -								if (realURL == null)
> -									throw new PluginNotFoundException(
> -											"Initialization error: "
> -											+ url
> -											+ " isn't a plugin loading url!");
> -								realURL = realURL.trim();
> -								if (logMINOR)
> -									Logger.minor(this, "Loaded new URL: "
> -											+ realURL + " from .url file");
> -								in.close();
> -							}
> -							assumeURLRedirect = !assumeURLRedirect;
> -						}
> +	Ticker getTicker() {
> +		return node.getTicker();
> +	}
>  
> -						// Load the class inside file
> -						URL[] serverURLs = new URL[] { new URL(realURL) };
> -						ClassLoader cl = new URLClassLoader(serverURLs);
> +	/**
> +	 * Tracks the progress of loading and starting a plugin.
> +	 * 
> +	 * @author David &lsquo;Bombe&rsquo; Roden 
&lt;bombe at freenetproject.org&gt;
> +	 * @version $Id$
> +	 */
> +	public static class PluginProgress {
>  
> -						// Handle automatic fetching of pluginclassname
> -						if (realClass.equals("*")) {
> +		/** State for downloading. */
> +		public static final PluginProgress DOWNLOADING = new PluginProgress();
>  
> -							// Clean URL
> -							URI liburi = URIPreEncoder.encodeURI(realURL);
> -							if (logMINOR)
> -								Logger.minor(this, "cleaned url: " + realURL
> -										+ " -> " + liburi.toString());
> -							realURL = liburi.toString();
> +		/** State for starting. */
> +		public static final PluginProgress STARTING = new PluginProgress();
>  
> -							URL url = new URL("jar:" + realURL + "!/");
> -							JarURLConnection jarConnection = (JarURLConnection) url
> -							.openConnection();
> -							// Java seems to cache even file: urls...
> -							jarConnection.setUseCaches(false);
> -							JarFile jf = jarConnection.getJarFile();
> -							// URLJarFile jf = new URLJarFile(new File(liburi));
> -							// is =
> -							// jf.getInputStream(jf.getJarEntry("META-INF/MANIFEST.MF"));
> +		/** The starting time. */
> +		private long startingTime = System.currentTimeMillis();
>  
> -							// BufferedReader manifest = new BufferedReader(new
> -							// 
InputStreamReader(cl.getResourceAsStream("/META-INF/MANIFEST.MF")));
> +		/** The current state. */
> +		private PluginProgress pluginProgress;
>  
> -							// URL url = new URL(parts[1]);
> -							// URLConnection uc =
> -							// cl.getResource("/META-INF/MANIFEST.MF").openConnection();
> +		/** The name by which the plugin is loaded. */
> +		private String name;
>  
> -							is = jf.getInputStream(jf
> -									.getJarEntry("META-INF/MANIFEST.MF"));
> -							in = new BufferedReader(new InputStreamReader(is));
> -							String line;
> -							while ((line = in.readLine()) != null) {
> -								// System.err.println(line + "\t\t\t" +
> -								// realClass);
> -								if (line.startsWith("Plugin-Main-Class: ")) {
> -									realClass = line.substring(
> -											"Plugin-Main-Class: ".length())
> -											.trim();
> -									if (logMINOR)
> -										Logger.minor(this,
> -												"Found plugin main class "
> -												+ realClass
> -												+ " from manifest");
> -								}
> -							}
> -							// System.err.println("Real classname: " +
> -							// realClass);
> -						}
> +		/**
> +		 * Private constructor for state constants.
> +		 */
> +		private PluginProgress() {
> +		}
>  
> -						cls = cl.loadClass(realClass);
> +		/**
> +		 * 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
> +		 */
> +		PluginProgress(String name) {
> +			this.name = name;
> +			pluginProgress = DOWNLOADING;
> +		}
>  
> -					} finally {
> -						try {
> -							if (is != null)
> -								is.close();
> -							if (in != null)
> -								in.close();
> -						} catch (IOException ioe) {
> -						}
> -					}
> -				} else {
> -					// Load class
> -					try {
> -						cls = Class.forName(filename);
> -					} catch (ClassNotFoundException e) {
> -						throw new PluginNotFoundException(filename);
> -					}
> -				}
> +		/**
> +		 * 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;
> +		}
>  
> -				if (cls == null)
> -					throw new PluginNotFoundException("Unknown error");
> -			} catch (Exception e) {
> -				Logger.normal(this, "Failed to load plugin " + filename + " : "
> -						+ e, e);
> -				if (tries >= 5)
> -					throw new PluginNotFoundException("Initialization error:"
> -							+ filename, e);
> +		/**
> +		 * Returns the name by which the plugin is loaded.
> +		 * 
> +		 * @return The name by which the plugin is loaded
> +		 */
> +		public String getName() {
> +			return name;
> +		}
>  
> -				try {
> -					Thread.sleep(100);
> -				} catch (Exception ee) {
> -				}
> -			}
> +		/**
> +		 * Returns the current state of the plugin start procedure.
> +		 * 
> +		 * @return The current state of the plugin
> +		 */
> +		public PluginProgress getProgress() {
> +			return pluginProgress;
>  		}
>  
> -		// Class loaded... Objectize it!
> -		Object o = null;
> -		try {
> -			o = cls.newInstance();
> -		} catch (Exception e) {
> -			throw new PluginNotFoundException("Could not re-create plugin:"
> -					+ origFilename, e);
> +		/**
> +		 * Sets the current state of the plugin start procedure
> +		 * 
> +		 * @param pluginProgress
> +		 *            The current state
> +		 */
> +		void setProgress(PluginProgress pluginProgress) {
> +			this.pluginProgress = pluginProgress;
>  		}
>  
> -		// See if we have the right type
> -		if (!(o instanceof FredPlugin)) {
> -			throw new PluginNotFoundException("Not a plugin: " + origFilename);
> +		/**
> +		 * 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() {
> +			if (this == DOWNLOADING) {
> +				return "downloading";
> +			} else if (this == STARTING) {
> +				return "starting";
> +			}
> +			return "PluginProgress[name=" + name + ",startingTime=" + startingTime 
+ ",progress=" + pluginProgress + "]";
>  		}
>  
> -		return (FredPlugin) o;
>  	}
>  
> -	Ticker getTicker() {
> -		return node.getTicker();
> -	}
>  }
> 
> Added: trunk/freenet/src/freenet/support/JarClassLoader.java
> ===================================================================
> --- trunk/freenet/src/freenet/support/JarClassLoader.java	                        
(rev 0)
> +++ trunk/freenet/src/freenet/support/JarClassLoader.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -0,0 +1,162 @@
> +/*
> + * freenet - JarClassLoader.java Copyright © 2007 David Roden
> + * 
> + * This program is free software; you can redistribute it and/or modify it 
under
> + * the terms of the GNU General Public License as published by the Free 
Software
> + * Foundation; either version 2 of the License, or (at your option) any 
later
> + * version.
> + * 
> + * This program is distributed in the hope that it will be useful, but 
WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS
> + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> + * details.
> + * 
> + * You should have received a copy of the GNU General Public License along 
with
> + * this program; if not, write to the Free Software Foundation, Inc., 59 
Temple
> + * Place - Suite 330, Boston, MA 02111-1307, USA.
> + */
> +
> +package freenet.support;
> +
> +import java.io.ByteArrayOutputStream;
> +import java.io.File;
> +import java.io.FileOutputStream;
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.net.MalformedURLException;
> +import java.net.URL;
> +import java.util.jar.JarEntry;
> +import java.util.jar.JarFile;
> +
> +import freenet.support.io.StreamCopier;
> +
> +/**
> + * Class loader that loads classes from a JAR file. The JAR file gets 
copied
> + * to a temporary location; requests for classes and resources from this 
class
> + * loader are then satisfied from this local copy.
> + * 
> + * @author <a href="mailto:dr at ina-germany.de">David Roden</a>
> + * @version $Id$
> + */
> +public class JarClassLoader extends ClassLoader {
> +
> +	/** The temporary jar file. */
> +	private JarFile tempJarFile;
> +
> +	/**
> +	 * Constructs a new jar class loader that loads classes from the jar file
> +	 * with the given name in the local file system.
> +	 * 
> +	 * @param fileName
> +	 *            The name of the jar file
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public JarClassLoader(String fileName) throws IOException {
> +		this(new File(fileName));
> +	}
> +
> +	/**
> +	 * Constructs a new jar class loader that loads classes from the specified
> +	 * URL.
> +	 * 
> +	 * @param fileUrl
> +	 *            The URL to load the jar file from
> +	 * @param length
> +	 *            The length of the jar file if known, <code>-1</code>
> +	 *            otherwise
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public JarClassLoader(URL fileUrl, long length) throws IOException {
> +		copyFileToTemp(fileUrl.openStream(), length);
> +	}
> +
> +	/**
> +	 * Constructs a new jar class loader that loads classes from the specified
> +	 * file.
> +	 * 
> +	 * @param file
> +	 *            The file to load classes from
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public JarClassLoader(File file) throws IOException {
> +		tempJarFile = new JarFile(file);
> +	}
> +
> +	/**
> +	 * Copies the contents of the input stream (which are supposed to be the
> +	 * contents of a jar file) to a temporary location.
> +	 * 
> +	 * @param inputStream
> +	 *            The input stream to read from
> +	 * @param length
> +	 *            The length of the stream if known, <code>-1</code> if the
> +	 *            length is not known
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	private void copyFileToTemp(InputStream inputStream, long length) throws 
IOException {
> +		File tempFile = File.createTempFile("jar-", ".tmp");
> +		FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
> +		StreamCopier.copy(inputStream, fileOutputStream, length);
> +		fileOutputStream.close();
> +		tempFile.deleteOnExit();
> +		tempJarFile = new JarFile(tempFile);
> +	}
> +
> +	/**
> +	 * {@inheritDoc}
> +	 * <p>
> +	 * This method searches the temporary copy of the jar file for an entry
> +	 * that is specified by the given class name.
> +	 * 
> +	 * @see java.lang.ClassLoader#findClass(java.lang.String)
> +	 */
> +	protected Class findClass(String name) throws ClassNotFoundException {
> +		try {
> +			String pathName = transformName(name);
> +			JarEntry jarEntry = tempJarFile.getJarEntry(pathName);
> +			if (jarEntry != null) {
> +				long size = jarEntry.getSize();
> +				InputStream jarEntryInputStream = tempJarFile.getInputStream(jarEntry);
> +				ByteArrayOutputStream classBytesOutputStream = new 
ByteArrayOutputStream((int) size);
> +				StreamCopier.copy(jarEntryInputStream, classBytesOutputStream, size);
> +				classBytesOutputStream.close();
> +				byte[] classBytes = classBytesOutputStream.toByteArray();
> +				Class clazz = defineClass(name, classBytes, 0, classBytes.length);
> +				return clazz;
> +			}
> +			return null;
> +		} catch (IOException e) {
> +			throw new ClassNotFoundException(e.getMessage(), e);
> +		}
> +	}
> +
> +	/**
> +	 * {@inheritDoc}
> +	 * 
> +	 * @see java.lang.ClassLoader#findResource(java.lang.String)
> +	 */
> +	protected URL findResource(String name) {
> +		try {
> +			return new URL("jar:" + new File(tempJarFile.getName()).toURL() + "!" + 
name);
> +		} catch (MalformedURLException e) {
> +		}
> +		return null;
> +	}
> +
> +	/**
> +	 * Transforms the class name into a file name that can be used to locate
> +	 * an entry in the jar file.
> +	 * 
> +	 * @param name
> +	 *            The name of the class
> +	 * @return The path name of the entry in the jar file
> +	 */
> +	private String transformName(String name) {
> +		return name.replace('.', '/') + ".class";
> +	}
> +
> +}
> 
> 
> Property changes on: trunk/freenet/src/freenet/support/JarClassLoader.java
> ___________________________________________________________________
> Name: svn:keywords
>    + Id
> 
> Added: trunk/freenet/src/freenet/support/io/Closer.java
> ===================================================================
> --- trunk/freenet/src/freenet/support/io/Closer.java	                        
(rev 0)
> +++ trunk/freenet/src/freenet/support/io/Closer.java	2007-10-28 18:44:08 UTC 
(rev 15624)
> @@ -0,0 +1,82 @@
> +/*
> + * freenet - Closer.java Copyright © 2007 David Roden
> + * 
> + * This program is free software; you can redistribute it and/or modify it 
under
> + * the terms of the GNU General Public License as published by the Free 
Software
> + * Foundation; either version 2 of the License, or (at your option) any 
later
> + * version.
> + * 
> + * This program is distributed in the hope that it will be useful, but 
WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS
> + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> + * details.
> + * 
> + * You should have received a copy of the GNU General Public License along 
with
> + * this program; if not, write to the Free Software Foundation, Inc., 59 
Temple
> + * Place - Suite 330, Boston, MA 02111-1307, USA.
> + */
> +
> +package freenet.support.io;
> +
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.OutputStream;
> +import java.util.jar.JarFile;
> +
> +/**
> + * Closes various resources. The resources are checked for being
> + * <code>null</code> before being closed, and every possible execption is
> + * swallowed. That makes this class perfect for use in the finally blocks 
of
> + * try-catch-finally blocks.
> + * 
> + * @author David &lsquo;Roden&rsquo; &lt;bombe at freenetproject.org&gt;
> + * @version $Id$
> + */
> +public class Closer {
> +
> +	/**
> +	 * Closes the given output stream.
> +	 * 
> +	 * @param outputStream
> +	 *            The output stream to close
> +	 */
> +	public static void close(OutputStream outputStream) {
> +		if (outputStream != null) {
> +			try {
> +				outputStream.close();
> +			} catch (IOException ioe1) {
> +			}
> +		}
> +	}
> +
> +	/**
> +	 * Closes the given input stream.
> +	 * 
> +	 * @param inputStream
> +	 *            The input stream to close
> +	 */
> +	public static void close(InputStream inputStream) {
> +		if (inputStream != null) {
> +			try {
> +				inputStream.close();
> +			} catch (IOException ioe1) {
> +			}
> +		}
> +	}
> +
> +	/**
> +	 * Closes the given jar file.
> +	 * 
> +	 * @param jarFile
> +	 *            The jar file to close
> +	 */
> +	public static void close(JarFile jarFile) {
> +		if (jarFile != null) {
> +			try {
> +				jarFile.close();
> +			} catch (IOException e) {
> +			}
> +		}
> +	}
> +
> +}
> 
> 
> Property changes on: trunk/freenet/src/freenet/support/io/Closer.java
> ___________________________________________________________________
> Name: svn:keywords
>    + Id
> 
> Added: trunk/freenet/src/freenet/support/io/StreamCopier.java
> ===================================================================
> --- trunk/freenet/src/freenet/support/io/StreamCopier.java	                        
(rev 0)
> +++ trunk/freenet/src/freenet/support/io/StreamCopier.java	2007-10-28 
18:44:08 UTC (rev 15624)
> @@ -0,0 +1,123 @@
> +/*
> + * freenet - StreamCopier.java Copyright © 2007 David Roden
> + * 
> + * This program is free software; you can redistribute it and/or modify it 
under
> + * the terms of the GNU General Public License as published by the Free 
Software
> + * Foundation; either version 2 of the License, or (at your option) any 
later
> + * version.
> + * 
> + * This program is distributed in the hope that it will be useful, but 
WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS
> + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> + * details.
> + * 
> + * You should have received a copy of the GNU General Public License along 
with
> + * this program; if not, write to the Free Software Foundation, Inc., 59 
Temple
> + * Place - Suite 330, Boston, MA 02111-1307, USA.
> + */
> +package freenet.support.io;
> +
> +import java.io.EOFException;
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.OutputStream;
> +
> +/**
> + * Helper class that copies bytes from an {@link InputStream} to an
> + * {@link OutputStream}.
> + * 
> + * @author David &lsquo;Roden&rsquo; &lt;bombe at freenetproject.org&gt;
> + * @version $Id$
> + */
> +public class StreamCopier {
> +
> +	/** Default buffer size is 64k. */
> +	private static final int DEFAULT_BUFFER_SIZE = 1 << 16;
> +
> +	/** The current buffer size. */
> +	private static int bufferSize = DEFAULT_BUFFER_SIZE;
> +
> +	/**
> +	 * Sets the buffer size for following transfers.
> +	 * 
> +	 * @param bufferSize
> +	 *            The new buffer size
> +	 */
> +	public static void setBufferSize(int bufferSize) {
> +		StreamCopier.bufferSize = bufferSize;
> +	}
> +
> +	/**
> +	 * Copies <code>length</code> bytes from the source input stream to the
> +	 * destination output stream. If <code>length</code> is <code>-1</code>
> +	 * as much bytes as possible will be copied (i.e. until
> +	 * {@link InputStream#read()} returns <code>-1</code> to signal the end of
> +	 * the stream).
> +	 * 
> +	 * @param source
> +	 *            The input stream to read from
> +	 * @param destination
> +	 *            The output stream to write to
> +	 * @param length
> +	 *            The number of bytes to copy
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public static void copy(InputStream source, OutputStream destination, long 
length) throws IOException {
> +		long remaining = length;
> +		byte[] buffer = new byte[bufferSize];
> +		int read = 0;
> +		while ((remaining == -1) || (remaining > 0)) {
> +			read = source.read(buffer, 0, ((remaining > bufferSize) || (remaining 
== -1)) ? bufferSize : (int) remaining);
> +			if (read == -1) {
> +				if (length == -1) {
> +					return;
> +				}
> +				throw new EOFException("stream reached eof");
> +			}
> +			destination.write(buffer, 0, read);
> +			remaining -= read;
> +		}
> +	}
> +
> +	/**
> +	 * Copies as much bytes as possible (i.e. until {@link InputStream#read()}
> +	 * returns <code>-1</code>) from the source input stream to the
> +	 * destination output stream.
> +	 * 
> +	 * @param source
> +	 *            The input stream to read from
> +	 * @param destination
> +	 *            The output stream to write to
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public static void copy(InputStream source, OutputStream destination) 
throws IOException {
> +		copy(source, destination, -1);
> +	}
> +
> +	/**
> +	 * Find the length of an input stream. This method will consume the 
complete
> +	 * input stream until its {@link InputStream#read(byte[])} method returns
> +	 * <code>-1</code>, thus signalling the end of the stream.
> +	 * 
> +	 * @param source
> +	 *            The input stream to find the length of
> +	 * @return The numbe of bytes that can be read from the stream
> +	 * @throws IOException
> +	 *             if an I/O error occurs
> +	 */
> +	public static long findLength(InputStream source) throws IOException {
> +		long length = 0;
> +		byte[] buffer = new byte[bufferSize];
> +		int read = 0;
> +		while (read > -1) {
> +			read = source.read(buffer);
> +			if (read != -1) {
> +				length += read;
> +			}
> +		}
> +		return length;
> +	}
> +
> +}
> 
> 
> Property changes on: trunk/freenet/src/freenet/support/io/StreamCopier.java
> ___________________________________________________________________
> Name: svn:keywords
>    + Id
> 
> _______________________________________________
> cvs mailing list
> cvs at freenetproject.org
> http://emu.freenetproject.org/cgi-bin/mailman/listinfo/cvs
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://emu.freenetproject.org/pipermail/devl/attachments/20071113/a1f3e6ca/attachment.pgp 


More information about the Devl mailing list