
com.threerings.getdown.net.Downloader Maven / Gradle / Ivy
//
// $Id: Downloader.java 315 2010-09-20 17:55:35Z samskivert $
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2010 Three Rings Design, Inc.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.threerings.getdown.net;
import java.io.File;
import java.io.IOException;
import java.util.List;
import com.threerings.getdown.data.Resource;
import static com.threerings.getdown.Log.log;
/**
* 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. Note: 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 -1
if
* the time can not yet be determined.
*
* @return true if the download should continue, false if it should be aborted.
*/
public boolean 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
* null
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 #download} 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.
*/
@Override
public void run ()
{
download();
}
/**
* 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 boolean download ()
{
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 (Resource resource : _resources) {
discoverSize(resource);
}
log.info("Downloading " + _totalSize + " bytes...");
// make a note of the time at which we started the download
_start = System.currentTimeMillis();
// now actually download the files
for (Resource resource : _resources) {
download(resource);
}
// finally report our download completion if we did not already do so when downloading
// our final resource
if (_obs != null && !_complete) {
if (!_obs.downloadProgress(100, 0)) {
return false;
}
}
} catch (DownloadAbortedException e) {
return false;
} catch (Exception e) {
if (_obs != null) {
_obs.downloadFailed(current, e);
} else {
log.warning("Observer failed.", e);
}
}
return true;
}
/**
* Notes the amount of data needed to download the given resource..
*/
protected void discoverSize (Resource rsrc)
throws IOException
{
// add this resource's size to our total download size
_totalSize += checkSize(rsrc);
}
/**
* Performs the protocol-specific portion of checking download size.
*/
protected abstract long checkSize (Resource rsrc) throws IOException;
/**
* 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.");
}
}
doDownload(rsrc);
}
/**
* Periodically called by the protocol-specific downloaders to update their progress.
*/
protected void updateObserver ()
throws IOException
{
// 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 = (_totalSize == 0) ? 0 : (int)((_currentSize * 100f) / _totalSize);
// estimate our time remaining
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);
if (!_obs.downloadProgress(pctdone, remaining)) {
throw new DownloadAbortedException();
}
}
}
}
/**
* Accomplishes the copying of the resource from remote location to local location using
* protocol-specific code
*/
protected abstract void doDownload (Resource rsrc) throws IOException;
/** 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 = 500L;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy