We have liftoff! There's no fancy GUI yet, but it now downloads invalid or
missing resources and launches the application like a champ.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Application.java,v 1.3 2004/07/02 17:03:33 mdb Exp $
|
||||
// $Id: Application.java,v 1.4 2004/07/06 05:13:35 mdb Exp $
|
||||
|
||||
package com.threerings.getdown.data;
|
||||
|
||||
@@ -215,6 +215,64 @@ public class Application
|
||||
return new File(_appdir, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the process associated with this application definition.
|
||||
*/
|
||||
public Process createProcess ()
|
||||
throws IOException
|
||||
{
|
||||
// create our classpath
|
||||
StringBuffer cpbuf = new StringBuffer();
|
||||
for (Iterator iter = _codes.iterator(); iter.hasNext(); ) {
|
||||
if (cpbuf.length() > 0) {
|
||||
cpbuf.append(File.pathSeparator);
|
||||
}
|
||||
Resource rsrc = (Resource)iter.next();
|
||||
cpbuf.append(rsrc.getLocal().getAbsolutePath());
|
||||
}
|
||||
|
||||
// we'll need the JVM, classpath, JVM args, class name and app args
|
||||
String[] args = new String[4 + _jvmargs.size() + _appargs.size()];
|
||||
int idx = 0;
|
||||
|
||||
// reconstruct the path to the JVM
|
||||
args[idx++] = System.getProperty("java.home") +
|
||||
File.separator + "bin" + File.separator + "java";
|
||||
|
||||
// add the classpath arguments
|
||||
args[idx++] = "-classpath";
|
||||
args[idx++] = cpbuf.toString();
|
||||
|
||||
// add the JVM arguments
|
||||
for (Iterator iter = _jvmargs.iterator(); iter.hasNext(); ) {
|
||||
args[idx++] = processArg((String)iter.next());
|
||||
}
|
||||
|
||||
// add the application class name
|
||||
args[idx++] = _class;
|
||||
|
||||
// finally add the application arguments
|
||||
for (Iterator iter = _appargs.iterator(); iter.hasNext(); ) {
|
||||
args[idx++] = processArg((String)iter.next());
|
||||
}
|
||||
|
||||
Log.info("Running " + StringUtil.join(args, "\n"));
|
||||
return Runtime.getRuntime().exec(args, null);
|
||||
}
|
||||
|
||||
/** Replaces the application directory and version in any argument. */
|
||||
protected String processArg (String arg)
|
||||
{
|
||||
if (arg.indexOf("%APPDIR%") != -1) {
|
||||
arg = StringUtil.replace(
|
||||
arg, "%APPDIR%", _appdir.getAbsolutePath());
|
||||
}
|
||||
if (arg.indexOf("%VERSION%") != -1) {
|
||||
arg = StringUtil.replace(arg, "%VERSION%", "" + _version);
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the <code>digest.txt</code> file and verifies the contents of
|
||||
* both that file and the <code>getdown.text</code> file. Then it
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Resource.java,v 1.3 2004/07/02 17:03:33 mdb Exp $
|
||||
// $Id: Resource.java,v 1.4 2004/07/06 05:13:36 mdb Exp $
|
||||
|
||||
package com.threerings.getdown.data;
|
||||
|
||||
@@ -39,6 +39,22 @@ public class Resource
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local location of this resource.
|
||||
*/
|
||||
public File getLocal ()
|
||||
{
|
||||
return _local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote location of this resource.
|
||||
*/
|
||||
public URL getRemote ()
|
||||
{
|
||||
return _remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the MD5 hash of this resource's underlying file.
|
||||
* <em>Note:</m> This is both CPU and I/O intensive.
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// $Id: Downloader.java,v 1.1 2004/07/06 05:13:36 mdb Exp $
|
||||
|
||||
package com.threerings.getdown.launcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.getdown.Log;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
/**
|
||||
* Handles the download of a collection of files, first issuing HTTP head
|
||||
* requests to obtain size information and then downloading the files
|
||||
* individually, reporting progress back via a callback interface.
|
||||
*/
|
||||
public class Downloader extends Thread
|
||||
{
|
||||
/**
|
||||
* An interface used to communicate status back to an external entity.
|
||||
* <em>Note:</em> these methods are all called on the download thread,
|
||||
* so implementors must take care to only execute thread-safe code or
|
||||
* simply pass a message to the AWT thread, for example.
|
||||
*/
|
||||
public interface Observer
|
||||
{
|
||||
/**
|
||||
* Called before the downloader begins the series of HTTP head
|
||||
* requests to determine the size of the files it needs to
|
||||
* download.
|
||||
*/
|
||||
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 downloads.
|
||||
* @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 if a failure occurs while checking for an update or
|
||||
* downloading a file.
|
||||
*
|
||||
* @param rsrc the resource 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 (Resource rsrc, Exception e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a downloader that will download the supplied list of
|
||||
* resources and communicate with the specified observer. The {@link
|
||||
* #start} method must be called on the downloader to initiate the
|
||||
* download process.
|
||||
*/
|
||||
public Downloader (List resources, Observer obs)
|
||||
{
|
||||
super("Downloader");
|
||||
_resources = resources;
|
||||
_obs = obs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked as the downloader thread and performs the
|
||||
* actual downloading.
|
||||
*/
|
||||
public void run ()
|
||||
{
|
||||
Resource current = null;
|
||||
try {
|
||||
// let the observer know that we're computing download size
|
||||
if (_obs != null) {
|
||||
_obs.resolvingDownloads();
|
||||
}
|
||||
|
||||
// first compute the total size of our download
|
||||
for (Iterator iter = _resources.iterator(); iter.hasNext(); ) {
|
||||
discoverSize((Resource)iter.next());
|
||||
}
|
||||
|
||||
// make a note of the time at which we started the download
|
||||
_start = System.currentTimeMillis();
|
||||
|
||||
// now actually download the files
|
||||
for (Iterator iter = _resources.iterator(); iter.hasNext(); ) {
|
||||
current = (Resource)iter.next();
|
||||
download(current);
|
||||
}
|
||||
|
||||
// finally report our download completion if we did not
|
||||
// already do so when downloading our final resource
|
||||
if (_obs != null && !_complete) {
|
||||
_obs.downloadProgress(100, 0);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
if (_obs != null) {
|
||||
_obs.downloadFailed(current, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a HEAD request for the specified resource and notes the
|
||||
* amount of data we will be downloading to account for it.
|
||||
*/
|
||||
protected void discoverSize (Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
// read the file information via an HTTP HEAD request
|
||||
HttpURLConnection ucon = (HttpURLConnection)
|
||||
rsrc.getRemote().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 " +
|
||||
rsrc.getRemote() + ": " + ucon.getResponseCode();
|
||||
throw new IOException(errmsg);
|
||||
}
|
||||
|
||||
// add this resource's size to our total download size
|
||||
_totalSize += ucon.getContentLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the specified resource from its remote location to its
|
||||
* local location.
|
||||
*/
|
||||
protected void download (Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
// make sure the resource's target directory exists
|
||||
File parent = new File(rsrc.getLocal().getParent());
|
||||
if (!parent.exists()) {
|
||||
if (!parent.mkdirs()) {
|
||||
Log.warning("Failed to create target directory for " +
|
||||
"resource '" + rsrc + "'. Download will " +
|
||||
"certainly fail.");
|
||||
}
|
||||
}
|
||||
|
||||
// download the resource from the specified URL
|
||||
HttpURLConnection ucon = (HttpURLConnection)
|
||||
rsrc.getRemote().openConnection();
|
||||
ucon.connect();
|
||||
|
||||
// make sure we got a satisfactory response code
|
||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
String errmsg = "Unable to download resource " +
|
||||
rsrc.getRemote() + ": " + ucon.getResponseCode();
|
||||
throw new IOException(errmsg);
|
||||
}
|
||||
|
||||
Log.info("Downloading resource [url=" + rsrc.getRemote() + "].");
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
in = ucon.getInputStream();
|
||||
out = new FileOutputStream(rsrc.getLocal());
|
||||
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.
|
||||
|
||||
// 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 have no observer, then don't bother computing
|
||||
// download statistics
|
||||
if (_obs == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// note that we've downloaded some data
|
||||
_currentSize += read;
|
||||
|
||||
// notify the observer if it's been sufficiently long
|
||||
// since our last notification
|
||||
long now = System.currentTimeMillis();
|
||||
if ((now - _lastUpdate) >= UPDATE_DELAY) {
|
||||
_lastUpdate = now;
|
||||
|
||||
// compute our bytes per second
|
||||
long secs = (now - _start) / 1000L;
|
||||
long bps = (secs == 0) ? 0 : (_currentSize / secs);
|
||||
|
||||
// compute our percentage completion
|
||||
int pctdone = (int)(
|
||||
(_currentSize / (float)_totalSize) * 100f);
|
||||
|
||||
// estimate our time remaining
|
||||
long remaining = (bps <= 0) ? -1 :
|
||||
(_totalSize - _currentSize) / bps;
|
||||
|
||||
// make sure we only report 100% exactly once
|
||||
if (pctdone < 100 || !_complete) {
|
||||
_complete = (pctdone == 100);
|
||||
_obs.downloadProgress(pctdone, remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
StreamUtil.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
/** The list of resources to be downloaded. */
|
||||
protected List _resources;
|
||||
|
||||
/** The observer with whom we are communicating. */
|
||||
protected Observer _obs;
|
||||
|
||||
/** Used while downloading. */
|
||||
protected byte[] _buffer = new byte[4096];
|
||||
|
||||
/** The total file size in bytes to be transferred. */
|
||||
protected long _totalSize;
|
||||
|
||||
/** The file size in bytes transferred thus far. */
|
||||
protected long _currentSize;
|
||||
|
||||
/** The time at which the file transfer began. */
|
||||
protected long _start;
|
||||
|
||||
/** The current transfer rate in bytes per second. */
|
||||
protected long _bytesPerSecond;
|
||||
|
||||
/** The time at which the last progress update was posted to the
|
||||
* progress observer. */
|
||||
protected long _lastUpdate;
|
||||
|
||||
/** Whether the download has completed and the progress observer
|
||||
* notified. */
|
||||
protected boolean _complete;
|
||||
|
||||
/** The delay in milliseconds between notifying progress observers of
|
||||
* file download progress. */
|
||||
protected static final long UPDATE_DELAY = 2500L;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Getdown.java,v 1.3 2004/07/02 17:03:33 mdb Exp $
|
||||
// $Id: Getdown.java,v 1.4 2004/07/06 05:13:36 mdb Exp $
|
||||
|
||||
package com.threerings.getdown.launcher;
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.apache.commons.io.TeeOutputStream;
|
||||
|
||||
import com.threerings.getdown.Log;
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
/**
|
||||
* Manages the main control for the Getdown application updater and
|
||||
@@ -29,26 +30,101 @@ public class Getdown
|
||||
public void run ()
|
||||
{
|
||||
try {
|
||||
_app.init();
|
||||
for (int ii = 0; ii < MAX_LOOPS; ii++) {
|
||||
_app.init();
|
||||
|
||||
if (_app.verifyMetadata()) {
|
||||
Log.info("Application requires update.");
|
||||
if (_app.verifyMetadata()) {
|
||||
Log.info("Application requires update.");
|
||||
|
||||
} else {
|
||||
Log.info("Metadata verified.");
|
||||
List failures = _app.verifyResources();
|
||||
if (failures == null) {
|
||||
Log.info("Resources verified.");
|
||||
} else {
|
||||
Log.info(failures.size() + " resources require update.");
|
||||
Log.info("Metadata verified.");
|
||||
List failures = _app.verifyResources();
|
||||
if (failures == null) {
|
||||
Log.info("Resources verified.");
|
||||
launch();
|
||||
return;
|
||||
|
||||
} else {
|
||||
Log.info(failures.size() + " rsrcs require update.");
|
||||
download(failures);
|
||||
// now we'll loop back and try it all again
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.warning("Pants! We couldn't get the job done.");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.logStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if the application is determined to be of an old version.
|
||||
*/
|
||||
protected void update ()
|
||||
{
|
||||
Log.info("We're needin' an update Cap'n!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if the application is determined to require resource
|
||||
* downloads.
|
||||
*/
|
||||
protected void download (List resources)
|
||||
{
|
||||
final Object lock = new Object();
|
||||
|
||||
// create a downloader to download our resources
|
||||
Downloader.Observer obs = new Downloader.Observer() {
|
||||
public void resolvingDownloads () {
|
||||
Log.info("Resolving downloads...");
|
||||
}
|
||||
|
||||
public void downloadProgress (int percent, long remaining) {
|
||||
Log.info("Download progress " + percent + "% " +
|
||||
remaining + "s remaining.");
|
||||
if (percent == 100) {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadFailed (Resource rsrc, Exception e) {
|
||||
Log.warning("Download failed [rsrc=" + rsrc + "].");
|
||||
Log.logStackTrace(e);
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
Downloader dl = new Downloader(resources, obs);
|
||||
dl.start();
|
||||
|
||||
// now wait for it to complete
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException ie) {
|
||||
Log.warning("Waitus interruptus " + ie + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to launch the application if everything is determined to be
|
||||
* ready to go.
|
||||
*/
|
||||
protected void launch ()
|
||||
{
|
||||
Log.info("All systems go!");
|
||||
try {
|
||||
Process proc = _app.createProcess();
|
||||
} catch (IOException ioe) {
|
||||
Log.logStackTrace(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
// ensure the proper parameters are passed
|
||||
@@ -85,4 +161,6 @@ public class Getdown
|
||||
}
|
||||
|
||||
protected Application _app;
|
||||
|
||||
protected static final int MAX_LOOPS = 5;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user