// 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 static net.sourceforge.jnlp.runtime.Translator.R; 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.config.DeploymentConfiguration; 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.
* * @author Jon A. Maxwell (JAM) - initial author * @version $Revision: 1.17 $ */ public class CacheUtil { /** * 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.
*
* @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);
// TODO: Should be toURI().toURL()
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.getConfiguration()
.getProperty(DeploymentConfiguration.KEY_USER_CACHE_DIR));
if (!(cacheDir.isDirectory())) {
return;
}
if (JNLPRuntime.isDebug()) {
System.err.println("Clearing cache directory: " + cacheDir);
}
try {
cacheDir = cacheDir.getCanonicalFile();
FileUtils.recursiveDelete(cacheDir, cacheDir);
} 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.getConfiguration()
.getProperty(DeploymentConfiguration.KEY_USER_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 {
String cacheDir = JNLPRuntime.getConfiguration()
.getProperty(DeploymentConfiguration.KEY_USER_CACHE_DIR);
File localFile = urlToPath(source, cacheDir);
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 given directory. For
* example a url with subdirectory /tmp/ will
* result in a File that is located somewhere within /tmp/
*
* @param location the url
* @param subdir the subdirectory
* @return the file
*/
public static File urlToPath(URL location, String subdir) {
if (subdir == null) {
throw new NullPointerException();
}
StringBuffer path = new StringBuffer();
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(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