diff options
author | andrew <devnull@localhost> | 2010-10-19 17:55:59 +0100 |
---|---|---|
committer | andrew <devnull@localhost> | 2010-10-19 17:55:59 +0100 |
commit | 7603e948d7a0a7eb2e72358cb4a40ae6779f95da (patch) | |
tree | c6441f7d14eafe8119d890cddd09b05b8f88c52a /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.java | 172 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/CacheUtil.java | 450 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java | 320 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/DownloadIndicator.java | 91 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/Resource.java | 269 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/ResourceTracker.java | 1049 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/UpdatePolicy.java | 88 | ||||
-rw-r--r-- | netx/net/sourceforge/jnlp/cache/package.html | 28 |
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 >= 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 >= 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->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> + + |