Using IOException to communicate a desire to abort the download is a little

pesky and pushes annoying requirements on users of the Getdown download
services (like Yohoho and ToyBox).

Instead we'll allow Downloader.download() to return false to indicate that the
download was aborted by an observer, and the observers will simply return false
to indicate that they wish the download to be aborted.

While I was in there, I moved the Downloader and its concrete implementations
into a getdown.net package because it annoyed me that they were in
getdown.launcher.
This commit is contained in:
Michael Bayne
2007-12-14 21:09:10 +00:00
parent abcf99251f
commit 6b3ad3ae5d
6 changed files with 176 additions and 104 deletions
@@ -1027,23 +1027,35 @@ public class Application
protected void downloadConfigFile ()
throws IOException
{
checkForAnotherGetdown();
requireNoOtherGetdownRunning();
downloadControlFile(CONFIG_FILE, false);
updateConfigModtime();
}
/**
* Checks the modtime on CONFIG_FILE and if it's changed since the last time this was called,
* raise a MultipleGetdownRunning exception
* Checks the modtime on CONFIG_FILE and returns whether it has changed since the last time
* this method was called.
*/
public void checkForAnotherGetdown ()
throws MultipleGetdownRunning
public boolean checkForAnotherGetdown ()
{
File config = getLocalPath(CONFIG_FILE);
if (_lastConfigModtime != -1 && _lastConfigModtime < config.lastModified()) {
throw new MultipleGetdownRunning();
return true;
}
_lastConfigModtime = config.lastModified();
return false;
}
/**
* Calls {@link #checkForAnotherGetdown} and throws a {@link MultipleGetdownRunning} if it
* detects that another Getdown instance is running.
*/
public void requireNoOtherGetdownRunning ()
throws MultipleGetdownRunning
{
if (checkForAnotherGetdown()) {
throw new MultipleGetdownRunning();
}
}
/**
@@ -1054,8 +1066,9 @@ public class Application
{
File config = getLocalPath(CONFIG_FILE);
_lastConfigModtime = System.currentTimeMillis();
if(!config.setLastModified(_lastConfigModtime) && !_warnedAboutSetLastModified){
Log.warning("Unable to set modtime on config file, will be unable to check for other instances of getdown running");
if (!config.setLastModified(_lastConfigModtime) && !_warnedAboutSetLastModified) {
Log.warning("Unable to set modtime on config file, will be unable to check for " +
"other instances of getdown running.");
_warnedAboutSetLastModified = true;
}
}
@@ -1152,7 +1165,7 @@ public class Application
// Check that another getdown hasn't started running since we started downloading this
// file. The rename will obliterate the modtime we're tracking to keep multiple instances
// from running.
checkForAnotherGetdown();
requireNoOtherGetdownRunning();
// now move the temporary file over the original
File original = getLocalPath(path);
@@ -62,6 +62,9 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.net.Downloader;
import com.threerings.getdown.net.HTTPDownloader;
import com.threerings.getdown.net.TorrentDownloader;
import com.threerings.getdown.tools.Patcher;
import com.threerings.getdown.util.ConfigUtil;
import com.threerings.getdown.util.LaunchUtil;
@@ -359,9 +362,9 @@ public abstract class Getdown extends Thread
// now force our UI to be recreated with the updated info
createInterface(true);
}
_app.checkForAnotherGetdown();
_app.requireNoOtherGetdownRunning();
// Update the modtime here to stake a claim that we're going to getdown eventually
_app.updateConfigModtime();
_app.updateConfigModtime();
if (_delay > 0) {
try {
Log.info("Waiting " + _delay + " minutes before beginning actual work");
@@ -413,7 +416,7 @@ public abstract class Getdown extends Thread
// Only launch if we aren't in silent mode. Some mystery program starting out
// of the blue would be disconcerting.
if (!_silent || _launchInSilent) {
_app.checkForAnotherGetdown();
_app.requireNoOtherGetdownRunning();
launch();
}
return;
@@ -628,34 +631,29 @@ public abstract class Getdown extends Thread
updateStatus("m.resolving");
}
public void downloadProgress (int percent, long remaining)
throws IOException {
// Check for another getdown running at 0 and every 10% after that
public boolean downloadProgress (int percent, long remaining) {
// check for another getdown running at 0 and every 10% after that
if (_lastCheck == -1 || percent >= _lastCheck + 10) {
_app.checkForAnotherGetdown();
if (_app.checkForAnotherGetdown()) {
return false;
}
_lastCheck = percent;
}
setStatus("m.downloading", percent, remaining, true);
if (percent > 0) {
reportTrackingEvent("progress", percent);
}
return true;
}
public void downloadFailed (Resource rsrc, Exception e)
throws IOException {
if (e instanceof MultipleGetdownRunning) {
throw (IOException)e;
} else {
updateStatus(MessageUtil.tcompose("m.failure", e.getMessage()));
Log.warning("Download failed [rsrc=" + rsrc + "].");
Log.logStackTrace(e);
}
public void downloadFailed (Resource rsrc, Exception e) {
updateStatus(MessageUtil.tcompose("m.failure", e.getMessage()));
Log.warning("Download failed [rsrc=" + rsrc + "].");
Log.logStackTrace(e);
}
/**
* The last percentage at which we checked for another getdown running, or -1 for not
* having checked at all.
*/
/** The last percentage at which we checked for another getdown running, or -1 for not
* having checked at all. */
protected int _lastCheck = -1;
};
@@ -680,7 +678,9 @@ public abstract class Getdown extends Thread
}
// start the download and wait for it to complete
dl.download();
if (!dl.download()) {
throw new MultipleGetdownRunning();
}
}
/**
@@ -0,0 +1,30 @@
//
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2006 Three Rings Design, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the: Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
package com.threerings.getdown.net;
import java.io.IOException;
/**
* Used to terminate the download process in its midst.
*/
public class DownloadAbortedException extends IOException
{
}
@@ -18,69 +18,63 @@
// this program; if not, write to the: Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
package com.threerings.getdown.launcher;
package com.threerings.getdown.net;
import java.io.File;
import java.io.IOException;
import java.util.List;
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.
* 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 abstract 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.
* 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.
* 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.
* 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.
* @throws IOException if getdown shouldn't continue in its current state.
* @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.
*
* @return true if the download should continue, false if it should be aborted.
*/
public void downloadProgress (int percent, long remaining) throws IOException;
public boolean downloadProgress (int percent, long remaining);
/**
* Called if a failure occurs while checking for an update or
* downloading a file.
* 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 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.
* @throws IOException if getdown shouldn't continue after this failure.
*/
public void downloadFailed (Resource rsrc, Exception e) throws IOException;
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
* #download} method must be called on the downloader to initiate the
* download process.
* Creates a downloader that will download the supplied list of resources and communicate with
* the specified observer. The {@link #download} method must be called on the downloader to
* initiate the download process.
*/
public Downloader (List<Resource> resources, Observer obs)
{
@@ -95,20 +89,16 @@ public abstract class Downloader extends Thread
@Override
public void run ()
{
try {
download();
} catch (IOException e) {
// This was either logged or reported to our observer in download, so we're good.
}
download();
}
/**
* Start downloading the resources in this Downloader.
*
* @throws IOException if an unrecoverable error was discovered in dowloading.
* Start downloading the resources in this downloader.
*
* @return true if the download completed or failed for unexpected reasons (in which case the
* observer will have been notified), false if it was aborted by the observer.
*/
public void download ()
throws IOException
public boolean download ()
{
Resource current = null;
try {
@@ -132,12 +122,17 @@ public abstract class Downloader extends Thread
download(resource);
}
// finally report our download completion if we did not
// already do so when downloading our final resource
// 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);
if (!_obs.downloadProgress(100, 0)) {
return false;
}
}
} catch (DownloadAbortedException e) {
return false;
} catch (Exception e) {
if (_obs != null) {
_obs.downloadFailed(current, e);
@@ -145,6 +140,7 @@ public abstract class Downloader extends Thread
Log.logStackTrace(e);
}
}
return true;
}
/**
@@ -163,8 +159,7 @@ public abstract class Downloader extends Thread
protected abstract long checkSize (Resource rsrc) throws IOException;
/**
* Downloads the specified resource from its remote location to its
* local location.
* Downloads the specified resource from its remote location to its local location.
*/
protected void download (Resource rsrc)
throws IOException
@@ -173,23 +168,20 @@ public abstract class Downloader extends Thread
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.");
Log.warning("Failed to create target directory for resource '" + rsrc + "'. " +
"Download will certainly fail.");
}
}
doDownload(rsrc);
}
/**
* Periodically called by the protocol-specific downloaders
* to update their progress.
* @throws IOException
* Periodically called by the protocol-specific downloaders to update their progress.
*/
protected void updateObserver () throws IOException
protected void updateObserver ()
throws IOException
{
// notify the observer if it's been sufficiently long
// since our last notification
// notify the observer if it's been sufficiently long since our last notification
long now = System.currentTimeMillis();
if ((now - _lastUpdate) >= UPDATE_DELAY) {
_lastUpdate = now;
@@ -199,24 +191,24 @@ public abstract class Downloader extends Thread
long bps = (secs == 0) ? 0 : (_currentSize / secs);
// compute our percentage completion
int pctdone = (_totalSize == 0) ? 0 :
(int)((_currentSize * 100f) / _totalSize);
int pctdone = (_totalSize == 0) ? 0 : (int)((_currentSize * 100f) / _totalSize);
// estimate our time remaining
long remaining = (bps <= 0 || _totalSize == 0) ? -1 :
(_totalSize - _currentSize) / bps;
long remaining = (bps <= 0 || _totalSize == 0) ? -1 : (_totalSize - _currentSize) / bps;
// make sure we only report 100% exactly once
if (pctdone < 100 || !_complete) {
_complete = (pctdone == 100);
_obs.downloadProgress(pctdone, remaining);
if (!_obs.downloadProgress(pctdone, remaining)) {
throw new DownloadAbortedException();
}
}
}
}
/**
* Accomplishes the copying of the resource from remote location to
* local location using protocol-specific code
* Accomplishes the copying of the resource from remote location to local location using
* protocol-specific code
*/
protected abstract void doDownload (Resource rsrc) throws IOException;
@@ -241,15 +233,13 @@ public abstract class Downloader extends Thread
/** 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. */
/** 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. */
/** 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. */
/** The delay in milliseconds between notifying progress observers of file download
* progress. */
protected static final long UPDATE_DELAY = 500L;
}
@@ -1,4 +1,24 @@
package com.threerings.getdown.launcher;
//
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2006 Three Rings Design, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the: Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
package com.threerings.getdown.net;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -59,8 +79,7 @@ public class HTTPDownloader extends Downloader
throws IOException
{
// download the resource from the specified URL
HttpURLConnection ucon = (HttpURLConnection)
rsrc.getRemote().openConnection();
HttpURLConnection ucon = (HttpURLConnection)rsrc.getRemote().openConnection();
ucon.connect();
// make sure we got a satisfactory response code
@@ -1,4 +1,24 @@
package com.threerings.getdown.launcher;
//
// $Id$
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2006 Three Rings Design, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the: Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
package com.threerings.getdown.net;
import java.io.IOException;
import java.util.HashMap;