Merge remote-tracking branch 'artelysGithub/master'
This commit is contained in:
@@ -43,8 +43,8 @@ applications.
|
||||
The latest version of Getdown can be obtained thusly:
|
||||
|
||||
* Download the pre-built jar file from Maven Central:
|
||||
[getdown-1.5.jar](http://repo2.maven.org/maven2/com/threerings/getdown/1.5/getdown-1.5.jar)
|
||||
* Obtain the jar artifact via Maven with the following identifier: `com.threerings:getdown:1.5`.
|
||||
[getdown-1.6.jar](http://repo2.maven.org/maven2/com/threerings/getdown/1.6/getdown-1.6.jar)
|
||||
* Obtain the jar artifact via Maven with the following identifier: `com.threerings:getdown:1.6`.
|
||||
* [Check out the code](https://github.com/threerings/getdown) and build it yourself.
|
||||
|
||||
You can also:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<groupId>com.threerings</groupId>
|
||||
<artifactId>getdown</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.7-SNAPSHOT</version>
|
||||
|
||||
<name>getdown</name>
|
||||
<description>An application installer and updater.</description>
|
||||
|
||||
@@ -904,7 +904,7 @@ public class Application
|
||||
// now re-download our control files; we download the digest first so that if it fails,
|
||||
// our config file will still reference the old version and re-running the updater will
|
||||
// start the whole process over again
|
||||
downloadDigestFile();
|
||||
downloadDigestFiles();
|
||||
downloadConfigFile();
|
||||
|
||||
} catch (IOException ex) {
|
||||
@@ -1171,7 +1171,7 @@ public class Application
|
||||
String olddig = (_digest == null) ? "" : _digest.getMetaDigest();
|
||||
try {
|
||||
status.updateStatus("m.checking");
|
||||
downloadDigestFile();
|
||||
downloadDigestFiles();
|
||||
_digest = new Digest(_appdir);
|
||||
if (!olddig.equals(_digest.getMetaDigest())) {
|
||||
log.info("Unversioned digest changed. Revalidating...");
|
||||
@@ -1189,7 +1189,7 @@ public class Application
|
||||
// exceptions to propagate up to the caller as there is nothing else we can do
|
||||
if (_digest == null) {
|
||||
status.updateStatus("m.updating_metadata");
|
||||
downloadDigestFile();
|
||||
downloadDigestFiles();
|
||||
_digest = new Digest(_appdir);
|
||||
}
|
||||
|
||||
@@ -1200,7 +1200,7 @@ public class Application
|
||||
// attempt to redownload both of our metadata files; again we pass errors up to our
|
||||
// caller because there's nothing we can do to automatically recover
|
||||
downloadConfigFile();
|
||||
downloadDigestFile();
|
||||
downloadDigestFiles();
|
||||
_digest = new Digest(_appdir);
|
||||
// revalidate everything if we end up downloading new metadata
|
||||
clearValidationMarkers();
|
||||
@@ -1459,13 +1459,15 @@ public class Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a copy of Digest.DIGEST_FILE and validates its signature.
|
||||
* Downloads the digest files and validates their signature.
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void downloadDigestFile ()
|
||||
protected void downloadDigestFiles ()
|
||||
throws IOException
|
||||
{
|
||||
downloadControlFile(Digest.DIGEST_FILE, true);
|
||||
for (int version = 1; version <= Digest.VERSION; version++) {
|
||||
downloadControlFile(Digest.digestFile(version), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1507,7 +1509,7 @@ public class Application
|
||||
FileInputStream dataInput = null;
|
||||
try {
|
||||
dataInput = new FileInputStream(target);
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
Signature sig = Signature.getInstance(Digest.SIG_ALGO);
|
||||
sig.initVerify(cert);
|
||||
while ((length = dataInput.read(buffer)) != -1) {
|
||||
sig.update(buffer, 0, length);
|
||||
|
||||
@@ -25,26 +25,96 @@ import com.threerings.getdown.util.ProgressObserver;
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an
|
||||
* Manages the <code>digest.txt</code> file and the computing and processing of digests for an
|
||||
* application.
|
||||
*/
|
||||
public class Digest
|
||||
{
|
||||
/** The name of our MD5 digest file. */
|
||||
public static final String DIGEST_FILE = "digest.txt";
|
||||
/** The current version of the digest protocol. */
|
||||
public static final int VERSION = 2;
|
||||
|
||||
/** The current algorithm used to sign digest files. */
|
||||
public static final String SIG_ALGO = "SHA256withRSA";
|
||||
|
||||
/**
|
||||
* Creates a digest instance which will parse and validate the <code>digest.txt</code> in the
|
||||
* supplied application directory.
|
||||
* Returns the name of the digest file for the specified protocol version.
|
||||
*/
|
||||
public Digest (File appdir)
|
||||
public static String digestFile (int version) {
|
||||
String infix = version > 1 ? String.valueOf(version) : "";
|
||||
return FILE_NAME + infix + FILE_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file at the specified location using the supplied list of resources.
|
||||
* @param version the version of the digest protocol to use.
|
||||
*/
|
||||
public static void createDigest (int version, List<Resource> resources, File output)
|
||||
throws IOException
|
||||
{
|
||||
// parse and validate our digest file contents
|
||||
MessageDigest md = getMessageDigest(version);
|
||||
StringBuilder data = new StringBuilder();
|
||||
File dfile = new File(appdir, DIGEST_FILE);
|
||||
PrintWriter pout = null;
|
||||
try {
|
||||
pout = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), "UTF-8"));
|
||||
|
||||
// compute and append the digest of each resource in the list
|
||||
for (Resource rsrc : resources) {
|
||||
String path = rsrc.getPath();
|
||||
try {
|
||||
String digest = rsrc.computeDigest(version, md, null);
|
||||
note(data, path, digest);
|
||||
pout.println(path + " = " + digest);
|
||||
} catch (Throwable t) {
|
||||
throw (IOException) new IOException(
|
||||
"Error computing digest for: " + rsrc).initCause(t);
|
||||
}
|
||||
}
|
||||
|
||||
// finally compute and append the digest for the file contents
|
||||
md.reset();
|
||||
byte[] contents = data.toString().getBytes("UTF-8");
|
||||
String filename = digestFile(version);
|
||||
pout.println(filename + " = " + StringUtil.hexlate(md.digest(contents)));
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(pout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an appropriate message digest instance for use by the Getdown system.
|
||||
*/
|
||||
public static MessageDigest getMessageDigest (int version)
|
||||
{
|
||||
String algo = version > 1 ? "SHA-256" : "MD5";
|
||||
try {
|
||||
return MessageDigest.getInstance(algo);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new RuntimeException("JVM does not support " + algo + ". Gurp!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest instance which will parse and validate the digest in the supplied
|
||||
* application directory, using the current digest version.
|
||||
*/
|
||||
public Digest (File appdir) throws IOException {
|
||||
this(appdir, VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest instance which will parse and validate the digest in the supplied
|
||||
* application directory.
|
||||
* @param version the version of the digest protocol to use.
|
||||
*/
|
||||
public Digest (File appdir, int version) throws IOException
|
||||
{
|
||||
// parse and validate our digest file contents
|
||||
String filename = digestFile(version);
|
||||
StringBuilder data = new StringBuilder();
|
||||
File dfile = new File(appdir, filename);
|
||||
for (String[] pair : ConfigUtil.parsePairs(dfile, false)) {
|
||||
if (pair[0].equals(DIGEST_FILE)) {
|
||||
if (pair[0].equals(filename)) {
|
||||
_metaDigest = pair[1];
|
||||
break;
|
||||
}
|
||||
@@ -53,11 +123,11 @@ public class Digest
|
||||
}
|
||||
|
||||
// we've reached the end, validate our contents
|
||||
MessageDigest md = getMessageDigest();
|
||||
MessageDigest md = getMessageDigest(version);
|
||||
byte[] contents = data.toString().getBytes("UTF-8");
|
||||
String md5 = StringUtil.hexlate(md.digest(contents));
|
||||
if (!md5.equals(_metaDigest)) {
|
||||
String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, md5);
|
||||
String hash = StringUtil.hexlate(md.digest(contents));
|
||||
if (!hash.equals(_metaDigest)) {
|
||||
String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, hash);
|
||||
throw new IOException(err);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +141,7 @@ public class Digest
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the MD5 hash of the specified resource and compares it with the value parsed from
|
||||
* Computes the hash of the specified resource and compares it with the value parsed from
|
||||
* the digest file. Logs a message if the resource fails validation.
|
||||
*
|
||||
* @return true if the resource is valid, false if it failed the digest check or if an I/O
|
||||
@@ -80,13 +150,13 @@ public class Digest
|
||||
public boolean validateResource (Resource resource, ProgressObserver obs)
|
||||
{
|
||||
try {
|
||||
String cmd5 = resource.computeDigest(getMessageDigest(), obs);
|
||||
String emd5 = _digests.get(resource.getPath());
|
||||
if (cmd5.equals(emd5)) {
|
||||
String chash = resource.computeDigest(VERSION, getMessageDigest(VERSION), obs);
|
||||
String ehash = _digests.get(resource.getPath());
|
||||
if (chash.equals(ehash)) {
|
||||
return true;
|
||||
}
|
||||
log.info("Resource failed digest check",
|
||||
"rsrc", resource, "computed", cmd5, "expected", emd5);
|
||||
"rsrc", resource, "computed", chash, "expected", ehash);
|
||||
} catch (Throwable t) {
|
||||
log.info("Resource failed digest check", "rsrc", resource, "error", t);
|
||||
}
|
||||
@@ -101,53 +171,6 @@ public class Digest
|
||||
return _digests.get(resource.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file at the specified location using the supplied list of resources.
|
||||
*/
|
||||
public static void createDigest (List<Resource> resources, File output)
|
||||
throws IOException
|
||||
{
|
||||
MessageDigest md = getMessageDigest();
|
||||
StringBuilder data = new StringBuilder();
|
||||
PrintWriter pout = null;
|
||||
try {
|
||||
pout = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), "UTF-8"));
|
||||
|
||||
// compute and append the MD5 digest of each resource in the list
|
||||
for (Resource rsrc : resources) {
|
||||
String path = rsrc.getPath();
|
||||
try {
|
||||
String digest = rsrc.computeDigest(md, null);
|
||||
note(data, path, digest);
|
||||
pout.println(path + " = " + digest);
|
||||
} catch (Throwable t) {
|
||||
throw (IOException) new IOException(
|
||||
"Error computing digest for: " + rsrc).initCause(t);
|
||||
}
|
||||
}
|
||||
|
||||
// finally compute and append the digest for the file contents
|
||||
md.reset();
|
||||
byte[] contents = data.toString().getBytes("UTF-8");
|
||||
pout.println(DIGEST_FILE + " = " + StringUtil.hexlate(md.digest(contents)));
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(pout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an appropriate message digest instance for use by the Getdown system.
|
||||
*/
|
||||
public static MessageDigest getMessageDigest ()
|
||||
{
|
||||
try {
|
||||
return MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new RuntimeException("JVM does not support MD5. Gurp!");
|
||||
}
|
||||
}
|
||||
|
||||
/** Used by {@link #createDigest} and {@link Digest}. */
|
||||
protected static void note (StringBuilder data, String path, String digest)
|
||||
{
|
||||
@@ -156,4 +179,7 @@ public class Digest
|
||||
|
||||
protected HashMap<String, String> _digests = new HashMap<String, String>();
|
||||
protected String _metaDigest = "";
|
||||
|
||||
protected static final String FILE_NAME = "digest";
|
||||
protected static final String FILE_SUFFIX = ".txt";
|
||||
}
|
||||
|
||||
@@ -27,6 +27,91 @@ import static com.threerings.getdown.Log.log;
|
||||
*/
|
||||
public class Resource
|
||||
{
|
||||
/**
|
||||
* Computes the MD5 hash of the supplied file.
|
||||
* @param version the version of the digest protocol to use.
|
||||
*/
|
||||
public static String computeDigest (int version, File target, MessageDigest md,
|
||||
ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
md.reset();
|
||||
byte[] buffer = new byte[DIGEST_BUFFER_SIZE];
|
||||
int read;
|
||||
|
||||
boolean isJar = isJar(target.getPath());
|
||||
boolean isPacked200Jar = isPacked200Jar(target.getPath());
|
||||
|
||||
// if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic
|
||||
// manner to properly correlate jardiff patched jars with their unpatched originals
|
||||
if (isJar || isPacked200Jar){
|
||||
File tmpJarFile = null;
|
||||
JarFile jar = null;
|
||||
try {
|
||||
// if this is a compressed jar file, uncompress it to compute the jar file digest
|
||||
if (isPacked200Jar){
|
||||
tmpJarFile = new File(target.getPath() + ".tmp");
|
||||
FileUtil.unpackPacked200Jar(target, tmpJarFile);
|
||||
jar = new JarFile(tmpJarFile);
|
||||
} else{
|
||||
jar = new JarFile(target);
|
||||
}
|
||||
|
||||
List<JarEntry> entries = Collections.list(jar.entries());
|
||||
Collections.sort(entries, ENTRY_COMP);
|
||||
|
||||
int eidx = 0;
|
||||
for (JarEntry entry : entries) {
|
||||
// old versions of the digest code skipped metadata
|
||||
if (version < 2) {
|
||||
if (entry.getName().startsWith("META-INF")) {
|
||||
updateProgress(obs, eidx, entries.size());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = jar.getInputStream(entry);
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, read);
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
}
|
||||
updateProgress(obs, eidx, entries.size());
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (jar != null) {
|
||||
try {
|
||||
jar.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe);
|
||||
}
|
||||
}
|
||||
if (tmpJarFile != null) {
|
||||
tmpJarFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
long totalSize = target.length(), position = 0L;
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(target);
|
||||
while ((read = fin.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, read);
|
||||
position += read;
|
||||
updateProgress(obs, position, totalSize);
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.close(fin);
|
||||
}
|
||||
}
|
||||
return StringUtil.hexlate(md.digest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resource with the supplied remote URL and local path.
|
||||
*/
|
||||
@@ -35,6 +120,7 @@ public class Resource
|
||||
_path = path;
|
||||
_remote = remote;
|
||||
_local = local;
|
||||
_localNew = new File(local.toString() + "_new");
|
||||
String lpath = _local.getPath();
|
||||
_marker = new File(lpath + "v");
|
||||
|
||||
@@ -66,6 +152,14 @@ public class Resource
|
||||
return _local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of the to-be-installed new version of this resource.
|
||||
*/
|
||||
public File getLocalNew ()
|
||||
{
|
||||
return _localNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location of the unpacked resource.
|
||||
*/
|
||||
@@ -102,11 +196,18 @@ public class Resource
|
||||
/**
|
||||
* Computes the MD5 hash of this resource's underlying file.
|
||||
* <em>Note:</em> This is both CPU and I/O intensive.
|
||||
* @param version the version of the digest protocol to use.
|
||||
*/
|
||||
public String computeDigest (MessageDigest md, ProgressObserver obs)
|
||||
public String computeDigest (int version, MessageDigest md, ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
return computeDigest(_local, md, obs);
|
||||
File file;
|
||||
if (_local.toString().toLowerCase().endsWith(Application.CONFIG_FILE)) {
|
||||
file = _local;
|
||||
} else {
|
||||
file = _localNew.exists() ? _localNew : _local;
|
||||
}
|
||||
return computeDigest(version, file, md, obs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,88 +300,6 @@ public class Resource
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the MD5 hash of the supplied file.
|
||||
*/
|
||||
public static String computeDigest (File target, MessageDigest md, ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
md.reset();
|
||||
byte[] buffer = new byte[DIGEST_BUFFER_SIZE];
|
||||
int read;
|
||||
|
||||
boolean isJar = isJar(target.getPath());
|
||||
boolean isPacked200Jar = isPacked200Jar(target.getPath());
|
||||
|
||||
// if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic
|
||||
// manner to properly correlate jardiff patched jars with their unpatched originals
|
||||
if (isJar || isPacked200Jar){
|
||||
File tmpJarFile = null;
|
||||
JarFile jar = null;
|
||||
try {
|
||||
// if this is a compressed jar file, uncompress it to compute the jar file digest
|
||||
if (isPacked200Jar){
|
||||
tmpJarFile = new File(target.getPath() + ".tmp");
|
||||
FileUtil.unpackPacked200Jar(target, tmpJarFile);
|
||||
jar = new JarFile(tmpJarFile);
|
||||
} else{
|
||||
jar = new JarFile(target);
|
||||
}
|
||||
|
||||
List<JarEntry> entries = Collections.list(jar.entries());
|
||||
Collections.sort(entries, ENTRY_COMP);
|
||||
|
||||
int eidx = 0;
|
||||
for (JarEntry entry : entries) {
|
||||
// skip metadata; we just want the goods
|
||||
if (entry.getName().startsWith("META-INF")) {
|
||||
updateProgress(obs, eidx, entries.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// add this file's data to the MD5 hash
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = jar.getInputStream(entry);
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, read);
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
}
|
||||
updateProgress(obs, eidx, entries.size());
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (jar != null) {
|
||||
try {
|
||||
jar.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe);
|
||||
}
|
||||
}
|
||||
if (tmpJarFile != null) {
|
||||
tmpJarFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
long totalSize = target.length(), position = 0L;
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(target);
|
||||
while ((read = fin.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, read);
|
||||
position += read;
|
||||
updateProgress(obs, position, totalSize);
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.close(fin);
|
||||
}
|
||||
}
|
||||
return StringUtil.hexlate(md.digest());
|
||||
}
|
||||
|
||||
/** Helper function to simplify the process of reporting progress. */
|
||||
protected static void updateProgress (ProgressObserver obs, long pos, long total)
|
||||
{
|
||||
@@ -291,7 +310,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)
|
||||
@@ -301,7 +320,7 @@ public class Resource
|
||||
|
||||
protected String _path;
|
||||
protected URL _remote;
|
||||
protected File _local, _marker, _unpacked;
|
||||
protected File _local, _localNew, _marker, _unpacked;
|
||||
protected boolean _unpack, _isJar, _isPacked200Jar;
|
||||
|
||||
/** Used to sort the entries in a jar file. */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package com.threerings.getdown.data;
|
||||
|
||||
import com.threerings.getdown.util.VersionUtil;
|
||||
import com.threerings.getdown.launcher.Getdown;
|
||||
|
||||
/**
|
||||
* This class encapsulates all system properties that are read and processed by Getdown. Don't
|
||||
@@ -49,6 +50,13 @@ public class SysProps
|
||||
return System.getProperty("silent") != null;
|
||||
}
|
||||
|
||||
/** If true, Getdown does not automatically install updates after downloading them. It waits
|
||||
* for the application to call {@link Getdown#install}.
|
||||
* Usage: {@code -Dno_install}. */
|
||||
public static boolean noInstall () {
|
||||
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;
|
||||
@@ -104,6 +105,7 @@ public abstract class Getdown extends Thread
|
||||
_launchInSilent = SysProps.launchInSilent();
|
||||
}
|
||||
_delay = SysProps.startDelay();
|
||||
_noInstall = SysProps.noInstall();
|
||||
} catch (SecurityException se) {
|
||||
// don't freak out, just assume non-silent and no delay; we're probably already
|
||||
// recovering from a security failure
|
||||
@@ -126,6 +128,39 @@ public abstract class Getdown extends Thread
|
||||
_startup = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are pending new resources, waiting to be installed.
|
||||
*/
|
||||
public boolean isUpdateAvailable ()
|
||||
{
|
||||
return _readyToInstall && !_toBeInstalledResouces.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the currently pending new resources.
|
||||
*/
|
||||
public void install () throws IOException, InterruptedException
|
||||
{
|
||||
if (_readyToInstall) {
|
||||
log.info("Installing downloaded resources:");
|
||||
for (Resource resource : _toBeInstalledResouces) {
|
||||
File source = resource.getLocalNew(), dest = resource.getLocal();
|
||||
log.info("- " + source);
|
||||
if (!FileUtil.renameTo(source, dest)) {
|
||||
throw new IOException("Failed to rename " + source + " to " + dest);
|
||||
}
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException("m.applet_stopped");
|
||||
}
|
||||
}
|
||||
_toBeInstalledResouces.clear();
|
||||
_readyToInstall = false;
|
||||
log.info("Install completed.");
|
||||
} else {
|
||||
log.info("Nothing to install.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by the applet which always needs a user interface and wants to load it as soon
|
||||
* as possible.
|
||||
@@ -410,6 +445,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
|
||||
@@ -471,6 +509,12 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
// assuming we're not doing anything funny, install the update
|
||||
_readyToInstall = true;
|
||||
if (!_noInstall) {
|
||||
install();
|
||||
}
|
||||
|
||||
// Only launch if we aren't in silent mode. Some mystery program starting out
|
||||
// of the blue would be disconcerting.
|
||||
if (!_silent || _launchInSilent) {
|
||||
@@ -486,6 +530,13 @@ public abstract class Getdown extends Thread
|
||||
return;
|
||||
}
|
||||
|
||||
// we have failures, those will be redownloaded so we note them as to-be-installed
|
||||
for (Resource r : failures) {
|
||||
if (!_toBeInstalledResouces.contains(r)) {
|
||||
_toBeInstalledResouces.add(r);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// if any of our resources have already been marked valid this is not a first
|
||||
// time install and we don't want to enable tracking
|
||||
@@ -499,6 +550,7 @@ public abstract class Getdown extends Thread
|
||||
download(failures);
|
||||
|
||||
reportTrackingEvent("app_complete", -1);
|
||||
|
||||
} finally {
|
||||
_enableTracking = false;
|
||||
}
|
||||
@@ -595,21 +647,10 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
vmjar.markAsValid();
|
||||
|
||||
// Sun, why dost thou spite me? Java doesn't know anything about file permissions (and by
|
||||
// extension then, neither does Jar), so on Joonix we have to hackily make java_vm/bin/java
|
||||
// executable by execing chmod; a pox on their children!
|
||||
if (!RunAnywhere.isWindows()) {
|
||||
String vmbin = LaunchUtil.LOCAL_JAVA_DIR + File.separator + "bin" +
|
||||
File.separator + "java";
|
||||
String cmd = "chmod a+rx " + _app.getLocalPath(vmbin);
|
||||
try {
|
||||
log.info("Please smack a Java engineer. Running: " + cmd);
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to mark VM binary as executable", "cmd", cmd, "error", e);
|
||||
// we should do something like tell the user or something but fucking fuck
|
||||
}
|
||||
}
|
||||
// these only run on non-Windows platforms, so we use Unix file separators
|
||||
String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/";
|
||||
makeExecutable(localJavaDir + "bin/java");
|
||||
makeExecutable(localJavaDir + "lib/amd64/jspawnhelper");
|
||||
|
||||
// lastly regenerate the .jsa dump file that helps Java to start up faster
|
||||
String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath(""));
|
||||
@@ -617,12 +658,29 @@ public abstract class Getdown extends Thread
|
||||
log.info("Regenerating classes.jsa for " + vmpath + "...");
|
||||
Runtime.getRuntime().exec(vmpath + " -Xshare:dump");
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to regenerate .jsa dum file", "error", e);
|
||||
log.warning("Failed to regenerate .jsa dump file", "error", e);
|
||||
}
|
||||
|
||||
reportTrackingEvent("jvm_complete", -1);
|
||||
}
|
||||
|
||||
protected void makeExecutable (String path) {
|
||||
// Java doesn't know anything about file permissions (and by extension then,
|
||||
// neither does Jar), so on Unix we have to hackily do so via chmod
|
||||
if (!RunAnywhere.isWindows()) {
|
||||
File target = _app.getLocalPath(path);
|
||||
String cmd = "chmod a+rx " + target;
|
||||
try {
|
||||
if (target.exists()) {
|
||||
log.info("Running: " + cmd);
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to mark VM binary as executable", "cmd", cmd, "error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if the application is determined to be of an old version.
|
||||
*/
|
||||
@@ -1219,9 +1277,13 @@ public abstract class Getdown extends Thread
|
||||
|
||||
protected boolean _dead;
|
||||
protected boolean _silent;
|
||||
protected boolean _noInstall;
|
||||
protected boolean _launchInSilent;
|
||||
protected long _startup;
|
||||
|
||||
protected List<Resource> _toBeInstalledResouces;
|
||||
protected boolean _readyToInstall;
|
||||
|
||||
protected boolean _enableTracking = true;
|
||||
protected int _reportedProgress = 0;
|
||||
|
||||
|
||||
@@ -11,11 +11,16 @@ import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
@@ -26,6 +31,7 @@ import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.getdown.data.Digest;
|
||||
import com.threerings.getdown.data.SysProps;
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
@@ -36,6 +42,20 @@ public class GetdownApp
|
||||
{
|
||||
public static void main (String[] argv)
|
||||
{
|
||||
try {
|
||||
start(argv);
|
||||
} catch (Exception e) {
|
||||
log.warning("main() failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Getdown as an application, using the arguments supplie as {@code argv}.
|
||||
* @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been
|
||||
* called on it.
|
||||
* @throws Exception if anything goes wrong starting Getdown.
|
||||
*/
|
||||
public static Getdown start (String[] argv) throws Exception {
|
||||
int aidx = 0;
|
||||
List<String> args = Arrays.asList(argv);
|
||||
|
||||
@@ -66,6 +86,22 @@ public class GetdownApp
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// load X.509 certificate if it exists
|
||||
File crtFile = new File(appDir, Digest.digestFile(Digest.VERSION) + ".crt");
|
||||
List<Certificate> crts = new ArrayList<Certificate>();
|
||||
if (crtFile.exists()) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(crtFile);
|
||||
X509Certificate certificate = (X509Certificate)
|
||||
CertificateFactory.getInstance("X.509").generateCertificate(fis);
|
||||
fis.close();
|
||||
crts.add(certificate);
|
||||
} catch (Exception e) {
|
||||
log.warning("Certificate error: " + e.getMessage());
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// pipe our output into a file in the application directory
|
||||
if (!SysProps.noLogRedir()) {
|
||||
File logFile = new File(appDir, "launcher.log");
|
||||
@@ -91,118 +127,114 @@ public class GetdownApp
|
||||
log.info("-- Cur dir: " + System.getProperty("user.dir"));
|
||||
log.info("---------------------------------------------");
|
||||
|
||||
try {
|
||||
Getdown app = new Getdown(appDir, appId, null, null, appArgs) {
|
||||
@Override
|
||||
protected Container createContainer () {
|
||||
// create our user interface, and display it
|
||||
String title = StringUtil.isBlank(_ifc.name) ? "" : _ifc.name;
|
||||
if (_frame == null) {
|
||||
_frame = new JFrame(title);
|
||||
_frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing (WindowEvent evt) {
|
||||
handleWindowClose();
|
||||
}
|
||||
});
|
||||
_frame.setUndecorated(_ifc.hideDecorations);
|
||||
_frame.setResizable(false);
|
||||
} else {
|
||||
_frame.setTitle(title);
|
||||
_frame.getContentPane().removeAll();
|
||||
}
|
||||
|
||||
if (_ifc.iconImages != null) {
|
||||
ArrayList<Image> icons = new ArrayList<Image>();
|
||||
for (String path : _ifc.iconImages) {
|
||||
Image img = loadImage(path);
|
||||
if (img == null) {
|
||||
log.warning("Error loading icon image", "path", path);
|
||||
} else {
|
||||
icons.add(img);
|
||||
}
|
||||
Getdown app = new Getdown(appDir, appId, crts, null, appArgs) {
|
||||
@Override
|
||||
protected Container createContainer () {
|
||||
// create our user interface, and display it
|
||||
String title = StringUtil.isBlank(_ifc.name) ? "" : _ifc.name;
|
||||
if (_frame == null) {
|
||||
_frame = new JFrame(title);
|
||||
_frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing (WindowEvent evt) {
|
||||
handleWindowClose();
|
||||
}
|
||||
if (icons.isEmpty()) {
|
||||
log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
|
||||
});
|
||||
_frame.setUndecorated(_ifc.hideDecorations);
|
||||
_frame.setResizable(false);
|
||||
} else {
|
||||
_frame.setTitle(title);
|
||||
_frame.getContentPane().removeAll();
|
||||
}
|
||||
|
||||
if (_ifc.iconImages != null) {
|
||||
ArrayList<Image> icons = new ArrayList<Image>();
|
||||
for (String path : _ifc.iconImages) {
|
||||
Image img = loadImage(path);
|
||||
if (img == null) {
|
||||
log.warning("Error loading icon image", "path", path);
|
||||
} else {
|
||||
SwingUtil.setFrameIcons(_frame, icons);
|
||||
icons.add(img);
|
||||
}
|
||||
}
|
||||
|
||||
_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
return _frame.getContentPane();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showContainer () {
|
||||
if (_frame != null) {
|
||||
_frame.pack();
|
||||
SwingUtil.centerWindow(_frame);
|
||||
_frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disposeContainer () {
|
||||
if (_frame != null) {
|
||||
_frame.dispose();
|
||||
_frame = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDocument (String url) {
|
||||
String[] cmdarray;
|
||||
if (RunAnywhere.isWindows()) {
|
||||
String osName = System.getProperty("os.name");
|
||||
if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
|
||||
cmdarray = new String[] {
|
||||
"command.com", "/c", "start", "\"" + url + "\"" };
|
||||
} else {
|
||||
cmdarray = new String[] {
|
||||
"cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" };
|
||||
}
|
||||
} else if (RunAnywhere.isMacOS()) {
|
||||
cmdarray = new String[] { "open", url };
|
||||
} else { // Linux, Solaris, etc.
|
||||
cmdarray = new String[] { "firefox", url };
|
||||
}
|
||||
try {
|
||||
Runtime.getRuntime().exec(cmdarray);
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to open browser.", "cmdarray", cmdarray, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exit (int exitCode) {
|
||||
// if we're running the app in the same JVM, don't call System.exit, but do
|
||||
// make double sure that the download window is closed.
|
||||
if (invokeDirect()) {
|
||||
disposeContainer();
|
||||
if (icons.isEmpty()) {
|
||||
log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
|
||||
} else {
|
||||
System.exit(exitCode);
|
||||
SwingUtil.setFrameIcons(_frame, icons);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fail (String message) {
|
||||
// if the frame was set to be undecorated, make window decoration available
|
||||
// to allow the user to close the window
|
||||
if (_frame != null && _frame.isUndecorated()) {
|
||||
_frame.dispose();
|
||||
_frame.setUndecorated(false);
|
||||
showContainer();
|
||||
}
|
||||
super.fail(message);
|
||||
_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
return _frame.getContentPane();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showContainer () {
|
||||
if (_frame != null) {
|
||||
_frame.pack();
|
||||
SwingUtil.centerWindow(_frame);
|
||||
_frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected JFrame _frame;
|
||||
};
|
||||
app.start();
|
||||
@Override
|
||||
protected void disposeContainer () {
|
||||
if (_frame != null) {
|
||||
_frame.dispose();
|
||||
_frame = null;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("main() failed.", e);
|
||||
}
|
||||
@Override
|
||||
protected void showDocument (String url) {
|
||||
String[] cmdarray;
|
||||
if (RunAnywhere.isWindows()) {
|
||||
String osName = System.getProperty("os.name");
|
||||
if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
|
||||
cmdarray = new String[] {
|
||||
"command.com", "/c", "start", "\"" + url + "\"" };
|
||||
} else {
|
||||
cmdarray = new String[] {
|
||||
"cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" };
|
||||
}
|
||||
} else if (RunAnywhere.isMacOS()) {
|
||||
cmdarray = new String[] { "open", url };
|
||||
} else { // Linux, Solaris, etc.
|
||||
cmdarray = new String[] { "firefox", url };
|
||||
}
|
||||
try {
|
||||
Runtime.getRuntime().exec(cmdarray);
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to open browser.", "cmdarray", cmdarray, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exit (int exitCode) {
|
||||
// if we're running the app in the same JVM, don't call System.exit, but do
|
||||
// make double sure that the download window is closed.
|
||||
if (invokeDirect()) {
|
||||
disposeContainer();
|
||||
} else {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fail (String message) {
|
||||
// if the frame was set to be undecorated, make window decoration available
|
||||
// to allow the user to close the window
|
||||
if (_frame != null && _frame.isUndecorated()) {
|
||||
_frame.dispose();
|
||||
_frame.setUndecorated(false);
|
||||
showContainer();
|
||||
}
|
||||
super.fail(message);
|
||||
}
|
||||
|
||||
protected JFrame _frame;
|
||||
};
|
||||
app.start();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.security.Signature;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import com.threerings.getdown.data.Digest;
|
||||
|
||||
/**
|
||||
* Produces a signed hash of the appbase, appname, and image path to ensure that signed copies of
|
||||
* Getdown are not hijacked to run malicious code.
|
||||
@@ -41,7 +43,7 @@ public class AppletParamSigner
|
||||
store.load(new BufferedInputStream(new FileInputStream(keystore)),
|
||||
storepass.toCharArray());
|
||||
PrivateKey key = (PrivateKey)store.getKey(alias, keypass.toCharArray());
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
Signature sig = Signature.getInstance(Digest.SIG_ALGO);
|
||||
sig.initSign(key);
|
||||
sig.update(params.getBytes());
|
||||
String signed = new String(Base64.encodeBase64(sig.sign()));
|
||||
|
||||
@@ -94,7 +94,8 @@ public class Differ
|
||||
ArrayList<Resource> nrsrcs, boolean verbose)
|
||||
throws IOException
|
||||
{
|
||||
MessageDigest md = Digest.getMessageDigest();
|
||||
int version = Digest.VERSION;
|
||||
MessageDigest md = Digest.getMessageDigest(version);
|
||||
JarOutputStream jout = null;
|
||||
try {
|
||||
jout = new JarOutputStream(
|
||||
@@ -107,8 +108,8 @@ public class Differ
|
||||
Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx);
|
||||
if (orsrc != null) {
|
||||
// first see if they are the same
|
||||
String odig = orsrc.computeDigest(md, null);
|
||||
String ndig = rsrc.computeDigest(md, null);
|
||||
String odig = orsrc.computeDigest(version, md, null);
|
||||
String ndig = rsrc.computeDigest(version, md, null);
|
||||
if (odig.equals(ndig)) {
|
||||
if (verbose) {
|
||||
System.out.println("Unchanged: " + rsrc.getPath());
|
||||
|
||||
@@ -36,24 +36,40 @@ public class Digester
|
||||
public static void main (String[] args)
|
||||
throws IOException, GeneralSecurityException
|
||||
{
|
||||
if (args.length != 1 && args.length != 4) {
|
||||
switch (args.length) {
|
||||
case 1:
|
||||
createDigests(new File(args[0]), null, null, null);
|
||||
break;
|
||||
case 4:
|
||||
createDigests(new File(args[0]), new File(args[1]), args[2], args[3]);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Usage: Digester app_dir [keystore_path password alias]");
|
||||
System.exit(255);
|
||||
}
|
||||
}
|
||||
|
||||
createDigest(new File(args[0]));
|
||||
if (args.length == 4) {
|
||||
signDigest(new File(args[0]), new File(args[1]), args[2], args[3]);
|
||||
/**
|
||||
* Creates digest file(s) and optionally signs them if {@code keystore} is not null.
|
||||
*/
|
||||
public static void createDigests (File appdir, File keystore, String password, String alias)
|
||||
throws IOException, GeneralSecurityException
|
||||
{
|
||||
for (int version = 1; version <= Digest.VERSION; version++) {
|
||||
createDigest(version, appdir);
|
||||
if (keystore != null) {
|
||||
signDigest(version, appdir, keystore, password, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file in the specified application directory.
|
||||
*/
|
||||
public static void createDigest (File appdir)
|
||||
public static void createDigest (int version, File appdir)
|
||||
throws IOException
|
||||
{
|
||||
File target = new File(appdir, Digest.DIGEST_FILE);
|
||||
File target = new File(appdir, Digest.digestFile(version));
|
||||
System.out.println("Generating digest file '" + target + "'...");
|
||||
|
||||
// create our application and instruct it to parse its business
|
||||
@@ -70,17 +86,19 @@ public class Digester
|
||||
}
|
||||
|
||||
// now generate the digest file
|
||||
Digest.createDigest(rsrcs, target);
|
||||
Digest.createDigest(version, rsrcs, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file in the specified application directory.
|
||||
*/
|
||||
public static void signDigest (File appdir, File storePath, String storePass, String storeAlias)
|
||||
public static void signDigest (int version, File appdir,
|
||||
File storePath, String storePass, String storeAlias)
|
||||
throws IOException, GeneralSecurityException
|
||||
{
|
||||
File inputFile = new File(appdir, Digest.DIGEST_FILE);
|
||||
File signatureFile = new File(appdir, Digest.DIGEST_FILE + Application.SIGNATURE_SUFFIX);
|
||||
String filename = Digest.digestFile(version);
|
||||
File inputFile = new File(appdir, filename);
|
||||
File signatureFile = new File(appdir, filename + Application.SIGNATURE_SUFFIX);
|
||||
|
||||
FileInputStream storeInput = null, dataInput = null;
|
||||
FileOutputStream signatureOutput = null;
|
||||
@@ -92,7 +110,8 @@ public class Digester
|
||||
PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray());
|
||||
|
||||
// sign the digest file
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
String algo = version > 1 ? Digest.SIG_ALGO : "SHA1withRSA";
|
||||
Signature sig = Signature.getInstance(algo);
|
||||
dataInput = new FileInputStream(inputFile);
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.security.GeneralSecurityException;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import com.threerings.getdown.data.Digest;
|
||||
|
||||
/**
|
||||
* An ant task used to create a <code>digest.txt</code> for a Getdown
|
||||
* application deployment.
|
||||
@@ -70,10 +72,7 @@ public class DigesterTask extends Task
|
||||
}
|
||||
|
||||
try {
|
||||
Digester.createDigest(_appdir);
|
||||
if (_storepath != null) {
|
||||
Digester.signDigest(_appdir, _storepath, _storepass, _storealias);
|
||||
}
|
||||
Digester.createDigests(_appdir, _storepath, _storepass, _storealias);
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
|
||||
@@ -60,6 +60,9 @@ public class FileUtil extends com.samskivert.util.FileUtil
|
||||
fin = new FileInputStream(source);
|
||||
fout = new FileOutputStream(dest);
|
||||
StreamUtil.copy(fin, fout);
|
||||
// close the input stream now so we can delete 'source'
|
||||
fin.close();
|
||||
fin = null;
|
||||
if (!source.delete()) {
|
||||
log.warning("Failed to delete " + source +
|
||||
" after brute force copy to " + dest + ".");
|
||||
|
||||
Reference in New Issue
Block a user