aboutsummaryrefslogtreecommitdiffstats
path: root/netx/net/sourceforge/jnlp/cache
diff options
context:
space:
mode:
authorandrew <devnull@localhost>2010-10-19 17:55:59 +0100
committerandrew <devnull@localhost>2010-10-19 17:55:59 +0100
commit7603e948d7a0a7eb2e72358cb4a40ae6779f95da (patch)
treec6441f7d14eafe8119d890cddd09b05b8f88c52a /netx/net/sourceforge/jnlp/cache
Initial import from IcedTea6.
2010-10-19 Andrew John Hughes <[email protected]> * .hgignore, * Makefile.am, * acinclude.m4, * autogen.sh, * configure.ac, * extra/net/sourceforge/jnlp/about/HTMLPanel.java, * extra/net/sourceforge/jnlp/about/Main.java, * extra/net/sourceforge/jnlp/about/resources/about.html, * extra/net/sourceforge/jnlp/about/resources/applications.html, * extra/net/sourceforge/jnlp/about/resources/notes.html, * javac.in, * javaws.desktop: Imported from IcedTea6. * launcher/java.c, * launcher/java.h, * launcher/java_md.c, * launcher/java_md.h, * launcher/jli_util.h, * launcher/jni.h, * launcher/jvm.h, * launcher/jvm_md.h, * launcher/manifest_info.h, * launcher/splashscreen.h, * launcher/splashscreen_stubs.c, * launcher/version_comp.h, * launcher/wildcard.h: Imported from OpenJDK. * netx/javaws.1, * netx/javax/jnlp/BasicService.java, * netx/javax/jnlp/ClipboardService.java, * netx/javax/jnlp/DownloadService.java, * netx/javax/jnlp/DownloadServiceListener.java, * netx/javax/jnlp/ExtendedService.java, * netx/javax/jnlp/ExtensionInstallerService.java, * netx/javax/jnlp/FileContents.java, * netx/javax/jnlp/FileOpenService.java, * netx/javax/jnlp/FileSaveService.java, * netx/javax/jnlp/JNLPRandomAccessFile.java, * netx/javax/jnlp/PersistenceService.java, * netx/javax/jnlp/PrintService.java, * netx/javax/jnlp/ServiceManager.java, * netx/javax/jnlp/ServiceManagerStub.java, * netx/javax/jnlp/SingleInstanceListener.java, * netx/javax/jnlp/SingleInstanceService.java, * netx/javax/jnlp/UnavailableServiceException.java, * netx/net/sourceforge/jnlp/AppletDesc.java, * netx/net/sourceforge/jnlp/ApplicationDesc.java, * netx/net/sourceforge/jnlp/AssociationDesc.java, * netx/net/sourceforge/jnlp/ComponentDesc.java, * netx/net/sourceforge/jnlp/DefaultLaunchHandler.java, * netx/net/sourceforge/jnlp/ExtensionDesc.java, * netx/net/sourceforge/jnlp/IconDesc.java, * netx/net/sourceforge/jnlp/InformationDesc.java, * netx/net/sourceforge/jnlp/InstallerDesc.java, * netx/net/sourceforge/jnlp/JARDesc.java, * netx/net/sourceforge/jnlp/JNLPFile.java, * netx/net/sourceforge/jnlp/JNLPSplashScreen.java, * netx/net/sourceforge/jnlp/JREDesc.java, * netx/net/sourceforge/jnlp/LaunchException.java, * netx/net/sourceforge/jnlp/LaunchHandler.java, * netx/net/sourceforge/jnlp/Launcher.java, * netx/net/sourceforge/jnlp/MenuDesc.java, * netx/net/sourceforge/jnlp/NetxPanel.java, * netx/net/sourceforge/jnlp/Node.java, * netx/net/sourceforge/jnlp/PackageDesc.java, * netx/net/sourceforge/jnlp/ParseException.java, * netx/net/sourceforge/jnlp/Parser.java, * netx/net/sourceforge/jnlp/PluginBridge.java, * netx/net/sourceforge/jnlp/PropertyDesc.java, * netx/net/sourceforge/jnlp/RelatedContentDesc.java, * netx/net/sourceforge/jnlp/ResourcesDesc.java, * netx/net/sourceforge/jnlp/SecurityDesc.java, * netx/net/sourceforge/jnlp/ShortcutDesc.java, * netx/net/sourceforge/jnlp/StreamEater.java, * netx/net/sourceforge/jnlp/UpdateDesc.java, * netx/net/sourceforge/jnlp/Version.java, * netx/net/sourceforge/jnlp/cache/CacheEntry.java, * netx/net/sourceforge/jnlp/cache/CacheUtil.java, * netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java, * netx/net/sourceforge/jnlp/cache/DownloadIndicator.java, * netx/net/sourceforge/jnlp/cache/Resource.java, * netx/net/sourceforge/jnlp/cache/ResourceTracker.java, * netx/net/sourceforge/jnlp/cache/UpdatePolicy.java, * netx/net/sourceforge/jnlp/cache/package.html, * netx/net/sourceforge/jnlp/event/ApplicationEvent.java, * netx/net/sourceforge/jnlp/event/ApplicationListener.java, * netx/net/sourceforge/jnlp/event/DownloadEvent.java, * netx/net/sourceforge/jnlp/event/DownloadListener.java, * netx/net/sourceforge/jnlp/event/package.html, * netx/net/sourceforge/jnlp/package.html, * netx/net/sourceforge/jnlp/resources/Manifest.mf, * netx/net/sourceforge/jnlp/resources/Messages.properties, * netx/net/sourceforge/jnlp/resources/about.jnlp, * netx/net/sourceforge/jnlp/resources/default.jnlp, * netx/net/sourceforge/jnlp/runtime/AppThreadGroup.java, * netx/net/sourceforge/jnlp/runtime/AppletAudioClip.java, * netx/net/sourceforge/jnlp/runtime/AppletEnvironment.java, * netx/net/sourceforge/jnlp/runtime/AppletInstance.java, * netx/net/sourceforge/jnlp/runtime/ApplicationInstance.java, * netx/net/sourceforge/jnlp/runtime/Boot.java, * netx/net/sourceforge/jnlp/runtime/Boot13.java, * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java, * netx/net/sourceforge/jnlp/runtime/JNLPPolicy.java, * netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java, * netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java, * netx/net/sourceforge/jnlp/runtime/package.html, * netx/net/sourceforge/jnlp/security/AccessWarningPane.java, * netx/net/sourceforge/jnlp/security/AppletWarningPane.java, * netx/net/sourceforge/jnlp/security/CertVerifier.java, * netx/net/sourceforge/jnlp/security/CertWarningPane.java, * netx/net/sourceforge/jnlp/security/CertsInfoPane.java, * netx/net/sourceforge/jnlp/security/HttpsCertVerifier.java, * netx/net/sourceforge/jnlp/security/MoreInfoPane.java, * netx/net/sourceforge/jnlp/security/NotAllSignedWarningPane.java, * netx/net/sourceforge/jnlp/security/SecurityDialogPanel.java, * netx/net/sourceforge/jnlp/security/SecurityUtil.java, * netx/net/sourceforge/jnlp/security/SecurityWarningDialog.java, * netx/net/sourceforge/jnlp/security/SingleCertInfoPane.java, * netx/net/sourceforge/jnlp/security/VariableX509TrustManager.java, * netx/net/sourceforge/jnlp/security/viewer/CertificatePane.java, * netx/net/sourceforge/jnlp/security/viewer/CertificateViewer.java, * netx/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java, * netx/net/sourceforge/jnlp/services/InstanceExistsException.java, * netx/net/sourceforge/jnlp/services/ServiceUtil.java, * netx/net/sourceforge/jnlp/services/SingleInstanceLock.java, * netx/net/sourceforge/jnlp/services/XBasicService.java, * netx/net/sourceforge/jnlp/services/XClipboardService.java, * netx/net/sourceforge/jnlp/services/XDownloadService.java, * netx/net/sourceforge/jnlp/services/XExtendedService.java, * netx/net/sourceforge/jnlp/services/XExtensionInstallerService.java, * netx/net/sourceforge/jnlp/services/XFileContents.java, * netx/net/sourceforge/jnlp/services/XFileOpenService.java, * netx/net/sourceforge/jnlp/services/XFileSaveService.java, * netx/net/sourceforge/jnlp/services/XJNLPRandomAccessFile.java, * netx/net/sourceforge/jnlp/services/XPersistenceService.java, * netx/net/sourceforge/jnlp/services/XPrintService.java, * netx/net/sourceforge/jnlp/services/XServiceManagerStub.java, * netx/net/sourceforge/jnlp/services/XSingleInstanceService.java, * netx/net/sourceforge/jnlp/services/package.html, * netx/net/sourceforge/jnlp/tools/CharacterEncoder.java, * netx/net/sourceforge/jnlp/tools/HexDumpEncoder.java, * netx/net/sourceforge/jnlp/tools/JarRunner.java, * netx/net/sourceforge/jnlp/tools/JarSigner.java, * netx/net/sourceforge/jnlp/tools/JarSignerResources.java, * netx/net/sourceforge/jnlp/tools/KeyStoreUtil.java, * netx/net/sourceforge/jnlp/tools/KeyTool.java, * netx/net/sourceforge/jnlp/util/FileUtils.java, * netx/net/sourceforge/jnlp/util/PropertiesFile.java, * netx/net/sourceforge/jnlp/util/Reflect.java, * netx/net/sourceforge/jnlp/util/WeakList.java, * netx/net/sourceforge/jnlp/util/XDesktopEntry.java, * netx/net/sourceforge/nanoxml/XMLElement.java, * netx/net/sourceforge/nanoxml/XMLParseException.java, * plugin/icedteanp/IcedTeaJavaRequestProcessor.cc, * plugin/icedteanp/IcedTeaJavaRequestProcessor.h, * plugin/icedteanp/IcedTeaNPPlugin.cc, * plugin/icedteanp/IcedTeaNPPlugin.h, * plugin/icedteanp/IcedTeaPluginRequestProcessor.cc, * plugin/icedteanp/IcedTeaPluginRequestProcessor.h, * plugin/icedteanp/IcedTeaPluginUtils.cc, * plugin/icedteanp/IcedTeaPluginUtils.h, * plugin/icedteanp/IcedTeaRunnable.cc, * plugin/icedteanp/IcedTeaRunnable.h, * plugin/icedteanp/IcedTeaScriptablePluginObject.cc, * plugin/icedteanp/IcedTeaScriptablePluginObject.h, * plugin/icedteanp/java/netscape/javascript/JSException.java, * plugin/icedteanp/java/netscape/javascript/JSObject.java, * plugin/icedteanp/java/netscape/javascript/JSObjectCreatePermission.java, * plugin/icedteanp/java/netscape/javascript/JSProxy.java, * plugin/icedteanp/java/netscape/javascript/JSRunnable.java, * plugin/icedteanp/java/netscape/javascript/JSUtil.java, * plugin/icedteanp/java/netscape/security/ForbiddenTargetException.java, * plugin/icedteanp/java/sun/applet/AppletSecurityContextManager.java, * plugin/icedteanp/java/sun/applet/GetMemberPluginCallRequest.java, * plugin/icedteanp/java/sun/applet/GetWindowPluginCallRequest.java, * plugin/icedteanp/java/sun/applet/JavaConsole.java, * plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java, * plugin/icedteanp/java/sun/applet/PasswordAuthenticationDialog.java, * plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java, * plugin/icedteanp/java/sun/applet/PluginAppletViewer.java, * plugin/icedteanp/java/sun/applet/PluginCallRequest.java, * plugin/icedteanp/java/sun/applet/PluginCallRequestFactory.java, * plugin/icedteanp/java/sun/applet/PluginClassLoader.java, * plugin/icedteanp/java/sun/applet/PluginCookieInfoRequest.java, * plugin/icedteanp/java/sun/applet/PluginCookieManager.java, * plugin/icedteanp/java/sun/applet/PluginDebug.java, * plugin/icedteanp/java/sun/applet/PluginException.java, * plugin/icedteanp/java/sun/applet/PluginMain.java, * plugin/icedteanp/java/sun/applet/PluginMessageConsumer.java, * plugin/icedteanp/java/sun/applet/PluginMessageHandlerWorker.java, * plugin/icedteanp/java/sun/applet/PluginObjectStore.java, * plugin/icedteanp/java/sun/applet/PluginProxyInfoRequest.java, * plugin/icedteanp/java/sun/applet/PluginProxySelector.java, * plugin/icedteanp/java/sun/applet/PluginStreamHandler.java, * plugin/icedteanp/java/sun/applet/RequestQueue.java, * plugin/icedteanp/java/sun/applet/TestEnv.java, * plugin/icedteanp/java/sun/applet/VoidPluginCallRequest.java, * plugin/tests/LiveConnect/DummyObject.java, * plugin/tests/LiveConnect/OverloadTestHelper1.java, * plugin/tests/LiveConnect/OverloadTestHelper2.java, * plugin/tests/LiveConnect/OverloadTestHelper3.java, * plugin/tests/LiveConnect/PluginTest.java, * plugin/tests/LiveConnect/build, * plugin/tests/LiveConnect/common.js, * plugin/tests/LiveConnect/index.html, * plugin/tests/LiveConnect/jjs_eval_test.js, * plugin/tests/LiveConnect/jjs_func_parameters_tests.js, * plugin/tests/LiveConnect/jjs_func_rettype_tests.js, * plugin/tests/LiveConnect/jjs_get_tests.js, * plugin/tests/LiveConnect/jjs_set_tests.js, * plugin/tests/LiveConnect/jsj_func_overload_tests.js, * plugin/tests/LiveConnect/jsj_func_parameters_tests.js, * plugin/tests/LiveConnect/jsj_func_rettype_tests.js, * plugin/tests/LiveConnect/jsj_get_tests.js, * plugin/tests/LiveConnect/jsj_set_tests.js, * plugin/tests/LiveConnect/jsj_type_casting_tests.js, * plugin/tests/LiveConnect/jsj_type_conversion_tests.js: Initial import from IcedTea6. * AUTHORS, * COPYING * INSTALL, * NEWS, * README: New documentation.
Diffstat (limited to 'netx/net/sourceforge/jnlp/cache')
-rw-r--r--netx/net/sourceforge/jnlp/cache/CacheEntry.java172
-rw-r--r--netx/net/sourceforge/jnlp/cache/CacheUtil.java450
-rw-r--r--netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java320
-rw-r--r--netx/net/sourceforge/jnlp/cache/DownloadIndicator.java91
-rw-r--r--netx/net/sourceforge/jnlp/cache/Resource.java269
-rw-r--r--netx/net/sourceforge/jnlp/cache/ResourceTracker.java1049
-rw-r--r--netx/net/sourceforge/jnlp/cache/UpdatePolicy.java88
-rw-r--r--netx/net/sourceforge/jnlp/cache/package.html28
8 files changed, 2467 insertions, 0 deletions
diff --git a/netx/net/sourceforge/jnlp/cache/CacheEntry.java b/netx/net/sourceforge/jnlp/cache/CacheEntry.java
new file mode 100644
index 0000000..7906e0c
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/CacheEntry.java
@@ -0,0 +1,172 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * Describes an entry in the cache.<p>
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.10 $
+ */
+public class CacheEntry {
+
+ /** the remote resource location */
+ private URL location;
+
+ /** the requested version */
+ private Version version;
+
+ /** info about the cached file */
+ private PropertiesFile properties;
+
+
+ /**
+ * Create a CacheEntry for the resources specified as a remote
+ * URL.
+ *
+ * @param location the remote resource location
+ * @param version the version of the resource
+ */
+ public CacheEntry(URL location, Version version) {
+ this.location = location;
+ this.version = version;
+
+ File infoFile = CacheUtil.getCacheFile(location, version);
+ infoFile = new File(infoFile.getPath()+".info"); // replace with something that can't be clobbered
+
+ properties = new PropertiesFile(infoFile, JNLPRuntime.getMessage("CAutoGen"));
+ }
+
+ /**
+ * Initialize the cache entry data from a connection to the
+ * remote resource (does not store data).
+ */
+ void initialize(URLConnection connection) {
+ long modified = connection.getLastModified();
+ long length = connection.getContentLength(); // an int
+
+ properties.setProperty("content-length", Long.toString(length));
+ properties.setProperty("last-modified", Long.toString(modified));
+ }
+
+ /**
+ * Returns the remote location this entry caches.
+ */
+ public URL getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns the time in the local system clock that the file was
+ * most recently checked for an update.
+ */
+ public long getLastUpdated() {
+ try {
+ return Long.parseLong(properties.getProperty("last-updated"));
+ }
+ catch (Exception ex) {
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the time in the local system clock that the file was
+ * most recently checked for an update.
+ */
+ public void setLastUpdated(long updatedTime) {
+ properties.setProperty("last-updated", Long.toString(updatedTime));
+ }
+
+ /**
+ * Returns whether there is a version of the URL contents in
+ * the cache and it is up to date. This method may not return
+ * immediately.
+ *
+ * @param connection a connection to the remote URL
+ * @return whether the cache contains the version
+ */
+ public boolean isCurrent(URLConnection connection) {
+ boolean cached = isCached();
+
+ if (!cached)
+ return false;
+
+ try {
+ long remoteModified = connection.getLastModified();
+ long cachedModified = Long.parseLong(properties.getProperty("last-modified"));
+
+ if (remoteModified > 0 && remoteModified <= cachedModified)
+ return true;
+ else
+ return false;
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ return cached; // if can't connect return whether already in cache
+ }
+ }
+
+ /**
+ * Returns true if the cache has a local copy of the contents
+ * of the URL matching the specified version string.
+ *
+ * @return true if the resource is in the cache
+ */
+ public boolean isCached() {
+ File localFile = CacheUtil.getCacheFile(location, version);
+ if (!localFile.exists())
+ return false;
+
+ try {
+ long cachedLength = localFile.length();
+ long remoteLength = Long.parseLong(properties.getProperty("content-length", "-1"));
+
+ if (remoteLength >= 0 && cachedLength != remoteLength)
+ return false;
+ else
+ return true;
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ return false; // should throw?
+ }
+ }
+
+ /**
+ * Save the current information for the cache entry.
+ */
+ protected void store() {
+ properties.store();
+ }
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/CacheUtil.java b/netx/net/sourceforge/jnlp/cache/CacheUtil.java
new file mode 100644
index 0000000..9623edb
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/CacheUtil.java
@@ -0,0 +1,450 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.nio.channels.FileChannel;
+import java.util.*;
+import java.lang.reflect.*;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.FileUtils;
+
+/**
+ * Provides static methods to interact with the cache, download
+ * indicator, and other utility methods.<p>
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.17 $
+ */
+public class CacheUtil {
+
+ private static String R(String key) {
+ return JNLPRuntime.getMessage(key);
+ }
+
+ private static String R(String key, Object param) {
+ return JNLPRuntime.getMessage(key, new Object[] {param});
+ }
+
+ /**
+ * Compares a URL using string compare of its protocol, host,
+ * port, path, query, and anchor. This method avoids the host
+ * name lookup that URL.equals does for http: protocol URLs.
+ * It may not return the same value as the URL.equals method
+ * (different hostnames that resolve to the same IP address,
+ * ie sourceforge.net and www.sourceforge.net).
+ */
+ public static boolean urlEquals(URL u1, URL u2) {
+ if (u1==u2)
+ return true;
+ if (u1==null || u2==null)
+ return false;
+
+ if (!compare(u1.getProtocol(), u2.getProtocol(), true) ||
+ !compare(u1.getHost(), u2.getHost(), true) ||
+ //u1.getDefaultPort() != u2.getDefaultPort() || // only in 1.4
+ !compare(u1.getPath(), u2.getPath(), false) ||
+ !compare(u1.getQuery(), u2.getQuery(), false) ||
+ !compare(u1.getRef(), u2.getRef(), false))
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Caches a resource and returns a URL for it in the cache;
+ * blocks until resource is cached. If the resource location is
+ * not cacheable (points to a local file, etc) then the original
+ * URL is returned.<p>
+ *
+ * @param location location of the resource
+ * @param version the version, or null
+ * @return either the location in the cache or the original location
+ */
+ public static URL getCachedResource(URL location, Version version, UpdatePolicy policy) {
+ ResourceTracker rt = new ResourceTracker();
+ rt.addResource(location, version, policy);
+ try {
+ File f = rt.getCacheFile(location);
+ return f.toURL();
+ }
+ catch (MalformedURLException ex) {
+ return location;
+ }
+ }
+
+ /**
+ * Compare strings that can be null.
+ */
+ private static boolean compare(String s1, String s2, boolean ignore) {
+ if (s1==s2)
+ return true;
+ if (s1==null || s2==null)
+ return false;
+
+ if (ignore)
+ return s1.equalsIgnoreCase(s2);
+ else
+ return s1.equals(s2);
+ }
+
+ /**
+ * Returns the Permission object necessary to access the
+ * resource, or null if no permission is needed.
+ */
+ public static Permission getReadPermission(URL location, Version version) {
+ if (CacheUtil.isCacheable(location, version)) {
+ File file = CacheUtil.getCacheFile(location, version);
+
+ return new FilePermission(file.getPath(), "read");
+ }
+ else {
+ try {
+ // this is what URLClassLoader does
+ return location.openConnection().getPermission();
+ }
+ catch (java.io.IOException ioe) {
+ // should try to figure out the permission
+ if (JNLPRuntime.isDebug())
+ ioe.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Clears the cache by deleting all the Netx cache files
+ *
+ * Note: Because of how our caching system works, deleting jars of another javaws
+ * process is using them can be quite disasterous. Hence why Launcher creates lock files
+ * and we check for those by calling {@link #okToClearCache()}
+ */
+ public static void clearCache() {
+
+ if (!okToClearCache()) {
+ System.err.println(R("CCannotClearCache"));
+ return;
+ }
+
+ File cacheDir = new File(JNLPRuntime.getBaseDir() + File.separator + "cache");
+ if (!(cacheDir.isDirectory())) {
+ return;
+ }
+
+ if (JNLPRuntime.isDebug()) {
+ System.err.println("Clearing cache directory: " + cacheDir);
+ }
+ try {
+ FileUtils.recursiveDelete(cacheDir, JNLPRuntime.getBaseDir());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns a boolean indicating if it ok to clear the netx application cache at this point
+ * @return true if the cache can be cleared at this time without problems
+ */
+ private static boolean okToClearCache() {
+ File otherJavawsRunning = new File(JNLPRuntime.NETX_RUNNING_FILE);
+ try {
+ if (otherJavawsRunning.isFile()) {
+ FileOutputStream fis = new FileOutputStream(otherJavawsRunning);
+ try {
+ FileChannel channel = fis.getChannel();
+ if (channel.tryLock() == null) {
+ if (JNLPRuntime.isDebug()) {
+ System.out.println("Other instances of netx are running");
+ }
+ return false;
+ }
+
+ if (JNLPRuntime.isDebug()) {
+ System.out.println("No other instances of netx are running");
+ }
+ return true;
+
+ } finally {
+ fis.close();
+ }
+ } else {
+ if (JNLPRuntime.isDebug()) {
+ System.out.println("No instance file found");
+ }
+ return true;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether there is a version of the URL contents in the
+ * cache and it is up to date. This method may not return
+ * immediately.
+ *
+ * @param source the source URL
+ * @param version the versions to check for
+ * @param connection a connection to the URL, or null
+ * @return whether the cache contains the version
+ * @throws IllegalArgumentException if the source is not cacheable
+ */
+ public static boolean isCurrent(URL source, Version version, URLConnection connection) {
+
+ if (!isCacheable(source, version))
+ throw new IllegalArgumentException(R("CNotCacheable", source));
+
+ try {
+ if (connection == null)
+ connection = source.openConnection();
+
+ connection.connect();
+
+ CacheEntry entry = new CacheEntry(source, version); // could pool this
+ boolean result = entry.isCurrent(connection);
+
+ if (JNLPRuntime.isDebug())
+ System.out.println("isCurrent: "+source+" = "+result);
+
+ return result;
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ return isCached(source, version); // if can't connect return whether already in cache
+ }
+ }
+
+ /**
+ * Returns true if the cache has a local copy of the contents of
+ * the URL matching the specified version string.
+ *
+ * @param source the source URL
+ * @param version the versions to check for
+ * @return true if the source is in the cache
+ * @throws IllegalArgumentException if the source is not cacheable
+ */
+ public static boolean isCached(URL source, Version version) {
+ if (!isCacheable(source, version))
+ throw new IllegalArgumentException(R("CNotCacheable", source));
+
+ CacheEntry entry = new CacheEntry(source, version); // could pool this
+ boolean result = entry.isCached();
+
+ if (JNLPRuntime.isDebug())
+ System.out.println("isCached: "+source+" = "+result);
+
+ return result;
+ }
+
+ /**
+ * Returns whether the resource can be cached as a local file;
+ * if not, then URLConnection.openStream can be used to obtain
+ * the contents.
+ */
+ public static boolean isCacheable(URL source, Version version) {
+ if (source == null)
+ return false;
+
+ if (source.getProtocol().equals("file"))
+ return false;
+
+ if (source.getProtocol().equals("jar"))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Returns the file for the locally cached contents of the
+ * source. This method returns the file location only and does
+ * not download the resource. The latest version of the
+ * resource that matches the specified version will be returned.
+ *
+ * @param source the source URL
+ * @param version the version id of the local file
+ * @return the file location in the cache, or null if no versions cached
+ * @throws IllegalArgumentException if the source is not cacheable
+ */
+ public static File getCacheFile(URL source, Version version) {
+ // ensure that version is an version id not version string
+
+ if (!isCacheable(source, version))
+ throw new IllegalArgumentException(R("CNotCacheable", source));
+
+ try {
+ File localFile = urlToPath(source, "cache");
+ localFile.getParentFile().mkdirs();
+
+ return localFile;
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ return null;
+ }
+ }
+
+ /**
+ * Returns a buffered output stream open for writing to the
+ * cache file.
+ *
+ * @param source the remote location
+ * @param version the file version to write to
+ */
+ public static OutputStream getOutputStream(URL source, Version version) throws IOException {
+ File localFile = getCacheFile(source, version);
+ OutputStream out = new FileOutputStream(localFile);
+
+ return new BufferedOutputStream(out);
+ }
+
+ /**
+ * Copies from an input stream to an output stream. On
+ * completion, both streams will be closed. Streams are
+ * buffered automatically.
+ */
+ public static void streamCopy(InputStream is, OutputStream os) throws IOException {
+ if (!(is instanceof BufferedInputStream))
+ is = new BufferedInputStream(is);
+
+ if (!(os instanceof BufferedOutputStream))
+ os = new BufferedOutputStream(os);
+
+ try {
+ byte b[] = new byte[4096];
+ while (true) {
+ int c = is.read(b, 0, b.length);
+ if (c == -1)
+ break;
+
+ os.write(b, 0, c);
+ }
+ }
+ finally {
+ is.close();
+ os.close();
+ }
+ }
+
+ /**
+ * Converts a URL into a local path string within the runtime's
+ * base directory.
+ *
+ * @param location the url
+ * @param subdir subdirectory under the base directory
+ * @return the file
+ */
+ public static File urlToPath(URL location, String subdir) {
+ StringBuffer path = new StringBuffer();
+
+ if (subdir != null) {
+ path.append(subdir);
+ path.append(File.separatorChar);
+ }
+
+ path.append(location.getProtocol());
+ path.append(File.separatorChar);
+ path.append(location.getHost());
+ path.append(File.separatorChar);
+ path.append(location.getPath().replace('/', File.separatorChar));
+
+ return new File(JNLPRuntime.getBaseDir(), FileUtils.sanitizePath(path.toString()));
+ }
+
+
+ /**
+ * Waits until the resources are downloaded, while showing a
+ * progress indicator.
+ *
+ * @param tracker the resource tracker
+ * @param resources the resources to wait for
+ * @param title name of the download
+ */
+ public static void waitForResources(ApplicationInstance app, ResourceTracker tracker, URL resources[], String title) {
+ DownloadIndicator indicator = JNLPRuntime.getDefaultDownloadIndicator();
+ DownloadServiceListener listener = null;
+
+ try {
+ if (indicator == null) {
+ tracker.waitForResources(resources, 0);
+ return;
+ }
+
+ // see if resources can be downloaded very quickly; avoids
+ // overhead of creating display components for the resources
+ if (tracker.waitForResources(resources, indicator.getInitialDelay()))
+ return;
+
+ // only resources not starting out downloaded are displayed
+ List urlList = new ArrayList();
+ for (int i=0; i < resources.length; i++) {
+ if (!tracker.checkResource(resources[i]))
+ urlList.add(resources[i]);
+ }
+ URL undownloaded[] = (URL[]) urlList.toArray( new URL[urlList.size()] );
+
+ listener = indicator.getListener(app, title, undownloaded);
+
+ do {
+ long read = 0;
+ long total = 0;
+
+ for (int i=0; i < undownloaded.length; i++) {
+ // add in any -1's; they're insignificant
+ total += tracker.getTotalSize(undownloaded[i]);
+ read += tracker.getAmountRead(undownloaded[i]);
+ }
+
+ int percent = (int)( (100*read)/Math.max(1,total) );
+
+ for (int i=0; i < undownloaded.length; i++)
+ listener.progress(undownloaded[i], "version",
+ tracker.getAmountRead(undownloaded[i]),
+ tracker.getTotalSize(undownloaded[i]),
+ percent);
+ }
+ while (!tracker.waitForResources(resources, indicator.getUpdateRate()));
+
+ // make sure they read 100% until indicator closes
+ for (int i=0; i < undownloaded.length; i++)
+ listener.progress(undownloaded[i], "version",
+ tracker.getTotalSize(undownloaded[i]),
+ tracker.getTotalSize(undownloaded[i]),
+ 100);
+
+ }
+ catch (InterruptedException ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+ }
+ finally {
+ if (listener != null)
+ indicator.disposeListener(listener);
+ }
+ }
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java b/netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java
new file mode 100644
index 0000000..bd1cbd9
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java
@@ -0,0 +1,320 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.util.*;
+import java.util.List;
+import javax.swing.*;
+import javax.swing.Timer;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+
+/**
+ * Show the progress of downloads.
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.3 $
+ */
+public class DefaultDownloadIndicator implements DownloadIndicator {
+
+ // todo: rewrite this to cut down on size/complexity; smarter
+ // panels (JList, renderer) understand resources instead of
+ // nested panels and grid-bag mess.
+
+ // todo: fix bug where user closes download box and it
+ // never(?) reappears.
+
+ // todo: UI for user to cancel/restart download
+
+ // todo: this should be synchronized at some point but conflicts
+ // aren't very likely.
+
+ private static String downloading = JNLPRuntime.getMessage("CDownloading");
+ private static String complete = JNLPRuntime.getMessage("CComplete");
+
+ /** time to wait after completing but before window closes */
+ private static final int CLOSE_DELAY = 750;
+
+ /** the display window */
+ private static JFrame frame;
+
+ /** shared constraint */
+ static GridBagConstraints vertical;
+ static GridBagConstraints verticalIndent;
+ static {
+ vertical = new GridBagConstraints();
+ vertical.gridwidth = GridBagConstraints.REMAINDER;
+ vertical.weightx = 1.0;
+ vertical.fill = GridBagConstraints.HORIZONTAL;
+ vertical.anchor = GridBagConstraints.WEST;
+
+ verticalIndent = (GridBagConstraints) vertical.clone();
+ verticalIndent.insets = new Insets(0, 10, 3, 0);
+ }
+
+ /**
+ * Return the update rate.
+ */
+ public int getUpdateRate() {
+ return 150; //ms
+ }
+
+ /**
+ * Return the initial delay before obtaining a listener.
+ */
+ public int getInitialDelay() {
+ return 300; //ms
+ }
+
+ /**
+ * Return a download service listener that displays the progress
+ * in a shared download info window.
+ *
+ * @param app the downloading application, or null if N/A
+ * @param downloadName name identifying the download to the user
+ * @param resources initial urls to display (not required)
+ */
+ public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) {
+ DownloadPanel result = new DownloadPanel(downloadName);
+
+ if (frame == null) {
+ frame = new JFrame(downloading+"...");
+ frame.getContentPane().setLayout(new GridBagLayout());
+ }
+
+ if (resources != null)
+ for (int i=0; i < resources.length; i++)
+ result.addProgressPanel(resources[i], null);
+
+ frame.getContentPane().add(result, vertical);
+ frame.pack();
+
+ if (!frame.isVisible()) {
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
+ Dimension screen = new Dimension(screenSize.width - insets.left ,
+ screenSize.height - insets.top);
+ frame.setLocation(screen.width-frame.getWidth(),
+ screen.height-frame.getHeight());
+ }
+
+ frame.show();
+
+ return result;
+ }
+
+ /**
+ * Remove a download service listener that was obtained by
+ * calling the getDownloadListener method from the shared
+ * download info window.
+ */
+ public void disposeListener(final DownloadServiceListener listener) {
+ if (!(listener instanceof DownloadPanel))
+ return;
+
+ ActionListener hider = new ActionListener() {
+ public void actionPerformed(ActionEvent evt) {
+ if (frame.getContentPane().getComponentCount() == 1)
+ frame.hide();
+
+ frame.getContentPane().remove((DownloadPanel) listener);
+ frame.pack();
+ }
+ };
+
+ Timer timer = new Timer(CLOSE_DELAY, hider);
+ timer.setRepeats(false);
+ timer.start();
+ }
+
+
+
+ /**
+ * Groups the url progress in a panel.
+ */
+ static class DownloadPanel extends JPanel implements DownloadServiceListener {
+
+ /** the download name */
+ private String downloadName;
+
+ /** Downloading part: */
+ private JLabel header = new JLabel();
+
+ /** list of URLs being downloaded */
+ private List urls = new ArrayList();
+
+ /** list of ProgressPanels */
+ private List panels = new ArrayList();
+
+
+ /**
+ * Create a new download panel for with the specified download
+ * name.
+ */
+ protected DownloadPanel(String downloadName) {
+ setLayout(new GridBagLayout());
+
+ this.downloadName = downloadName;
+ this.add(header, vertical);
+ header.setFont(header.getFont().deriveFont(Font.BOLD));
+
+ setOverallPercent(0);
+ }
+
+ /**
+ * Add a ProgressPanel for a URL.
+ */
+ protected void addProgressPanel(URL url, String version) {
+ if (!urls.contains(url)) {
+ ProgressPanel panel = new ProgressPanel(url, version);
+
+ add(panel, verticalIndent);
+ frame.pack();
+
+ urls.add(url);
+ panels.add(panel);
+ }
+ }
+
+ /**
+ * Update the download progress of a url.
+ */
+ protected void update(final URL url, final String version, final long readSoFar, final long total, final int overallPercent) {
+ Runnable r = new Runnable() {
+ public void run() {
+ if (!urls.contains(url))
+ addProgressPanel(url, version);
+
+ setOverallPercent(overallPercent);
+
+ ProgressPanel panel = (ProgressPanel) panels.get(urls.indexOf(url));
+ panel.setProgress(readSoFar, total);
+ panel.repaint();
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ /**
+ * Sets the overall percent completed.
+ */
+ public void setOverallPercent(int percent) {
+ // don't get whole string from resource and sub in
+ // values because it'll be doing a MessageFormat for
+ // each update.
+ header.setText(downloading+" "+downloadName+": "+percent+"% "+complete+".");
+ }
+
+ /**
+ * Called when a download failed.
+ */
+ public void downloadFailed(URL url, String version) {
+ update(url, version, -1, -1, -1);
+ }
+
+ /**
+ * Called when a download has progressed.
+ */
+ public void progress(URL url, String version, long readSoFar, long total, int overallPercent) {
+ update(url, version, readSoFar, total, overallPercent);
+ }
+
+ /**
+ * Called when an archive is patched.
+ */
+ public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) {
+ update(url, version, patchPercent, 100, overallPercent);
+ }
+
+ /**
+ * Called when a download is being validated.
+ */
+ public void validating(URL url, String version, long entry, long total, int overallPercent) {
+ update(url, version, entry, total, overallPercent);
+ }
+
+ };
+
+
+
+ /**
+ * A progress bar with the URL next to it.
+ */
+ static class ProgressPanel extends JPanel {
+ private JPanel bar = new JPanel();
+
+ private long total;
+ private long readSoFar;
+
+ ProgressPanel(URL url, String version) {
+ JLabel location = new JLabel(" "+url.getHost()+"/"+url.getFile());
+
+ bar.setMinimumSize(new Dimension(80,15));
+ bar.setPreferredSize(new Dimension(80,15));
+ bar.setOpaque(false);
+
+ setLayout(new GridBagLayout());
+
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.weightx = 0.0;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridwidth = GridBagConstraints.RELATIVE;
+ add(bar, gbc);
+
+ gbc.insets = new Insets(0, 3, 0, 0);
+ gbc.weightx = 1.0;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.gridwidth = GridBagConstraints.REMAINDER;
+ gbc.anchor = GridBagConstraints.WEST;
+ add(location, gbc);
+ }
+
+ public void setProgress(long readSoFar, long total) {
+ this.readSoFar = readSoFar;
+ this.total = total;
+ }
+
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ int x = bar.getX();
+ int y = bar.getY();
+ int h = bar.getHeight();
+ int w = bar.getWidth();
+
+ if (readSoFar <= 0 || total <= 0) {
+ // make barber pole
+ }
+ else {
+ double progress = (double)readSoFar / (double)total;
+ int divide = (int)(w * progress);
+
+ g.setColor(Color.white);
+ g.fillRect(x, y, w, h);
+ g.setColor(Color.blue);
+ g.fillRect(x+1, y+1, divide-1, h-1);
+ }
+ }
+ };
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/DownloadIndicator.java b/netx/net/sourceforge/jnlp/cache/DownloadIndicator.java
new file mode 100644
index 0000000..a6eecb8
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/DownloadIndicator.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.util.*;
+import java.util.List;
+import javax.swing.*;
+import javax.swing.Timer;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+
+/**
+ * A DownloadIndicator creates DownloadServiceListeners that are
+ * notified of resources being transferred and their progress.
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.8 $
+ */
+public interface DownloadIndicator {
+
+ /**
+ * Return a download service listener that displays the progress
+ * of downloading resources. Update messages may be reported
+ * for URLs that are not included initially.<p>
+ *
+ * Progress messages are sent as if the DownloadServiceListener
+ * were listening to a DownloadService request. The listener
+ * will receive progress messages from time to time during the
+ * download. <p>
+ *
+ * @param app JNLP application downloading the files, or null if not applicable
+ * @param downloadName name identifying the download to the user
+ * @param resources initial urls to display, empty if none known at start
+ */
+ public DownloadServiceListener getListener(ApplicationInstance app,
+ String downloadName,
+ URL resources[]);
+
+ /**
+ * Indicates that a download service listener that was obtained
+ * from the getDownloadListener method will no longer be used.
+ * This method can be used to ensure that progress dialogs are
+ * properly removed once a particular download is finished.
+ *
+ * @param listener the listener that is no longer in use
+ */
+ public void disposeListener(DownloadServiceListener listener);
+
+ /**
+ * Return the desired time in milliseconds between updates.
+ * Updates are not guarenteed to occur based on this value; for
+ * example, they may occur based on the download percent or some
+ * other factor.
+ *
+ * @return rate in milliseconds, must be &gt;= 0
+ */
+ public int getUpdateRate();
+
+ /**
+ * Return a time in milliseconds to wait for a download to
+ * complete before obtaining a listener for the download. This
+ * value can be used to skip lengthy operations, such as
+ * initializing a GUI, for downloads that complete quickly. The
+ * getListener method is not called if the download completes
+ * in less time than the returned delay.
+ *
+ * @return delay in milliseconds, must be &gt;= 0
+ */
+ public int getInitialDelay();
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/Resource.java b/netx/net/sourceforge/jnlp/cache/Resource.java
new file mode 100644
index 0000000..172220f
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/Resource.java
@@ -0,0 +1,269 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * Information about a single resource to download.
+ * This class tracks the downloading of various resources of a
+ * JNLP file to local files. It can be used to download icons,
+ * jnlp and extension files, jars, and jardiff files using the
+ * version based protocol or any file using the basic download
+ * protocol.<p>
+ *
+ * Resources can be put into download groups by specifying a part
+ * name for the resource. The resource tracker can also be
+ * configured to prefetch resources, which are downloaded in the
+ * order added to the media tracker.<p>
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.9 $
+ */
+public class Resource {
+
+ // todo: fix resources to handle different versions
+
+ // todo: IIRC, any resource is checked for being up-to-date
+ // only once, regardless of UpdatePolicy. verify and fix.
+
+ /** status bits */
+ public static final int UNINITIALIZED = 0;
+ public static final int CONNECT = 1;
+ public static final int CONNECTING = 2;
+ public static final int CONNECTED = 4;
+ public static final int DOWNLOAD = 8;
+ public static final int DOWNLOADING = 16;
+ public static final int DOWNLOADED = 32;
+ public static final int ERROR = 64;
+ public static final int STARTED = 128; // enqueued or being worked on
+
+ /** list of weak references of resources currently in use */
+ private static WeakList resources = new WeakList();
+
+ /** weak list of trackers monitoring this resource */
+ private WeakList trackers = new WeakList();
+
+ /** the remote location of the resource */
+ URL location;
+
+ /** the local file downloaded to */
+ File localFile;
+
+ /** the requested version */
+ Version requestVersion;
+
+ /** the version downloaded from server */
+ Version downloadVersion;
+
+ /** connection to resource */
+ URLConnection connection;
+
+ /** amount in bytes transferred */
+ long transferred = 0;
+
+ /** total size of the resource, or -1 if unknown */
+ long size = -1;
+
+ /** the status of the resource */
+ int status = UNINITIALIZED;
+
+ /** Update policy for this resource */
+ UpdatePolicy updatePolicy;
+
+ /**
+ * Create a resource.
+ */
+ private Resource(URL location, Version requestVersion, UpdatePolicy updatePolicy) {
+ this.location = location;
+ this.requestVersion = requestVersion;
+ this.updatePolicy = updatePolicy;
+ }
+
+ /**
+ * Return a shared Resource object representing the given
+ * location and version.
+ */
+ public static Resource getResource(URL location, Version requestVersion, UpdatePolicy updatePolicy) {
+ synchronized (resources) {
+ Resource resource = new Resource(location, requestVersion, updatePolicy);
+
+ int index = resources.indexOf(resource);
+ if (index >= 0) { // return existing object
+ Resource result = (Resource) resources.get(index);
+ if (result != null)
+ return result;
+ }
+
+ resources.add(resource);
+ resources.trimToSize();
+
+ return resource;
+ }
+ }
+
+ /**
+ * Returns the remote location of the resource.
+ */
+ public URL getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns the tracker that first created or monitored the
+ * resource, or null if no trackers are monitoring the resource.
+ */
+ ResourceTracker getTracker() {
+ synchronized (trackers) {
+ List t = trackers.hardList();
+ if (t.size() > 0)
+ return (ResourceTracker) t.get(0);
+
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if any of the specified flags are set.
+ */
+ public boolean isSet(int flag) {
+ if (flag == UNINITIALIZED)
+ return status == UNINITIALIZED;
+ else
+ return (status & flag) != 0;
+ }
+
+ /**
+ * Returns the update policy for this resource
+ *
+ * @return The update policy
+ */
+ public UpdatePolicy getUpdatePolicy() {
+ return this.updatePolicy;
+ }
+
+ /**
+ * Returns a human-readable status string.
+ */
+ private String getStatusString(int flag) {
+ StringBuffer result = new StringBuffer();
+
+ if (flag == 0) result.append("<> ");
+ if ((flag & CONNECT) != 0) result.append("CONNECT ");
+ if ((flag & CONNECTING) != 0) result.append("CONNECTING ");
+ if ((flag & CONNECTED) != 0) result.append("CONNECTED ");
+ if ((flag & DOWNLOAD) != 0) result.append("DOWNLOAD ");
+ if ((flag & DOWNLOADING) != 0) result.append("DOWNLOADING ");
+ if ((flag & DOWNLOADED) != 0) result.append("DOWNLOADED ");
+ if ((flag & ERROR) != 0) result.append("ERROR ");
+ if ((flag & STARTED) != 0) result.append("STARTED ");
+
+ return result.deleteCharAt(result.length()-1).toString();
+ }
+
+ /**
+ * Changes the status by clearing the flags in the first
+ * parameter and setting the flags in the second. This method
+ * is synchronized on this resource.
+ */
+ public void changeStatus(int clear, int add) {
+ int orig = 0;
+
+ synchronized(this) {
+ orig = status;
+
+ this.status &= ~clear;
+ this.status |= add;
+ }
+
+ if (JNLPRuntime.isDebug())
+ if (status != orig) {
+ System.out.print("Status: "+getStatusString(status));
+ if ((status & ~orig) != 0)
+ System.out.print(" +("+getStatusString(status & ~orig)+")");
+ if ((~status & orig) != 0)
+ System.out.print(" -("+getStatusString(~status & orig)+")");
+ System.out.println(" @ "+location.getPath());
+ }
+ }
+
+ /**
+ * Removes the tracker to the list of trackers monitoring this
+ * resource.
+ */
+ public void removeTracker(ResourceTracker tracker) {
+ synchronized (trackers) {
+ trackers.remove(tracker);
+ trackers.trimToSize();
+ }
+ }
+
+ /**
+ * Adds the tracker to the list of trackers monitoring this
+ * resource.
+ */
+ public void addTracker(ResourceTracker tracker) {
+ synchronized (trackers) {
+ List t = trackers.hardList(); // prevent GC between contains and add
+ if (!t.contains(tracker))
+ trackers.add(tracker);
+
+ trackers.trimToSize();
+ }
+ }
+
+ /**
+ * Instructs the trackers monitoring this resource to fire a
+ * download event.
+ */
+ protected void fireDownloadEvent() {
+ List send;
+
+ synchronized (trackers) {
+ send = trackers.hardList();
+ }
+
+ for (int i=0; i < send.size(); i++) {
+ ResourceTracker rt = (ResourceTracker) send.get(i);
+ rt.fireDownloadEvent(this);
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof Resource) {
+ // this prevents the URL handler from looking up the IP
+ // address and doing name resolution; much faster so less
+ // time spent in synchronized addResource determining if
+ // Resource is already in a tracker, and better for offline
+ // mode on some OS.
+ return CacheUtil.urlEquals(location, ((Resource)other).location);
+ }
+ return false;
+ }
+
+ public String toString() {
+ return "location="+location.toString() + " state="+getStatusString(status);
+ }
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/ResourceTracker.java b/netx/net/sourceforge/jnlp/cache/ResourceTracker.java
new file mode 100644
index 0000000..d65c840
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/ResourceTracker.java
@@ -0,0 +1,1049 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.jar.Pack200.Unpacker;
+import java.util.zip.GZIPInputStream;
+
+import net.sourceforge.jnlp.Version;
+import net.sourceforge.jnlp.event.DownloadEvent;
+import net.sourceforge.jnlp.event.DownloadListener;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.util.WeakList;
+
+/**
+ * This class tracks the downloading of various resources of a
+ * JNLP file to local files in the cache. It can be used to
+ * download icons, jnlp and extension files, jars, and jardiff
+ * files using the version based protocol or any file using the
+ * basic download protocol (jardiff and version not implemented
+ * yet).<p>
+ *
+ * The resource tracker can be configured to prefetch resources,
+ * which are downloaded in the order added to the media
+ * tracker.<p>
+ *
+ * Multiple threads are used to download and cache resources that
+ * are actively being waited for (blocking a caller) or those that
+ * have been started downloading by calling the startDownload
+ * method. Resources that are prefetched are downloaded one at a
+ * time and only if no other trackers have requested downloads.
+ * This allows the tracker to start downloading many items without
+ * using many system resources, but still quickly download items
+ * as needed.<p>
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.22 $
+ */
+public class ResourceTracker {
+
+ // todo: use event listener arrays instead of lists
+
+ // todo: see if there is a way to set the socket options just
+ // for use by the tracker so checks for updates don't hang for
+ // a long time.
+
+ // todo: ability to restart/retry a hung download
+
+ // todo: move resource downloading/processing code into Resource
+ // class, threading stays in ResourceTracker
+
+ // todo: get status method? and some way to convey error status
+ // to the caller.
+
+ // todo: might make a tracker be able to download more than one
+ // version of a resource, but probably not very useful.
+
+
+ // defines
+ // ResourceTracker.Downloader (download threads)
+
+ // separately locks on (in order of aquire order, ie, sync on prefetch never syncs on lock):
+ // lock, prefetch, this.resources, each resource, listeners
+
+ /** notified on initialization or download of a resource */
+ private static Object lock = new Integer(0); // used to lock static structures
+
+ // shortcuts
+ private static final int UNINITIALIZED = Resource.UNINITIALIZED;
+ private static final int CONNECT = Resource.CONNECT;
+ private static final int CONNECTING = Resource.CONNECTING;
+ private static final int CONNECTED = Resource.CONNECTED;
+ private static final int DOWNLOAD = Resource.DOWNLOAD;
+ private static final int DOWNLOADING = Resource.DOWNLOADING;
+ private static final int DOWNLOADED = Resource.DOWNLOADED;
+ private static final int ERROR = Resource.ERROR;
+ private static final int STARTED = Resource.STARTED;
+
+ /** max threads */
+ private static final int maxThreads = 5;
+
+ /** running threads */
+ private static int threads = 0;
+
+ /** weak list of resource trackers with resources to prefetch */
+ private static WeakList prefetchTrackers = new WeakList();
+
+ /** resources requested to be downloaded */
+ private static ArrayList queue = new ArrayList();
+
+ /** resource trackers threads are working for (used for load balancing across multi-tracker downloads) */
+ private static ArrayList active = new ArrayList(); //
+
+ /** the resources known about by this resource tracker */
+ private List resources = new ArrayList();
+
+ /** download listeners for this tracker */
+ private List listeners = new ArrayList();
+
+ /** whether to download parts before requested */
+ private boolean prefetch;
+
+
+ /**
+ * Creates a resource tracker that does not prefetch resources.
+ */
+ public ResourceTracker() {
+ this(false);
+ }
+
+ /**
+ * Creates a resource tracker.
+ *
+ * @param prefetch whether to download resources before requested.
+ */
+ public ResourceTracker(boolean prefetch) {
+ this.prefetch = prefetch;
+
+ if (prefetch) {
+ synchronized (prefetchTrackers) {
+ prefetchTrackers.add(this);
+ prefetchTrackers.trimToSize();
+ }
+ }
+ }
+
+ /**
+ * Add a resource identified by the specified location and
+ * version. The tracker only downloads one version of a given
+ * resource per instance (ie cannot download both versions 1 and
+ * 2 of a resource in the same tracker).
+ *
+ * @param location the location of the resource
+ * @param version the resource version
+ * @param updatePolicy whether to check for updates if already in cache
+ */
+ public void addResource(URL location, Version version, UpdatePolicy updatePolicy) {
+ if (location == null)
+ throw new IllegalArgumentException("location==null");
+
+ Resource resource = Resource.getResource(location, version, updatePolicy);
+ boolean downloaded = false;
+
+ synchronized (resources) {
+ if (resources.contains(resource))
+ return;
+ resource.addTracker(this);
+ resources.add(resource);
+ }
+
+ // checkCache may take a while (loads properties file). this
+ // should really be synchronized on resources, but the worst
+ // case should be that the resource will be updated once even
+ // if unnecessary.
+ downloaded = checkCache(resource, updatePolicy);
+
+ synchronized (lock) {
+ if (!downloaded)
+ if (prefetch && threads == 0) // existing threads do pre-fetch when queue empty
+ startThread();
+ }
+ }
+
+ /**
+ * Removes a resource from the tracker. This method is useful
+ * to allow memory to be reclaimed, but calling this method is
+ * not required as resources are reclaimed when the tracker is
+ * collected.
+ *
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public void removeResource(URL location) {
+ synchronized (resources) {
+ Resource resource = getResource(location);
+
+ if (resource != null) {
+ resources.remove(resource);
+ resource.removeTracker(this);
+ }
+
+ // should remove from queue? probably doesn't matter
+ }
+ }
+
+ /**
+ * Check the cache for a resource, and initialize the resource
+ * as already downloaded if found. <p>
+ *
+ * @param updatePolicy whether to check for updates if already in cache
+ * @return whether the resource are already downloaded
+ */
+ private boolean checkCache(Resource resource, UpdatePolicy updatePolicy) {
+ if (!CacheUtil.isCacheable(resource.location, resource.downloadVersion)) {
+ // pretend that they are already downloaded; essentially
+ // they will just 'pass through' the tracker as if they were
+ // never added (for example, not affecting the total download size).
+ synchronized (resource) {
+ resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
+ }
+ fireDownloadEvent(resource);
+ return true;
+ }
+
+ if (updatePolicy != UpdatePolicy.ALWAYS && updatePolicy != UpdatePolicy.FORCE) { // save loading entry props file
+ CacheEntry entry = new CacheEntry(resource.location, resource.downloadVersion);
+
+ if (entry.isCached() && !updatePolicy.shouldUpdate(entry)) {
+ if (JNLPRuntime.isDebug())
+ System.out.println("not updating: "+resource.location);
+
+ synchronized (resource) {
+ resource.localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
+ resource.size = resource.localFile.length();
+ resource.transferred = resource.localFile.length();
+ resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
+ }
+ fireDownloadEvent(resource);
+ return true;
+ }
+ }
+
+ if (updatePolicy == UpdatePolicy.FORCE) { // ALWAYS update
+ // When we are "always" updating, we update for each instance. Reset resource status.
+ resource.changeStatus(Integer.MAX_VALUE, 0);
+ }
+
+ // may or may not be cached, but check update when connection
+ // is open to possibly save network communication time if it
+ // has to be downloaded, and allow this call to return quickly
+ return false;
+ }
+
+ /**
+ * Adds the listener to the list of objects interested in
+ * receivind DownloadEvents.<p>
+ *
+ * @param location the resource to add a callback for
+ * @param runnable the runnable to call when resource is completed
+ */
+ public void addDownloadListener(DownloadListener listener) {
+ synchronized (listeners) {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a download listener.
+ */
+ public void removeDownloadListener(DownloadListener listener) {
+ synchronized (listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Fires the download event corresponding to the resource's
+ * state. This method is typicall called by the Resource itself
+ * on each tracker that is monitoring the resource. Do not call
+ * this method with any locks because the listeners may call
+ * back to this ResourceTracker.
+ */
+ protected void fireDownloadEvent(Resource resource) {
+ DownloadListener l[] = null;
+ synchronized (listeners) {
+ l = (DownloadListener[]) listeners.toArray(new DownloadListener[0]);
+ }
+
+ int status;
+ synchronized (resource) {
+ status = resource.status;
+ }
+
+ DownloadEvent event = new DownloadEvent(this, resource);
+ for (int i=0; i < l.length; i++) {
+ if (0 != ((ERROR|DOWNLOADED) & status))
+ l[i].downloadCompleted(event);
+ else if (0 != (DOWNLOADING & status))
+ l[i].downloadStarted(event);
+ else if (0 != (CONNECTING & status))
+ l[i].updateStarted(event);
+ }
+ }
+
+ /**
+ * Returns a URL pointing to the cached location of the
+ * resource, or the resource itself if it is a non-cacheable
+ * resource.<p>
+ *
+ * If the resource has not downloaded yet, the method will block
+ * until it has been transferred to the cache.<p>
+ *
+ * @param location the resource location
+ * @return the resource, or null if it could not be downloaded
+ * @throws IllegalArgumentException if the resource is not being tracked
+ * @see CacheUtil#isCacheable
+ */
+ public URL getCacheURL(URL location) {
+ try {
+ File f = getCacheFile(location);
+ if (f != null)
+ return f.toURL();
+ }
+ catch (MalformedURLException ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+ }
+
+ return location;
+ }
+
+ /**
+ * Returns a file containing the downloaded resource. If the
+ * resource is non-cacheable then null is returned unless the
+ * resource is a local file (the original file is returned).<p>
+ *
+ * If the resource has not downloaded yet, the method will block
+ * until it has been transferred to the cache.<p>
+ *
+ * @param location the resource location
+ * @return a local file containing the resource, or null
+ * @throws IllegalArgumentException if the resource is not being tracked
+ * @see CacheUtil#isCacheable
+ */
+ public File getCacheFile(URL location) {
+ try {
+ Resource resource = getResource(location);
+ if (!resource.isSet(DOWNLOADED|ERROR))
+ waitForResource(location, 0);
+
+ if (resource.isSet(ERROR))
+ return null;
+
+ if (resource.localFile != null)
+ return resource.localFile;
+
+ if (location.getProtocol().equalsIgnoreCase("file")) {
+ File file = new File(location.getFile());
+ if (file.exists())
+ return file;
+ }
+
+ return null;
+ }
+ catch (InterruptedException ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ return null; // need an error exception to throw
+ }
+ }
+
+ /**
+ * Returns an input stream that reads the contents of the
+ * resource. For non-cacheable resources, an InputStream that
+ * reads from the source location is returned. Otherwise the
+ * InputStream reads the cached resource.<p>
+ *
+ * This method will block while the resource is downloaded to
+ * the cache.
+ *
+ * @throws IOException if there was an error opening the stream
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public InputStream getInputStream(URL location) throws IOException {
+ try {
+ Resource resource = getResource(location);
+ if (!resource.isSet(DOWNLOADED|ERROR))
+ waitForResource(location, 0);
+
+ if (resource.localFile != null)
+ return new FileInputStream(resource.localFile);
+
+ return resource.location.openStream();
+ }
+ catch (InterruptedException ex) {
+ throw new IOException("wait was interrupted");
+ }
+ }
+
+ /**
+ * Wait for a group of resources to be downloaded and made
+ * available locally.
+ *
+ * @param urls the resources to wait for
+ * @param timeout the time in ms to wait before returning, 0 for no timeout
+ * @return whether the resources downloaded before the timeout
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public boolean waitForResources(URL urls[], long timeout) throws InterruptedException {
+ Resource resources[] = new Resource[ urls.length ];
+
+ synchronized(resources) {
+ // keep the lock so getResource doesn't have to aquire it each time
+ for (int i=0; i < urls.length; i++)
+ resources[i] = getResource(urls[i]);
+ }
+
+ if (resources.length > 0)
+ return wait(resources, timeout);
+
+ return true;
+ }
+
+ /**
+ * Wait for a particular resource to be downloaded and made
+ * available.
+ *
+ * @param location the resource to wait for
+ * @param timeout the timeout, or 0 to wait until completed
+ * @return whether the resource downloaded before the timeout
+ * @throws InterruptedException if another thread interrupted the wait
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public boolean waitForResource(URL location, long timeout) throws InterruptedException {
+ return wait(new Resource[] { getResource(location) }, timeout);
+ }
+
+ /**
+ * Returns the number of bytes downloaded for a resource.
+ *
+ * @param location the resource location
+ * @return the number of bytes transferred
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public long getAmountRead(URL location) {
+ // not atomic b/c transferred is a long, but so what (each
+ // byte atomic? so probably won't affect anything...)
+ return getResource(location).transferred;
+ }
+
+ /**
+ * Returns whether a resource is available for use (ie, can be
+ * accessed with the getCacheFile method).
+ *
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public boolean checkResource(URL location) {
+ return getResource(location).isSet(DOWNLOADED|ERROR); // isSet atomic
+ }
+
+ /**
+ * Starts loading the resource if it is not already being
+ * downloaded or already cached. Resources started downloading
+ * using this method may download faster than those prefetched
+ * by the tracker because the tracker will only prefetch one
+ * resource at a time to conserve system resources.
+ *
+ * @return true if the resource is already downloaded (or an error occurred)
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public boolean startResource(URL location) {
+ Resource resource = getResource(location);
+
+ return startResource(resource);
+ }
+
+ /**
+ * Sets the resource status to connect and download, and
+ * enqueues the resource if not already started.
+ *
+ * @return true if the resource is already downloaded (or an error occurred)
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ private boolean startResource(Resource resource) {
+ boolean enqueue = false;
+
+ synchronized (resource) {
+ if (resource.isSet(ERROR))
+ return true;
+
+ enqueue = !resource.isSet(STARTED);
+
+ if (!resource.isSet(CONNECTED | CONNECTING))
+ resource.changeStatus(0, CONNECT|STARTED);
+ if (!resource.isSet(DOWNLOADED | DOWNLOADING))
+ resource.changeStatus(0, DOWNLOAD|STARTED);
+
+ if (!resource.isSet(DOWNLOAD|CONNECT))
+ enqueue = false;
+ }
+
+ if (enqueue)
+ queueResource(resource);
+
+ return !enqueue;
+ }
+
+ /**
+ * Returns the number of total size in bytes of a resource, or
+ * -1 it the size is not known.
+ *
+ * @param location the resource location
+ * @return the number of bytes, or -1
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ public long getTotalSize(URL location) {
+ return getResource(location).size; // atomic
+ }
+
+ /**
+ * Start a new download thread if there are not too many threads
+ * already running.<p>
+ *
+ * Calls to this method should be synchronized on lock.
+ */
+ protected void startThread() {
+ if (threads < maxThreads) {
+ threads++;
+
+ Thread thread = new Thread(new Downloader());
+ thread.start();
+ }
+ }
+
+ /**
+ * A thread is ending, called by the thread itself.<p>
+ *
+ * Calls to this method should be synchronized.
+ */
+ private void endThread() {
+ threads--;
+
+ if (threads < 0) {
+ // this should never happen but try to recover
+ threads = 0;
+
+ if (queue.size() > 0) // if any on queue make sure a thread is running
+ startThread(); // look into whether this could create a loop
+
+ throw new RuntimeException("tracker threads < 0");
+ }
+
+ if (threads == 0) {
+ synchronized (prefetchTrackers) {
+ queue.trimToSize(); // these only accessed by threads so no sync needed
+ active.clear(); // no threads so no trackers actively downloading
+ active.trimToSize();
+ prefetchTrackers.trimToSize();
+ }
+ }
+ }
+
+ /**
+ * Add a resource to the queue and start a thread to download or
+ * initialize it.
+ */
+ private void queueResource(Resource resource) {
+ synchronized (lock) {
+ if (!resource.isSet(CONNECT|DOWNLOAD))
+ throw new IllegalArgumentException("Invalid resource state (resource: "+resource+")");
+
+ queue.add(resource);
+ startThread();
+ }
+ }
+
+ /**
+ * Process the resource by either downloading it or initializing
+ * it.
+ */
+ private void processResource(Resource resource) {
+ boolean doConnect = false;
+ boolean doDownload = false;
+
+ synchronized (resource) {
+ if (resource.isSet(CONNECTING))
+ doConnect = true;
+ }
+ if (doConnect)
+ initializeResource(resource);
+
+ synchronized (resource) {
+ // return to queue if we just initalized but it still needs
+ // to download (not cached locally / out of date)
+ if (resource.isSet(DOWNLOAD)) // would be DOWNLOADING if connected before this method
+ queueResource(resource);
+
+ if (resource.isSet(DOWNLOADING))
+ doDownload = true;
+ }
+ if (doDownload)
+ downloadResource(resource);
+ }
+
+ /**
+ * Downloads a resource to a file, uncompressing it if required
+ *
+ * @param resource the resource to download
+ */
+ private void downloadResource(Resource resource) {
+ resource.fireDownloadEvent(); // fire DOWNLOADING
+
+ try {
+ // create out second in case in does not exist
+ URLConnection con = getVersionedResourceURL(resource).openConnection();
+ con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+
+ con.connect();
+
+ /*
+ * We dont really know what we are downloading. If we ask for
+ * foo.jar, the server might send us foo.jar.pack.gz or foo.jar.gz
+ * instead. So we save the file with the appropriate extension
+ */
+ URL downloadLocation = resource.location;
+
+ String contentEncoding = con.getContentEncoding();
+
+ if (JNLPRuntime.isDebug()) {
+ System.err.println("Content encoding for " + resource.location + ": "
+ + contentEncoding);
+ }
+
+ if (contentEncoding != null) {
+ if (contentEncoding.equals("gzip")) {
+ downloadLocation = new URL(downloadLocation.toString() + ".gz");
+ } else if (contentEncoding.equals("pack200-gzip")) {
+ downloadLocation = new URL(downloadLocation.toString() + ".pack.gz");
+ }
+ }
+
+ InputStream in = new BufferedInputStream(con.getInputStream());
+ OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.downloadVersion);
+ byte buf[] = new byte[1024];
+ int rlen;
+
+ while (-1 != (rlen = in.read(buf))) {
+ resource.transferred += rlen;
+ out.write(buf, 0, rlen);
+ }
+
+ in.close();
+ out.close();
+
+ // explicitly close the URLConnection.
+ if (con instanceof HttpURLConnection)
+ ((HttpURLConnection)con).disconnect();
+
+ /*
+ * If the file was compressed, uncompress it.
+ */
+ if (contentEncoding != null) {
+ if (contentEncoding.equals("gzip")) {
+ GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil
+ .getCacheFile(downloadLocation, resource.downloadVersion)));
+ InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+ BufferedOutputStream outputStream = new BufferedOutputStream(
+ new FileOutputStream(CacheUtil.getCacheFile(resource.location,
+ resource.downloadVersion)));
+
+ while (-1 != (rlen = inputStream.read(buf))) {
+ outputStream.write(buf, 0, rlen);
+ }
+
+ outputStream.close();
+ inputStream.close();
+ gzInputStream.close();
+
+ } else if (contentEncoding.equals("pack200-gzip")) {
+ GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(
+ CacheUtil.getCacheFile(downloadLocation, resource.downloadVersion)));
+ InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+ JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(
+ CacheUtil.getCacheFile(resource.location, resource.downloadVersion)));
+
+ Unpacker unpacker = Pack200.newUnpacker();
+ unpacker.unpack(inputStream, outputStream);
+
+ outputStream.close();
+ inputStream.close();
+ gzInputStream.close();
+ }
+ }
+
+ resource.changeStatus(DOWNLOADING, DOWNLOADED);
+ synchronized(lock) {
+ lock.notifyAll(); // wake up wait's to check for completion
+ }
+ resource.fireDownloadEvent(); // fire DOWNLOADED
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ resource.changeStatus(0, ERROR);
+ synchronized(lock) {
+ lock.notifyAll(); // wake up wait's to check for completion
+ }
+ resource.fireDownloadEvent(); // fire ERROR
+ }
+ }
+
+ /**
+ * Open a URL connection and get the content length and other
+ * fields.
+ */
+ private void initializeResource(Resource resource) {
+ resource.fireDownloadEvent(); // fire CONNECTING
+
+ try {
+ File localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
+
+ // connect
+ URLConnection connection = getVersionedResourceURL(resource).openConnection(); // this won't change so should be okay unsynchronized
+ connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+
+ int size = connection.getContentLength();
+ boolean current = CacheUtil.isCurrent(resource.location, resource.requestVersion, connection) && resource.getUpdatePolicy() != UpdatePolicy.FORCE;
+
+ synchronized(resource) {
+ resource.localFile = localFile;
+ // resource.connection = connection;
+ resource.size = size;
+ resource.changeStatus(CONNECT|CONNECTING, CONNECTED);
+
+ // check if up-to-date; if so set as downloaded
+ if (current)
+ resource.changeStatus(DOWNLOAD|DOWNLOADING, DOWNLOADED);
+ }
+
+ // update cache entry
+ CacheEntry entry = new CacheEntry(resource.location, resource.requestVersion);
+ if (!current)
+ entry.initialize(connection);
+
+ entry.setLastUpdated(System.currentTimeMillis());
+ entry.store();
+
+ synchronized(lock) {
+ lock.notifyAll(); // wake up wait's to check for completion
+ }
+ resource.fireDownloadEvent(); // fire CONNECTED
+
+ // explicitly close the URLConnection.
+ if (connection instanceof HttpURLConnection)
+ ((HttpURLConnection)connection).disconnect();
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+
+ resource.changeStatus(0, ERROR);
+ synchronized(lock) {
+ lock.notifyAll(); // wake up wait's to check for completion
+ }
+ resource.fireDownloadEvent(); // fire ERROR
+ }
+ }
+
+ /**
+ * Returns the versioned url for a resource
+ * @param resource the resource to get the url for
+ */
+ private URL getVersionedResourceURL(Resource resource) {
+ String actualLocation = resource.location.getProtocol() + "://"
+ + resource.location.getHost();
+ if (resource.location.getPort() != -1) {
+ actualLocation += ":" + resource.location.getPort();
+ }
+ actualLocation += resource.location.getPath();
+ if (resource.requestVersion != null
+ && resource.requestVersion.isVersionId()) {
+ actualLocation += "?version-id=" + resource.requestVersion;
+ }
+ URL versionedURL;
+ try {
+ versionedURL = new URL(actualLocation);
+ } catch (MalformedURLException e) {
+ return resource.location;
+ }
+ return versionedURL;
+ }
+
+
+ /**
+ * Pick the next resource to download or initialize. If there
+ * are no more resources requested then one is taken from a
+ * resource tracker with prefetch enabled.<p>
+ *
+ * The resource state is advanced before it is returned
+ * (CONNECT-&gt;CONNECTING).<p>
+ *
+ * Calls to this method should be synchronized on lock.<p>
+ *
+ * @return the resource to initialize or download, or null
+ */
+ private static Resource selectNextResource() {
+ Resource result;
+
+ // pick from queue
+ result = selectByFlag(queue, CONNECT, ERROR); // connect but not error
+ if (result == null)
+ result = selectByFlag(queue, DOWNLOAD, ERROR|CONNECT|CONNECTING);
+
+ // remove from queue if found
+ if (result != null)
+ queue.remove(result);
+
+ // prefetch if nothing found so far and this is the last thread
+ if (result == null && threads == 1)
+ result = getPrefetch();
+
+ if (result == null)
+ return null;
+
+ synchronized (result) {
+ if (result.isSet(CONNECT)) {
+ result.changeStatus(CONNECT, CONNECTING);
+ }
+ else if (result.isSet(DOWNLOAD)) {
+ // only download if *not* connecting, when done connecting
+ // select next will pick up the download part. This makes
+ // all requested connects happen before any downloads, so
+ // the size is known as early as possible.
+ result.changeStatus(DOWNLOAD, DOWNLOADING);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the next resource to be prefetched before
+ * requested.<p>
+ *
+ * Calls to this method should be synchronized on lock.<p>
+ */
+ private static Resource getPrefetch() {
+ Resource result = null;
+ Resource alternate = null;
+
+ // first find one to initialize
+ synchronized (prefetchTrackers) {
+ for (int i=0; i < prefetchTrackers.size() && result == null; i++) {
+ ResourceTracker tracker = (ResourceTracker) prefetchTrackers.get(i);
+ if (tracker == null)
+ continue;
+
+ synchronized (tracker.resources) {
+ result = selectByFlag(tracker.resources, UNINITIALIZED, ERROR);
+
+ if (result == null && alternate == null)
+ alternate = selectByFlag(tracker.resources, CONNECTED, ERROR|DOWNLOADED|DOWNLOADING|DOWNLOAD);
+ }
+ }
+ }
+
+ // if none to initialize, switch to download
+ if (result == null)
+ result = alternate;
+
+ if (result == null)
+ return null;
+
+ synchronized (result) {
+ ResourceTracker tracker = result.getTracker();
+ if (tracker == null)
+ return null; // GC of tracker happened between above code and here
+
+ // prevents startResource from putting it on queue since
+ // we're going to return it.
+ result.changeStatus(0, STARTED);
+
+ tracker.startResource(result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Selects a resource from the source list that has the
+ * specified flag set.<p>
+ *
+ * Calls to this method should be synchronized on lock and
+ * source list.<p>
+ */
+ private static Resource selectByFlag(List source, int flag, int notflag) {
+ Resource result = null;
+ int score = Integer.MAX_VALUE;
+
+ for (int i=0; i < source.size(); i++) {
+ Resource resource = (Resource) source.get(i);
+ boolean selectable = false;
+
+ synchronized (resource) {
+ if (resource.isSet(flag) && !resource.isSet(notflag))
+ selectable = true;
+ }
+
+ if (selectable) {
+ int activeCount = 0;
+
+ for (int j=0; j < active.size(); j++)
+ if ((ResourceTracker)active.get(j) == resource.getTracker())
+ activeCount++;
+
+ // try to spread out the downloads so that a slow host
+ // won't monopolize the downloads
+ if (activeCount < score) {
+ result = resource;
+ score = activeCount;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Return the resource matching the specified URL.
+ *
+ * @throws IllegalArgumentException if the resource is not being tracked
+ */
+ private Resource getResource(URL location) {
+ synchronized (resources) {
+ for (int i=0; i < resources.size(); i++) {
+ Resource resource = (Resource) resources.get(i);
+
+ if (CacheUtil.urlEquals(resource.location, location))
+ return resource;
+ }
+ }
+
+ throw new IllegalArgumentException("Location does not specify a resource being tracked.");
+ }
+
+ /**
+ * Wait for some resources.
+ *
+ * @param resources the resources to wait for
+ * @param timeout the timeout, or 0 to wait until completed
+ * @returns true if the resources were downloaded or had errors,
+ * false if the timeout was reached
+ * @throws InterruptedException if another thread interrupted the wait
+ */
+ private boolean wait(Resource resources[], long timeout) throws InterruptedException {
+ long startTime = System.currentTimeMillis();
+
+ // start them downloading / connecting in background
+ for (int i=0; i < resources.length; i++)
+ startResource(resources[i]);
+
+ // wait for completion
+ while (true) {
+ boolean finished = true;
+
+ synchronized (lock) {
+ // check for completion
+ for (int i=0; i < resources.length; i++) {
+ //NetX Deadlocking may be solved by removing this
+ //synch block.
+ synchronized (resources[i]) {
+ if (!resources[i].isSet(DOWNLOADED | ERROR)) {
+ finished = false;
+ break;
+ }
+ }
+ }
+ if (finished)
+ return true;
+
+ // wait
+ long waitTime = 0;
+
+ if (timeout > 0) {
+ waitTime = timeout - (System.currentTimeMillis()-startTime);
+ if (waitTime <= 0)
+ return false;
+ }
+
+ lock.wait(waitTime);
+ }
+ }
+ }
+
+
+ // inner classes
+
+ /**
+ * This class downloads and initializes the queued resources.
+ */
+ class Downloader implements Runnable {
+ Resource resource = null;
+
+ public void run() {
+ while (true) {
+ synchronized (lock) {
+ // remove from active list, used for load balancing
+ if (resource != null)
+ active.remove(resource.getTracker());
+
+ resource = selectNextResource();
+
+ if (resource == null) {
+ endThread();
+ break;
+ }
+
+ // add to active list, used for load balancing
+ active.add(resource.getTracker());
+ }
+
+ try {
+ processResource(resource);
+ }
+ catch (Exception ex) {
+ if (JNLPRuntime.isDebug())
+ ex.printStackTrace();
+ }
+ }
+ // should have a finally in case some exception is thrown by
+ // selectNextResource();
+ }
+ };
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/UpdatePolicy.java b/netx/net/sourceforge/jnlp/cache/UpdatePolicy.java
new file mode 100644
index 0000000..157e38c
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/UpdatePolicy.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2002 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.*;
+/**
+ * A policy that determines when a resource should be checked for
+ * an updated version.<p>
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.3 $
+ */
+public class UpdatePolicy {
+
+ // todo: implement session updating
+
+ // todo: doesn't seem to work in the same JVM, probably because
+ // Resource is being held by a tracker so it isn't collected;
+ // then next time a tracker adds the resource even if
+ // shouldUpdate==true it's state is already marked
+ // CONNECTED|DOWNLOADED. Let the resource be collected or reset
+ // to UNINITIALIZED.
+
+ public static UpdatePolicy ALWAYS = new UpdatePolicy(0);
+ public static UpdatePolicy SESSION = new UpdatePolicy(-1);
+ public static UpdatePolicy FORCE = new UpdatePolicy(Long.MIN_VALUE);
+ public static UpdatePolicy NEVER = new UpdatePolicy(Long.MAX_VALUE);
+
+ private long timeDiff = -1;
+
+
+ /**
+ * Create a new update policy; this policy always updates the
+ * entry unless the shouldUpdate method is overridden.
+ */
+ public UpdatePolicy() {
+ }
+
+ /**
+ * Create an update policy that only checks a file for being
+ * updated if it has not been checked for longer than the
+ * specified time.
+ *
+ * @param timeDiff how long in ms until update needed
+ */
+ public UpdatePolicy(long timeDiff) {
+ this.timeDiff = timeDiff;
+ }
+
+ /**
+ * Returns whether the resource should be checked for being
+ * up-to-date.
+ */
+ public boolean shouldUpdate(CacheEntry entry) {
+ long updated = entry.getLastUpdated();
+ long current = System.currentTimeMillis();
+
+ if (current - updated >= timeDiff)
+ return true;
+ else
+ return false;
+ }
+
+}
diff --git a/netx/net/sourceforge/jnlp/cache/package.html b/netx/net/sourceforge/jnlp/cache/package.html
new file mode 100644
index 0000000..4911ec0
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/cache/package.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+This package contains the JNLP cache.
+
+<h2>Package Specification</h2>
+
+<ul>
+<li><a target="_top" href="http://java.sun.com/products/javawebstart/download-spec.html">JNLP specification</a>
+</ul>
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+<li><a target="_top" href="http://jnlp.sourceforge.net/netx/">Netx JNLP Client</a>
+<li><a target="_top" href="http://java.sun.com/products/javawebstart/">Java Web Start JNLP Client</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
+
+