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


Reply via email to