Moved src/java into src/main/java, added src/test/java, added build targets for
tests. Some changes (and tests to make sure I'm not fucking things up) forthcoming.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by the Getdown code.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.getdown");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.text.MessageUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.getdown.util.ConfigUtil;
|
||||
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 application.
|
||||
*/
|
||||
public class Digest
|
||||
{
|
||||
/** The name of our MD5 digest file. */
|
||||
public static final String DIGEST_FILE = "digest.txt";
|
||||
|
||||
/**
|
||||
* Creates a digest instance which will parse and validate the
|
||||
* <code>digest.txt</code> in the supplied application directory.
|
||||
*/
|
||||
public Digest (File appdir)
|
||||
throws IOException
|
||||
{
|
||||
// parse and validate our digest file contents
|
||||
StringBuilder data = new StringBuilder();
|
||||
File dfile = new File(appdir, DIGEST_FILE);
|
||||
for (String[] pair : ConfigUtil.parsePairs(dfile, false)) {
|
||||
if (pair[0].equals(DIGEST_FILE)) {
|
||||
_metaDigest = pair[1];
|
||||
break;
|
||||
}
|
||||
_digests.put(pair[0], pair[1]);
|
||||
note(data, pair[0], pair[1]);
|
||||
}
|
||||
|
||||
// we've reached the end, validate our contents
|
||||
MessageDigest md = getMessageDigest();
|
||||
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);
|
||||
throw new IOException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the digest for the digest file.
|
||||
*/
|
||||
public String getMetaDigest ()
|
||||
{
|
||||
return _metaDigest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the MD5 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 error was encountered during the
|
||||
* validation process.
|
||||
*/
|
||||
public boolean validateResource (Resource resource, ProgressObserver obs)
|
||||
{
|
||||
try {
|
||||
String cmd5 = resource.computeDigest(getMessageDigest(), obs);
|
||||
String emd5 = _digests.get(resource.getPath());
|
||||
if (cmd5.equals(emd5)) {
|
||||
return true;
|
||||
}
|
||||
log.info("Resource failed digest check [rsrc=" + resource +
|
||||
", computed=" + cmd5 + ", expected=" + emd5 + "].");
|
||||
} catch (Throwable t) {
|
||||
log.info("Resource failed digest check [rsrc=" + resource +
|
||||
", error=" + t + "].");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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)));
|
||||
|
||||
pout.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
data.append(path).append(" = ").append(digest).append("\n");
|
||||
}
|
||||
|
||||
protected HashMap<String, String> _digests = new HashMap<String, String>();
|
||||
protected String _metaDigest = "";
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
// 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.data;
|
||||
|
||||
/**
|
||||
* System property constants associated with Getdown.
|
||||
*/
|
||||
public class Properties
|
||||
{
|
||||
/** This property will be set to "true" on the application when it is being run by getdown. */
|
||||
public static final String GETDOWN = "com.threerings.getdown";
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.FileUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.getdown.util.ProgressObserver;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Models a single file resource used by an {@link Application}.
|
||||
*/
|
||||
public class Resource
|
||||
{
|
||||
/**
|
||||
* Creates a resource with the supplied remote URL and local path.
|
||||
*/
|
||||
public Resource (String path, URL remote, File local, boolean unpack)
|
||||
{
|
||||
_path = path;
|
||||
_remote = remote;
|
||||
_local = local;
|
||||
_marker = new File(_local.getPath() + "v");
|
||||
_unpack = unpack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path associated with this resource.
|
||||
*/
|
||||
public String getPath ()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local location of this resource.
|
||||
*/
|
||||
public File getLocal ()
|
||||
{
|
||||
return _local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote location of this resource.
|
||||
*/
|
||||
public URL getRemote ()
|
||||
{
|
||||
return _remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this resource should be unpacked as a part of the
|
||||
* validation process.
|
||||
*/
|
||||
public boolean shouldUnpack ()
|
||||
{
|
||||
return _unpack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the MD5 hash of this resource's underlying file.
|
||||
* <em>Note:</em> This is both CPU and I/O intensive.
|
||||
*/
|
||||
public String computeDigest (MessageDigest md, ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
return computeDigest(_local, md, obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this resource has an associated "validated" marker
|
||||
* file.
|
||||
*/
|
||||
public boolean isMarkedValid ()
|
||||
{
|
||||
if (!_local.exists()) {
|
||||
clearMarker();
|
||||
return false;
|
||||
}
|
||||
return _marker.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "validated" marker file for this resource to indicate
|
||||
* that its MD5 hash has been computed and compared with the value in
|
||||
* the digest file.
|
||||
*
|
||||
* @throws IOException if we fail to create the marker file.
|
||||
*/
|
||||
public void markAsValid ()
|
||||
throws IOException
|
||||
{
|
||||
_marker.createNewFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any "validated" marker file associated with this resource.
|
||||
*/
|
||||
public void clearMarker ()
|
||||
{
|
||||
if (_marker.exists()) {
|
||||
if (!_marker.delete()) {
|
||||
log.warning("Failed to erase marker file '" + _marker + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks this resource file into the directory that contains it. Returns
|
||||
* false if an error occurs while unpacking it.
|
||||
*/
|
||||
public boolean unpack ()
|
||||
{
|
||||
// sanity check
|
||||
if (!_local.getPath().endsWith(".jar")) {
|
||||
log.warning("Requested to unpack non-jar file '" + _local + "'.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return FileUtil.unpackJar(new JarFile(_local), _local.getParentFile());
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to create JarFile from '" + _local + "': " + ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipes this resource file along with any "validated" marker file
|
||||
* that may be associated with it.
|
||||
*/
|
||||
public void erase ()
|
||||
{
|
||||
clearMarker();
|
||||
if (_local.exists()) {
|
||||
if (!_local.delete()) {
|
||||
log.warning("Failed to erase resource '" + _local + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If our path is equal, we are equal.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
if (other instanceof Resource) {
|
||||
return _path.equals(((Resource)other)._path);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We hash on our path.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode ()
|
||||
{
|
||||
return _path.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this instance.
|
||||
*/
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
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;
|
||||
|
||||
// if this is a jar file, 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 (target.getPath().endsWith(".jar")) {
|
||||
JarFile jar = new JarFile(target);
|
||||
try {
|
||||
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 {
|
||||
try {
|
||||
jar.close();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Error closing jar [path=" + target + ", error=" + ioe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
} 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)
|
||||
{
|
||||
if (obs != null) {
|
||||
obs.progress((int)(100 * pos / total));
|
||||
}
|
||||
}
|
||||
|
||||
protected String _path;
|
||||
protected URL _remote;
|
||||
protected File _local, _marker;
|
||||
protected boolean _unpack;
|
||||
|
||||
/** Used to sort the entries in a jar file. */
|
||||
protected static final Comparator<JarEntry> ENTRY_COMP =
|
||||
new Comparator<JarEntry>() {
|
||||
public int compare (JarEntry e1, JarEntry e2) {
|
||||
return e1.getName().compareTo(e2.getName());
|
||||
}
|
||||
};
|
||||
|
||||
protected static final int DIGEST_BUFFER_SIZE = 5 * 1025;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.samskivert.swing.GroupLayout;
|
||||
import com.samskivert.swing.Spacer;
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
import com.samskivert.text.MessageUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Displays a confirmation that the user wants to abort installation.
|
||||
*/
|
||||
public class AbortPanel extends JFrame
|
||||
implements ActionListener
|
||||
{
|
||||
public AbortPanel (Getdown getdown, ResourceBundle msgs)
|
||||
{
|
||||
_getdown = getdown;
|
||||
_msgs = msgs;
|
||||
|
||||
setLayout(new VGroupLayout());
|
||||
setResizable(false);
|
||||
setTitle(get("m.abort_title"));
|
||||
|
||||
JLabel message = new JLabel(get("m.abort_confirm"));
|
||||
message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
add(message);
|
||||
add(new Spacer(5, 5));
|
||||
|
||||
JPanel row = GroupLayout.makeButtonBox(GroupLayout.CENTER);
|
||||
JButton button;
|
||||
row.add(button = new JButton(get("m.abort_ok")));
|
||||
button.setActionCommand("ok");
|
||||
button.addActionListener(this);
|
||||
row.add(button = new JButton(get("m.abort_cancel")));
|
||||
button.setActionCommand("cancel");
|
||||
button.addActionListener(this);
|
||||
getRootPane().setDefaultButton(button);
|
||||
add(row);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
public Dimension getPreferredSize ()
|
||||
{
|
||||
// this is annoyingly hardcoded, but we can't just force the width
|
||||
// or the JLabel will claim a bogus height thinking it can lay its
|
||||
// text out all on one line which will booch the whole UI's
|
||||
// preferred size
|
||||
return new Dimension(300, 200);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("ok")) {
|
||||
System.exit(0);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to look up localized messages. */
|
||||
protected String get (String key)
|
||||
{
|
||||
// if this string is tainted, we don't translate it, instead we
|
||||
// simply remove the taint character and return it to the caller
|
||||
if (MessageUtil.isTainted(key)) {
|
||||
return MessageUtil.untaint(key);
|
||||
}
|
||||
try {
|
||||
return _msgs.getString(key);
|
||||
} catch (MissingResourceException mre) {
|
||||
log.warning("Missing translation message '" + key + "'.");
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
protected Getdown _getdown;
|
||||
protected ResourceBundle _msgs;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Container;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Does something extraordinary.
|
||||
*/
|
||||
public class GetdownApp
|
||||
{
|
||||
public static void main (String[] argArray)
|
||||
{
|
||||
// maybe they specified the appdir in a system property
|
||||
int aidx = 0;
|
||||
List<String> args = Arrays.asList(argArray);
|
||||
String adarg = System.getProperty("appdir");
|
||||
// if not, check for a command line argument
|
||||
if (StringUtil.isBlank(adarg)) {
|
||||
if (args.isEmpty()) {
|
||||
System.err.println(
|
||||
"Usage: java -jar getdown.jar app_dir [app_id] [app args]");
|
||||
System.exit(-1);
|
||||
}
|
||||
adarg = args.get(aidx++);
|
||||
}
|
||||
|
||||
// look for a specific app identifier
|
||||
String appId = (aidx < args.size()) ? args.get(aidx++) : null;
|
||||
|
||||
// pass along anything after that as jvm args
|
||||
String[] appargs = (aidx < args.size())
|
||||
? args.subList(aidx, args.size()).toArray(new String[0]) : null;
|
||||
|
||||
// ensure a valid directory was supplied
|
||||
File appDir = new File(adarg);
|
||||
if (!appDir.exists() || !appDir.isDirectory()) {
|
||||
log.warning("Invalid app_dir '" + adarg + "'.");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// pipe our output into a file in the application directory
|
||||
if (System.getProperty("no_log_redir") == null) {
|
||||
File logFile = new File(appDir, "launcher.log");
|
||||
try {
|
||||
PrintStream logOut = new PrintStream(
|
||||
new BufferedOutputStream(new FileOutputStream(logFile)), true);
|
||||
System.setOut(logOut);
|
||||
System.setErr(logOut);
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Unable to redirect output to '" + logFile + "': " + ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// record a few things for posterity
|
||||
log.info("------------------ VM Info ------------------");
|
||||
log.info("-- OS Name: " + System.getProperty("os.name"));
|
||||
log.info("-- OS Arch: " + System.getProperty("os.arch"));
|
||||
log.info("-- OS Vers: " + System.getProperty("os.version"));
|
||||
log.info("-- Java Vers: " + System.getProperty("java.version"));
|
||||
log.info("-- Java Home: " + System.getProperty("java.home"));
|
||||
log.info("-- User Name: " + System.getProperty("user.name"));
|
||||
log.info("-- User Home: " + System.getProperty("user.home"));
|
||||
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.setResizable(false);
|
||||
} else {
|
||||
_frame.setTitle(title);
|
||||
_frame.getContentPane().removeAll();
|
||||
}
|
||||
_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 exit (int exitCode) {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
protected JFrame _frame;
|
||||
};
|
||||
app.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("main() failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Container;
|
||||
import java.awt.Image;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import javax.swing.JApplet;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* An applet that can be used to launch a Getdown application (when signed and given privileges).
|
||||
*/
|
||||
public class GetdownApplet extends JApplet
|
||||
implements ImageLoader
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void init ()
|
||||
{
|
||||
_config = new GetdownAppletConfig(this);
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
// Check our permissions, download getdown.txt, etc.
|
||||
_config.init();
|
||||
} catch (Exception e) {
|
||||
_errmsg = e.getMessage();
|
||||
}
|
||||
|
||||
// XXX getSigners() returns all certificates used to sign this applet which may allow
|
||||
// a third party to insert a trusted certificate. This should be replaced with
|
||||
// statically included trusted keys.
|
||||
_getdown = new Getdown(_config.appdir, null, GetdownApplet.class.getSigners(),
|
||||
_config.jvmargs, null) {
|
||||
@Override
|
||||
protected Container createContainer () {
|
||||
getContentPane().removeAll();
|
||||
return getContentPane();
|
||||
}
|
||||
@Override
|
||||
protected RotatingBackgrounds getBackground () {
|
||||
return _config.getBackgroundImages(GetdownApplet.this);
|
||||
}
|
||||
@Override
|
||||
protected void showContainer () {
|
||||
((JPanel)getContentPane()).revalidate();
|
||||
}
|
||||
@Override
|
||||
protected void disposeContainer () {
|
||||
// nothing to do as we're in an applet
|
||||
}
|
||||
@Override
|
||||
protected boolean invokeDirect () {
|
||||
return _config.invokeDirect;
|
||||
}
|
||||
@Override
|
||||
protected JApplet getApplet () {
|
||||
return GetdownApplet.this;
|
||||
}
|
||||
@Override
|
||||
protected void exit (int exitCode) {
|
||||
_app.releaseLock();
|
||||
_config.redirect();
|
||||
}
|
||||
};
|
||||
|
||||
// set up our user interface
|
||||
_config.config(_getdown);
|
||||
_getdown.preInit();
|
||||
|
||||
} catch (Exception e) {
|
||||
// assume that if we already encountered an error, that is the root cause that we want
|
||||
// to report back to the user
|
||||
if (_errmsg == null) {
|
||||
_errmsg = e.getMessage();
|
||||
}
|
||||
log.warning("init() failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// implemented from ImageLoader
|
||||
public Image loadImage (String path)
|
||||
{
|
||||
try {
|
||||
return getImage(new URL(getDocumentBase(), path));
|
||||
} catch (MalformedURLException e) {
|
||||
log.warning("Failed to load background image", "path", path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void start ()
|
||||
{
|
||||
if (_errmsg != null) {
|
||||
_getdown.fail(_errmsg);
|
||||
} else {
|
||||
try {
|
||||
_getdown.start();
|
||||
} catch (Exception e) {
|
||||
log.warning("start() failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void stop ()
|
||||
{
|
||||
// Interrupt the getdown thread to tell it to kill its current downloading or verifying
|
||||
// before launching
|
||||
_getdown.interrupt();
|
||||
// release the lock if the applet window is closed or replaced
|
||||
_getdown._app.releaseLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the specified file and writes the supplied contents to it.
|
||||
*/
|
||||
protected boolean writeToFile (File tofile, String contents)
|
||||
{
|
||||
try {
|
||||
PrintStream out = new PrintStream(new FileOutputStream(tofile));
|
||||
out.println(contents);
|
||||
out.close();
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to create '" + tofile + "'.", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The Getdown configuration as pulled from the applet params */
|
||||
protected GetdownAppletConfig _config;
|
||||
|
||||
/** Handles all the actual getting down. */
|
||||
protected Getdown _getdown;
|
||||
|
||||
/** An error encountered during initialization. */
|
||||
protected String _errmsg;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.swing.JApplet;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.launcher.ImageLoader;
|
||||
import com.threerings.getdown.launcher.RotatingBackgrounds;
|
||||
import com.threerings.getdown.util.ConfigUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
public class GetdownAppletConfig
|
||||
{
|
||||
public static final String APPBASE = "appbase";
|
||||
|
||||
public static final String APPNAME = "appname";
|
||||
|
||||
public static final String BGIMAGE = "bgimage";
|
||||
|
||||
public static final String ERRORBGIMAGE = "errorbgimage";
|
||||
|
||||
public static final String PROPERTIES = "properties";
|
||||
|
||||
public static final String PARAM_DELIMITER = ".";
|
||||
|
||||
public static final String APP_PROPERTIES = "app_properties";
|
||||
|
||||
public static final String DIRECT = "direct";
|
||||
|
||||
public static final String REDIRECT_ON_FINISH = "redirect_on_finish";
|
||||
|
||||
public static final String REDIRECT_ON_FINISH_TARGET = "redirect_on_finish_target";
|
||||
|
||||
public String appbase;
|
||||
|
||||
public String appname;
|
||||
|
||||
/** The list of background images and their display time */
|
||||
public String imgpath;
|
||||
|
||||
/** The error background image */
|
||||
public String errorimgpath;
|
||||
|
||||
public Properties properties = new Properties();
|
||||
|
||||
public File appdir;
|
||||
|
||||
public String[] jvmargs;
|
||||
|
||||
/** An optional URL to which the applet should redirect when done. */
|
||||
public URL redirectUrl;
|
||||
|
||||
public String redirectTarget;
|
||||
|
||||
public String installerFileContents;
|
||||
|
||||
/**
|
||||
* Indicates whether the downloaded app should be launched in the parent applet (true) or as a
|
||||
* separate java process (false).
|
||||
*/
|
||||
public boolean invokeDirect;
|
||||
|
||||
/** Optional default bounds for the status panel. */
|
||||
public Rectangle statusBounds;
|
||||
|
||||
public Color statusColor;
|
||||
|
||||
public GetdownAppletConfig (JApplet applet)
|
||||
{
|
||||
this(applet, null);
|
||||
}
|
||||
|
||||
public GetdownAppletConfig (JApplet applet, String paramPrefix)
|
||||
{
|
||||
_applet = applet;
|
||||
_prefix = paramPrefix;
|
||||
|
||||
appbase = getParameter(APPBASE, "");
|
||||
appname = getParameter(APPNAME, "");
|
||||
log.info("App Base: " + appbase);
|
||||
log.info("App Name: " + appname);
|
||||
|
||||
imgpath = getParameter(BGIMAGE);
|
||||
errorimgpath = getParameter(ERRORBGIMAGE);
|
||||
|
||||
String props = getParameter(PROPERTIES);
|
||||
if (props != null) {
|
||||
String[] proparray = props.split(" ");
|
||||
for (String property : proparray) {
|
||||
String key = property.substring(property.indexOf("-D") + 2, property.indexOf("="));
|
||||
String value = property.substring(property.indexOf("=") + 1);
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
String root;
|
||||
if (RunAnywhere.isWindows()) {
|
||||
root = "Application Data";
|
||||
String verStr = System.getProperty("os.version");
|
||||
try {
|
||||
if (Float.parseFloat(verStr) >= 6.0f) {
|
||||
// Vista makes us write it here.... Yay.
|
||||
root = "AppData" + File.separator + "LocalLow";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Couldn't parse OS version", "vers", verStr, "error", e);
|
||||
}
|
||||
} else if (RunAnywhere.isMacOS()) {
|
||||
root = "Library" + File.separator + "Application Support";
|
||||
} else /* isLinux() or something wacky */{
|
||||
root = ".getdown";
|
||||
}
|
||||
appdir = new File(System.getProperty("user.home") + File.separator + root + File.separator
|
||||
+ appname);
|
||||
|
||||
installerFileContents = getParameter("installer");
|
||||
|
||||
// Pull out -D properties to pass through to the launched vm if they exist
|
||||
String params = getParameter(APP_PROPERTIES);
|
||||
if (params == null) {
|
||||
jvmargs = new String[0];
|
||||
} else {
|
||||
jvmargs = params.split(",");
|
||||
for (int ii = 0; ii < jvmargs.length; ii++) {
|
||||
jvmargs[ii] = "-D" + jvmargs[ii];
|
||||
}
|
||||
}
|
||||
|
||||
String direct = getParameter(DIRECT, "false");
|
||||
invokeDirect = Boolean.valueOf(direct);
|
||||
|
||||
String redirectURL = getParameter(REDIRECT_ON_FINISH);
|
||||
if (redirectURL != null) {
|
||||
try {
|
||||
redirectUrl = new URL(redirectURL);
|
||||
redirectTarget = getParameter(REDIRECT_ON_FINISH_TARGET);
|
||||
} catch (MalformedURLException e) {
|
||||
log.warning("URL in " + REDIRECT_ON_FINISH + " param is malformed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to configure the status panel from applet parameters in case something
|
||||
// goes horribly wrong before we get a chance to read getdown.txt (like when the user
|
||||
// rejects write permission for the applet)
|
||||
statusBounds = Application.parseRect(getParameter("ui.status"));
|
||||
statusColor = Application.parseColor(getParameter("ui.status_text"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does all the fiddly initialization of Getdown and throws an exception if something goes
|
||||
* horribly wrong.
|
||||
*/
|
||||
public void init () throws Exception
|
||||
{
|
||||
securityCheck();
|
||||
setSystemProperties();
|
||||
ensureAppdirExists();
|
||||
createFiles();
|
||||
|
||||
// record a few things for posterity
|
||||
log.info("------------------ VM Info ------------------");
|
||||
log.info("-- OS Name: " + System.getProperty("os.name"));
|
||||
log.info("-- OS Arch: " + System.getProperty("os.arch"));
|
||||
log.info("-- OS Vers: " + System.getProperty("os.version"));
|
||||
log.info("-- Java Vers: " + System.getProperty("java.version"));
|
||||
log.info("-- Java Home: " + System.getProperty("java.home"));
|
||||
log.info("-- User Name: " + System.getProperty("user.name"));
|
||||
log.info("-- User Home: " + System.getProperty("user.home"));
|
||||
log.info("-- Cur dir: " + System.getProperty("user.dir"));
|
||||
log.info("---------------------------------------------");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets getdown status panel size and text color from applet params.
|
||||
*/
|
||||
public void config (Getdown getdown)
|
||||
{
|
||||
if (statusBounds != null) {
|
||||
getdown._ifc.status = statusBounds;
|
||||
}
|
||||
if (statusColor != null) {
|
||||
getdown._ifc.statusText = statusColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the named parameter. If the param is not set, this will return null.
|
||||
*/
|
||||
public String getParameter (String param)
|
||||
{
|
||||
if (_prefix != null) {
|
||||
return _applet.getParameter(_prefix + PARAM_DELIMITER + param);
|
||||
}
|
||||
return _applet.getParameter(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of an optional Applet parameter. If the param is not set, the default value is
|
||||
* returned.
|
||||
*/
|
||||
public String getParameter (String param, String defaultValue)
|
||||
{
|
||||
String value = getParameter(param);
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the Applet context to redirect the browser to a URL (intended for use when the applet
|
||||
* is done downloading). If the redirect_on_finish parameter was not set, this does nothing.
|
||||
*/
|
||||
public void redirect ()
|
||||
{
|
||||
if (redirectUrl != null) {
|
||||
if (redirectTarget == null) {
|
||||
_applet.getAppletContext().showDocument(redirectUrl);
|
||||
} else {
|
||||
_applet.getAppletContext().showDocument(redirectUrl, redirectTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation background images and error image.The given image loader will be used if
|
||||
* the images have not been loaded yet. The locations of the images are pulled from the Applet
|
||||
* parameters.
|
||||
*/
|
||||
public RotatingBackgrounds getBackgroundImages (ImageLoader loader)
|
||||
{
|
||||
if (bgimages == null) {
|
||||
// Load background images
|
||||
bgimages = getBackgroundImages(imgpath, errorimgpath, loader);
|
||||
}
|
||||
return bgimages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation background images and error image.The given image loader will be used if
|
||||
* the images have not been loaded yet.
|
||||
*/
|
||||
public static RotatingBackgrounds getBackgroundImages (String imageParam,
|
||||
String errorImagePath, ImageLoader loader)
|
||||
{
|
||||
// Parse the image parameter and load the background images
|
||||
RotatingBackgrounds images;
|
||||
if (imageParam == null) {
|
||||
images = new RotatingBackgrounds();
|
||||
} else if (imageParam.indexOf(",") > -1) {
|
||||
images = new RotatingBackgrounds(imageParam.split(","), errorImagePath, loader);
|
||||
} else {
|
||||
images = new RotatingBackgrounds(loader.loadImage(imageParam));
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks whether the user has accepted our signed
|
||||
*/
|
||||
protected void securityCheck () throws Exception
|
||||
{
|
||||
// getdown requires full read/write permissions to the system; if we don't have this, then
|
||||
// we need to not do anything unsafe, and display a message to the user telling them they
|
||||
// need to (groan) close out of the web browser entirely and re-launch the browser, go to
|
||||
// our site, and accept the certificate
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
try {
|
||||
sm.checkPropertiesAccess();
|
||||
sm.checkWrite("getdown");
|
||||
} catch (SecurityException se) {
|
||||
log.warning("Signed applet rejected by user", "se", se);
|
||||
throw new Exception("m.insufficient_permissions_error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the contents of the <code>properties</code> into the System properties.
|
||||
*/
|
||||
protected void setSystemProperties ()
|
||||
{
|
||||
System.getProperties().putAll(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sire that the appdir directory exists, creating it if necessary. Throws an exception if
|
||||
* the directory does not exist and cannot be created.
|
||||
*/
|
||||
protected void ensureAppdirExists () throws Exception
|
||||
{
|
||||
// if our application directory does not exist, auto-create it
|
||||
if (!appdir.exists() || !appdir.isDirectory()) {
|
||||
if (!appdir.mkdirs()) {
|
||||
throw new Exception("m.create_appdir_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the installer.txt and getdown.txt files to the local file system as needed.
|
||||
*/
|
||||
protected void createFiles () throws Exception
|
||||
{
|
||||
// if an installer.txt file is desired, create that
|
||||
if (!StringUtil.isBlank(installerFileContents)) {
|
||||
File infile = new File(appdir, "installer.txt");
|
||||
if (!infile.exists()) {
|
||||
writeToFile(infile, installerFileContents);
|
||||
}
|
||||
}
|
||||
|
||||
// if our getdown.txt file does not exist, or it is corrupt, auto-/recreate it
|
||||
File gdfile = new File(appdir, "getdown.txt");
|
||||
boolean createGetdown = !gdfile.exists();
|
||||
if (!createGetdown) {
|
||||
try {
|
||||
Map<String,Object> cdata = ConfigUtil.parseConfig(gdfile, false);
|
||||
String oappbase = StringUtil.trim((String)cdata.get(APPBASE));
|
||||
createGetdown = (appbase != null && !appbase.trim().equals(oappbase));
|
||||
if (createGetdown) {
|
||||
log.warning("Recreating getdown.txt due to appbase mismatch",
|
||||
"nappbase", appbase, "oappbase", oappbase);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure checking validity of getdown.txt, forcing recreate.",
|
||||
"error", e);
|
||||
createGetdown = true;
|
||||
}
|
||||
}
|
||||
if (createGetdown) {
|
||||
if (StringUtil.isBlank(appbase)) {
|
||||
throw new Exception("m.missing_appbase");
|
||||
}
|
||||
if (!writeToFile(gdfile, "appbase = " + appbase)) {
|
||||
throw new Exception("m.create_getdown_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the specified file and writes the supplied contents to it.
|
||||
*/
|
||||
protected boolean writeToFile (File tofile, String contents)
|
||||
{
|
||||
try {
|
||||
PrintStream out = new PrintStream(new FileOutputStream(tofile));
|
||||
out.println(contents);
|
||||
out.close();
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to create '" + tofile + "'.", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** A reference to the Applet in which Getdown is running */
|
||||
protected JApplet _applet;
|
||||
|
||||
/** An optional prefix to prepend when looking for Getdown Applet params */
|
||||
protected String _prefix;
|
||||
|
||||
/** The background images displayed on the status panel as Getdown is getting down. */
|
||||
protected RotatingBackgrounds bgimages;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Image;
|
||||
|
||||
public interface ImageLoader
|
||||
{
|
||||
|
||||
public Image loadImage (String path);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown when it's detected that multiple instances of the same getdown installer are running.
|
||||
*/
|
||||
public class MultipleGetdownRunning extends IOException
|
||||
{
|
||||
public MultipleGetdownRunning ()
|
||||
{
|
||||
super("m.another_getdown_running");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import com.samskivert.swing.GroupLayout;
|
||||
import com.samskivert.swing.Spacer;
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
import com.samskivert.text.MessageUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Displays an interface with which the user can configure their proxy
|
||||
* settings.
|
||||
*/
|
||||
public class ProxyPanel extends JPanel
|
||||
implements ActionListener
|
||||
{
|
||||
public ProxyPanel (Getdown getdown, ResourceBundle msgs)
|
||||
{
|
||||
_getdown = getdown;
|
||||
_msgs = msgs;
|
||||
|
||||
setLayout(new VGroupLayout());
|
||||
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
add(new JLabel(get("m.configure_proxy")));
|
||||
add(new Spacer(5, 5));
|
||||
|
||||
JPanel row = new JPanel(new BorderLayout(5, 5));
|
||||
row.add(new JLabel(get("m.proxy_host")), BorderLayout.WEST);
|
||||
row.add(_host = new SaneTextField());
|
||||
add(row);
|
||||
|
||||
row = new JPanel(new BorderLayout(5, 5));
|
||||
row.add(new JLabel(get("m.proxy_port")), BorderLayout.WEST);
|
||||
row.add(_port = new SaneTextField());
|
||||
add(row);
|
||||
|
||||
add(new Spacer(5, 5));
|
||||
add(new JLabel(get("m.proxy_extra")));
|
||||
|
||||
row = GroupLayout.makeButtonBox(GroupLayout.CENTER);
|
||||
JButton button;
|
||||
row.add(button = new JButton(get("m.proxy_ok")));
|
||||
button.setActionCommand("ok");
|
||||
button.addActionListener(this);
|
||||
row.add(button = new JButton(get("m.proxy_cancel")));
|
||||
button.setActionCommand("cancel");
|
||||
button.addActionListener(this);
|
||||
add(row);
|
||||
|
||||
// set up any existing proxy defaults
|
||||
String host = System.getProperty("http.proxyHost");
|
||||
if (host != null) {
|
||||
_host.setText(host);
|
||||
}
|
||||
String port = System.getProperty("http.proxyPort");
|
||||
if (port != null) {
|
||||
_port.setText(port);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_host.requestFocusInWindow();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
public Dimension getPreferredSize ()
|
||||
{
|
||||
// this is annoyingly hardcoded, but we can't just force the width
|
||||
// or the JLabel will claim a bogus height thinking it can lay its
|
||||
// text out all on one line which will booch the whole UI's
|
||||
// preferred size
|
||||
return new Dimension(500, 350);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("ok")) {
|
||||
// communicate this info back to getdown
|
||||
_getdown.configureProxy(_host.getText(), _port.getText());
|
||||
|
||||
} else {
|
||||
// they canceled, we're outta here
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to look up localized messages. */
|
||||
protected String get (String key)
|
||||
{
|
||||
// if this string is tainted, we don't translate it, instead we
|
||||
// simply remove the taint character and return it to the caller
|
||||
if (MessageUtil.isTainted(key)) {
|
||||
return MessageUtil.untaint(key);
|
||||
}
|
||||
try {
|
||||
return _msgs.getString(key);
|
||||
} catch (MissingResourceException mre) {
|
||||
log.warning("Missing translation message '" + key + "'.");
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SaneTextField extends JTextField
|
||||
{
|
||||
@Override
|
||||
public Dimension getPreferredSize () {
|
||||
Dimension d = super.getPreferredSize();
|
||||
d.width = Math.max(d.width, 150);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
protected Getdown _getdown;
|
||||
protected ResourceBundle _msgs;
|
||||
|
||||
protected JTextField _host;
|
||||
protected JTextField _port;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Image;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
public class RotatingBackgrounds
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a placeholder if there are no images. Just returns null from getImage every time.
|
||||
*/
|
||||
public RotatingBackgrounds ()
|
||||
{
|
||||
makeEmpty();
|
||||
}
|
||||
|
||||
/** Creates a single image background. */
|
||||
public RotatingBackgrounds (Image background)
|
||||
{
|
||||
percentages = new int[] { 0 };
|
||||
minDisplayTime = new int[] { 0 };
|
||||
images = new Image[] { background };
|
||||
errorImage = images[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sequence of images to be rotated through from <code>backgrounds</code>.
|
||||
*
|
||||
* Each String in backgrounds should be the path to the image, a semicolon, and the minimum
|
||||
* amount of time to display the image in seconds. Each image will be active for an equal
|
||||
* percentage of the download process, unless one hasn't been active for its minimum display
|
||||
* time when the next should be shown. In that case, it's left up until its been there for its
|
||||
* minimum display time and then the next one gets to come up.
|
||||
*/
|
||||
public RotatingBackgrounds (String[] backgrounds, String errorBackground, ImageLoader loader)
|
||||
{
|
||||
percentages = new int[backgrounds.length];
|
||||
minDisplayTime = new int[backgrounds.length];
|
||||
images = new Image[backgrounds.length];
|
||||
for (int ii = 0; ii < backgrounds.length; ii++) {
|
||||
String[] pieces = backgrounds[ii].split(";");
|
||||
if (pieces.length != 2) {
|
||||
log.warning("Unable to parse background image '" + backgrounds[ii] + "'");
|
||||
makeEmpty();
|
||||
return;
|
||||
}
|
||||
images[ii] = loader.loadImage(pieces[0]);
|
||||
try {
|
||||
minDisplayTime[ii] = Integer.parseInt(pieces[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warning("Unable to parse background image display time '" +
|
||||
backgrounds[ii] + "'");
|
||||
makeEmpty();
|
||||
return;
|
||||
}
|
||||
percentages[ii] = (int)((ii/(float)backgrounds.length) * 100);
|
||||
}
|
||||
if (errorBackground == null) {
|
||||
errorImage = images[0];
|
||||
} else {
|
||||
errorImage = loader.loadImage(errorBackground);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the image to display at the given progress or null if there aren't any.
|
||||
*/
|
||||
public Image getImage (int progress)
|
||||
{
|
||||
if (images.length == 0) {
|
||||
return null;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
if (current != images.length - 1
|
||||
&& (current == -1 || (progress >= percentages[current + 1] &&
|
||||
(now - currentDisplayStart) / 1000 > minDisplayTime[current]))) {
|
||||
current++;
|
||||
currentDisplayStart = now;
|
||||
}
|
||||
return images[current];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image to display if an error has caused getdown to fail.
|
||||
*/
|
||||
public Image getErrorImage ()
|
||||
{
|
||||
return errorImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of images in this RotatingBackgrounds
|
||||
*/
|
||||
public int getNumImages() {
|
||||
return images.length;
|
||||
}
|
||||
|
||||
protected void makeEmpty ()
|
||||
{
|
||||
percentages = new int[] {};
|
||||
minDisplayTime = new int[] {};
|
||||
images = new Image[] {};
|
||||
}
|
||||
|
||||
/** Time at which the currently displayed image was first displayed in millis. */
|
||||
protected long currentDisplayStart;
|
||||
|
||||
/** The index of the currently displayed image or -1 if we haven't displayed any. */
|
||||
protected int current = -1;
|
||||
|
||||
protected Image[] images;
|
||||
|
||||
/** The image to display if getdown has failed due to an error. */
|
||||
protected Image errorImage;
|
||||
|
||||
/** Percentage at which each image should be displayed. */
|
||||
protected int[] percentages;
|
||||
|
||||
/** Time to show each image in seconds. */
|
||||
protected int[] minDisplayTime;
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.launcher;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import com.samskivert.swing.Label;
|
||||
import com.samskivert.swing.LabelStyleConstants;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.text.MessageUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Throttle;
|
||||
|
||||
import com.threerings.getdown.data.Application.UpdateInterface;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Displays download and patching status.
|
||||
*/
|
||||
public class StatusPanel extends JComponent
|
||||
{
|
||||
public StatusPanel (ResourceBundle msgs)
|
||||
{
|
||||
_msgs = msgs;
|
||||
}
|
||||
|
||||
public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg)
|
||||
{
|
||||
_ifc = ifc;
|
||||
_bg = bg;
|
||||
Image img = _bg.getImage(_progress);
|
||||
if (img == null) {
|
||||
Rectangle bounds = ifc.progress.union(ifc.status);
|
||||
bounds.grow(5, 5);
|
||||
_psize = bounds.getSize();
|
||||
} else {
|
||||
_psize = new Dimension(img.getWidth(null), img.getHeight(null));
|
||||
}
|
||||
_barimg = barimg;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the progress display to the specified percentage.
|
||||
*/
|
||||
public void setProgress (int percent, long remaining)
|
||||
{
|
||||
_progress = percent;
|
||||
String msg = "m.complete";
|
||||
String remstr = "";
|
||||
if (remaining > 1) {
|
||||
// skip this estimate if it's been less than a second since
|
||||
// our last one came in
|
||||
if (!_rthrottle.throttleOp()) {
|
||||
_remain[_ridx++%_remain.length] = remaining;
|
||||
}
|
||||
|
||||
// smooth the remaining time by taking the trailing average of
|
||||
// the last four values
|
||||
remaining = 0;
|
||||
int values = Math.min(_ridx, _remain.length);
|
||||
for (int ii = 0; ii < values; ii++) {
|
||||
remaining += _remain[ii];
|
||||
}
|
||||
remaining /= values;
|
||||
|
||||
// now compute our display value
|
||||
msg = "m.complete_remain";
|
||||
int minutes = (int)(remaining / 60);
|
||||
int seconds = (int)(remaining % 60);
|
||||
remstr = minutes + ":";
|
||||
if (seconds < 10) {
|
||||
remstr += "0";
|
||||
}
|
||||
remstr += seconds;
|
||||
}
|
||||
msg = get(msg);
|
||||
String label = MessageFormat.format(msg, new Object[] {
|
||||
new Integer(percent), remstr });
|
||||
_newplab = new Label(label, _ifc.progressText, _font);
|
||||
if (_ifc.textShadow != null) {
|
||||
_newplab.setAlternateColor(_ifc.textShadow);
|
||||
_newplab.setStyle(LabelStyleConstants.SHADOW);
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the specified status string.
|
||||
*/
|
||||
public void setStatus (String status, boolean displayError)
|
||||
{
|
||||
_displayError = displayError;
|
||||
status = xlate(status);
|
||||
_newlab = new Label(status, _ifc.statusText, _font);
|
||||
int width = getWidth() - _ifc.status.x*2;
|
||||
width = width > 0 ? Math.min(_ifc.status.width, width) : _ifc.status.width;
|
||||
_newlab.setTargetWidth(width);
|
||||
if (_ifc.textShadow != null) {
|
||||
_newlab.setAlternateColor(_ifc.textShadow);
|
||||
_newlab.setStyle(LabelStyleConstants.SHADOW);
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
public void paintComponent (Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
Graphics2D gfx = (Graphics2D)g;
|
||||
|
||||
Image img;
|
||||
if (_displayError) {
|
||||
img = _bg.getErrorImage();
|
||||
} else {
|
||||
img = _bg.getImage(_progress);
|
||||
}
|
||||
if (img != null) {
|
||||
gfx.drawImage(img, 0, 0, this);
|
||||
} else {
|
||||
gfx.setColor(getBackground());
|
||||
gfx.fillRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
Object oalias = SwingUtil.activateAntiAliasing(gfx);
|
||||
|
||||
// if we have new labels; lay them out
|
||||
if (_newlab != null) {
|
||||
_newlab.layout(gfx);
|
||||
_label = _newlab;
|
||||
_newlab = null;
|
||||
}
|
||||
if (_newplab != null) {
|
||||
_newplab.layout(gfx);
|
||||
_plabel = _newplab;
|
||||
_newplab = null;
|
||||
}
|
||||
|
||||
if (_barimg != null) {
|
||||
gfx.setClip(_ifc.progress.x, _ifc.progress.y,
|
||||
_progress * _ifc.progress.width / 100,
|
||||
_ifc.progress.height);
|
||||
gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null);
|
||||
gfx.setClip(null);
|
||||
} else {
|
||||
gfx.setColor(_ifc.progressBar);
|
||||
gfx.fillRect(_ifc.progress.x, _ifc.progress.y,
|
||||
_progress * _ifc.progress.width / 100,
|
||||
_ifc.progress.height);
|
||||
}
|
||||
|
||||
if (_plabel != null) {
|
||||
int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2;
|
||||
int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2;
|
||||
_plabel.render(gfx, _ifc.progress.x + xmarg,
|
||||
_ifc.progress.y + ymarg);
|
||||
}
|
||||
|
||||
if (_label != null) {
|
||||
// if the status region is higher than the progress region, we
|
||||
// want to align the label with the bottom of its region
|
||||
// rather than the top
|
||||
int ly;
|
||||
if (_ifc.status.y > _ifc.progress.y) {
|
||||
ly = _ifc.status.y;
|
||||
} else {
|
||||
ly = _ifc.status.y + (_ifc.status.height -
|
||||
_label.getSize().height);
|
||||
}
|
||||
_label.render(gfx, _ifc.status.x, ly);
|
||||
}
|
||||
|
||||
SwingUtil.restoreAntiAliasing(gfx, oalias);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
public Dimension getPreferredSize ()
|
||||
{
|
||||
return _psize;
|
||||
}
|
||||
|
||||
/** Used by {@link #setStatus}. */
|
||||
protected String xlate (String compoundKey)
|
||||
{
|
||||
// to be more efficient about creating unnecessary objects, we
|
||||
// do some checking before splitting
|
||||
int tidx = compoundKey.indexOf('|');
|
||||
if (tidx == -1) {
|
||||
return get(compoundKey);
|
||||
|
||||
} else {
|
||||
String key = compoundKey.substring(0, tidx);
|
||||
String argstr = compoundKey.substring(tidx+1);
|
||||
String[] args = StringUtil.split(argstr, "|");
|
||||
// unescape and translate the arguments
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
// if the argument is tainted, do no further translation
|
||||
// (it might contain |s or other fun stuff)
|
||||
if (MessageUtil.isTainted(args[i])) {
|
||||
args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i]));
|
||||
} else {
|
||||
args[i] = xlate(MessageUtil.unescape(args[i]));
|
||||
}
|
||||
}
|
||||
return get(key, args);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used by {@link #setStatus}. */
|
||||
protected String get (String key, Object[] args)
|
||||
{
|
||||
String msg = get(key);
|
||||
return (msg != null) ?
|
||||
MessageFormat.format(MessageUtil.escape(msg), args)
|
||||
: (key + StringUtil.toString(args));
|
||||
}
|
||||
|
||||
/** Used by {@link #setStatus}, and {@link #setProgress}. */
|
||||
protected String get (String key)
|
||||
{
|
||||
// if we have no _msgs that means we're probably recovering from a
|
||||
// failure to load the translation messages in the first place, so
|
||||
// just give them their key back because it's probably an english
|
||||
// string; whee!
|
||||
if (_msgs == null) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// if this string is tainted, we don't translate it, instead we
|
||||
// simply remove the taint character and return it to the caller
|
||||
if (MessageUtil.isTainted(key)) {
|
||||
return MessageUtil.untaint(key);
|
||||
}
|
||||
try {
|
||||
return _msgs.getString(key);
|
||||
} catch (MissingResourceException mre) {
|
||||
log.warning("Missing translation message '" + key + "'.");
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
protected Image _barimg;
|
||||
protected RotatingBackgrounds _bg;
|
||||
protected Dimension _psize;
|
||||
|
||||
protected ResourceBundle _msgs;
|
||||
|
||||
protected int _progress = 0;
|
||||
protected boolean _displayError;
|
||||
protected Label _label, _newlab;
|
||||
protected Label _plabel, _newplab;
|
||||
|
||||
protected UpdateInterface _ifc;
|
||||
|
||||
protected long[] _remain = new long[4];
|
||||
protected int _ridx;
|
||||
protected Throttle _rthrottle = new Throttle(1, 1000L);
|
||||
|
||||
protected static final Font _font = new Font("SansSerif", Font.BOLD, 12);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# Getdown translation messages
|
||||
|
||||
m.abort_title = Abort installation?
|
||||
m.abort_confirm = <html>Are you sure you want to stop installation? \
|
||||
You can resume at a later time by running the application again.</html>
|
||||
m.abort_ok = Quit
|
||||
m.abort_cancel = Continue installation
|
||||
|
||||
m.detecting_proxy = Trying to auto-detect proxy settings...
|
||||
|
||||
m.configure_proxy = <html>We were unable to connect to our servers to \
|
||||
download the game data. \
|
||||
<ul><li> If Windows Firewall or Norton Internet Security was instructed \
|
||||
to block <code>javaw.exe</code> we cannot download the game. You will need \
|
||||
to allow <code>javaw.exe</code> to access the internet. You can try \
|
||||
running the game again, but you may need to clear javaw.exe from your \
|
||||
firewall configuration ( Start -> Control Panel -> Windows Firewall ).</ul> \
|
||||
<p> Your computer may access the Internet through a proxy and we were \
|
||||
unable to automatically detect your proxy settings. If you know your \
|
||||
proxy settings, you can enter them below.</html>
|
||||
|
||||
m.proxy_extra = <html>If you are sure that you don't use a proxy then \
|
||||
perhaps there is a temporary Internet outage that is preventing us from \
|
||||
communicating with the servers. In this case, you can cancel and try \
|
||||
installing again later.</html>
|
||||
|
||||
m.proxy_host = Proxy IP
|
||||
m.proxy_port = Proxy port
|
||||
m.proxy_ok = OK
|
||||
m.proxy_cancel = Cancel
|
||||
|
||||
m.downloading_java = Downloading Java Virtual Machine...
|
||||
m.unpacking_java = Unpacking Java Virtual Machine...
|
||||
|
||||
m.resolving = Resolving downloads...
|
||||
m.downloading = Downloading data...
|
||||
m.failure = Download failed: {0}
|
||||
|
||||
m.checking = Checking for update...
|
||||
m.validating = Validating...
|
||||
m.patching = Patching...
|
||||
m.launching = Launching...
|
||||
|
||||
m.complete = {0}% complete
|
||||
m.complete_remain = {0}% complete {1} remaining
|
||||
|
||||
m.updating_metadata = Downloading control files...
|
||||
|
||||
m.init_failed = Our configuration file is missing or corrupt. Attempting \
|
||||
to download a new copy...
|
||||
|
||||
m.java_download_failed = We were unable to automatically download the \
|
||||
necessary verson of Java for your computer.\n\n\
|
||||
Please go to www.java.com and download the latest version of \
|
||||
Java, then try running the application again.
|
||||
|
||||
m.java_unpack_failed = We were unable to unpack an updated version of \
|
||||
Java. Please make sure you have at least 100 MB of free space on your \
|
||||
harddrive and try running the application again.\n\n\
|
||||
If that does not solve the problem, go to www.java.com and download and \
|
||||
install the latest version of Java and try again.
|
||||
|
||||
m.unable_to_repair = We were unable to download the necessary files after \
|
||||
five attempts. You can try running the application again, but if it \
|
||||
fails you may need to uninstall and reinstall.
|
||||
|
||||
m.unknown_error = The application has failed to launch due to some strange \
|
||||
error from which we could not recover. Please visit\n{0} for information on \
|
||||
how to recover.
|
||||
m.init_error = The application has failed to launch due to the following \
|
||||
error:\n{0}\n\nPlease visit\n{1} for \
|
||||
information on how to handle such problems.
|
||||
|
||||
m.readonly_error = The directory in which this application is installed: \
|
||||
\n{0}\nis read-only. Please install the applicaton into a directory where \
|
||||
you have write access.
|
||||
|
||||
m.missing_resource = The application has failed to launch due to a missing \
|
||||
resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \
|
||||
problems.
|
||||
|
||||
m.insufficient_permissions_error = You did not accept this application's \
|
||||
digital signature. If you want to run the application, you will need to accept \
|
||||
its digital signature.\n\nTo do so, you will need to quit your web browser, \
|
||||
restart it, and return to this web page to relaunch the application. When the \
|
||||
security dialog is shown, click the button to accept the digital signature \
|
||||
and grant this application the privileges it needs to run.
|
||||
|
||||
m.corrupt_digest_signature_error = We couldn't verify the application's digital \
|
||||
signature.\nPlease check that you are launching the application from\nthe \
|
||||
correct website.
|
||||
|
||||
m.default_install_error = the support section of the website
|
||||
|
||||
m.another_getdown_running = Multiple instances of this application's \
|
||||
installer are running. This one will stop and let another complete.
|
||||
|
||||
m.applet_stopped = Getdown's applet was told to stop working.
|
||||
|
||||
# application/digest errors
|
||||
m.missing_appbase = The configuration file is missing the 'appbase'.
|
||||
m.invalid_version = The configuration file specifies an invalid version.
|
||||
m.invalid_appbase = The configuration file specifies an invalid 'appbase'.
|
||||
m.missing_class = The configuration file is missing the application class.
|
||||
m.missing_code = The configuration file specifies no code resources.
|
||||
m.invalid_digest_file = The digest file is invalid.
|
||||
@@ -0,0 +1,99 @@
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# Getdown translation messages
|
||||
|
||||
m.abort_title = Installation abbrechen?
|
||||
m.abort_confirm = <html>Installation wirklich abbrechen? \
|
||||
Die Installation kann sp\u00e4ter fortgesetzt werden.</html>
|
||||
m.abort_ok = Beenden
|
||||
m.abort_cancel = Installation fortsetzen
|
||||
|
||||
m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln...
|
||||
|
||||
m.configure_proxy = <html>Es war nicht m\u00f6glich, eine Verbindung zu unserem \
|
||||
Server herzustellen, um die Spieldaten herunterzuladen. \
|
||||
<ul><li> Falls eine Firewall <code>javaw.exe</code> blockiert, kann das \
|
||||
Spiel nicht heruntergeladen werden. Bitte <code>javaw.exe</code> aus \
|
||||
der Firewall-Konfiguration entfernen \
|
||||
(Start -> Systemsteuerung -> Windows Firewall). </ul> \
|
||||
<p> Eventuell ist der Computer durch einen Proxy mit dem Internet verbunden. \
|
||||
Es war uns nicht m\u00f6glich, die Proxy-Einstellungen \
|
||||
automatisch zu ermitteln. Falls bekannt, k\u00f6nnen diese \
|
||||
unten eingetragen werden.</html>
|
||||
|
||||
m.proxy_extra = <html>Falls kein Proxy verwendet wird, liegt \
|
||||
m\u00f6glicherweise eine St\u00f6rung in der Internetverbindung \
|
||||
zu unserem Server vor. In diesem Fall bitte sp\u00e4ter noch einmal \
|
||||
versuchen.</html>
|
||||
|
||||
m.proxy_host = Proxy IP
|
||||
m.proxy_port = Proxy port
|
||||
m.proxy_ok = OK
|
||||
m.proxy_cancel = Abbrechen
|
||||
|
||||
m.downloading_java = Lade Java Virtual Machine herunter...
|
||||
m.unpacking_java = Entpacke Java Virtual Machine...
|
||||
|
||||
m.resolving = Bereite Download vor...
|
||||
m.downloading = Lade Daten herunter...
|
||||
m.failure = Download fehlgeschlagen: {0}
|
||||
|
||||
m.checking = Suche nach Updates...
|
||||
m.validating = Validiere Download...
|
||||
m.patching = Patche...
|
||||
m.launching = Starte...
|
||||
|
||||
m.complete = {0}% abgeschlossen
|
||||
m.complete_remain = {0}% abgeschlossen {1} \u00fcbrig
|
||||
|
||||
m.updating_metadata = Lade Steuerungsdateien herunter...
|
||||
|
||||
m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \
|
||||
Versuche, eine neue Kopie herunterzuladen...
|
||||
|
||||
m.java_download_failed = Wir konnten die notwendige Javaversion nicht automatisch hertunerladen. \n\n\
|
||||
Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die Anwendung erneut starten.
|
||||
|
||||
m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der Festplatte sind und versuche dann, die Anwendung erneut zu starten.\n\n\
|
||||
Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle Javaversion herunterladen und installieren und dann erneut versuchen.
|
||||
|
||||
m.unable_to_repair = Wir konnten die notwendigen Dateien nach \
|
||||
5 Versuchen nicht herunterladen. Sie k\u00f6nnen versuchen, die Anwendung \
|
||||
erneut zu starten, aber wenn dies erneut fehlschl\u00e4gt, m\u00fcssen Sie \
|
||||
die Anwendung deinstallieren und erneut installieren.
|
||||
|
||||
m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \
|
||||
nicht gestartet werden. Bitte auf \n{0} weiterlesen.
|
||||
|
||||
m.init_error = Die Anwendung konnte wegen folgendem Fehler \
|
||||
nicht gestartet werden:\n{0}\n\n Bitte auf \n{1} weiterlesen.
|
||||
|
||||
m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \
|
||||
\n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \
|
||||
Schreibzugriff installieren.
|
||||
|
||||
m.missing_resource = Die Anwendung konnte nicht gestartet werden, \
|
||||
da die folgende Quelle nicht gefunden wurde:\n{0}\n\n\
|
||||
Bitte auf \n{1} weiterlesen.
|
||||
|
||||
m.insufficient_permissions_error = Du hast die digitale Signatur dieser Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, musst du deinen Browser beenden, neu starten und erneut die Anwendung von der Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die digitale Signatur akzeptieren.
|
||||
|
||||
m.corrupt_digest_signature_error = Wir konnten die digitale Signatur dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung von der richtigen Webseite aus startest.
|
||||
|
||||
m.default_install_error = der Support-Webseite
|
||||
|
||||
m.another_getdown_running = Diese Installationsanwendung l\u221a\u00a7uft \
|
||||
in mehreren Instanzen. Diese Instanz wird sich beenden und eine andere \
|
||||
Instanz den Vorgang erledigen lassen.
|
||||
|
||||
m.applet_stopped = Die Anwendung wurde beendet.
|
||||
|
||||
|
||||
# application/digest errors
|
||||
m.missing_appbase = In der Konfigurations-Datei fehlt die 'appbase'.
|
||||
m.invalid_version = In der Konfigurations-Datei steht die falsche Version.
|
||||
m.invalid_appbase = In der Konfigurations-Datei steht die falsche 'appbase'.
|
||||
m.missing_class = In der Konfigurations-Datei fehlt die Anwendungs-Klasse.
|
||||
m.missing_code = Die Konfigurations-Datei enth\u00e4lt keine Code-Quellen.
|
||||
m.invalid_digest_file = Die \u00dcbersichts-Datei ist ung\u00fcltig
|
||||
@@ -0,0 +1,104 @@
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# Getdown translation messages
|
||||
|
||||
m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f
|
||||
m.abort_confirm = <html>\u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \
|
||||
\u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002</html>
|
||||
m.abort_ok = \u4e2d\u6b62
|
||||
m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c
|
||||
|
||||
m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d\u2026
|
||||
|
||||
m.configure_proxy = <html>\u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \
|
||||
\u5931\u6557\u3057\u307e\u3057\u305f\u3002 \
|
||||
<ul><li>\u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \
|
||||
<code>javaw.exe</code>\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 \u8a2d\u5b9a\u3092 \
|
||||
<code>javaw.exe</code>\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \
|
||||
\u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \
|
||||
\u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002</ul> \
|
||||
<p>\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \
|
||||
\u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002 \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \
|
||||
\u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002</html>
|
||||
|
||||
m.proxy_extra = <html>\u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \
|
||||
\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \
|
||||
\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \
|
||||
\u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002</html>
|
||||
|
||||
m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP
|
||||
m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8
|
||||
m.proxy_ok = OK
|
||||
m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb
|
||||
|
||||
m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026
|
||||
m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d\u2026
|
||||
|
||||
m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d\u2026
|
||||
m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026
|
||||
m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a {0}
|
||||
|
||||
m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d\u2026
|
||||
m.validating = \u8a8d\u8a3c\u4e2d\u2026
|
||||
m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d\u2026
|
||||
m.launching = \u5b9f\u884c\u4e2d\u2026
|
||||
|
||||
m.complete = {0}\uff05\u5b8c\u4e86
|
||||
m.complete_remain = {0}\uff05\u5b8c\u4e86\u3000\u6b8b\u308a{1}
|
||||
|
||||
m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026
|
||||
|
||||
m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \
|
||||
\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026
|
||||
|
||||
m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \
|
||||
\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \
|
||||
www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \
|
||||
\u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \
|
||||
\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \
|
||||
\u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \
|
||||
\u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \
|
||||
\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \
|
||||
\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \
|
||||
\u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \
|
||||
\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \
|
||||
\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \
|
||||
\u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \
|
||||
\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f \
|
||||
\n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \
|
||||
\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \
|
||||
\u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \
|
||||
\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \
|
||||
\u3055\u308c\u307e\u3057\u305f\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \
|
||||
\u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \
|
||||
\u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044 \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \
|
||||
\u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \
|
||||
\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \
|
||||
\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \
|
||||
\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002
|
||||
|
||||
m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a
|
||||
|
||||
# application/digest errors
|
||||
m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002
|
||||
m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002
|
||||
m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002
|
||||
m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002
|
||||
m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
|
||||
m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002
|
||||
@@ -0,0 +1,58 @@
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# Getdown translation messages
|
||||
|
||||
m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668...
|
||||
|
||||
m.configure_proxy = <html>\u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \
|
||||
\u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \
|
||||
\u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002</html>
|
||||
|
||||
m.proxy_extra = <html>\u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \
|
||||
\u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002<br><br> \
|
||||
\u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \
|
||||
\u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002</html>
|
||||
|
||||
m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740
|
||||
m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7
|
||||
m.proxy_ok = \u786e\u5b9a
|
||||
m.proxy_cancel = \u53d6\u6d88
|
||||
|
||||
m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9...
|
||||
m.downloading = \u4e0b\u8f7d\u6570\u636e...
|
||||
m.failure = \u4e0b\u8f7d\u5931\u8d25: {0}
|
||||
|
||||
m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9...
|
||||
m.validating = \u786e\u8ba4...
|
||||
m.patching = \u5347\u7ea7...
|
||||
m.launching = \u542f\u52a8...
|
||||
|
||||
m.complete = {0}% \u5b8c\u6210
|
||||
m.complete_remain = {0}% \u5b8c\u6210 {1} \u5269\u4f59\u65f6\u95f4
|
||||
|
||||
m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6...
|
||||
|
||||
m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d...
|
||||
|
||||
m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\
|
||||
\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002
|
||||
|
||||
|
||||
m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\
|
||||
\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002
|
||||
|
||||
m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \
|
||||
\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002
|
||||
|
||||
|
||||
m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \
|
||||
\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002
|
||||
|
||||
# application/digest errors
|
||||
m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002
|
||||
m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002
|
||||
m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002
|
||||
m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002
|
||||
m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002
|
||||
m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.IOException;
|
||||
|
||||
/**
|
||||
* Used to terminate the download process in its midst.
|
||||
*/
|
||||
public class DownloadAbortedException extends IOException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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. <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.
|
||||
*/
|
||||
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 <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 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
|
||||
* <code>null</code> 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<Resource> 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<Resource> _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;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Implements downloading files over HTTP
|
||||
*/
|
||||
public class HTTPDownloader extends Downloader
|
||||
{
|
||||
public HTTPDownloader (List<Resource> resources, Observer obs)
|
||||
{
|
||||
super(resources, obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* A method of instantiating a downloader to take over the job partway.
|
||||
*/
|
||||
public HTTPDownloader (List<Resource> resources, Observer obs,
|
||||
long totalSize)
|
||||
{
|
||||
super(resources, obs);
|
||||
_totalSize = totalSize;
|
||||
_start = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a HEAD request for the specified resource and notes the
|
||||
* amount of data we will be downloading to account for it.
|
||||
*/
|
||||
@Override
|
||||
protected long checkSize (Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
// read the file information via an HTTP HEAD request
|
||||
HttpURLConnection ucon = (HttpURLConnection)
|
||||
rsrc.getRemote().openConnection();
|
||||
ucon.setRequestMethod("HEAD");
|
||||
ucon.connect();
|
||||
|
||||
// make sure we got a satisfactory response code
|
||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
String errmsg = "Unable to check up-to-date for " +
|
||||
rsrc.getRemote() + ": " + ucon.getResponseCode();
|
||||
throw new IOException(errmsg);
|
||||
}
|
||||
|
||||
return ucon.getContentLength();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
protected void doDownload (Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
// download the resource from the specified URL
|
||||
HttpURLConnection ucon = (HttpURLConnection)rsrc.getRemote().openConnection();
|
||||
ucon.connect();
|
||||
|
||||
// make sure we got a satisfactory response code
|
||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
String errmsg = "Unable to download resource " +
|
||||
rsrc.getRemote() + ": " + ucon.getResponseCode();
|
||||
throw new IOException(errmsg);
|
||||
}
|
||||
|
||||
log.info("Downloading resource [url=" + rsrc.getRemote() + "].");
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
in = ucon.getInputStream();
|
||||
out = new FileOutputStream(rsrc.getLocal());
|
||||
int read;
|
||||
|
||||
// TODO: look to see if we have a download info file
|
||||
// containing info on potentially partially downloaded data;
|
||||
// if so, use a "Range: bytes=HAVE-" header.
|
||||
|
||||
// read in the file data
|
||||
while ((read = in.read(_buffer)) != -1) {
|
||||
// write it out to our local copy
|
||||
out.write(_buffer, 0, read);
|
||||
|
||||
// if we have no observer, then don't bother computing
|
||||
// download statistics
|
||||
if (_obs == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// note that we've downloaded some data
|
||||
_currentSize += read;
|
||||
updateObserver();
|
||||
}
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
StreamUtil.close(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.klomp.snark.Snark;
|
||||
import org.klomp.snark.SnarkShutdown;
|
||||
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Implements downloading data using BitTorrent
|
||||
*/
|
||||
public class TorrentDownloader extends Downloader
|
||||
{
|
||||
public TorrentDownloader (List<Resource> resources, Observer obs)
|
||||
{
|
||||
super(resources, obs);
|
||||
log.info("Using bittorrent to fetch files");
|
||||
for (Resource resource : resources) {
|
||||
String url = resource.getRemote().toString() + ".torrent";
|
||||
Snark snark = new Snark(url, null, -1, null, null);
|
||||
SnarkShutdown snarkStopper = new SnarkShutdown(snark, null);
|
||||
Runtime.getRuntime().addShutdownHook(snarkStopper);
|
||||
_torrentmap.put(resource, snark);
|
||||
_stoppermap.put(resource, snarkStopper);
|
||||
if (resource.getPath().equals("full")) {
|
||||
_metaDownload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
protected long checkSize(Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
if (_metaDownload && !rsrc.getPath().equals("full")) {
|
||||
return 0;
|
||||
}
|
||||
if (_fallback != null) {
|
||||
return _fallback.checkSize(rsrc);
|
||||
}
|
||||
Snark snark = _torrentmap.get(rsrc);
|
||||
long length = -1;
|
||||
try {
|
||||
snark.setupNetwork();
|
||||
length = snark.meta.getTotalLength();
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Bittorrent failed, falling back to HTTP");
|
||||
SnarkShutdown stopper = _stoppermap.get(rsrc);
|
||||
stopper.run();
|
||||
Runtime.getRuntime().removeShutdownHook(stopper);
|
||||
fallback();
|
||||
if (_metaDownload && rsrc.getPath().equals("full")) {
|
||||
length = 0;
|
||||
} else {
|
||||
length = _fallback.checkSize(rsrc);
|
||||
}
|
||||
_metaDownload = false;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@Override
|
||||
protected void doDownload(Resource rsrc)
|
||||
throws IOException
|
||||
{
|
||||
if (_metaDownload && !rsrc.getPath().equals("full")) {
|
||||
return;
|
||||
}
|
||||
if (_fallback != null) {
|
||||
if (rsrc.getPath().equals("full")) {
|
||||
return;
|
||||
} else {
|
||||
_fallback.doDownload(rsrc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Snark snark = _torrentmap.get(rsrc);
|
||||
SnarkShutdown snarkStopper = _stoppermap.get(rsrc);
|
||||
snark.collectPieces();
|
||||
// Override the start time, since Snark allocates storage prior to
|
||||
// doing any downloading
|
||||
_start = System.currentTimeMillis();
|
||||
while (!snark.coordinator.completed()) {
|
||||
long now = System.currentTimeMillis();
|
||||
if ((now - _lastUpdate) >= UPDATE_DELAY) {
|
||||
_currentSize = snark.coordinator.getDownloaded();
|
||||
if ((_currentSize < SIZE_THRESHOLD &&
|
||||
(now - _start) >= TIME_THRESHOLD)) {
|
||||
log.info("Torrenting too slow, falling back to HTTP.");
|
||||
// The download isn't going as planned, abort;
|
||||
snarkStopper.run();
|
||||
Runtime.getRuntime().removeShutdownHook(snarkStopper);
|
||||
snarkStopper = null;
|
||||
_stoppermap.remove(rsrc);
|
||||
if (_metaDownload) {
|
||||
_metaDownload = false;
|
||||
}
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateObserver();
|
||||
}
|
||||
// Manually set completion, just to be extra-safe.
|
||||
_currentSize = _totalSize;
|
||||
updateObserver();
|
||||
|
||||
if (snarkStopper != null) {
|
||||
snarkStopper.run();
|
||||
Runtime.getRuntime().removeShutdownHook(snarkStopper);
|
||||
_stoppermap.remove(rsrc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If torrent downloading either bugs out or is too slow, switch to a
|
||||
* different method by creating the fallback downloader.
|
||||
*/
|
||||
protected void fallback ()
|
||||
{
|
||||
_fallback = new HTTPDownloader(_resources, _obs, _totalSize);
|
||||
}
|
||||
|
||||
/** Keeps a mapping of resource names to torrent downloaders */
|
||||
protected HashMap<Resource, Snark> _torrentmap =
|
||||
new HashMap<Resource, Snark>();
|
||||
|
||||
/** Keeps a mapping of resource names to torrent stoppers */
|
||||
protected HashMap<Resource, SnarkShutdown> _stoppermap =
|
||||
new HashMap<Resource, SnarkShutdown>();
|
||||
|
||||
/** If we fail, revert to using this HTTP download transport */
|
||||
protected HTTPDownloader _fallback = null;
|
||||
|
||||
/** The length of time before we check for adequate progress*/
|
||||
protected static final long TIME_THRESHOLD = 60 * 1000l;
|
||||
|
||||
/**
|
||||
* Whether we are downloading an artificially-generated metafile
|
||||
* representing all of the {@link Resource}s at the end of the file.
|
||||
*/
|
||||
protected boolean _metaDownload = false;
|
||||
|
||||
/**
|
||||
* The minimum amount of data that must be downloaded within the
|
||||
* initial period in order to continue using BitTorrent
|
||||
*/
|
||||
protected static final long SIZE_THRESHOLD = 4000l;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class AppletParamSigner
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
try {
|
||||
if (args.length != 7) {
|
||||
System.err.println("AppletParamSigner keystore storepass alias keypass " +
|
||||
"appbase appname imgpath");
|
||||
System.exit(255);
|
||||
}
|
||||
|
||||
String keystore = args[0];
|
||||
String storepass = args[1];
|
||||
String alias = args[2];
|
||||
String keypass = args[3];
|
||||
String appbase = args[4];
|
||||
String appname = args[5];
|
||||
String imgpath = args[6];
|
||||
String params = appbase + appname + imgpath;
|
||||
|
||||
KeyStore store = KeyStore.getInstance("JKS");
|
||||
store.load(new BufferedInputStream(new FileInputStream(keystore)),
|
||||
storepass.toCharArray());
|
||||
PrivateKey key = (PrivateKey)store.getKey(alias, keypass.toCharArray());
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initSign(key);
|
||||
sig.update(params.getBytes());
|
||||
String signed = new String(Base64.encodeBase64(sig.sign()));
|
||||
System.out.println("<param name=\"appbase\" value=\"" + appbase + "\" />");
|
||||
System.out.println("<param name=\"appname\" value=\"" + appname + "\" />");
|
||||
System.out.println("<param name=\"bgimage\" value=\"" + imgpath + "\" />");
|
||||
System.out.println("<param name=\"signature\" value=\"" + signed + "\" />");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to produce signature.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import com.sun.javaws.jardiff.JarDiff;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.data.Digest;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
/**
|
||||
* Generates patch files between two particular revisions of an
|
||||
* application. The differences between all the files in the two
|
||||
* revisions are bundled into a single patch file which is placed into the
|
||||
* target version directory.
|
||||
*/
|
||||
public class Differ
|
||||
{
|
||||
/**
|
||||
* Creates a single patch file that contains the differences between
|
||||
* the two specified application directories. The patch file will be
|
||||
* created in the <code>nvdir</code> directory with name
|
||||
* <code>patchV.dat</code> where V is the old application version.
|
||||
*/
|
||||
public void createDiff (File nvdir, File ovdir, boolean verbose)
|
||||
throws IOException
|
||||
{
|
||||
// sanity check
|
||||
String nvers = nvdir.getName();
|
||||
String overs = ovdir.getName();
|
||||
try {
|
||||
if (Long.parseLong(nvers) <= Long.parseLong(overs)) {
|
||||
String err = "New version (" + nvers + ") must be greater " +
|
||||
"than old version (" + overs + ").";
|
||||
throw new IOException(err);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Non-numeric versions? [nvers=" + nvers +
|
||||
", overs=" + overs + "].");
|
||||
}
|
||||
|
||||
Application oapp = new Application(ovdir, null);
|
||||
oapp.init(false);
|
||||
ArrayList<Resource> orsrcs = new ArrayList<Resource>();
|
||||
orsrcs.addAll(oapp.getCodeResources());
|
||||
orsrcs.addAll(oapp.getResources());
|
||||
|
||||
Application napp = new Application(nvdir, null);
|
||||
napp.init(false);
|
||||
ArrayList<Resource> nrsrcs = new ArrayList<Resource>();
|
||||
nrsrcs.addAll(napp.getCodeResources());
|
||||
nrsrcs.addAll(napp.getResources());
|
||||
|
||||
// first create a patch for the main application
|
||||
File patch = new File(nvdir, "patch" + overs + ".dat");
|
||||
createPatch(patch, orsrcs, nrsrcs, verbose);
|
||||
|
||||
// next create patches for any auxiliary resource groups
|
||||
for (String auxgroup : napp.getAuxGroups()) {
|
||||
orsrcs = new ArrayList<Resource>();
|
||||
orsrcs.addAll(oapp.getResources(auxgroup));
|
||||
nrsrcs = new ArrayList<Resource>();
|
||||
nrsrcs.addAll(napp.getResources(auxgroup));
|
||||
patch = new File(nvdir, "patch-" + auxgroup + overs + ".dat");
|
||||
createPatch(patch, orsrcs, nrsrcs, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPatch (File patch, ArrayList<Resource> orsrcs,
|
||||
ArrayList<Resource> nrsrcs, boolean verbose)
|
||||
throws IOException
|
||||
{
|
||||
MessageDigest md = Digest.getMessageDigest();
|
||||
JarOutputStream jout = null;
|
||||
try {
|
||||
jout = new JarOutputStream(
|
||||
new BufferedOutputStream(new FileOutputStream(patch)));
|
||||
|
||||
// for each file in the new application, it either already exists
|
||||
// in the old application, or it is new
|
||||
for (Resource rsrc : nrsrcs) {
|
||||
int oidx = orsrcs.indexOf(rsrc);
|
||||
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);
|
||||
if (odig.equals(ndig)) {
|
||||
if (verbose) {
|
||||
System.out.println("Unchanged: " + rsrc.getPath());
|
||||
}
|
||||
// by leaving it out, it will be left as is during the
|
||||
// patching process
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise potentially create a jar diff
|
||||
if (rsrc.getPath().endsWith(".jar")) {
|
||||
if (verbose) {
|
||||
System.out.println("JarDiff: " + rsrc.getPath());
|
||||
}
|
||||
// here's a juicy one: JarDiff blindly pulls ZipEntry
|
||||
// objects out of one jar file and stuffs them into
|
||||
// another without clearing out things like the
|
||||
// compressed size, so if, for whatever reason (like
|
||||
// different JRE versions or phase of the moon) the
|
||||
// compressed size in the old jar file is different
|
||||
// than the compressed size generated when creating the
|
||||
// jardiff jar file, ZipOutputStream will choke and
|
||||
// we'll be hosed; so we recreate the jar files in
|
||||
// their entirety before running jardiff on 'em
|
||||
File otemp = rebuildJar(orsrc.getLocal());
|
||||
File temp = rebuildJar(rsrc.getLocal());
|
||||
jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.PATCH));
|
||||
jarDiff(otemp, temp, jout);
|
||||
otemp.delete();
|
||||
temp.delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
System.out.println("Addition: " + rsrc.getPath());
|
||||
}
|
||||
jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.CREATE));
|
||||
pipe(rsrc.getLocal(), jout);
|
||||
}
|
||||
|
||||
// now any file remaining in orsrcs needs to be removed
|
||||
for (Resource rsrc : orsrcs) {
|
||||
// add an entry with the resource name and the deletion suffix
|
||||
if (verbose) {
|
||||
System.out.println("Removal: " + rsrc.getPath());
|
||||
}
|
||||
jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.DELETE));
|
||||
}
|
||||
|
||||
StreamUtil.close(jout);
|
||||
System.out.println("Created patch file: " + patch);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
StreamUtil.close(jout);
|
||||
patch.delete();
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
protected File rebuildJar (File target)
|
||||
throws IOException
|
||||
{
|
||||
JarFile jar = new JarFile(target);
|
||||
File temp = File.createTempFile("differ", "jar");
|
||||
JarOutputStream jout = new JarOutputStream(
|
||||
new BufferedOutputStream(new FileOutputStream(temp)));
|
||||
byte[] buffer = new byte[4096];
|
||||
for (Enumeration<JarEntry> iter = jar.entries(); iter.hasMoreElements(); ) {
|
||||
JarEntry entry = iter.nextElement();
|
||||
entry.setCompressedSize(-1);
|
||||
jout.putNextEntry(entry);
|
||||
InputStream in = jar.getInputStream(entry);
|
||||
int size = in.read(buffer);
|
||||
while (size != -1) {
|
||||
jout.write(buffer, 0, size);
|
||||
size = in.read(buffer);
|
||||
}
|
||||
in.close();
|
||||
}
|
||||
jout.close();
|
||||
jar.close();
|
||||
return temp;
|
||||
}
|
||||
|
||||
protected void jarDiff (File ofile, File nfile, JarOutputStream jout)
|
||||
throws IOException
|
||||
{
|
||||
JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false);
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 2) {
|
||||
System.err.println(
|
||||
"Usage: Differ [-verbose] new_vers_dir old_vers_dir");
|
||||
System.exit(255);
|
||||
}
|
||||
Differ differ = new Differ();
|
||||
boolean verbose = false;
|
||||
int aidx = 0;
|
||||
if (args[0].equals("-verbose")) {
|
||||
verbose = true;
|
||||
aidx++;
|
||||
}
|
||||
try {
|
||||
differ.createDiff(new File(args[aidx++]),
|
||||
new File(args[aidx++]), verbose);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error: " + ioe.getMessage());
|
||||
System.exit(255);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void pipe (File file, JarOutputStream jout)
|
||||
throws IOException
|
||||
{
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
StreamUtil.copy(fin = new FileInputStream(file), jout);
|
||||
} finally {
|
||||
StreamUtil.close(fin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.data.Digest;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
|
||||
/**
|
||||
* An ant task used to create a <code>digest.txt</code> for a Getdown
|
||||
* application deployment.
|
||||
*/
|
||||
public class DigesterTask extends Task
|
||||
{
|
||||
/**
|
||||
* Sets the application directory.
|
||||
*/
|
||||
public void setAppdir (File appdir)
|
||||
{
|
||||
_appdir = appdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the digest signing keystore.
|
||||
*/
|
||||
public void setKeystore (File path)
|
||||
{
|
||||
_storepath = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keystore decryption key.
|
||||
*/
|
||||
public void setStorepass (String password)
|
||||
{
|
||||
_storepass = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the private key alias.
|
||||
*/
|
||||
public void setAlias (String alias)
|
||||
{
|
||||
_storealias = alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
@Override
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// make sure appdir is set
|
||||
if (_appdir == null) {
|
||||
throw new BuildException("Must specify the path to the application directory " +
|
||||
"via the 'appdir' attribute.");
|
||||
}
|
||||
|
||||
// make sure _storepass and _keyalias are set, if _storepath is set
|
||||
if (_storepath != null) {
|
||||
if (_storepass == null || _storealias == null) {
|
||||
throw new BuildException(
|
||||
"Must specify both a keystore password and a private key alias.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
createDigest(_appdir);
|
||||
if (_storepath != null) {
|
||||
signDigest(_appdir, _storepath, _storepass, _storealias);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new BuildException("Error creating signature: " + gse.getMessage(), gse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file in the specified application directory.
|
||||
*/
|
||||
public static void createDigest (File appdir)
|
||||
throws IOException
|
||||
{
|
||||
File target = new File(appdir, Digest.DIGEST_FILE);
|
||||
System.out.println("Generating digest file '" + target + "'...");
|
||||
|
||||
// create our application and instruct it to parse its business
|
||||
Application app = new Application(appdir, null);
|
||||
app.init(false);
|
||||
|
||||
ArrayList<Resource> rsrcs = new ArrayList<Resource>();
|
||||
rsrcs.add(app.getConfigResource());
|
||||
rsrcs.addAll(app.getCodeResources());
|
||||
rsrcs.addAll(app.getResources());
|
||||
for (String auxgroup : app.getAuxGroups()) {
|
||||
rsrcs.addAll(app.getResources(auxgroup));
|
||||
}
|
||||
|
||||
// now generate the digest file
|
||||
Digest.createDigest(rsrcs, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a digest file in the specified application directory.
|
||||
*/
|
||||
public static void signDigest (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);
|
||||
|
||||
// initialize the keystore
|
||||
KeyStore store = KeyStore.getInstance("JKS");
|
||||
FileInputStream storeInput = new FileInputStream(storePath);
|
||||
store.load(storeInput, storePass.toCharArray());
|
||||
PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray());
|
||||
|
||||
// sign the digest file
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
FileInputStream dataInput = new FileInputStream(inputFile);
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
sig.initSign(key);
|
||||
while ((length = dataInput.read(buffer)) != -1) {
|
||||
sig.update(buffer, 0, length);
|
||||
}
|
||||
|
||||
// Write out the signature
|
||||
FileOutputStream signatureOutput = new FileOutputStream(signatureFile);
|
||||
String signed = new String(Base64.encodeBase64(sig.sign()));
|
||||
signatureOutput.write(signed.getBytes("utf8"));
|
||||
}
|
||||
|
||||
/** The application directory in which we're creating a digest file. */
|
||||
protected File _appdir;
|
||||
|
||||
/** The path to the keystore we'll use to sign the digest file, if any. */
|
||||
protected File _storepath;
|
||||
|
||||
/** The decryption key for the keystore. */
|
||||
protected String _storepass;
|
||||
|
||||
/** The private key alias. */
|
||||
protected String _storealias;
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import com.threerings.getdown.util.ProgressObserver;
|
||||
|
||||
/**
|
||||
* Applies a jardiff patch to a jar file.
|
||||
*/
|
||||
public class JarDiffPatcher
|
||||
{
|
||||
/** The name of the jardiff control file. */
|
||||
public static final String INDEX_NAME = "META-INF/INDEX.JD";
|
||||
|
||||
/** The version header used in the control file. */
|
||||
public static final String VERSION_HEADER = "version 1.0";
|
||||
|
||||
/** A jardiff command to remove an entry. */
|
||||
public static final String REMOVE_COMMAND = "remove";
|
||||
|
||||
/** A jardiff command to move an entry. */
|
||||
public static final String MOVE_COMMAND = "move";
|
||||
|
||||
/**
|
||||
* Patches the specified jar file using the supplied patch file and writing
|
||||
* the new jar file to the supplied target.
|
||||
*
|
||||
* @param jarPath the path to the original jar file.
|
||||
* @param diffPath the path to the jardiff patch file.
|
||||
* @param target the output stream to which we will write the patched jar.
|
||||
* @param observer an optional observer to be notified of patching progress.
|
||||
*
|
||||
* @throws IOException if any problem occurs during patching.
|
||||
*/
|
||||
public void patchJar (String jarPath, String diffPath, OutputStream target,
|
||||
ProgressObserver observer)
|
||||
throws IOException
|
||||
{
|
||||
File oldFile = new File(jarPath);
|
||||
File diffFile = new File(diffPath);
|
||||
JarOutputStream jos = new JarOutputStream(target);
|
||||
JarFile oldJar = new JarFile(oldFile);
|
||||
JarFile jarDiff = new JarFile(diffFile);
|
||||
Set<String> ignoreSet = new HashSet<String>();
|
||||
|
||||
Map<String, String> renameMap = new HashMap<String, String>();
|
||||
determineNameMapping(jarDiff, ignoreSet, renameMap);
|
||||
|
||||
// get all keys in renameMap
|
||||
String[] keys = renameMap.keySet().toArray(new String[renameMap.size()]);
|
||||
|
||||
// Files to implicit move
|
||||
Set<String> oldjarNames = new HashSet<String>();
|
||||
Enumeration<JarEntry> oldEntries = oldJar.entries();
|
||||
if (oldEntries != null) {
|
||||
while (oldEntries.hasMoreElements()) {
|
||||
oldjarNames.add(oldEntries.nextElement().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// size depends on the three parameters below, which is basically the
|
||||
// counter for each loop that do the actual writes to the output file
|
||||
// since oldjarNames.size() changes in the first two loop below, we
|
||||
// need to adjust the size accordingly also when oldjarNames.size()
|
||||
// changes
|
||||
double size = oldjarNames.size() + keys.length + jarDiff.size();
|
||||
double currentEntry = 0;
|
||||
|
||||
// Handle all remove commands
|
||||
oldjarNames.removeAll(ignoreSet);
|
||||
size -= ignoreSet.size();
|
||||
|
||||
// Add content from JARDiff
|
||||
Enumeration<JarEntry> entries = jarDiff.entries();
|
||||
if (entries != null) {
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (!INDEX_NAME.equals(entry.getName())) {
|
||||
updateObserver(observer, currentEntry, size);
|
||||
currentEntry++;
|
||||
writeEntry(jos, entry, jarDiff);
|
||||
|
||||
// Remove entry from oldjarNames since no implicit move is
|
||||
// needed
|
||||
boolean wasInOld = oldjarNames.remove(entry.getName());
|
||||
|
||||
// Update progress counters. If it was in old, we do not
|
||||
// need an implicit move, so adjust total size.
|
||||
if (wasInOld) {
|
||||
size--;
|
||||
}
|
||||
|
||||
} else {
|
||||
// no write is done, decrement size
|
||||
size--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go through the renameMap and apply move for each entry
|
||||
for (String newName : keys) {
|
||||
// Apply move <oldName> <newName> command
|
||||
String oldName = renameMap.get(newName);
|
||||
|
||||
// Get source JarEntry
|
||||
JarEntry oldEntry = oldJar.getJarEntry(oldName);
|
||||
if (oldEntry == null) {
|
||||
String moveCmd = MOVE_COMMAND + oldName + " " + newName;
|
||||
throw new IOException("error.badmove: " + moveCmd);
|
||||
}
|
||||
|
||||
// Create dest JarEntry
|
||||
JarEntry newEntry = new JarEntry(newName);
|
||||
newEntry.setTime(oldEntry.getTime());
|
||||
newEntry.setSize(oldEntry.getSize());
|
||||
newEntry.setCompressedSize(oldEntry.getCompressedSize());
|
||||
newEntry.setCrc(oldEntry.getCrc());
|
||||
newEntry.setMethod(oldEntry.getMethod());
|
||||
newEntry.setExtra(oldEntry.getExtra());
|
||||
newEntry.setComment(oldEntry.getComment());
|
||||
|
||||
updateObserver(observer, currentEntry, size);
|
||||
currentEntry++;
|
||||
|
||||
writeEntry(jos, newEntry, oldJar.getInputStream(oldEntry));
|
||||
|
||||
// Remove entry from oldjarNames since no implicit move is needed
|
||||
boolean wasInOld = oldjarNames.remove(oldName);
|
||||
|
||||
// Update progress counters. If it was in old, we do not need an
|
||||
// implicit move, so adjust total size.
|
||||
if (wasInOld) {
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
// implicit move
|
||||
Iterator<String> iEntries = oldjarNames.iterator();
|
||||
if (iEntries != null) {
|
||||
while (iEntries.hasNext()) {
|
||||
String name = iEntries.next();
|
||||
JarEntry entry = oldJar.getJarEntry(name);
|
||||
updateObserver(observer, currentEntry, size);
|
||||
currentEntry++;
|
||||
writeEntry(jos, entry, oldJar);
|
||||
}
|
||||
}
|
||||
updateObserver(observer, currentEntry, size);
|
||||
|
||||
jos.finish();
|
||||
}
|
||||
|
||||
protected void updateObserver (ProgressObserver observer,
|
||||
double currentSize, double size)
|
||||
{
|
||||
if (observer != null) {
|
||||
observer.progress((int)(100*currentSize/size));
|
||||
}
|
||||
}
|
||||
|
||||
protected void determineNameMapping (
|
||||
JarFile jarDiff, Set<String> ignoreSet, Map<String, String> renameMap)
|
||||
throws IOException
|
||||
{
|
||||
InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME));
|
||||
if (is == null) {
|
||||
throw new IOException("error.noindex");
|
||||
}
|
||||
|
||||
LineNumberReader indexReader =
|
||||
new LineNumberReader(new InputStreamReader(is, "UTF-8"));
|
||||
String line = indexReader.readLine();
|
||||
if (line == null || !line.equals(VERSION_HEADER)) {
|
||||
throw new IOException("jardiff.error.badheader: " + line);
|
||||
}
|
||||
|
||||
while ((line = indexReader.readLine()) != null) {
|
||||
if (line.startsWith(REMOVE_COMMAND)) {
|
||||
List<String> sub = getSubpaths(
|
||||
line.substring(REMOVE_COMMAND.length()));
|
||||
|
||||
if (sub.size() != 1) {
|
||||
throw new IOException("error.badremove: " + line);
|
||||
}
|
||||
ignoreSet.add(sub.get(0));
|
||||
|
||||
} else if (line.startsWith(MOVE_COMMAND)) {
|
||||
List<String> sub = getSubpaths(
|
||||
line.substring(MOVE_COMMAND.length()));
|
||||
if (sub.size() != 2) {
|
||||
throw new IOException("error.badmove: " + line);
|
||||
}
|
||||
|
||||
// target of move should be the key
|
||||
if (renameMap.put(sub.get(1), sub.get(0)) != null) {
|
||||
// invalid move - should not move to same target twice
|
||||
throw new IOException("error.badmove: " + line);
|
||||
}
|
||||
|
||||
} else if (line.length() > 0) {
|
||||
throw new IOException("error.badcommand: " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> getSubpaths (String path)
|
||||
{
|
||||
int index = 0;
|
||||
int length = path.length();
|
||||
ArrayList<String> sub = new ArrayList<String>();
|
||||
|
||||
while (index < length) {
|
||||
while (index < length && Character.isWhitespace
|
||||
(path.charAt(index))) {
|
||||
index++;
|
||||
}
|
||||
if (index < length) {
|
||||
int start = index;
|
||||
int last = start;
|
||||
String subString = null;
|
||||
|
||||
while (index < length) {
|
||||
char aChar = path.charAt(index);
|
||||
if (aChar == '\\' && (index + 1) < length &&
|
||||
path.charAt(index + 1) == ' ') {
|
||||
|
||||
if (subString == null) {
|
||||
subString = path.substring(last, index);
|
||||
} else {
|
||||
subString += path.substring(last, index);
|
||||
}
|
||||
last = ++index;
|
||||
} else if (Character.isWhitespace(aChar)) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (last != index) {
|
||||
if (subString == null) {
|
||||
subString = path.substring(last, index);
|
||||
} else {
|
||||
subString += path.substring(last, index);
|
||||
}
|
||||
}
|
||||
sub.add(subString);
|
||||
}
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
protected void writeEntry (
|
||||
JarOutputStream jos, JarEntry entry, JarFile file)
|
||||
throws IOException
|
||||
{
|
||||
writeEntry(jos, entry, file.getInputStream(entry));
|
||||
}
|
||||
|
||||
protected void writeEntry (
|
||||
JarOutputStream jos, JarEntry entry, InputStream data)
|
||||
throws IOException
|
||||
{
|
||||
jos.putNextEntry(new JarEntry(entry.getName()));
|
||||
|
||||
// Read the entry
|
||||
int size = data.read(newBytes);
|
||||
while (size != -1) {
|
||||
jos.write(newBytes, 0, size);
|
||||
size = data.read(newBytes);
|
||||
}
|
||||
data.close();
|
||||
}
|
||||
|
||||
protected static final int DEFAULT_READ_SIZE = 2048;
|
||||
|
||||
protected static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
|
||||
protected static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.getdown.util.FileUtil;
|
||||
import com.threerings.getdown.util.ProgressObserver;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Applies a unified patch file to an application directory, providing
|
||||
* percentage completion feedback along the way. <em>Note:</em> the
|
||||
* patcher is not thread safe. Create a separate patcher instance for each
|
||||
* patching action that is desired.
|
||||
*/
|
||||
public class Patcher
|
||||
{
|
||||
/** A suffix appended to file names to indicate that a file should be newly created. */
|
||||
public static final String CREATE = ".create";
|
||||
|
||||
/** A suffix appended to file names to indicate that a file should be patched. */
|
||||
public static final String PATCH = ".patch";
|
||||
|
||||
/** A suffix appended to file names to indicate that a file should be deleted. */
|
||||
public static final String DELETE = ".delete";
|
||||
|
||||
/**
|
||||
* Applies the specified patch file to the application living in the
|
||||
* specified application directory. The supplied observer, if
|
||||
* non-null, will be notified of progress along the way.
|
||||
*
|
||||
* <p><em>Note:</em> this method runs on the calling thread, thus the
|
||||
* caller may want to make use of a separate thread in conjunction
|
||||
* with the patcher so that the user interface is not blocked for the
|
||||
* duration of the patch.
|
||||
*/
|
||||
public void patch (File appdir, File patch, ProgressObserver obs)
|
||||
throws IOException
|
||||
{
|
||||
// save this information for later
|
||||
_obs = obs;
|
||||
_plength = patch.length();
|
||||
|
||||
JarFile file = new JarFile(patch);
|
||||
Enumeration<JarEntry> entries = file.entries(); // old skool!
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String path = entry.getName();
|
||||
long elength = entry.getCompressedSize();
|
||||
|
||||
// depending on the suffix, we do The Right Thing (tm)
|
||||
if (path.endsWith(CREATE)) {
|
||||
path = strip(path, CREATE);
|
||||
System.out.println("Creating " + path + "...");
|
||||
createFile(file, entry, new File(appdir, path));
|
||||
|
||||
} else if (path.endsWith(PATCH)) {
|
||||
path = strip(path, PATCH);
|
||||
System.out.println("Patching " + path + "...");
|
||||
patchFile(file, entry, appdir, path);
|
||||
|
||||
} else if (path.endsWith(DELETE)) {
|
||||
path = strip(path, DELETE);
|
||||
System.out.println("Removing " + path + "...");
|
||||
File target = new File(appdir, path);
|
||||
if (!target.delete()) {
|
||||
System.err.println("Failure deleting '" + target + "'.");
|
||||
}
|
||||
|
||||
} else {
|
||||
System.err.println("Skipping bogus patch file entry: " + path);
|
||||
}
|
||||
|
||||
// note that we've completed this entry
|
||||
_complete += elength;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
protected String strip (String path, String suffix)
|
||||
{
|
||||
return path.substring(0, path.length() - suffix.length());
|
||||
}
|
||||
|
||||
protected void createFile (JarFile file, ZipEntry entry, File target)
|
||||
{
|
||||
// create our copy buffer if necessary
|
||||
if (_buffer == null) {
|
||||
_buffer = new byte[COPY_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
// make sure the file's parent directory exists
|
||||
File pdir = target.getParentFile();
|
||||
if (!pdir.exists()) {
|
||||
if (!pdir.mkdirs()) {
|
||||
log.warning("Failed to create parent for '" + target + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
FileOutputStream fout = null;
|
||||
try {
|
||||
in = file.getInputStream(entry);
|
||||
fout = new FileOutputStream(target);
|
||||
int total = 0, read;
|
||||
while ((read = in.read(_buffer)) != -1) {
|
||||
total += read;
|
||||
fout.write(_buffer, 0, read);
|
||||
updateProgress(total);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error creating '" + target + "': " + ioe);
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(in);
|
||||
StreamUtil.close(fout);
|
||||
}
|
||||
}
|
||||
|
||||
protected void patchFile (JarFile file, ZipEntry entry,
|
||||
File appdir, String path)
|
||||
{
|
||||
File target = new File(appdir, path);
|
||||
File patch = new File(appdir, entry.getName());
|
||||
File otarget = new File(appdir, path + ".old");
|
||||
JarDiffPatcher patcher = null;
|
||||
|
||||
// make sure no stale old target is lying around to mess us up
|
||||
otarget.delete();
|
||||
|
||||
// pipe the contents of the patch into a file
|
||||
InputStream in = null;
|
||||
FileOutputStream fout = null;
|
||||
try {
|
||||
StreamUtil.copy(in = file.getInputStream(entry), fout = new FileOutputStream(patch));
|
||||
StreamUtil.close(fout);
|
||||
fout = null;
|
||||
|
||||
// move the current version of the jar to .old
|
||||
if (!FileUtil.renameTo(target, otarget)) {
|
||||
System.err.println("Failed to .oldify '" + target + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll need this to pass progress along to our observer
|
||||
final long elength = entry.getCompressedSize();
|
||||
ProgressObserver obs = new ProgressObserver() {
|
||||
public void progress (int percent) {
|
||||
updateProgress((int)(percent * elength / 100));
|
||||
}
|
||||
};
|
||||
|
||||
// now apply the patch to create the new target file
|
||||
patcher = new JarDiffPatcher();
|
||||
fout = new FileOutputStream(target);
|
||||
patcher.patchJar(otarget.getPath(), patch.getPath(), fout, obs);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (patcher == null) {
|
||||
System.err.println("Failed to write patch file '" + patch + "': " + ioe);
|
||||
} else {
|
||||
System.err.println("Error patching '" + target + "': " + ioe);
|
||||
}
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(fout);
|
||||
StreamUtil.close(in);
|
||||
// clean up our temporary files
|
||||
if (!patch.delete()) {
|
||||
patch.deleteOnExit();
|
||||
}
|
||||
if (!otarget.delete()) {
|
||||
otarget.deleteOnExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateProgress (int progress)
|
||||
{
|
||||
if (_obs != null) {
|
||||
_obs.progress((int)(100 * (_complete + progress) / _plength));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length != 2) {
|
||||
System.err.println("Usage: Patcher appdir patch_file");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
Patcher patcher = new Patcher();
|
||||
try {
|
||||
patcher.patch(new File(args[0]), new File(args[1]), null);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error: " + ioe.getMessage());
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
protected ProgressObserver _obs;
|
||||
protected long _complete, _plength;
|
||||
protected byte[] _buffer;
|
||||
|
||||
protected static final int COPY_BUFFER_SIZE = 4096;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Parses a file containing key/value pairs and returns a {@link HashMap} with the values. Keys may
|
||||
* be repeated, in which case they will be made to reference an array of values.
|
||||
*/
|
||||
public class ConfigUtil
|
||||
{
|
||||
/**
|
||||
* Parses a configuration file containing key/value pairs. The file must be in the UTF-8
|
||||
* encoding.
|
||||
*
|
||||
* @return a list of <code>String[]</code> instances containing the key/value pairs in the
|
||||
* order they were parsed from the file.
|
||||
*/
|
||||
public static List<String[]> parsePairs (File config, boolean checkPlatform)
|
||||
throws IOException
|
||||
{
|
||||
ArrayList<String[]> pairs = new ArrayList<String[]>();
|
||||
String osname = System.getProperty("os.name");
|
||||
osname = (osname == null) ? "" : osname.toLowerCase();
|
||||
|
||||
// parse our configuration file
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(config);
|
||||
BufferedReader bin = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
|
||||
String line = null;
|
||||
while ((line = bin.readLine()) != null) {
|
||||
// nix comments
|
||||
int cidx = line.indexOf("#");
|
||||
if (cidx != -1) {
|
||||
line = line.substring(0, cidx);
|
||||
}
|
||||
|
||||
// trim whitespace and skip blank lines
|
||||
line = line.trim();
|
||||
if (StringUtil.isBlank(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse our key/value pair
|
||||
String[] pair = new String[2];
|
||||
int eidx = line.indexOf("=");
|
||||
if (eidx != -1) {
|
||||
pair[0] = line.substring(0, eidx).trim();
|
||||
pair[1] = line.substring(eidx+1).trim();
|
||||
} else {
|
||||
pair[0] = line;
|
||||
pair[1] = "";
|
||||
}
|
||||
|
||||
// allow a value to have a [Linux]
|
||||
if (pair[1].startsWith("[")) {
|
||||
cidx = pair[1].indexOf("]");
|
||||
if (cidx == -1) {
|
||||
log.warning("Bogus platform specifier [key=" + pair[0] +
|
||||
", value=" + pair[1] + "].");
|
||||
} else {
|
||||
String platform = pair[1].substring(1, cidx);
|
||||
platform = platform.trim().toLowerCase();
|
||||
pair[1] = pair[1].substring(cidx+1).trim();
|
||||
if (checkPlatform) {
|
||||
if (platform.startsWith("!")) {
|
||||
platform = platform.substring(1);
|
||||
if (osname.indexOf(platform) != -1) {
|
||||
log.info("Skipping [platform=!" + platform +
|
||||
", key=" + pair[0] + ", value=" + pair[1] + "].");
|
||||
continue;
|
||||
}
|
||||
} else if (osname.indexOf(platform) == -1) {
|
||||
log.info("Skipping [platform=" + platform +
|
||||
", key=" + pair[0] + ", value=" + pair[1] + "].");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pairs.add(pair);
|
||||
}
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(fin);
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a configuration file containing key/value pairs. The file must be in the UTF-8
|
||||
* encoding.
|
||||
*
|
||||
* @return a map from keys to values, where a value will be an array of strings if more than
|
||||
* one key/value pair in the config file was associated with the same key.
|
||||
*/
|
||||
public static HashMap<String, Object> parseConfig (File config, boolean checkPlatform)
|
||||
throws IOException
|
||||
{
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
|
||||
// I thought that we could use HashMap<String, String[]> and put new String[] {pair[1]} for
|
||||
// the null case, but it mysteriously dies on launch, so leaving it as HashMap<String,
|
||||
// Object> for now
|
||||
for (String[] pair : parsePairs(config, checkPlatform)) {
|
||||
Object value = data.get(pair[0]);
|
||||
if (value == null) {
|
||||
data.put(pair[0], pair[1]);
|
||||
} else if (value instanceof String) {
|
||||
data.put(pair[0], new String[] { (String)value, pair[1] });
|
||||
} else if (value instanceof String[]) {
|
||||
String[] values = (String[])value;
|
||||
String[] nvalues = new String[values.length+1];
|
||||
System.arraycopy(values, 0, nvalues, 0, values.length);
|
||||
nvalues[values.length] = pair[1];
|
||||
data.put(pair[0], nvalues);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Massages a single string into an array and leaves existing array values as is. Simplifies
|
||||
* access to parameters that are expected to be arrays.
|
||||
*/
|
||||
public static String[] getMultiValue (HashMap<String, Object> data, String name)
|
||||
{
|
||||
Object value = data.get(name);
|
||||
if (value instanceof String) {
|
||||
return new String[] { (String)value };
|
||||
} else {
|
||||
return (String[])value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* File related utilities.
|
||||
*/
|
||||
public class FileUtil
|
||||
{
|
||||
/**
|
||||
* Gets the specified source file to the specified destination file by hook or crook. Windows
|
||||
* has all sorts of problems which we work around in this method.
|
||||
*
|
||||
* @return true if we managed to get the job done, false otherwise.
|
||||
*/
|
||||
public static boolean renameTo (File source, File dest)
|
||||
{
|
||||
// if we're on a civilized operating system we may be able to simple rename it
|
||||
if (source.renameTo(dest)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// fall back to trying to rename the old file out of the way, rename the new file into
|
||||
// place and then delete the old file
|
||||
if (dest.exists()) {
|
||||
File temp = new File(dest.getPath() + "_old");
|
||||
if (temp.exists()) {
|
||||
if (!temp.delete()) {
|
||||
log.warning("Failed to delete old intermediate file " + temp + ".");
|
||||
// the subsequent code will probably fail
|
||||
}
|
||||
}
|
||||
if (dest.renameTo(temp)) {
|
||||
if (source.renameTo(dest)) {
|
||||
if (temp.delete()) {
|
||||
log.warning("Failed to delete intermediate file " + temp + ".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// as a last resort, try copying the old data over the new
|
||||
FileInputStream fin = null;
|
||||
FileOutputStream fout = null;
|
||||
try {
|
||||
fin = new FileInputStream(source);
|
||||
fout = new FileOutputStream(dest);
|
||||
StreamUtil.copy(fin, fout);
|
||||
if (!source.delete()) {
|
||||
log.warning("Failed to delete " + source +
|
||||
" after brute force copy to " + dest + ".");
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to copy " + source + " to " + dest + ": " + ioe);
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(fin);
|
||||
StreamUtil.close(fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Useful routines for launching Java applications from within other Java
|
||||
* applications.
|
||||
*/
|
||||
public class LaunchUtil
|
||||
{
|
||||
/** The directory into which a local VM installation should be unpacked. */
|
||||
public static final String LOCAL_JAVA_DIR = "java_vm";
|
||||
|
||||
/**
|
||||
* Writes a <code>version.txt</code> file into the specified application directory and
|
||||
* attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly
|
||||
* specified version and relaunch the application.
|
||||
*
|
||||
* @param appdir the directory in which the application is installed.
|
||||
* @param getdownJarName the name of the getdown jar file in the application directory. This is
|
||||
* probably <code>getdown-pro.jar</code> or <code>getdown-retro-pro.jar</code> if you are using
|
||||
* the results of the standard build.
|
||||
* @param newVersion the new version to which Getdown will update when it is executed.
|
||||
*
|
||||
* @return true if the relaunch succeeded, false if we were unable to relaunch due to being on
|
||||
* Windows 9x where we cannot launch subprocesses without waiting around for them to exit,
|
||||
* reading their stdout and stderr all the while. If true is returned, the application may exit
|
||||
* after making this call as it will be upgraded and restarted. If false is returned, the
|
||||
* application should tell the user that they must restart the application manually.
|
||||
*
|
||||
* @exception IOException thrown if we were unable to create the <code>version.txt</code> file
|
||||
* in the supplied application directory. If the version.txt file cannot be created, restarting
|
||||
* Getdown will not cause the application to be upgraded, so the application will have to
|
||||
* resort to telling the user that it is in a bad way.
|
||||
*/
|
||||
public static boolean updateVersionAndRelaunch (
|
||||
File appdir, String getdownJarName, String newVersion)
|
||||
throws IOException
|
||||
{
|
||||
// create the file that instructs Getdown to upgrade
|
||||
File vfile = new File(appdir, "version.txt");
|
||||
PrintStream ps = new PrintStream(new FileOutputStream(vfile));
|
||||
ps.println(newVersion);
|
||||
ps.close();
|
||||
|
||||
// make sure that we can find our getdown.jar file and can safely launch children
|
||||
File pro = new File(appdir, getdownJarName);
|
||||
if (mustMonitorChildren() || !pro.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// do the deed
|
||||
String[] args = new String[] {
|
||||
getJVMPath(appdir), "-jar", pro.toString(), appdir.getPath()
|
||||
};
|
||||
log.info("Running " + StringUtil.join(args, "\n "));
|
||||
try {
|
||||
Runtime.getRuntime().exec(args, null);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to run getdown", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the path to the JVM used to launch this process.
|
||||
*/
|
||||
public static String getJVMPath (File appdir)
|
||||
{
|
||||
return getJVMPath(appdir, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the path to the JVM used to launch this process.
|
||||
*
|
||||
* @param windebug if true we will use java.exe instead of javaw.exe on Windows.
|
||||
*/
|
||||
public static String getJVMPath (File appdir, boolean windebug)
|
||||
{
|
||||
// first look in our application directory for an installed VM
|
||||
String vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR).getPath(), windebug);
|
||||
|
||||
// then fall back to the VM in which we're already running
|
||||
if (vmpath == null) {
|
||||
vmpath = checkJVMPath(System.getProperty("java.home"), windebug);
|
||||
}
|
||||
|
||||
// then throw up our hands and hope for the best
|
||||
if (vmpath == null) {
|
||||
log.warning("Unable to find java [appdir=" + appdir +
|
||||
", java.home=" + System.getProperty("java.home") + "]!");
|
||||
vmpath = "java";
|
||||
}
|
||||
|
||||
// Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched
|
||||
// from /usr/bin/java, and not if launched by directly referring to <java.home>/bin/java,
|
||||
// even though the former is a symlink to the latter! To work around this, see if the
|
||||
// desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead.
|
||||
if (RunAnywhere.isMacOS()) {
|
||||
try {
|
||||
File localVM = new File("/usr/bin/java").getCanonicalFile();
|
||||
if (localVM.equals(new File(vmpath).getCanonicalFile())) {
|
||||
vmpath = "/usr/bin/java";
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to check Mac OS canonical VM path.", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
return vmpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades Getdown by moving an installation managed copy of the Getdown jar file over the
|
||||
* non-managed copy (which would be used to run Getdown itself).
|
||||
*
|
||||
* <p> If the upgrade fails for a variety of reasons, warnings are logged but no other actions
|
||||
* are taken. There's not much else one can do other than try again next time around.
|
||||
*/
|
||||
public static void upgradeGetdown (File oldgd, File curgd, File newgd)
|
||||
{
|
||||
// we assume getdown's jar file size changes with every upgrade, this is not guaranteed,
|
||||
// but in reality it will, and it allows us to avoid pointlessly upgrading getdown every
|
||||
// time the client is updated which is unnecessarily flirting with danger
|
||||
if (!newgd.exists() || newgd.length() == curgd.length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Updating Getdown with " + newgd + "...");
|
||||
|
||||
// clear out any old getdown
|
||||
if (oldgd.exists()) {
|
||||
oldgd.delete();
|
||||
}
|
||||
|
||||
// now try updating using renames
|
||||
if (!curgd.exists() || curgd.renameTo(oldgd)) {
|
||||
if (newgd.renameTo(curgd)) {
|
||||
oldgd.delete(); // yay!
|
||||
try {
|
||||
// copy the moved file back to getdown-dop-new.jar so that we don't end up
|
||||
// downloading another copy next time
|
||||
StreamUtil.copy(new FileInputStream(curgd), new FileOutputStream(newgd));
|
||||
} catch (IOException e) {
|
||||
log.warning("Error copying updated Getdown back: " + e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log.warning("Unable to renameTo(" + oldgd + ").");
|
||||
// try to unfuck ourselves
|
||||
if (!oldgd.renameTo(curgd)) {
|
||||
log.warning("Oh God, why dost thee scorn me so.");
|
||||
}
|
||||
}
|
||||
|
||||
// that didn't work, let's try copying it
|
||||
log.info("Attempting to upgrade by copying over " + curgd + "...");
|
||||
try {
|
||||
StreamUtil.copy(new FileInputStream(newgd), new FileOutputStream(curgd));
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Mayday! Brute force copy method also failed.", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if, on this operating system, we have to stick around and read the stderr from
|
||||
* our children processes to prevent them from filling their output buffers and hanging.
|
||||
*/
|
||||
public static boolean mustMonitorChildren ()
|
||||
{
|
||||
String osname = System.getProperty("os.name").toLowerCase();
|
||||
return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a Java Virtual Machine can be located in the supplied path.
|
||||
*/
|
||||
protected static String checkJVMPath (String vmhome, boolean windebug)
|
||||
{
|
||||
String vmbase = vmhome + File.separator + "bin" + File.separator;
|
||||
String vmpath = vmbase + "java";
|
||||
if (new File(vmpath).exists()) {
|
||||
return vmpath;
|
||||
}
|
||||
|
||||
if (!windebug) {
|
||||
vmpath = vmbase + "javaw.exe";
|
||||
if (new File(vmpath).exists()) {
|
||||
return vmpath;
|
||||
}
|
||||
}
|
||||
|
||||
vmpath = vmbase + "java.exe";
|
||||
if (new File(vmpath).exists()) {
|
||||
return vmpath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.util;
|
||||
|
||||
/**
|
||||
* Accumulates the progress from a number of elements into a single
|
||||
* smoothly progressing progress.
|
||||
*/
|
||||
public class MetaProgressObserver implements ProgressObserver
|
||||
{
|
||||
public MetaProgressObserver (ProgressObserver target, long totalSize)
|
||||
{
|
||||
_target = target;
|
||||
_totalSize = totalSize;
|
||||
}
|
||||
|
||||
public void startElement (long elementSize)
|
||||
{
|
||||
_currentSize += elementSize;
|
||||
_elementSize = elementSize;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void progress (int percent)
|
||||
{
|
||||
if (_target != null && _elementSize > 0) {
|
||||
long position = _currentSize + (100 * percent / _elementSize);
|
||||
_target.progress((int)(100 * position / _totalSize));
|
||||
}
|
||||
}
|
||||
|
||||
protected ProgressObserver _target;
|
||||
protected long _totalSize, _currentSize, _elementSize;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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.util;
|
||||
|
||||
/**
|
||||
* Used to communicate progress.
|
||||
*/
|
||||
public interface ProgressObserver
|
||||
{
|
||||
/**
|
||||
* Informs the observer that we have completed the specified
|
||||
* percentage of the process.
|
||||
*/
|
||||
public void progress (int percent);
|
||||
}
|
||||
Reference in New Issue
Block a user