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:
Saeid Nourian
2016-06-17 11:03:56 -04:00
parent 3a4b4ae539
commit 176cb8ca99
5 changed files with 69 additions and 7 deletions
@@ -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();
}
@@ -410,6 +412,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++) {
// if we aren't running in a JVM that meets our version requirements, either
@@ -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);
@@ -755,6 +774,27 @@ public abstract class Getdown extends Thread
}
}
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 + ".");