Avoid corrupted resources when download is interrupted
1) Download resources first into a temporary file 2) Perform verification and digest comparison with the downloaded temporary file instead of the original if temporary file exist 3) Only when verification completes successfully rename all the temporary files to override the original. This step can be done automatically or manually by calling GetDown.install() which is useful for those who want to download in background and install upon program exit.
This commit is contained in:
@@ -13,7 +13,6 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
@@ -36,6 +35,7 @@ public class Resource
|
||||
_path = path;
|
||||
_remote = remote;
|
||||
_local = local;
|
||||
_local_new = new File(local.toString() + "_new");
|
||||
String lpath = _local.getPath();
|
||||
_marker = new File(lpath + "v");
|
||||
|
||||
@@ -67,6 +67,11 @@ public class Resource
|
||||
return _local;
|
||||
}
|
||||
|
||||
public File getLocalNew ()
|
||||
{
|
||||
return _local_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of the unpacked resource.
|
||||
*/
|
||||
@@ -107,7 +112,16 @@ public class Resource
|
||||
public String computeDigest (MessageDigest md, ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
return computeDigest(_local, md, obs);
|
||||
File file;
|
||||
if (_local.toString().toLowerCase().endsWith(Application.CONFIG_FILE))
|
||||
file = _local;
|
||||
else {
|
||||
if (_local_new.exists())
|
||||
file = _local_new;
|
||||
else
|
||||
file = _local;
|
||||
}
|
||||
return computeDigest(file, md, obs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +306,7 @@ public class Resource
|
||||
|
||||
protected static boolean isJar (String path)
|
||||
{
|
||||
return path.endsWith(".jar");
|
||||
return path.endsWith(".jar") || path.endsWith(".jar_new");
|
||||
}
|
||||
|
||||
protected static boolean isPacked200Jar (String path)
|
||||
@@ -302,7 +316,7 @@ public class Resource
|
||||
|
||||
protected String _path;
|
||||
protected URL _remote;
|
||||
protected File _local, _marker, _unpacked;
|
||||
protected File _local, _local_new, _marker, _unpacked;
|
||||
protected boolean _unpack, _isJar, _isPacked200Jar;
|
||||
|
||||
/** Used to sort the entries in a jar file. */
|
||||
|
||||
@@ -49,6 +49,10 @@ public class SysProps
|
||||
return System.getProperty("silent") != null;
|
||||
}
|
||||
|
||||
public static boolean install () {
|
||||
return System.getProperty("no_install") == null;
|
||||
}
|
||||
|
||||
/** If true, Getdown installs the app without ever bringing up a UI and then launches it.
|
||||
* Usage: {@code -Dsilent=launch}. */
|
||||
public static boolean launchInSilent () {
|
||||
|
||||
@@ -68,6 +68,7 @@ import com.threerings.getdown.net.HTTPDownloader;
|
||||
import com.threerings.getdown.tools.Patcher;
|
||||
import com.threerings.getdown.util.ConfigUtil;
|
||||
import com.threerings.getdown.util.ConnectionUtil;
|
||||
import com.threerings.getdown.util.FileUtil;
|
||||
import com.threerings.getdown.util.LaunchUtil;
|
||||
import com.threerings.getdown.util.ProgressAggregator;
|
||||
import com.threerings.getdown.util.ProgressObserver;
|
||||
@@ -100,6 +101,7 @@ public abstract class Getdown extends Thread
|
||||
// If the silent property exists, install without bringing up any gui. If it equals
|
||||
// launch, start the application after installing. Otherwise, just install and exit.
|
||||
_silent = SysProps.silent();
|
||||
_install = SysProps.install();
|
||||
if (_silent) {
|
||||
_launchInSilent = SysProps.launchInSilent();
|
||||
}
|
||||
@@ -409,6 +411,9 @@ public abstract class Getdown extends Thread
|
||||
|
||||
// we'll keep track of all the resources we unpack
|
||||
Set<Resource> unpacked = new HashSet<Resource>();
|
||||
|
||||
toBeInstalledResouces = new ArrayList<Resource>();
|
||||
readyToInstall = false;
|
||||
|
||||
//setStep(Step.START);
|
||||
for (int ii = 0; ii < MAX_LOOPS; ii++) {
|
||||
@@ -442,6 +447,7 @@ public abstract class Getdown extends Thread
|
||||
setStep(Step.VERIFY_RESOURCES);
|
||||
setStatusAsync("m.validating", -1, -1L, false);
|
||||
List<Resource> failures = _app.verifyResources(_progobs, alreadyValid, unpacked);
|
||||
addToBeInstalledResources(failures);
|
||||
if (failures == null) {
|
||||
log.info("Resources verified.");
|
||||
|
||||
@@ -471,6 +477,10 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
readyToInstall = true;
|
||||
if (_install)
|
||||
install();
|
||||
|
||||
// Only launch if we aren't in silent mode. Some mystery program starting out
|
||||
// of the blue would be disconcerting.
|
||||
if (!_silent || _launchInSilent) {
|
||||
@@ -531,7 +541,16 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
private void addToBeInstalledResources(List<Resource> resources) {
|
||||
if (resources == null)
|
||||
return;
|
||||
else
|
||||
for (Resource r : resources)
|
||||
if (!toBeInstalledResouces.contains(r))
|
||||
toBeInstalledResouces.add(r);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void updateStatus (String message)
|
||||
{
|
||||
setStatusAsync(message, -1, -1L, true);
|
||||
@@ -754,7 +773,28 @@ public abstract class Getdown extends Thread
|
||||
throw new MultipleGetdownRunning();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void install ()
|
||||
throws IOException, InterruptedException
|
||||
{
|
||||
if (readyToInstall) {
|
||||
log.info("Installing downloaded resources:");
|
||||
for (Resource resource : toBeInstalledResouces) {
|
||||
log.info(resource);
|
||||
if (!FileUtil.renameTo(resource.getLocalNew(), resource.getLocal()))
|
||||
throw new IOException("Failed to rename(" + resource.getLocalNew() + ", " + resource.getLocal() + ")");
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException("m.applet_stopped");
|
||||
}
|
||||
}
|
||||
toBeInstalledResouces.clear();
|
||||
readyToInstall = false;
|
||||
log.info("Install completed.");
|
||||
} else {
|
||||
log.info("Nothing to install.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to launch the application if everything is determined to be ready to go.
|
||||
*/
|
||||
@@ -1219,6 +1259,7 @@ public abstract class Getdown extends Thread
|
||||
|
||||
protected boolean _dead;
|
||||
protected boolean _silent;
|
||||
protected boolean _install;
|
||||
protected boolean _launchInSilent;
|
||||
protected long _startup;
|
||||
|
||||
@@ -1239,4 +1280,6 @@ public abstract class Getdown extends Thread
|
||||
protected static final long PLAY_AGAIN_TIME = 3000L;
|
||||
protected static final String PROXY_REGISTRY =
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
||||
protected static List<Resource> toBeInstalledResouces;
|
||||
protected static boolean readyToInstall;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class HTTPDownloader extends Downloader
|
||||
long currentSize = 0L;
|
||||
try {
|
||||
in = conn.getInputStream();
|
||||
out = new FileOutputStream(rsrc.getLocal());
|
||||
out = new FileOutputStream(rsrc.getLocalNew());
|
||||
int read;
|
||||
|
||||
// TODO: look to see if we have a download info file
|
||||
|
||||
@@ -57,6 +57,7 @@ public class FileUtil extends com.samskivert.util.FileUtil
|
||||
fin = new FileInputStream(source);
|
||||
fout = new FileOutputStream(dest);
|
||||
StreamUtil.copy(fin, fout);
|
||||
fin.close(); // needed otherwise cannot delete source
|
||||
if (!source.delete()) {
|
||||
log.warning("Failed to delete " + source +
|
||||
" after brute force copy to " + dest + ".");
|
||||
|
||||
Reference in New Issue
Block a user