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: 
<https://emu.freenetproject.org/pipermail/devl/attachments/20071113/a1f3e6ca/attachment.pgp>

Reply via email to