From 0e853085d5df2b858cfc175353dea04de25e96ee Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 14 Jul 2004 14:06:46 +0000 Subject: [PATCH] OK boys, we're going in. Revamped the resource manager now that it doesn't handle downloading of resources, it simply manages them in situ. Getdown is now responsible for all the downloading. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3060 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/resource/DownloadManager.java | 390 --------- .../com/threerings/resource/Downloader.java | 181 ---- .../threerings/resource/FileDownloader.java | 33 - .../threerings/resource/HTTPDownloader.java | 78 -- .../threerings/resource/JNLPDownloader.java | 307 ------- .../threerings/resource/PatchException.java | 17 - .../com/threerings/resource/ProgressInfo.java | 60 -- .../threerings/resource/ResourceBundle.java | 6 +- .../threerings/resource/ResourceManager.java | 786 +++++------------- 9 files changed, 192 insertions(+), 1666 deletions(-) delete mode 100644 src/java/com/threerings/resource/DownloadManager.java delete mode 100644 src/java/com/threerings/resource/Downloader.java delete mode 100644 src/java/com/threerings/resource/FileDownloader.java delete mode 100644 src/java/com/threerings/resource/HTTPDownloader.java delete mode 100644 src/java/com/threerings/resource/JNLPDownloader.java delete mode 100644 src/java/com/threerings/resource/PatchException.java delete mode 100644 src/java/com/threerings/resource/ProgressInfo.java diff --git a/src/java/com/threerings/resource/DownloadManager.java b/src/java/com/threerings/resource/DownloadManager.java deleted file mode 100644 index bebf77bc8..000000000 --- a/src/java/com/threerings/resource/DownloadManager.java +++ /dev/null @@ -1,390 +0,0 @@ -// -// $Id: DownloadManager.java,v 1.15 2004/02/25 14:49:39 mdb Exp $ - -package com.threerings.resource; - -import java.awt.EventQueue; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import com.samskivert.util.Queue; -import com.samskivert.util.StringUtil; - -/** - * Manages the asynchronous downloading of files that are usually located - * on an HTTP server but that may also be located on the local filesystem. - */ -public class DownloadManager -{ - /** Indicates whether or not we're using versioned resources. */ - public static boolean VERSIONING = false; - - /** - * Provides facilities for notifying an observer of file download - * progress. - */ - public interface DownloadObserver - { - /** - * If this method returns true the download observer callbacks - * will be called on the AWT thread, allowing the observer to do - * things like safely update user interfaces, etc. If false, it - * will be called on the download thread. - */ - public boolean notifyOnAWTThread (); - - /** - * Called when the download manager is about to check all - * downloads to see whether they are in need of an update. - */ - public void resolvingDownloads (); - - /** - * Called to inform the observer of ongoing progress toward - * completion of the overall downloading task. The caller is - * guaranteed to get at least one call reporting 100% completion. - * - * @param percent the percent completion, in terms of total file - * size, of the download request. - * @param remaining the estimated download time remaining in - * seconds, or -1 if the time can not yet be - * determined. - */ - public void downloadProgress (int percent, long remaining); - - /** - * Called on the download thread when the patching of jar files - * has begun. - */ - public void patching (); - - /** - * Called after the download and patching has completed on the - * download manager thread. - */ - public void postDownloadHook (); - - /** - * Called on the preferred notification thread after the download - * is complete and the post-download hook has run to completion. - */ - public void downloadComplete (); - - /** - * Called if a failure occurs while checking for an update or - * downloading a file. - * - * @param desc the file that was being downloaded when the error - * occurred, or null if the failure occurred while - * resolving downloads. - * @param e the exception detailing the failure. - */ - public void downloadFailed (DownloadDescriptor desc, Exception e); - } - - /** - * Describes a single file to be downloaded. - */ - public static class DownloadDescriptor - { - /** The URL from which the file is to be downloaded. */ - public URL sourceURL; - - /** The destination file to which the file is to be written. */ - public File destFile; - - /** The version of the file to be downloaded. */ - public String version; - - /** The last-modified timestamp of the source. */ - public long lastModified; - - /** The last-modified timestamp of the destination file. */ - public long destLastModified; - - /** The size in bytes of the destination file. */ - public long destFileSize; - - /** - * Constructs a download descriptor to retrieve the specified - * version of the given file from the given URL. - */ - public DownloadDescriptor (URL url, File file, String version) - { - this.sourceURL = url; - this.destFile = file; - this.version = version; - } - - /** - * Creates the appropriate type of downloader for this descriptor. - */ - public Downloader createDownloader () - throws IOException - { - String protocol = sourceURL.getProtocol(); - if (protocol.equals("file")) { - return new FileDownloader(); - } else if (protocol.equals("http")) { - return VERSIONING ? (Downloader)new JNLPDownloader() : - (Downloader)new HTTPDownloader(); - } else { - throw new IOException( - "Unknown source file protocol " + - "[protocol=" + protocol + ", desc=" + this + "]."); - } - } - - /** Returns a string representation of this instance. */ - public String toString () - { - return StringUtil.fieldsToString(this); - } - } - - /** - * Downloads the supplied list of descriptors, notifying the given - * download observer of download progress and status. - * - * @param descriptors the list of files to be downloaded. - * @param fragile if true, reports failure and ceases any further - * downloading if an error occurs; else will continue attempting to - * download the remainder of the descriptors. - * @param downloadObs the observer to notify of progress, success and - * failure. - */ - public void download ( - List descriptors, boolean fragile, DownloadObserver downloadObs) - { - // add the download request to the download queue - DownloadRecord dlrec = new DownloadRecord(); - dlrec.descriptors = descriptors; - dlrec.obs = downloadObs; - dlrec.fragile = fragile; - _dlqueue.append(dlrec); - - synchronized (this) { - // if we've not yet got our downloading thread... - if (_dlthread == null) { - // create the thread - _dlthread = new Thread() { - public void run () { - processDownloads(); - } - }; - _dlthread.setDaemon(true); - // and start it going - _dlthread.start(); - } - } - } - - /** - * Called by the download thread to process all download requests in - * the download queue. - */ - protected void processDownloads () - { - // create the data buffer to be used when reading files - _buffer = new byte[BUFFER_SIZE]; - - DownloadRecord dlrec; - while (true) { - synchronized (this) { - // kill the download thread if we have no remaining - // download requests - if (_dlqueue.size() == 0) { - _dlthread = null; - // free up the data buffer - _buffer = null; - return; - } - - // pop download off the queue - dlrec = (DownloadRecord)_dlqueue.getNonBlocking(); - } - - // handle the download request - processDownloadRequest(dlrec.descriptors, dlrec.obs, dlrec.fragile); - } - } - - /** - * Processes a single download request. - */ - protected void processDownloadRequest ( - List descriptors, final DownloadObserver obs, boolean fragile) - { - // let the observer know that we're about to resolve all files to - // be downloaded - if (obs.notifyOnAWTThread()) { - EventQueue.invokeLater(new Runnable() { - public void run () { - obs.resolvingDownloads(); - } - }); - } else { - obs.resolvingDownloads(); - } - - // check the size and last-modified information for each file to - // ascertain whether our local copy needs to be refreshed - ArrayList fetch = new ArrayList(); - ProgressInfo pinfo = new ProgressInfo(); - int size = descriptors.size(); - for (int ii = 0; ii < size; ii++) { - DownloadDescriptor desc = (DownloadDescriptor)descriptors.get(ii); - - try { - Downloader loader = desc.createDownloader(); - loader.init(desc); - if (loader.checkUpdate(pinfo)) { - fetch.add(loader); - } - - } catch (final IOException ioe) { - notifyFailed(obs, null, ioe); - if (fragile) { - return; - } - } - } - - if (pinfo.totalSize > 0) { - Log.info("Initiating download of " + pinfo.totalSize + " bytes."); - } - - // download all stale files - size = fetch.size(); - DownloadDescriptor fdesc = null; - pinfo.start = System.currentTimeMillis(); - for (int ii = 0; ii < size; ii++) { - Downloader loader = (Downloader)fetch.get(ii); - try { - loader.processDownload(this, obs, pinfo, _buffer); - } catch (IOException ioe) { - notifyFailed(obs, loader.getDescriptor(), ioe); - if (fragile) { - return; - } else { - continue; // skip the postDownload for that bundle. - } - } - - try { - loader.postDownload(this, obs, pinfo); - } catch (IOException ioe) { - // we want to try to apply as many of the patches as we - // can, so we don't fail entirely here, just keep track of - // the last failure and report that when we're done - fdesc = loader.getDescriptor(); - Log.warning("Downloader failed in postDownload hook " + - "[desc=" + fdesc + "]."); - Log.logStackTrace(ioe); - } - } - - // if we had any failure, go ahead and report it now - if (fdesc != null) { - PatchException pe = new PatchException( - "Failed to patch one or more updated bundles."); - notifyFailed(obs, fdesc, pe); - - } else { - // make sure to always let the observer know that we've - // wrapped up by reporting 100% completion - notifyProgress(obs, 100, 0L); - } - } - - /** Helper function. */ - protected void notifyProgress (final DownloadObserver obs, - final int progress, final long remaining) - { - if (obs.notifyOnAWTThread()) { - EventQueue.invokeLater(new Runnable() { - public void run () { - obs.downloadProgress(progress, remaining); - } - }); - } else { - obs.downloadProgress(progress, remaining); - } - - if (progress == 100) { - // if we're at 100%, run the post-download hook - try { - obs.postDownloadHook(); - } catch (Exception e) { - Log.warning("Observer choked in post-download hook."); - Log.logStackTrace(e); - } - - // notify of total and final download completion - if (obs.notifyOnAWTThread()) { - EventQueue.invokeLater(new Runnable() { - public void run () { - obs.downloadComplete(); - } - }); - } else { - obs.downloadComplete(); - } - } - } - - /** Helper function. */ - protected void notifyFailed (final DownloadObserver obs, - final DownloadDescriptor desc, - final Exception e) - { - if (obs.notifyOnAWTThread()) { - EventQueue.invokeLater(new Runnable() { - public void run () { - obs.downloadFailed(desc, e); - } - }); - } else { - obs.downloadFailed(desc, e); - } - } - - /** - * A record describing a single download request. - */ - protected static class DownloadRecord - { - /** The list of download descriptors to be downloaded. */ - public List descriptors; - - /** The download observer to notify of download progress. */ - public DownloadObserver obs; - - /** Whether to abort downloading if an error occurs. */ - public boolean fragile; - } - - /** The downloading thread. */ - protected Thread _dlthread; - - /** The queue of download requests. */ - protected Queue _dlqueue = new Queue(); - - /** The data buffer used when reading file data. */ - protected byte[] _buffer; - - /** The data buffer size for reading file data. */ - protected static final int BUFFER_SIZE = 2048; - - static { - try { - VERSIONING = "true".equalsIgnoreCase( - System.getProperty("versioned_rsrcs")); - } catch (Throwable t) { - // no versioning, no problem - } - } -} diff --git a/src/java/com/threerings/resource/Downloader.java b/src/java/com/threerings/resource/Downloader.java deleted file mode 100644 index e08965d81..000000000 --- a/src/java/com/threerings/resource/Downloader.java +++ /dev/null @@ -1,181 +0,0 @@ -// -// $Id: Downloader.java,v 1.4 2004/06/16 09:44:23 mdb Exp $ - -package com.threerings.resource; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URLConnection; - -import com.threerings.resource.DownloadManager.DownloadDescriptor; -import com.threerings.resource.DownloadManager.DownloadObserver; - -/** - * Does something extraordinary. - */ -public abstract class Downloader -{ - /** - * Provides this downloader with a reference to the download - * descriptor it needs to do its business. - */ - public void init (DownloadDescriptor ddesc) - { - _desc = ddesc; - _desc.destFileSize = _desc.destFile.length(); - _desc.destLastModified = _desc.destFile.lastModified(); - } - - /** - * Returns a reference to the descriptor we are downloading. - */ - public DownloadDescriptor getDescriptor () - { - return _desc; - } - - /** - * Requests that this downloader determine whether it needs to be - * updated and if so, it should add its total download requirement to - * {@link ProgressInfo#totalSize} the supplied {@link ProgressInfo} - * record. - * - * @return true if this downloader needs be updated, false if not. - */ - public abstract boolean checkUpdate (ProgressInfo info) - throws IOException; - - /** - * Compares the supplied size and last modified time with the size and - * last modified time of our local copy of the file. - */ - protected boolean compareWithLocal (long fileSize, long lastModified) - { - // check to see whether we have a local copy at all, and if so, - // whether its file size or last-modified time differs from the - // source file modulo a delta to account for inexactness on the - // part of the file-system (thanks Microsoft) - return ((!_desc.destFile.exists()) || - (_desc.destFileSize != fileSize) || - (_desc.destLastModified < (lastModified - DELTA)) || - (_desc.destLastModified > (lastModified + DELTA))); - } - - /** - * Processes a single download descriptor. - */ - public void processDownload (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo, byte[] buffer) - throws IOException - { - // download the resource bundle from the specified URL - URLConnection ucon = _desc.sourceURL.openConnection(); - - // prepare to read the data from the URL into the cache file - Log.info("Downloading file [url=" + _desc.sourceURL + "]."); - downloadContent(dmgr, obs, pinfo, buffer, ucon, _desc.destFile); - - // if we have a last modified time, we want to adjust our cache - // file accordingly - if (_desc.lastModified != 0) { - if (!_desc.destFile.setLastModified(_desc.lastModified)) { - Log.warning("Failed to set last-modified date " + - "[file=" + _desc.destFile + "]."); - } - } - } - - /** - * Called after the download phase has completed to allow patching or - * other post-download activities. - */ - public void postDownload (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo) - throws IOException - { - // nothing to do by default - } - - /** - * Downloads the content from the supplied URL connection into the - * specified destination file, informing the supplied download manager - * and observer of our progress along the way. - */ - protected void downloadContent (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo, byte[] buffer, - URLConnection ucon, File destFile) - throws IOException - { - InputStream in = ucon.getInputStream(); - FileOutputStream out = new FileOutputStream(destFile); - int read; - - // TODO: look to see if we have a download info file containing - // info on potentially partially downloaded data; if so, use a - // "Range: bytes=HAVE-" header. - - // if we were unable to determine our content length, record a - // single "byte" of progress to indicate that we've started to - // download this file - if (_contentLength <= 0) { - pinfo.currentSize += 1; - } - - // read in the file data - while ((read = in.read(buffer)) != -1) { - // write it out to our local copy - out.write(buffer, 0, read); - - // if we know we added something to the total download size, - // then report our progress to the download observer as a - // percentage of the total file data to be transferred - if (_contentLength > 0) { - pinfo.currentSize += read; - } - - // update our percent completion; if we're totally done, hold - // off on notifying the observer until the download manager - // finishes fiddling to ensure that any action the observer - // may take on the downloaded files can be safely undertaken - int pctdone = pinfo.getPercentDone(); - pinfo.complete = (pctdone >= 100); - if (pinfo.complete) { - continue; - } - - // update the transfer rate to reflect the bit of data we just - // transferred - long now = System.currentTimeMillis(); - pinfo.updateXferRate(now); - - // notify the progress observer if it's been sufficiently long - // since our last notification - if ((now - pinfo.lastUpdate) >= UPDATE_DELAY) { - pinfo.lastUpdate = now; - long remaining = pinfo.getXferTimeRemaining(); - dmgr.notifyProgress(obs, pctdone, remaining); - } - } - - // close the streams - in.close(); - out.close(); - } - - /** The descriptor that we're downloading. */ - protected DownloadDescriptor _desc; - - /** We need this to cope gracefully with a missing content-length. */ - protected long _contentLength; - - /** The last-modified difference in milliseconds allowed between the - * source and destination file without considering the destination - * file to be out of date. */ - protected static final long DELTA = 5000L; - - /** The delay in milliseconds between notifying progress observers of - * file download progress. */ - protected static final long UPDATE_DELAY = 2500L; -} diff --git a/src/java/com/threerings/resource/FileDownloader.java b/src/java/com/threerings/resource/FileDownloader.java deleted file mode 100644 index 8ee914227..000000000 --- a/src/java/com/threerings/resource/FileDownloader.java +++ /dev/null @@ -1,33 +0,0 @@ -// -// $Id: FileDownloader.java,v 1.2 2004/06/16 09:44:23 mdb Exp $ - -package com.threerings.resource; - -import java.io.File; -import java.io.IOException; - -/** - * "Downloads" a file from a location on the filesystem. - */ -public class FileDownloader extends Downloader -{ - // documentation inherited - public boolean checkUpdate (ProgressInfo info) - throws IOException - { - // read the file information directly from the file system - File tfile = new File(_desc.sourceURL.getPath()); - _contentLength = tfile.length(); - _desc.lastModified = tfile.lastModified(); - - if (compareWithLocal(_contentLength, _desc.lastModified)) { - // increment the total file size to be fetched - info.totalSize += _contentLength; - Log.debug("File deemed stale [url=" + _desc.sourceURL + "]."); - return true; - } else { - Log.debug("File deemed up-to-date [url=" + _desc.sourceURL + "]."); - return false; - } - } -} diff --git a/src/java/com/threerings/resource/HTTPDownloader.java b/src/java/com/threerings/resource/HTTPDownloader.java deleted file mode 100644 index 9f5a00b5d..000000000 --- a/src/java/com/threerings/resource/HTTPDownloader.java +++ /dev/null @@ -1,78 +0,0 @@ -// -// $Id: HTTPDownloader.java,v 1.4 2004/06/17 03:02:55 mdb Exp $ - -package com.threerings.resource; - -import java.io.IOException; -import java.net.HttpURLConnection; - -import com.threerings.resource.DownloadManager.DownloadObserver; - -/** - * Downloads resources via HTTP using on last modification timestamps to - * determine whether updates are needed. - */ -public class HTTPDownloader extends Downloader -{ - // documentation inherited - public boolean checkUpdate (ProgressInfo info) - throws IOException - { - // read the file information via an HTTP HEAD request - HttpURLConnection ucon = (HttpURLConnection) - _desc.sourceURL.openConnection(); - ucon.setRequestMethod("HEAD"); - ucon.connect(); - - // make sure we got a satisfactory response code - if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) { - String errmsg = "Unable to check up-to-date for " + - _desc.sourceURL + ": " + ucon.getResponseCode(); - throw new IOException(errmsg); - } - - // read size and last modified information from the HEAD response - _contentLength = ucon.getContentLength(); - _desc.lastModified = ucon.getLastModified(); - - if (compareWithLocal(_contentLength, _desc.lastModified)) { - // increment the total file size to be fetched - info.totalSize += _contentLength; - Log.debug("Resource deemed stale [url=" + _desc.sourceURL + "]."); - return true; - } else { - Log.debug("Resource up-to-date [url=" + _desc.sourceURL + "]."); - return false; - } - } - - // documentation inherited - public void processDownload (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo, byte[] buffer) - throws IOException - { - // download the resource bundle from the specified URL - HttpURLConnection ucon = (HttpURLConnection) - _desc.sourceURL.openConnection(); - ucon.connect(); - - // make sure we got a satisfactory response code - if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) { - String errmsg = "Unable to download update for " + - _desc.sourceURL + ": " + ucon.getResponseCode(); - throw new IOException(errmsg); - } - - Log.info("Downloading updated jar [url=" + _desc.sourceURL + "]."); - downloadContent(dmgr, obs, pinfo, buffer, ucon, _desc.destFile); - - // if we have a last modified time, we want to adjust our cache - // file accordingly - if (_desc.lastModified != 0) { - if (!_desc.destFile.setLastModified(_desc.lastModified)) { - Log.warning("Failed to set last-modified date " + - "[file=" + _desc.destFile + "]."); - } - } - } -} diff --git a/src/java/com/threerings/resource/JNLPDownloader.java b/src/java/com/threerings/resource/JNLPDownloader.java deleted file mode 100644 index b1435a23d..000000000 --- a/src/java/com/threerings/resource/JNLPDownloader.java +++ /dev/null @@ -1,307 +0,0 @@ -// -// $Id: JNLPDownloader.java,v 1.18 2004/06/16 09:44:23 mdb Exp $ - -package com.threerings.resource; - -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; - -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -import com.sun.javaws.cache.Patcher; -import com.sun.javaws.jardiff.JarDiffPatcher; - -import com.samskivert.io.StreamUtil; -import com.samskivert.util.FileUtil; -import com.samskivert.util.StringUtil; - -import com.threerings.resource.DownloadManager.DownloadDescriptor; -import com.threerings.resource.DownloadManager.DownloadObserver; - -/** - * Does something extraordinary. - */ -public class JNLPDownloader extends Downloader -{ - // documentation inherited - public void init (DownloadDescriptor ddesc) - { - super.init(ddesc); - - String dpath = _desc.destFile.getPath(); - - // determine the path of our version file before we version the - // path of the destination file - _vfile = new File(FileUtil.resuffix(_desc.destFile, ".jar", ".vers")); - - // if we're using a version, adjust our destination file path - // based on said version - if (!StringUtil.blank(_desc.version)) { - _desc.destFile = new File( - ResourceManager.versionPath(dpath, _desc.version, ".jar")); - } - - // determine which version we already have, if any - if (_vfile.exists()) { - try { - BufferedReader vin = new BufferedReader(new FileReader(_vfile)); - _cvers = vin.readLine(); - - // make sure the version referenced by that file still - // exists; if not ignore our "current version" - _curFile = new File(ResourceManager.versionPath( - dpath, _cvers, ".jar")); - if (!_curFile.exists()) { - _cvers = null; - } - - } catch (IOException ioe) { - Log.warning("Error reading version file [path=" + _vfile + - ", error=" + ioe + "]."); - } - } - } - - // documentation inherited - public boolean checkUpdate (ProgressInfo info) - throws IOException - { - // if we're doing versioning and we have the appropriate version, - // no need to update - if (_desc.version != null && _desc.version.equals(_cvers)) { - return false; - } - - // read the file information via an HTTP HEAD request - HttpURLConnection ucon = (HttpURLConnection) - getResourceURL().openConnection(); - ucon.setRequestMethod("HEAD"); - ucon.connect(); - - // make sure we got a satisfactory response code - if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK || - JNLP_ERROR_TYPE.equals(ucon.getContentType())) { - String errmsg = "Unable to check up-to-date for " + - getResourceURL() + ": " + ucon.getResponseCode(); - throw new IOException(errmsg); - } - - // determine our content length - _contentLength = ucon.getContentLength(); - if (_contentLength > 0) { - // and record it to the total size if it is a sane value - info.totalSize += _contentLength; - } else { - // if it's not sane, record a single byte which we will then - // properly interepret later during actual downloading - info.totalSize += 1; - } - Log.info(getResourceURL() + " requires " + _contentLength + - " byte update."); - return true; - } - - // documentation inherited - public void processDownload (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo, byte[] buffer) - throws IOException - { - // download the resource bundle from the specified URL - URL rsrcURL = getResourceURL(); - HttpURLConnection ucon = (HttpURLConnection)rsrcURL.openConnection(); - ucon.connect(); - - // make sure we got a satisfactory response code - if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK || - JNLP_ERROR_TYPE.equals(ucon.getContentType())) { - String errmsg = "Unable to download update for " + - getResourceURL() + ": " + ucon.getResponseCode(); - throw new IOException(errmsg); - } - - // determine whether or not this is a patch - if (ucon.getContentType().equals(JARDIFF_TYPE)) { - Log.info("Downloading patch [url=" + rsrcURL + "]."); - _patchFile = new File( - FileUtil.resuffix(_curFile, ".jar", ".diff")); - downloadContent(dmgr, obs, pinfo, buffer, ucon, _patchFile); - - } else { - Log.info("Downloading whole jar [url=" + rsrcURL + "]."); - downloadContent(dmgr, obs, pinfo, buffer, ucon, _desc.destFile); - } - } - - // documentation inherited - public void postDownload (DownloadManager dmgr, DownloadObserver obs, - ProgressInfo pinfo) - throws IOException - { - if (_patchFile != null) { - // now apply the patch - Log.info("Applying patch [old=" + _curFile + - ", patch=" + _patchFile + - ", new=" + _desc.destFile + "]."); - Patcher.PatchDelegate delegate = new Patcher.PatchDelegate() { - public void patching (int value) { - // System.out.println("Patching " + _desc.destFile + ": " + - // value); - } - }; - - JarDiffPatcher patcher = new JarDiffPatcher(); - BufferedOutputStream out = null; - try { - out = new BufferedOutputStream( - new FileOutputStream(_desc.destFile)); - patcher.applyPatch(delegate, _curFile.getPath(), - _patchFile.getPath(), out); - out.close(); - - } catch (IOException ioe) { - Log.warning("Failure applying patch [rfile=" + _desc.destFile + - ", error=" + ioe + "]. Cleaning up and failing."); - StreamUtil.close(out); - cleanUpAndFail(ioe); - } - - // clean up the old jar and the patch file - if (!_curFile.delete()) { - Log.warning("Failed to delete old bundle " + _curFile + "."); - } - if (!_patchFile.delete()) { - Log.warning("Failed to delete patch file " + _patchFile + "."); - } - - // delete any old unversioned version of the .jar file - File unverDest = new File(ResourceManager.unversionPath( - _desc.destFile.getPath(), ".jar")); - if (unverDest.exists()) { - if (!unverDest.delete()) { - Log.warning("Failed to delete old unversioned bundle '" + - unverDest + "'."); - } - } - } - - // attempt to delete any old stale bundles as well - if (_desc.version != null) { - try { - String cpath = _desc.destFile.getPath(); - String pcpath = ResourceManager.unversionPath(cpath, ".jar"); - File pdir = _desc.destFile.getParentFile(); - File[] files = pdir.listFiles(); - for (int ii = 0; ii < files.length; ii++) { - String path = files[ii].getPath(); - if (path.equals(cpath) || !path.endsWith(".jar")) { - continue; - } - String ppath = ResourceManager.unversionPath(path, ".jar"); - if (!pcpath.equals(ppath)) { - continue; - } - if (!files[ii].delete()) { - Log.warning("Unable to delete stale bundle '" + - files[ii].getPath() + "'."); - } else { - Log.info("Deleted stale bundle '" + files[ii] + "'."); - } - } - } catch (Exception e) { - Log.warning("Failure deleting stale bundles."); - Log.logStackTrace(e); - } - } - - PrintWriter pout = new PrintWriter( - new BufferedWriter(new FileWriter(_vfile))); - pout.println(_desc.version); - pout.close(); - // Log.info("Updated version to " + _desc.version + "."); - } - - /** - * Constructs the resource URL using the source URL supplied in the - * download descriptor and any version information that is - * appropriate. - */ - protected URL getResourceURL () - { - if (_desc.version == null) { - return _desc.sourceURL; - } - URL rsrcURL = _desc.sourceURL; - String vargs = _desc.sourceURL.getPath() + - "?version-id=" + _desc.version; - if (_cvers != null) { - vargs += "¤t-version-id=" + _cvers; - } - try { - rsrcURL = new URL(rsrcURL, vargs); - } catch (MalformedURLException mue) { - Log.warning("Error creating versioned resource URL " + - "[url=" + rsrcURL + ", vargs=" + vargs + "]."); - } - return rsrcURL; - } - - /** - * Attempts to wipe out everything relating to this resource so that - * the next time we attempt to run the client we can download it - * completely afresh. - */ - protected void cleanUpAndFail (IOException cause) - throws IOException - { - if (_curFile != null) { - Log.warning("Failed to delete " + _desc.destFile + - " in cleanUpAndFail()."); - } - if (_patchFile != null) { - _patchFile.delete(); - } - if (_vfile != null) { - if (!_vfile.delete()) { - Log.warning("Failed to delete " + _vfile + - " in cleanUpAndFail()."); - } - } - - IOException failure = new IOException( - "Failed to patch " + _desc.destFile + "."); - if (cause != null) { - failure.initCause(cause); - } - throw failure; - } - - /** A file that contains the current resource version. */ - protected File _vfile; - - /** The file that contains our patch if we have one. */ - protected File _patchFile; - - /** The current version of the resource if we have one. */ - protected String _cvers; - - /** The existing version of our bundle file. */ - protected File _curFile; - - /** The mime-type of a jardiff patch file. */ - protected static final String JARDIFF_TYPE = - "application/x-java-archive-diff"; - - /** The mime-type indicating a jnlp-servlet error. Why they don't just - * use an HTTP error response code, I dare not attempt to imagine. */ - protected static final String JNLP_ERROR_TYPE = - "application/x-java-jnlp-error"; -} diff --git a/src/java/com/threerings/resource/PatchException.java b/src/java/com/threerings/resource/PatchException.java deleted file mode 100644 index e443c4fe0..000000000 --- a/src/java/com/threerings/resource/PatchException.java +++ /dev/null @@ -1,17 +0,0 @@ -// -// $Id: PatchException.java,v 1.1 2003/08/09 00:31:27 mdb Exp $ - -package com.threerings.resource; - -import java.io.IOException; - -/** - * An exception thrown when we fail to patch one or more updated files. - */ -public class PatchException extends IOException -{ - public PatchException (String msg) - { - super(msg); - } -} diff --git a/src/java/com/threerings/resource/ProgressInfo.java b/src/java/com/threerings/resource/ProgressInfo.java deleted file mode 100644 index 68c7a6b82..000000000 --- a/src/java/com/threerings/resource/ProgressInfo.java +++ /dev/null @@ -1,60 +0,0 @@ -// -// $Id: ProgressInfo.java,v 1.1 2003/08/05 01:33:20 mdb Exp $ - -package com.threerings.resource; - -/** - * Used to track download progress. - */ -public class ProgressInfo -{ - /** The total file size in bytes to be transferred. */ - public long totalSize; - - /** The file size in bytes transferred thus far. */ - public long currentSize; - - /** The time at which the file transfer began. */ - public long start; - - /** The current transfer rate in bytes per second. */ - public long bytesPerSecond; - - /** The time at which the last progress update was posted to the - * progress observer. */ - public long lastUpdate; - - /** Whether the download has completed and the progress observer - * notified. */ - public boolean complete; - - /** - * Returns the percent completion, based on data size transferred, - * of the file transfer. - */ - public int getPercentDone () - { - return (int)((currentSize / (float)totalSize) * 100f); - } - - /** - * Updates the bytes per second transfer rate for the download - * associated with this progress info record. - */ - public void updateXferRate (long now) - { - long secs = (now - start) / 1000L; - bytesPerSecond = (secs == 0) ? 0 : (currentSize / secs); - } - - /** - * Returns the estimated transfer time remaining for the download - * associated with this progress info record, or -1 if - * the transfer time cannot currently be estimated. - */ - public long getXferTimeRemaining () - { - return (bytesPerSecond == 0) ? -1 : - (totalSize - currentSize) / bytesPerSecond; - } -} diff --git a/src/java/com/threerings/resource/ResourceBundle.java b/src/java/com/threerings/resource/ResourceBundle.java index f67951243..eefaa13b6 100644 --- a/src/java/com/threerings/resource/ResourceBundle.java +++ b/src/java/com/threerings/resource/ResourceBundle.java @@ -1,5 +1,5 @@ // -// $Id: ResourceBundle.java,v 1.29 2004/07/13 16:37:40 mdb Exp $ +// $Id: ResourceBundle.java,v 1.30 2004/07/14 14:06:46 mdb Exp $ package com.threerings.resource; @@ -49,9 +49,7 @@ public class ResourceBundle { _source = source; if (unpack) { - String root = ResourceManager.unversionPath( - source.getPath(), ".jar"); - root = stripSuffix(root); + String root = stripSuffix(source.getPath()); _unpacked = new File(root + ".stamp"); _cache = new File(root); } diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java index 6ab065a5e..13d79287c 100644 --- a/src/java/com/threerings/resource/ResourceManager.java +++ b/src/java/com/threerings/resource/ResourceManager.java @@ -1,5 +1,5 @@ // -// $Id: ResourceManager.java,v 1.43 2004/07/13 16:37:40 mdb Exp $ +// $Id: ResourceManager.java,v 1.44 2004/07/14 14:06:46 mdb Exp $ package com.threerings.resource; @@ -9,13 +9,11 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -28,14 +26,10 @@ import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; -import org.apache.commons.io.CopyUtils; - +import com.samskivert.net.PathUtil; import com.samskivert.util.ResultListener; import com.samskivert.util.StringUtil; -import com.threerings.resource.DownloadManager.DownloadDescriptor; -import com.threerings.resource.DownloadManager.DownloadObserver; - /** * The resource manager is responsible for maintaining a repository of * resources that are synchronized with a remote source. This is @@ -53,10 +47,10 @@ import com.threerings.resource.DownloadManager.DownloadObserver; * *

Applications that wish to make use of resource sets and their * associated bundles must call {@link #initBundles} after constructing - * the resource manager, providing the URL of a resource definition file + * the resource manager, providing the path to a resource definition file * which describes these resource sets. The definition file will be loaded * and the resource bundles defined within will be loaded relative to the - * resource definition URL. The bundles will be cached in the user's home + * resource directory. The bundles will be cached in the user's home * directory and only reloaded when the source resources have been * updated. The resource definition file looks something like the * following: @@ -86,64 +80,51 @@ import com.threerings.resource.DownloadManager.DownloadObserver; public class ResourceManager { /** - * Provides facilities for notifying an observer of resource bundle - * download progress. + * Provides facilities for notifying an observer of the resource + * unpacking process. */ - public interface BundleDownloadObserver + public interface InitObserver { /** - * If this method returns true the download observer callbacks - * will be called on the AWT thread, allowing the observer to do - * things like safely update user interfaces, etc. If false, it - * will be called on a special download thread. + * Indicates a percent completion along with an estimated time + * remaining in seconds. */ - public boolean notifyOnAWTThread (); + public void progress (int percent, long remaining); /** - * Called when the resource manager is about to check for an - * update of any of our resource sets. + * Indicates that there was a failure unpacking our resource + * bundles. */ - public void checkingForUpdate (); + public void initializationFailed (Exception e); + } - /** - * Called to inform the observer of ongoing progress toward - * completion of the overall bundle downloading task. The caller - * is guaranteed to get at least one call reporting 100% - * completion. - * - * @param percent the percent completion of the download. - * @param remaining the estimated download time remaining in - * seconds, or -1 if the time can not yet be - * determined. - */ - public void downloadProgress (int percent, long remaining); + /** + * An adapter that wraps an {@link InitObserver} and routes all method + * invocations to the AWT thread. + */ + public static class AWTInitObserver implements InitObserver + { + public AWTInitObserver (InitObserver obs) { + _obs = obs; + } - /** - * Called to inform the observer that the bundle patching process - * has begun. This follows the download phase, but is optional. - */ - public void patching (); + public void progress (final int percent, final long remaining) { + EventQueue.invokeLater(new Runnable() { + public void run () { + _obs.progress(percent, remaining); + } + }); + } - /** - * Called to inform the observer that the bundle unpacking process - * has begun. This follows the download or patching phase. - */ - public void unpacking (); + public void initializationFailed (final Exception e) { + EventQueue.invokeLater(new Runnable() { + public void run () { + _obs.initializationFailed(e); + } + }); + } - /** - * Called to indicate that we have validated and unpacked our - * bundles and are fully ready to go. A call to {@link - * #dowloadProgress} with 100 percent completion is guaranteed to - * precede this call and optionally one to {@link #patching} and - * {@link #unpacking}. - */ - public void downloadComplete (); - - /** - * Called if a failure occurs while checking for an update or - * downloading all resource sets. - */ - public void downloadFailed (Exception e); + protected InitObserver _obs; } /** @@ -164,52 +145,12 @@ public class ResourceManager // keep track of our root path _rootPath = resourceRoot; - // make root path end with a slash (not the platform dependent - // file system separator character as resource paths are passed to - // ClassLoader.getResource() which requires / as its separator) - if (!_rootPath.endsWith("/")) { - _rootPath = _rootPath + "/"; - } - // use the classloader that loaded us _loader = getClass().getClassLoader(); // set up a URL handler so that things can be loaded via // urls with the 'resource' protocol Handler.registerHandler(this); - - // sort our our default cache path - _baseCachePath = ""; - try { - // first check for an explicitly specified cache directory - _baseCachePath = System.getProperty("narya.resource.cache_dir"); - // if that's null, try putting it into their home directory - if (_baseCachePath == null) { - _baseCachePath = System.getProperty("user.home"); - } - if (!_baseCachePath.endsWith(File.separator)) { - _baseCachePath += File.separator; - } - _baseCachePath += (CACHE_PATH + File.separator); - } catch (SecurityException se) { - Log.info("Can't obtain user.home system property. Probably " + - "won't be able to create our cache directory " + - "either. [error=" + se + "]."); - } - } - - /** - * Instructs the resource manager to store cached resources in the - * specified directory. This must be called before {@link - * #initBundles} if it is going to be called at all. If it is not - * called, the default cache directory will be used. - */ - public void setCachePath (String cachePath) - { - if (!cachePath.endsWith(File.separator)) { - cachePath += File.separator; - } - _baseCachePath = cachePath; } /** @@ -217,100 +158,56 @@ public class ResourceManager * manager. Applications that wish to make use of resource bundles * should call this method after constructing the resource manager. * - * @param resourceURL the base URL from which resources are loaded. - * Relative paths specified in the resource definition file will be - * loaded relative to this path. If this is null, the system property - * resource_url will be used, if available. - * @param configPath the path (relative to the resource URL) of the + * @param resourceDir the base directory to which the paths in the + * supplied configuration file are relative. If this is null, the + * system property resource_dir will be used, if + * available. + * @param configPath the path (relative to the resource dir) of the * resource definition file. - * @param version the version of the resource bundles to be - * downloaded. - * @param downloadObs the bundle download observer to notify of - * download progress and success or failure, or null if + * @param initObs a bundle initialization observer to notify of + * unpacking progress and success or failure, or null if * the caller doesn't care to be informed; note that in the latter - * case, the calling thread will block until bundle updating is + * case, the calling thread will block until bundle unpacking is * complete. * - * @exception IOException thrown if we are unable to download our - * resource manager configuration from the support resource URL. + * @exception IOException thrown if we are unable to read our resource + * manager configuration. */ - public void initBundles (String resourceURL, String configPath, - String version, BundleDownloadObserver downloadObs) + public void initBundles ( + String resourceDir, String configPath, InitObserver initObs) throws IOException { - _version = version; - - // if the resource URL wasn't provided, we try to figure it out - // for ourselves - if (resourceURL == null) { + // if the resource directory wasn't provided, we try to figure it + // out for ourselves + if (resourceDir == null) { try { // first look for the explicit system property - resourceURL = System.getProperty("resource_url"); - + resourceDir = System.getProperty("resource_dir"); // if that doesn't work, fall back to the current directory - if (resourceURL == null) { - resourceURL = "file:" + System.getProperty("user.dir"); + if (resourceDir == null) { + resourceDir = System.getProperty("user.dir"); } } catch (SecurityException se) { - resourceURL = "file:" + File.separator; + resourceDir = File.separator; } } - // make sure there's a slash at the end of the URL - if (!resourceURL.endsWith("/")) { - resourceURL += "/"; + // make sure there's a trailing slash + if (!resourceDir.endsWith(File.separator)) { + resourceDir += File.separator; } - - try { - _rurl = new URL(resourceURL); - } catch (MalformedURLException mue) { - Log.warning("Invalid resource URL [url=" + resourceURL + - ", error=" + mue + "]."); - throw new IOException("Invalid resource url '" + resourceURL + "'"); - } - - // if no dynamic resource URL has been specified, use the normal - // resource URL in its stead - if (_drurl == null) { - _drurl = _rurl; - } - - Log.debug("Resource manager ready [rurl=" + _rurl + - ", drurl=" + _drurl + "]."); + _rdir = new File(resourceDir); // load up our configuration Properties config = new Properties(); - URL curl = null; try { - if (configPath != null) { - curl = new URL(_rurl, configPath); - } - - } catch (MalformedURLException mue) { - Log.warning("Unable to construct config URL [resourceURL=" + _rurl + - ", configPath=" + configPath + ", error=" + mue + "]."); - String errmsg = "Invalid config url [resourceURL=" + _rurl + - ", configPath=" + configPath + "]"; - throw new IOException(errmsg); - } - - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try { - if (curl != null) { - // download the properties file into a buffer - CopyUtils.copy(curl.openStream(), bout); - config.load(new ByteArrayInputStream(bout.toByteArray())); - } - + config.load(new FileInputStream(new File(_rdir, configPath))); } catch (Exception e) { - Log.warning("Unable to load resource mgr properties " + - "[resourceURL=" + _rurl + ", configPath=" + configPath + - ", error=" + e + "]."); - Log.warning("Bogus properties: " + bout.toString("UTF-8")); - Log.logStackTrace(e); String errmsg = "Unable to load resource manager config " + - "[resourceURL=" + _rurl + ", configPath=" + configPath + "]"; + "[rdir=" + _rdir + ", cpath=" + configPath + "]"; + Log.warning(errmsg + "."); + Log.logStackTrace(e); throw new IOException(errmsg); } @@ -326,74 +223,62 @@ public class ResourceManager resolveResourceSet(setName, config.getProperty(key), dlist); } - // start the download, blocking if we've no observer - DownloadManager dlmgr = new DownloadManager(); - if (downloadObs == null) { - downloadBlocking(dlmgr, dlist); - } else { - downloadNonBlocking(dlmgr, dlist, downloadObs); + // if there's no observer, we'll need to block the caller + if (initObs == null) { + initObs = BLOCKER; + } + + // start a thread to unpack our bundles + Unpacker unpack = new Unpacker(dlist, initObs); + unpack.start(); + + if (initObs == BLOCKER) { + try { + synchronized (BLOCKER) { + initObs.wait(); + } + } catch (InterruptedException ie) { + Log.warning("Interrupted while waiting for bundles to unpack."); + } } } /** - * Configures the resource manager with a custom dynamic resource - * URL. If it is not thusly configured, it will use the normal - * resource URL for dynamic resources. + * Given a path relative to the resource directory, the path is + * properly jimmied (assuming we always use /) and combined with the + * resource directory to yield a {@link File} object that can be used + * to access the resource. */ - public void setDynamicResourceURL (String dynResURL) + public File getResourceFile (String path) { - // make sure there's a slash at the end of the URL - if (!dynResURL.endsWith("/")) { - dynResURL += "/"; - } - - try { - _drurl = new URL(dynResURL); - } catch (MalformedURLException mue) { - Log.warning("Invalid dynamic resource URL " + - "[url=" + dynResURL + ", error=" + mue + "]."); + if (!"/".equals(File.separator)) { + path = StringUtil.replace(path, "/", File.separator); } + return new File(_rdir, path); } /** - * Create the resource bundle for the specified set and path. + * Checks to see if the specified bundle exists, is unpacked and is + * ready to be used. */ - protected ResourceBundle createResourceBundle (String setName, String path) + public boolean checkBundle (String path) { - createCacheDirectory(setName); - File cfile = new File(genCachePath(setName, path)); - - return new ResourceBundle(cfile, true, true); + return new ResourceBundle( + getResourceFile(path), true, true).isUnpacked(); } /** - * Checks to see if the specified dynamic bundle is already here - * and ready to roll. + * Resolve the specified bundle (the bundle file must already exist in + * the appropriate place on the file system) and return it on the + * specified result listener. Note that the result listener may be + * notified before this method returns on the caller's thread if the + * bundle is already resolved, or it may be notified on a brand new + * thread if the bundle requires unpacking. */ - public boolean checkDynamicBundle (String path) + public void resolveBundle (String path, final ResultListener listener) { - return createResourceBundle(_dnset, path).isUnpacked(); - } - - /** - * Resolve the specified dynamic bundle and return it on the specified - * result listener. - */ - public void resolveDynamicBundle (String path, String version, - final ResultListener listener) - { - URL burl; - try { - burl = new URL(_drurl, path); - } catch (MalformedURLException mue) { - listener.requestFailed(mue); - return; - } - - // note our dynamic bundle set - _dnset = DYNAMIC_BUNDLE_SET + StringUtil.md5hex(_drurl.toString()); - - final ResourceBundle bundle = createResourceBundle(_dnset, path); + final ResourceBundle bundle = + new ResourceBundle(getResourceFile(path), true, true); if (bundle.isUnpacked()) { if (bundle.sourceIsReady()) { listener.requestCompleted(bundle); @@ -404,319 +289,20 @@ public class ResourceManager return; } - // slap this on the list for retrieval or update by the download - // manager + // start a thread to unpack our bundles ArrayList list = new ArrayList(); - list.add(new DownloadDescriptor(burl, bundle.getSource(), version)); - - // TODO: There should only be one download manager for all dynamic - // bundles, with each bundle waiting its turn to use it. - DownloadObserver obs = new DownloadObserver() { - public boolean notifyOnAWTThread () { - return false; - } - - public void resolvingDownloads () { - } - - public void downloadProgress (int percent, long remaining) { - } - - public void patching () { - } - - public void postDownloadHook () { - } - - public void downloadComplete () - { - final boolean unpacked = bundle.sourceIsReady(); - EventQueue.invokeLater(new Runnable() { - public void run () { - if (unpacked) { - listener.requestCompleted(bundle); - } else { - String errmsg = "Bundle initialization failed."; - listener.requestFailed(new IOException(errmsg)); - } - } - }); - } - - public void downloadFailed ( - DownloadDescriptor desc, final Exception e) - { - Log.warning("Failed to download file " + - "[desc=" + desc + ", e=" + e + "]."); - EventQueue.invokeLater(new Runnable() { - public void run () { - listener.requestFailed(e); - } - }); - } - }; - - DownloadManager dlmgr = new DownloadManager(); - dlmgr.download(list, true, obs); - } - - /** - * Downloads the files in the supplied download list, blocking the - * calling thread until the download is complete or a failure has - * occurred. - */ - protected void downloadBlocking (DownloadManager dlmgr, List dlist) - { - // create an object to wait on while the download takes place - final Object lock = new Object(); - - // create the observer that will notify us when all is finished - DownloadObserver obs = new DownloadObserver() { - public boolean notifyOnAWTThread () { - return false; - } - - public void resolvingDownloads () { - // nothing for now - } - - public void downloadProgress (int percent, long remaining) { - // nothing for now - } - - public void patching () { - // nothing for now - } - - public void postDownloadHook () { - bundlesDownloaded(); - } - - public void downloadComplete () { - synchronized (lock) { - // wake things up as the download is finished - lock.notify(); + list.add(bundle); + Unpacker unpack = new Unpacker(list, new InitObserver() { + public void progress (int percent, long remaining) { + if (percent == 100) { + listener.requestCompleted(bundle); } } - - public void downloadFailed (DownloadDescriptor desc, Exception e) { - Log.warning("Failed to download file " + - "[desc=" + desc + ", e=" + e + "]."); - synchronized (lock) { - // wake things up since we're fragile and so a - // single failure means all is booched - lock.notify(); - } + public void initializationFailed (Exception e) { + listener.requestFailed(e); } - }; - - synchronized (lock) { - // pass the descriptors on to the download manager - dlmgr.download(dlist, true, obs); - - try { - // block until the download has completed - lock.wait(); - } catch (InterruptedException ie) { - Log.warning("Thread interrupted while waiting for download " + - "to complete [ie=" + ie + "]."); - } - } - } - - /** - * Downloads the files in the supplied download list asynchronously, - * notifying the download observer of ongoing progress. - */ - protected void downloadNonBlocking ( - DownloadManager dlmgr, List dlist, final BundleDownloadObserver obs) - { - // pass the descriptors on to the download manager - dlmgr.download(dlist, true, new DownloadObserver() { - public boolean notifyOnAWTThread () { - return obs.notifyOnAWTThread(); - } - - public void resolvingDownloads () { - obs.checkingForUpdate(); - } - - public void downloadProgress (int percent, long remaining) { - obs.downloadProgress(percent, remaining); - } - - public void patching () { - obs.patching(); - } - - public void postDownloadHook () { - obs.unpacking(); - _unpacked = bundlesDownloaded(); - } - - public void downloadComplete () { - if (_unpacked) { - obs.downloadComplete(); - } else { - String errmsg = "Bundle(s) failed initialization."; - obs.downloadFailed(new IOException(errmsg)); - } - } - - public void downloadFailed (DownloadDescriptor desc, Exception e) { - obs.downloadFailed(e); - } - - protected boolean _unpacked; }); - } - - /** - * Called when our resource bundle downloads have completed. - * - * @return true if we are fully operational, false if one or more - * bundles failed to initialize. - */ - protected boolean bundlesDownloaded () - { - // let our bundles know that it's ok for them to access their - // resource files - Iterator iter = _sets.values().iterator(); - boolean unpacked = true; - while (iter.hasNext()) { - ResourceBundle[] bundles = (ResourceBundle[])iter.next(); - for (int ii = 0; ii < bundles.length; ii++) { - unpacked = bundles[ii].sourceIsReady() && unpacked; - } - } - return unpacked; - } - - /** - * Ensures that the cache directory for the specified resource set is - * created. - * - * @return true if the directory was successfully created (or was - * already there), false if we failed to create it. - */ - protected boolean createCacheDirectory (String setName) - { - // compute the path to the top-level cache directory if we haven't - // already been so configured - if (_cachePath == null) { - // start with the base cache path - _cachePath = _baseCachePath; - if (!createDirectory(_cachePath)) { - return false; - } - - // next incorporate the resource URL into the cache path so - // that files fetched from different resource roots do not - // overwrite one another - _cachePath += StringUtil.md5hex(_rurl.toString()) + File.separator; - if (!createDirectory(_cachePath)) { - return false; - } - - Log.debug("Caching bundles in '" + _cachePath + "'."); - } - - // ensure that the set-specific cache directory exists - return createDirectory(_cachePath + setName); - } - - /** - * Creates the specified directory if it doesn't already exist. - * - * @return true if directory was created (or existed), false if not. - */ - protected boolean createDirectory (String path) - { - File cdir = new File(path); - if (cdir.exists()) { - if (!cdir.isDirectory()) { - Log.warning("Cache dir exists but isn't a directory?! " + - "[path=" + path + "]."); - return false; - } - - } else { - if (!cdir.mkdirs()) { - Log.warning("Unable to create cache dir. " + - "[path=" + path + "]."); - return false; - } - } - - return true; - } - - /** - * Generates the name of the bundle cache file given the name of the - * resource set to which it belongs and the relative path URL. - */ - protected String genCachePath (String setName, String resourcePath) - { - return _cachePath + setName + File.separator + - StringUtil.replace(resourcePath, "/", "-"); - } - - /** - * Loads up a resource set based on the supplied definition - * information. - */ - protected void resolveResourceSet ( - String setName, String definition, List dlist) - { - StringTokenizer tok = new StringTokenizer(definition, ":"); - ArrayList set = new ArrayList(); - - while (tok.hasMoreTokens()) { - String path = tok.nextToken().trim(); - URL burl = null; - - try { - burl = new URL(_rurl, path); - - // make sure the cache directory exists for this set - createCacheDirectory(setName); - - // compute the versioned path for this bundle if we have a - // version configured - String vpath = path; - if (DownloadManager.VERSIONING && !StringUtil.blank(_version)) { - vpath = versionPath( - path, _version, getSuffix(path, ".jar")); - } - - // compute the path to the cache file for this bundle - File cfile = new File(genCachePath(setName, path)); - File vfile = new File(genCachePath(setName, vpath)); - - // slap this on the list for retrieval or update by the - // download manager - dlist.add(new DownloadDescriptor(burl, cfile, _version)); - - // finally, add the file that will be cached to the set as - // a resource bundle - set.add(new ResourceBundle(vfile, true, true)); - - } catch (MalformedURLException mue) { - Log.warning("Unable to create URL for resource " + - "[set=" + setName + ", path=" + path + - ", error=" + mue + "]."); - } - } - - // convert our array list into an array and stick it in the table - ResourceBundle[] setvec = new ResourceBundle[set.size()]; - set.toArray(setvec); - _sets.put(setName, setvec); - - // if this is our default resource bundle, keep a reference to it - if (DEFAULT_RESOURCE_SET.equals(setName)) { - _default = setvec; - } + unpack.start(); } /** @@ -742,7 +328,7 @@ public class ResourceManager } // if we didn't find anything, try the classloader - String rpath = _rootPath + path; + String rpath = PathUtil.appendPath(_rootPath, path); in = _loader.getResourceAsStream(rpath); if (in != null) { return in; @@ -776,7 +362,7 @@ public class ResourceManager } // if we didn't find anything, try the classloader - String rpath = _rootPath + path; + String rpath = PathUtil.appendPath(_rootPath, path); InputStream in = _loader.getResourceAsStream(rpath); if (in != null) { return new MemoryCacheImageInputStream(new BufferedInputStream(in)); @@ -880,87 +466,89 @@ public class ResourceManager } /** - * Creates a versioned path using the supplied version string, path - * and file suffix. For example foo/bar/baz.jar becomes - * foo/bar/baz__Vversion.jar. + * Loads up a resource set based on the supplied definition + * information. */ - public static String versionPath ( - String path, String version, String suffix) + protected void resolveResourceSet ( + String setName, String definition, List dlist) { - if (!suffix.startsWith(".")) { - suffix = "." + suffix; + StringTokenizer tok = new StringTokenizer(definition, ":"); + ArrayList set = new ArrayList(); + + while (tok.hasMoreTokens()) { + String path = tok.nextToken().trim(); + ResourceBundle bundle = + new ResourceBundle(getResourceFile(path), true, true); + set.add(bundle); + if (bundle.isUnpacked() && bundle.sourceIsReady()) { + continue; + } + dlist.add(bundle); } - int sidx = path.lastIndexOf(suffix); - if (sidx == -1) { - Log.warning("Invalid unversioned path, missing suffix " + - "[path=" + path + ", suffix=" + suffix + "]."); - return path; + + // convert our array list into an array and stick it in the table + ResourceBundle[] setvec = new ResourceBundle[set.size()]; + set.toArray(setvec); + _sets.put(setName, setvec); + + // if this is our default resource bundle, keep a reference to it + if (DEFAULT_RESOURCE_SET.equals(setName)) { + _default = setvec; } - return path.substring(0, sidx) + "__V" + version + suffix; } - /** - * Creates an unversioned version of the supplied path. If the path is - * not versioned, the existing path will be returned. - */ - public static String unversionPath (String path, String suffix) + /** Used to unpack bundles on a separate thread. */ + protected static class Unpacker extends Thread { - if (!suffix.startsWith(".")) { - suffix = "." + suffix; + public Unpacker (List bundles, InitObserver obs) + { + _bundles = bundles; + _obs = obs; } - int vidx = path.indexOf("__V"); - if (vidx == -1) { - return path; - } - int sidx = path.lastIndexOf(suffix); - if (sidx == -1) { - Log.warning("Invalid versioned path, missing suffix " + - "[path=" + path + ", suffix=" + suffix + "]."); - return path; - } - return path.substring(0, vidx) + suffix; - } - /** - * Returns the suffix of the specified path, ie. .jar if - * the path references a file that ends in .jar. - */ - public static String getSuffix (String path, String defsuf) - { - int didx = path.lastIndexOf("."); - if (didx == -1) { - return defsuf; - } else { - return path.substring(didx); + public void run () + { + try { + int count = 0; + for (Iterator iter = _bundles.iterator(); iter.hasNext(); ) { + ResourceBundle bundle = (ResourceBundle)iter.next(); + if (!bundle.sourceIsReady()) { + Log.warning("Bundle failed to initialize " + + bundle + "."); + } + if (_obs != null) { + int pct = count*100/_bundles.size(); + if (pct < 100) { + _obs.progress(pct, 1); + } + } + count++; + } + if (_obs != null) { + _obs.progress(100, 0); + } + + } catch (Exception e) { + if (_obs != null) { + _obs.initializationFailed(e); + } + } } + + protected List _bundles; + protected InitObserver _obs; } /** The classloader we use for classpath-based resource loading. */ protected ClassLoader _loader; - /** The version of the resource bundles we'll be downloading. */ - protected String _version; - - /** The url via which we download our bundles. */ - protected URL _rurl; - - /** The url via which we download our dynamically generated bundles. */ - protected URL _drurl; - - /** The resource set we use for dynamic resources from a particular - * dynamic bundle URL. */ - protected String _dnset; + /** The directory that contains our resource bundles. */ + protected File _rdir; /** The prefix we prepend to resource paths before attempting to load * them from the classpath. */ protected String _rootPath; - /** The path to the directory that will contain our bundle cache. */ - protected String _baseCachePath; - - /** The path to our bundle cache directory. */ - protected String _cachePath; - /** Our default resource set. */ protected ResourceBundle[] _default = new ResourceBundle[0]; @@ -974,9 +562,15 @@ public class ResourceManager /** The name of the default resource set. */ protected static final String DEFAULT_RESOURCE_SET = "default"; - /** The name of our resource bundle cache directory. */ - protected static final String CACHE_PATH = ".narya"; - - /** The name of the resource set where we store dynamic bundles. */ - protected static final String DYNAMIC_BUNDLE_SET = "_dynamic"; + /** Used to block observerless bundle unpacking. */ + protected static InitObserver BLOCKER = new InitObserver() { + public void progress (int percent, long remaining) { + if (percent >= 100) { + synchronized (this) { notify(); } + } + } + public void initializationFailed (Exception e) { + synchronized (this) { notify(); } + } + }; }