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;
|
package com.threerings.getdown.data;
|
||||||
|
|
||||||
@@ -215,6 +215,64 @@ public class Application
|
|||||||
return new File(_appdir, path);
|
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
|
* Loads the <code>digest.txt</code> file and verifies the contents of
|
||||||
* both that file and the <code>getdown.text</code> file. Then it
|
* 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;
|
package com.threerings.getdown.data;
|
||||||
|
|
||||||
@@ -39,6 +39,22 @@ public class Resource
|
|||||||
return _path;
|
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.
|
* Computes the MD5 hash of this resource's underlying file.
|
||||||
* <em>Note:</m> This is both CPU and I/O intensive.
|
* <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;
|
package com.threerings.getdown.launcher;
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ import org.apache.commons.io.TeeOutputStream;
|
|||||||
|
|
||||||
import com.threerings.getdown.Log;
|
import com.threerings.getdown.Log;
|
||||||
import com.threerings.getdown.data.Application;
|
import com.threerings.getdown.data.Application;
|
||||||
|
import com.threerings.getdown.data.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the main control for the Getdown application updater and
|
* Manages the main control for the Getdown application updater and
|
||||||
@@ -29,26 +30,101 @@ public class Getdown
|
|||||||
public void run ()
|
public void run ()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
_app.init();
|
for (int ii = 0; ii < MAX_LOOPS; ii++) {
|
||||||
|
_app.init();
|
||||||
|
|
||||||
if (_app.verifyMetadata()) {
|
if (_app.verifyMetadata()) {
|
||||||
Log.info("Application requires update.");
|
Log.info("Application requires update.");
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.info("Metadata verified.");
|
|
||||||
List failures = _app.verifyResources();
|
|
||||||
if (failures == null) {
|
|
||||||
Log.info("Resources verified.");
|
|
||||||
} else {
|
} 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) {
|
} catch (Exception e) {
|
||||||
Log.logStackTrace(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)
|
public static void main (String[] args)
|
||||||
{
|
{
|
||||||
// ensure the proper parameters are passed
|
// ensure the proper parameters are passed
|
||||||
@@ -85,4 +161,6 @@ public class Getdown
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Application _app;
|
protected Application _app;
|
||||||
|
|
||||||
|
protected static final int MAX_LOOPS = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user