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
This commit is contained in:
@@ -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 <code>-1</code> 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 <code>null</code> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 + "].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <code>-1</code> if
|
|
||||||
* the transfer time cannot currently be estimated.
|
|
||||||
*/
|
|
||||||
public long getXferTimeRemaining ()
|
|
||||||
{
|
|
||||||
return (bytesPerSecond == 0) ? -1 :
|
|
||||||
(totalSize - currentSize) / bytesPerSecond;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
package com.threerings.resource;
|
||||||
|
|
||||||
@@ -49,9 +49,7 @@ public class ResourceBundle
|
|||||||
{
|
{
|
||||||
_source = source;
|
_source = source;
|
||||||
if (unpack) {
|
if (unpack) {
|
||||||
String root = ResourceManager.unversionPath(
|
String root = stripSuffix(source.getPath());
|
||||||
source.getPath(), ".jar");
|
|
||||||
root = stripSuffix(root);
|
|
||||||
_unpacked = new File(root + ".stamp");
|
_unpacked = new File(root + ".stamp");
|
||||||
_cache = new File(root);
|
_cache = new File(root);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
package com.threerings.resource;
|
||||||
|
|
||||||
@@ -9,13 +9,11 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -28,14 +26,10 @@ import javax.imageio.stream.FileImageInputStream;
|
|||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
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.ResultListener;
|
||||||
import com.samskivert.util.StringUtil;
|
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
|
* The resource manager is responsible for maintaining a repository of
|
||||||
* resources that are synchronized with a remote source. This is
|
* resources that are synchronized with a remote source. This is
|
||||||
@@ -53,10 +47,10 @@ import com.threerings.resource.DownloadManager.DownloadObserver;
|
|||||||
*
|
*
|
||||||
* <p> Applications that wish to make use of resource sets and their
|
* <p> Applications that wish to make use of resource sets and their
|
||||||
* associated bundles must call {@link #initBundles} after constructing
|
* 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
|
* which describes these resource sets. The definition file will be loaded
|
||||||
* and the resource bundles defined within will be loaded relative to the
|
* 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
|
* directory and only reloaded when the source resources have been
|
||||||
* updated. The resource definition file looks something like the
|
* updated. The resource definition file looks something like the
|
||||||
* following:
|
* following:
|
||||||
@@ -86,64 +80,51 @@ import com.threerings.resource.DownloadManager.DownloadObserver;
|
|||||||
public class ResourceManager
|
public class ResourceManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Provides facilities for notifying an observer of resource bundle
|
* Provides facilities for notifying an observer of the resource
|
||||||
* download progress.
|
* unpacking process.
|
||||||
*/
|
*/
|
||||||
public interface BundleDownloadObserver
|
public interface InitObserver
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* If this method returns true the download observer callbacks
|
* Indicates a percent completion along with an estimated time
|
||||||
* will be called on the AWT thread, allowing the observer to do
|
* remaining in seconds.
|
||||||
* things like safely update user interfaces, etc. If false, it
|
|
||||||
* will be called on a special download thread.
|
|
||||||
*/
|
*/
|
||||||
public boolean notifyOnAWTThread ();
|
public void progress (int percent, long remaining);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the resource manager is about to check for an
|
* Indicates that there was a failure unpacking our resource
|
||||||
* update of any of our resource sets.
|
* bundles.
|
||||||
*/
|
*/
|
||||||
public void checkingForUpdate ();
|
public void initializationFailed (Exception e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to inform the observer of ongoing progress toward
|
* An adapter that wraps an {@link InitObserver} and routes all method
|
||||||
* completion of the overall bundle downloading task. The caller
|
* invocations to the AWT thread.
|
||||||
* is guaranteed to get at least one call reporting 100%
|
*/
|
||||||
* completion.
|
public static class AWTInitObserver implements InitObserver
|
||||||
*
|
{
|
||||||
* @param percent the percent completion of the download.
|
public AWTInitObserver (InitObserver obs) {
|
||||||
* @param remaining the estimated download time remaining in
|
_obs = obs;
|
||||||
* seconds, or <code>-1</code> if the time can not yet be
|
}
|
||||||
* determined.
|
|
||||||
*/
|
|
||||||
public void downloadProgress (int percent, long remaining);
|
|
||||||
|
|
||||||
/**
|
public void progress (final int percent, final long remaining) {
|
||||||
* Called to inform the observer that the bundle patching process
|
EventQueue.invokeLater(new Runnable() {
|
||||||
* has begun. This follows the download phase, but is optional.
|
public void run () {
|
||||||
*/
|
_obs.progress(percent, remaining);
|
||||||
public void patching ();
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public void initializationFailed (final Exception e) {
|
||||||
* Called to inform the observer that the bundle unpacking process
|
EventQueue.invokeLater(new Runnable() {
|
||||||
* has begun. This follows the download or patching phase.
|
public void run () {
|
||||||
*/
|
_obs.initializationFailed(e);
|
||||||
public void unpacking ();
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
protected InitObserver _obs;
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,52 +145,12 @@ public class ResourceManager
|
|||||||
// keep track of our root path
|
// keep track of our root path
|
||||||
_rootPath = resourceRoot;
|
_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
|
// use the classloader that loaded us
|
||||||
_loader = getClass().getClassLoader();
|
_loader = getClass().getClassLoader();
|
||||||
|
|
||||||
// set up a URL handler so that things can be loaded via
|
// set up a URL handler so that things can be loaded via
|
||||||
// urls with the 'resource' protocol
|
// urls with the 'resource' protocol
|
||||||
Handler.registerHandler(this);
|
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
|
* manager. Applications that wish to make use of resource bundles
|
||||||
* should call this method after constructing the resource manager.
|
* should call this method after constructing the resource manager.
|
||||||
*
|
*
|
||||||
* @param resourceURL the base URL from which resources are loaded.
|
* @param resourceDir the base directory to which the paths in the
|
||||||
* Relative paths specified in the resource definition file will be
|
* supplied configuration file are relative. If this is null, the
|
||||||
* loaded relative to this path. If this is null, the system property
|
* system property <code>resource_dir</code> will be used, if
|
||||||
* <code>resource_url</code> will be used, if available.
|
* available.
|
||||||
* @param configPath the path (relative to the resource URL) of the
|
* @param configPath the path (relative to the resource dir) of the
|
||||||
* resource definition file.
|
* resource definition file.
|
||||||
* @param version the version of the resource bundles to be
|
* @param initObs a bundle initialization observer to notify of
|
||||||
* downloaded.
|
* unpacking progress and success or failure, or <code>null</code> if
|
||||||
* @param downloadObs the bundle download observer to notify of
|
|
||||||
* download progress and success or failure, or <code>null</code> if
|
|
||||||
* the caller doesn't care to be informed; note that in the latter
|
* 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.
|
* complete.
|
||||||
*
|
*
|
||||||
* @exception IOException thrown if we are unable to download our
|
* @exception IOException thrown if we are unable to read our resource
|
||||||
* resource manager configuration from the support resource URL.
|
* manager configuration.
|
||||||
*/
|
*/
|
||||||
public void initBundles (String resourceURL, String configPath,
|
public void initBundles (
|
||||||
String version, BundleDownloadObserver downloadObs)
|
String resourceDir, String configPath, InitObserver initObs)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
_version = version;
|
// if the resource directory wasn't provided, we try to figure it
|
||||||
|
// out for ourselves
|
||||||
// if the resource URL wasn't provided, we try to figure it out
|
if (resourceDir == null) {
|
||||||
// for ourselves
|
|
||||||
if (resourceURL == null) {
|
|
||||||
try {
|
try {
|
||||||
// first look for the explicit system property
|
// 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 that doesn't work, fall back to the current directory
|
||||||
if (resourceURL == null) {
|
if (resourceDir == null) {
|
||||||
resourceURL = "file:" + System.getProperty("user.dir");
|
resourceDir = System.getProperty("user.dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SecurityException se) {
|
} catch (SecurityException se) {
|
||||||
resourceURL = "file:" + File.separator;
|
resourceDir = File.separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure there's a slash at the end of the URL
|
// make sure there's a trailing slash
|
||||||
if (!resourceURL.endsWith("/")) {
|
if (!resourceDir.endsWith(File.separator)) {
|
||||||
resourceURL += "/";
|
resourceDir += File.separator;
|
||||||
}
|
}
|
||||||
|
_rdir = new File(resourceDir);
|
||||||
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 + "].");
|
|
||||||
|
|
||||||
// load up our configuration
|
// load up our configuration
|
||||||
Properties config = new Properties();
|
Properties config = new Properties();
|
||||||
URL curl = null;
|
|
||||||
try {
|
try {
|
||||||
if (configPath != null) {
|
config.load(new FileInputStream(new File(_rdir, configPath)));
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} 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 " +
|
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);
|
throw new IOException(errmsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,74 +223,62 @@ public class ResourceManager
|
|||||||
resolveResourceSet(setName, config.getProperty(key), dlist);
|
resolveResourceSet(setName, config.getProperty(key), dlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the download, blocking if we've no observer
|
// if there's no observer, we'll need to block the caller
|
||||||
DownloadManager dlmgr = new DownloadManager();
|
if (initObs == null) {
|
||||||
if (downloadObs == null) {
|
initObs = BLOCKER;
|
||||||
downloadBlocking(dlmgr, dlist);
|
}
|
||||||
} else {
|
|
||||||
downloadNonBlocking(dlmgr, dlist, downloadObs);
|
// 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
|
* Given a path relative to the resource directory, the path is
|
||||||
* URL. If it is not thusly configured, it will use the normal
|
* properly jimmied (assuming we always use /) and combined with the
|
||||||
* resource URL for dynamic resources.
|
* 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 (!"/".equals(File.separator)) {
|
||||||
if (!dynResURL.endsWith("/")) {
|
path = StringUtil.replace(path, "/", File.separator);
|
||||||
dynResURL += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_drurl = new URL(dynResURL);
|
|
||||||
} catch (MalformedURLException mue) {
|
|
||||||
Log.warning("Invalid dynamic resource URL " +
|
|
||||||
"[url=" + dynResURL + ", error=" + mue + "].");
|
|
||||||
}
|
}
|
||||||
|
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);
|
return new ResourceBundle(
|
||||||
File cfile = new File(genCachePath(setName, path));
|
getResourceFile(path), true, true).isUnpacked();
|
||||||
|
|
||||||
return new ResourceBundle(cfile, true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if the specified dynamic bundle is already here
|
* Resolve the specified bundle (the bundle file must already exist in
|
||||||
* and ready to roll.
|
* 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();
|
final ResourceBundle bundle =
|
||||||
}
|
new ResourceBundle(getResourceFile(path), true, true);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
if (bundle.isUnpacked()) {
|
if (bundle.isUnpacked()) {
|
||||||
if (bundle.sourceIsReady()) {
|
if (bundle.sourceIsReady()) {
|
||||||
listener.requestCompleted(bundle);
|
listener.requestCompleted(bundle);
|
||||||
@@ -404,319 +289,20 @@ public class ResourceManager
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// slap this on the list for retrieval or update by the download
|
// start a thread to unpack our bundles
|
||||||
// manager
|
|
||||||
ArrayList list = new ArrayList();
|
ArrayList list = new ArrayList();
|
||||||
list.add(new DownloadDescriptor(burl, bundle.getSource(), version));
|
list.add(bundle);
|
||||||
|
Unpacker unpack = new Unpacker(list, new InitObserver() {
|
||||||
// TODO: There should only be one download manager for all dynamic
|
public void progress (int percent, long remaining) {
|
||||||
// bundles, with each bundle waiting its turn to use it.
|
if (percent == 100) {
|
||||||
DownloadObserver obs = new DownloadObserver() {
|
listener.requestCompleted(bundle);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void initializationFailed (Exception e) {
|
||||||
public void downloadFailed (DownloadDescriptor desc, Exception e) {
|
listener.requestFailed(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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
}
|
unpack.start();
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -742,7 +328,7 @@ public class ResourceManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we didn't find anything, try the classloader
|
// if we didn't find anything, try the classloader
|
||||||
String rpath = _rootPath + path;
|
String rpath = PathUtil.appendPath(_rootPath, path);
|
||||||
in = _loader.getResourceAsStream(rpath);
|
in = _loader.getResourceAsStream(rpath);
|
||||||
if (in != null) {
|
if (in != null) {
|
||||||
return in;
|
return in;
|
||||||
@@ -776,7 +362,7 @@ public class ResourceManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we didn't find anything, try the classloader
|
// if we didn't find anything, try the classloader
|
||||||
String rpath = _rootPath + path;
|
String rpath = PathUtil.appendPath(_rootPath, path);
|
||||||
InputStream in = _loader.getResourceAsStream(rpath);
|
InputStream in = _loader.getResourceAsStream(rpath);
|
||||||
if (in != null) {
|
if (in != null) {
|
||||||
return new MemoryCacheImageInputStream(new BufferedInputStream(in));
|
return new MemoryCacheImageInputStream(new BufferedInputStream(in));
|
||||||
@@ -880,87 +466,89 @@ public class ResourceManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a versioned path using the supplied version string, path
|
* Loads up a resource set based on the supplied definition
|
||||||
* and file suffix. For example <code>foo/bar/baz.jar</code> becomes
|
* information.
|
||||||
* <code>foo/bar/baz__Vversion.jar</code>.
|
|
||||||
*/
|
*/
|
||||||
public static String versionPath (
|
protected void resolveResourceSet (
|
||||||
String path, String version, String suffix)
|
String setName, String definition, List dlist)
|
||||||
{
|
{
|
||||||
if (!suffix.startsWith(".")) {
|
StringTokenizer tok = new StringTokenizer(definition, ":");
|
||||||
suffix = "." + suffix;
|
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) {
|
// convert our array list into an array and stick it in the table
|
||||||
Log.warning("Invalid unversioned path, missing suffix " +
|
ResourceBundle[] setvec = new ResourceBundle[set.size()];
|
||||||
"[path=" + path + ", suffix=" + suffix + "].");
|
set.toArray(setvec);
|
||||||
return path;
|
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Used to unpack bundles on a separate thread. */
|
||||||
* Creates an unversioned version of the supplied path. If the path is
|
protected static class Unpacker extends Thread
|
||||||
* not versioned, the existing path will be returned.
|
|
||||||
*/
|
|
||||||
public static String unversionPath (String path, String suffix)
|
|
||||||
{
|
{
|
||||||
if (!suffix.startsWith(".")) {
|
public Unpacker (List bundles, InitObserver obs)
|
||||||
suffix = "." + suffix;
|
{
|
||||||
|
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void run ()
|
||||||
* Returns the suffix of the specified path, ie. <code>.jar</code> if
|
{
|
||||||
* the path references a file that ends in <code>.jar</code>.
|
try {
|
||||||
*/
|
int count = 0;
|
||||||
public static String getSuffix (String path, String defsuf)
|
for (Iterator iter = _bundles.iterator(); iter.hasNext(); ) {
|
||||||
{
|
ResourceBundle bundle = (ResourceBundle)iter.next();
|
||||||
int didx = path.lastIndexOf(".");
|
if (!bundle.sourceIsReady()) {
|
||||||
if (didx == -1) {
|
Log.warning("Bundle failed to initialize " +
|
||||||
return defsuf;
|
bundle + ".");
|
||||||
} else {
|
}
|
||||||
return path.substring(didx);
|
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. */
|
/** The classloader we use for classpath-based resource loading. */
|
||||||
protected ClassLoader _loader;
|
protected ClassLoader _loader;
|
||||||
|
|
||||||
/** The version of the resource bundles we'll be downloading. */
|
/** The directory that contains our resource bundles. */
|
||||||
protected String _version;
|
protected File _rdir;
|
||||||
|
|
||||||
/** 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 prefix we prepend to resource paths before attempting to load
|
/** The prefix we prepend to resource paths before attempting to load
|
||||||
* them from the classpath. */
|
* them from the classpath. */
|
||||||
protected String _rootPath;
|
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. */
|
/** Our default resource set. */
|
||||||
protected ResourceBundle[] _default = new ResourceBundle[0];
|
protected ResourceBundle[] _default = new ResourceBundle[0];
|
||||||
|
|
||||||
@@ -974,9 +562,15 @@ public class ResourceManager
|
|||||||
/** The name of the default resource set. */
|
/** The name of the default resource set. */
|
||||||
protected static final String DEFAULT_RESOURCE_SET = "default";
|
protected static final String DEFAULT_RESOURCE_SET = "default";
|
||||||
|
|
||||||
/** The name of our resource bundle cache directory. */
|
/** Used to block observerless bundle unpacking. */
|
||||||
protected static final String CACHE_PATH = ".narya";
|
protected static InitObserver BLOCKER = new InitObserver() {
|
||||||
|
public void progress (int percent, long remaining) {
|
||||||
/** The name of the resource set where we store dynamic bundles. */
|
if (percent >= 100) {
|
||||||
protected static final String DYNAMIC_BUNDLE_SET = "_dynamic";
|
synchronized (this) { notify(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void initializationFailed (Exception e) {
|
||||||
|
synchronized (this) { notify(); }
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user