SDK updater: refactor install into Archive.
This commit is contained in:
@@ -16,8 +16,16 @@
|
|||||||
|
|
||||||
package com.android.sdklib.internal.repository;
|
package com.android.sdklib.internal.repository;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,6 +40,8 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
*/
|
*/
|
||||||
public class Archive implements IDescription {
|
public class Archive implements IDescription {
|
||||||
|
|
||||||
|
public static final int NUM_MONITOR_INC = 100;
|
||||||
|
|
||||||
/** The checksum type. */
|
/** The checksum type. */
|
||||||
public enum ChecksumType {
|
public enum ChecksumType {
|
||||||
/** A SHA1 checksum, represented as a 40-hex string. */
|
/** A SHA1 checksum, represented as a 40-hex string. */
|
||||||
@@ -269,5 +279,450 @@ public class Archive implements IDescription {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install this {@link Archive}s.
|
||||||
|
* The archive will be skipped if it is incompatible.
|
||||||
|
*
|
||||||
|
* @return True if the archive was installed, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean install(String osSdkRoot, ITaskMonitor monitor) {
|
||||||
|
|
||||||
|
File archiveFile = null;
|
||||||
|
try {
|
||||||
|
String name = getParentPackage().getShortDescription();
|
||||||
|
|
||||||
|
// TODO: we should not see this test fail if we had the filter UI above.
|
||||||
|
if (!isCompatible()) {
|
||||||
|
monitor.setResult("Skipping incompatible archive: %1$s", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveFile = downloadFile(monitor);
|
||||||
|
if (archiveFile != null) {
|
||||||
|
if (unarchive(osSdkRoot, archiveFile, monitor)) {
|
||||||
|
monitor.setResult("Installed: %1$s", name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Delete the temp archive if it exists
|
||||||
|
deleteFileOrFolder(archiveFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads an archive and returns the temp file with it.
|
||||||
|
* Caller is responsible with deleting the temp file when done.
|
||||||
|
*/
|
||||||
|
private File downloadFile(ITaskMonitor monitor) {
|
||||||
|
|
||||||
|
File tmpFileToDelete = null;
|
||||||
|
try {
|
||||||
|
File tmpFile = File.createTempFile("sdkupload", ".bin"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
tmpFileToDelete = tmpFile;
|
||||||
|
|
||||||
|
String name = getParentPackage().getShortDescription();
|
||||||
|
String desc = String.format("Downloading %1$s", name);
|
||||||
|
monitor.setDescription(desc);
|
||||||
|
|
||||||
|
String link = getUrl();
|
||||||
|
if (!link.startsWith("http://") //$NON-NLS-1$
|
||||||
|
&& !link.startsWith("https://") //$NON-NLS-1$
|
||||||
|
&& !link.startsWith("ftp://")) { //$NON-NLS-1$
|
||||||
|
// Make the URL absolute by prepending the source
|
||||||
|
Package pkg = getParentPackage();
|
||||||
|
RepoSource src = pkg.getParentSource();
|
||||||
|
if (src == null) {
|
||||||
|
monitor.setResult("Internal error: no source for archive %1$s", name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the URL to the repository.xml and remove the last component
|
||||||
|
// to get the base
|
||||||
|
String repoXml = src.getUrl();
|
||||||
|
int pos = repoXml.lastIndexOf('/');
|
||||||
|
String base = repoXml.substring(0, pos + 1);
|
||||||
|
|
||||||
|
link = base + link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fetchUrl(tmpFile, link, desc, monitor)) {
|
||||||
|
// Fetching was successful, don't delete the temp file here!
|
||||||
|
tmpFileToDelete = null;
|
||||||
|
return tmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
monitor.setResult(e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
deleteFileOrFolder(tmpFileToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually performs the download.
|
||||||
|
* Also computes the SHA1 of the file on the fly.
|
||||||
|
* <p/>
|
||||||
|
* Success is defined as downloading as many bytes as was expected and having the same
|
||||||
|
* SHA1 as expected. Returns true on success or false if any of those checks fail.
|
||||||
|
* <p/>
|
||||||
|
* Increments the monitor by {@link #NUM_MONITOR_INC}.
|
||||||
|
*/
|
||||||
|
private boolean fetchUrl(File tmpFile,
|
||||||
|
String urlString,
|
||||||
|
String description,
|
||||||
|
ITaskMonitor monitor) {
|
||||||
|
URL url;
|
||||||
|
|
||||||
|
description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";
|
||||||
|
|
||||||
|
FileOutputStream os = null;
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
url = new URL(urlString);
|
||||||
|
is = url.openStream();
|
||||||
|
os = new FileOutputStream(tmpFile);
|
||||||
|
|
||||||
|
MessageDigest digester = getChecksumType().getMessageDigest();
|
||||||
|
|
||||||
|
byte[] buf = new byte[65536];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
long total = 0;
|
||||||
|
long size = getSize();
|
||||||
|
long inc = size / NUM_MONITOR_INC;
|
||||||
|
long next_inc = inc;
|
||||||
|
|
||||||
|
long startMs = System.currentTimeMillis();
|
||||||
|
long nextMs = startMs + 2000; // start update after 2 seconds
|
||||||
|
|
||||||
|
while ((n = is.read(buf)) >= 0) {
|
||||||
|
if (n > 0) {
|
||||||
|
os.write(buf, 0, n);
|
||||||
|
digester.update(buf, 0, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeMs = System.currentTimeMillis();
|
||||||
|
|
||||||
|
total += n;
|
||||||
|
if (total >= next_inc) {
|
||||||
|
monitor.incProgress(1);
|
||||||
|
next_inc += inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeMs > nextMs) {
|
||||||
|
long delta = timeMs - startMs;
|
||||||
|
if (total > 0 && delta > 0) {
|
||||||
|
// percent left to download
|
||||||
|
int percent = (int) (100 * total / size);
|
||||||
|
// speed in KiB/s
|
||||||
|
float speed = (float)total / (float)delta * (1000.f / 1024.f);
|
||||||
|
// time left to download the rest at the current KiB/s rate
|
||||||
|
int timeLeft = (speed > 1e-3) ?
|
||||||
|
(int)(((size - total) / 1024.0f) / speed) :
|
||||||
|
0;
|
||||||
|
String timeUnit = "seconds";
|
||||||
|
if (timeLeft > 120) {
|
||||||
|
timeUnit = "minutes";
|
||||||
|
timeLeft /= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.setDescription(description, percent, speed, timeLeft, timeUnit);
|
||||||
|
}
|
||||||
|
nextMs = timeMs + 1000; // update every second
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.isCancelRequested()) {
|
||||||
|
monitor.setResult("Download aborted by user at %1$d bytes.", total);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total != size) {
|
||||||
|
monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
|
||||||
|
size, total);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an hex string from the digest
|
||||||
|
byte[] digest = digester.digest();
|
||||||
|
n = digest.length;
|
||||||
|
String hex = "0123456789abcdef"; //$NON-NLS-1$
|
||||||
|
char[] hexDigest = new char[n * 2];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int b = digest[i] & 0x0FF;
|
||||||
|
hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
|
||||||
|
hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
String expected = getChecksum();
|
||||||
|
String actual = new String(hexDigest);
|
||||||
|
if (!actual.equalsIgnoreCase(expected)) {
|
||||||
|
monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",
|
||||||
|
expected, actual);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
monitor.setResult(e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (os != null) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the given archive in the given folder.
|
||||||
|
*/
|
||||||
|
private boolean unarchive(String osSdkRoot, File archiveFile, ITaskMonitor monitor) {
|
||||||
|
String name = getParentPackage().getShortDescription();
|
||||||
|
String desc = String.format("Installing %1$s", name);
|
||||||
|
monitor.setDescription(desc);
|
||||||
|
|
||||||
|
File destFolder = getParentPackage().getInstallFolder(osSdkRoot);
|
||||||
|
|
||||||
|
File unzipDestFolder = destFolder;
|
||||||
|
File renamedDestFolder = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If this folder already exists, unzip in a temporary folder and then move/unlink.
|
||||||
|
if (destFolder.exists()) {
|
||||||
|
// Find a new temp folder that doesn't exist yet
|
||||||
|
unzipDestFolder = findTempFolder(destFolder, "new"); //$NON-NLS-1$
|
||||||
|
|
||||||
|
if (unzipDestFolder == null) {
|
||||||
|
// this should not seriously happen.
|
||||||
|
monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",
|
||||||
|
destFolder.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unzipDestFolder.mkdirs()) {
|
||||||
|
monitor.setResult("Failed to create temp directory %1$s",
|
||||||
|
unzipDestFolder.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unzipFolder(archiveFile, getSize(), unzipDestFolder, desc, monitor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzipDestFolder != destFolder) {
|
||||||
|
// Swap the old folder by the new one.
|
||||||
|
// Both original folders will be deleted in the finally clause below.
|
||||||
|
renamedDestFolder = findTempFolder(destFolder, "old"); //$NON-NLS-1$
|
||||||
|
if (renamedDestFolder == null) {
|
||||||
|
// this should not seriously happen.
|
||||||
|
monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",
|
||||||
|
destFolder.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!destFolder.renameTo(renamedDestFolder)) {
|
||||||
|
monitor.setResult("Failed to rename directory %1$s to %2$s",
|
||||||
|
destFolder.getPath(), renamedDestFolder.getPath());
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!unzipDestFolder.renameTo(destFolder)) {
|
||||||
|
monitor.setResult("Failed to rename directory %1$s to %2$s",
|
||||||
|
unzipDestFolder.getPath(), destFolder.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Cleanup if the unzip folder is still set.
|
||||||
|
deleteFileOrFolder(renamedDestFolder);
|
||||||
|
if (unzipDestFolder != destFolder) {
|
||||||
|
deleteFileOrFolder(unzipDestFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unzipFolder(File archiveFile,
|
||||||
|
long compressedSize,
|
||||||
|
File unzipDestFolder,
|
||||||
|
String description,
|
||||||
|
ITaskMonitor monitor) {
|
||||||
|
|
||||||
|
description += " (%1$d%%)";
|
||||||
|
|
||||||
|
FileInputStream fis = null;
|
||||||
|
ZipInputStream zis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(archiveFile);
|
||||||
|
zis = new ZipInputStream(fis);
|
||||||
|
|
||||||
|
// To advance the percent and the progress bar, we don't know the number of
|
||||||
|
// items left to unzip. However we know the size of the archive and the size of
|
||||||
|
// each uncompressed item. The zip file format overhead is negligible so that's
|
||||||
|
// a good approximation.
|
||||||
|
long incStep = compressedSize / NUM_MONITOR_INC;
|
||||||
|
long incTotal = 0;
|
||||||
|
long incCurr = 0;
|
||||||
|
int lastPercent = 0;
|
||||||
|
|
||||||
|
byte[] buf = new byte[65536];
|
||||||
|
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
|
||||||
|
String name = entry.getName();
|
||||||
|
|
||||||
|
// ZipFile entries should have forward slashes, but not all Zip
|
||||||
|
// implementations can be expected to do that.
|
||||||
|
name = name.replace('\\', '/');
|
||||||
|
|
||||||
|
File destFile = new File(unzipDestFolder, name);
|
||||||
|
|
||||||
|
if (name.endsWith("/")) { //$NON-NLS-1$
|
||||||
|
// Create directory if it doesn't exist yet. This allows us to create
|
||||||
|
// empty directories.
|
||||||
|
if (!destFile.isDirectory() && !destFile.mkdirs()) {
|
||||||
|
monitor.setResult("Failed to create temp directory %1$s",
|
||||||
|
destFile.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (name.indexOf('/') != -1) {
|
||||||
|
// Otherwise it's a file in a sub-directory.
|
||||||
|
// Make sure the parent directory has been created.
|
||||||
|
File parentDir = destFile.getParentFile();
|
||||||
|
if (!parentDir.isDirectory()) {
|
||||||
|
if (!parentDir.mkdirs()) {
|
||||||
|
monitor.setResult("Failed to create temp directory %1$s",
|
||||||
|
parentDir.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(destFile);
|
||||||
|
int n;
|
||||||
|
while ((n = zis.read(buf)) != -1) {
|
||||||
|
if (n > 0) {
|
||||||
|
fos.write(buf, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (fos != null) {
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment progress bar to match. We update only between files.
|
||||||
|
for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
|
||||||
|
monitor.incProgress(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int percent = (int) (100 * incTotal / compressedSize);
|
||||||
|
if (percent != lastPercent) {
|
||||||
|
monitor.setDescription(description, percent);
|
||||||
|
lastPercent = percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.isCancelRequested()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
monitor.setResult("Unzip failed: %1$s", e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (zis != null) {
|
||||||
|
try {
|
||||||
|
zis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a temp folder which name is similar to the one of the ideal folder
|
||||||
|
* and with a ".tmpN" appended.
|
||||||
|
* <p/>
|
||||||
|
* This operation is not atomic so there's no guarantee the folder can't get
|
||||||
|
* created in between. This is however unlikely and the caller can assume the
|
||||||
|
* returned folder does not exist yet.
|
||||||
|
* <p/>
|
||||||
|
* Returns null if no such folder can be found (e.g. if all candidates exist),
|
||||||
|
* which is rather unlikely.
|
||||||
|
*/
|
||||||
|
private File findTempFolder(File idealFolder, String suffix) {
|
||||||
|
String basePath = idealFolder.getPath();
|
||||||
|
|
||||||
|
for (int i = 1; i < 100; i++) {
|
||||||
|
File folder = new File(String.format("%1$s.%2$s%3$02d", basePath, suffix, i)); //$NON-NLS-1$
|
||||||
|
if (!folder.exists()) {
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a file or a directory.
|
||||||
|
* Directories are deleted recursively.
|
||||||
|
* The argument can be null.
|
||||||
|
*/
|
||||||
|
private void deleteFileOrFolder(File fileOrFolder) {
|
||||||
|
if (fileOrFolder != null) {
|
||||||
|
if (fileOrFolder.isDirectory()) {
|
||||||
|
// Must delete content recursively first
|
||||||
|
for (File item : fileOrFolder.listFiles()) {
|
||||||
|
deleteFileOrFolder(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fileOrFolder.delete()) {
|
||||||
|
fileOrFolder.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import com.android.sdklib.ISdkLog;
|
|||||||
import com.android.sdklib.internal.repository.Archive;
|
import com.android.sdklib.internal.repository.Archive;
|
||||||
import com.android.sdklib.internal.repository.ITask;
|
import com.android.sdklib.internal.repository.ITask;
|
||||||
import com.android.sdklib.internal.repository.ITaskMonitor;
|
import com.android.sdklib.internal.repository.ITaskMonitor;
|
||||||
import com.android.sdklib.internal.repository.Package;
|
|
||||||
import com.android.sdklib.internal.repository.RepoSource;
|
import com.android.sdklib.internal.repository.RepoSource;
|
||||||
import com.android.sdklib.repository.SdkRepository;
|
import com.android.sdklib.repository.SdkRepository;
|
||||||
|
|
||||||
@@ -42,26 +41,16 @@ import org.eclipse.swt.widgets.Display;
|
|||||||
import org.eclipse.swt.widgets.List;
|
import org.eclipse.swt.widgets.List;
|
||||||
import org.eclipse.swt.widgets.Shell;
|
import org.eclipse.swt.widgets.Shell;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.net.URL;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the private implementation of the UpdateWindow.
|
* This is the private implementation of the UpdateWindow.
|
||||||
*/
|
*/
|
||||||
public class UpdaterWindowImpl {
|
public class UpdaterWindowImpl {
|
||||||
|
|
||||||
private static final int NUM_MONITOR_INC = 100;
|
|
||||||
|
|
||||||
/** Internal data shared between the window and its pages. */
|
/** Internal data shared between the window and its pages. */
|
||||||
private final UpdaterData mUpdaterData = new UpdaterData();
|
private final UpdaterData mUpdaterData = new UpdaterData();
|
||||||
/** The array of pages instances. Only one is visible at a time. */
|
/** The array of pages instances. Only one is visible at a time. */
|
||||||
@@ -351,7 +340,7 @@ public class UpdaterWindowImpl {
|
|||||||
mTaskFactory.start("Installing Archives", new ITask() {
|
mTaskFactory.start("Installing Archives", new ITask() {
|
||||||
public void run(ITaskMonitor monitor) {
|
public void run(ITaskMonitor monitor) {
|
||||||
|
|
||||||
final int progressPerArchive = 2 * NUM_MONITOR_INC + 10;
|
final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
|
||||||
monitor.setProgressMax(archives.size() * progressPerArchive);
|
monitor.setProgressMax(archives.size() * progressPerArchive);
|
||||||
monitor.setDescription("Preparing to install archives");
|
monitor.setDescription("Preparing to install archives");
|
||||||
|
|
||||||
@@ -359,34 +348,20 @@ public class UpdaterWindowImpl {
|
|||||||
for (Archive archive : archives) {
|
for (Archive archive : archives) {
|
||||||
|
|
||||||
int nextProgress = monitor.getProgress() + progressPerArchive;
|
int nextProgress = monitor.getProgress() + progressPerArchive;
|
||||||
File archiveFile = null;
|
|
||||||
try {
|
try {
|
||||||
if (monitor.isCancelRequested()) {
|
if (monitor.isCancelRequested()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String name = archive.getParentPackage().getShortDescription();
|
if (archive.install(mUpdaterData.getOsSdkRoot(), monitor)) {
|
||||||
|
numInstalled++;
|
||||||
// TODO: we should not see this test fail if we had the filter UI above.
|
|
||||||
if (!archive.isCompatible()) {
|
|
||||||
monitor.setResult("Skipping incompatible archive: %1$s", name);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
archiveFile = downloadArchive(archive, monitor);
|
|
||||||
if (archiveFile != null) {
|
|
||||||
if (installArchive(archive, archiveFile, monitor)) {
|
|
||||||
monitor.setResult("Installed: %1$s", name);
|
|
||||||
numInstalled++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// Display anything unexpected in the monitor.
|
// Display anything unexpected in the monitor.
|
||||||
monitor.setResult("Unexpected Error: %1$s", t.getMessage());
|
monitor.setResult("Unexpected Error: %1$s", t.getMessage());
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Delete the temp archive if it exists
|
|
||||||
deleteFileOrFolder(archiveFile);
|
|
||||||
|
|
||||||
// Always move the progress bar to the desired position.
|
// Always move the progress bar to the desired position.
|
||||||
// This allows internal methods to not have to care in case
|
// This allows internal methods to not have to care in case
|
||||||
@@ -406,417 +381,6 @@ public class UpdaterWindowImpl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads an archive and returns the temp file with it.
|
|
||||||
* Caller is responsible with deleting the temp file when done.
|
|
||||||
*/
|
|
||||||
private File downloadArchive(Archive archive, ITaskMonitor monitor) {
|
|
||||||
|
|
||||||
File tmpFileToDelete = null;
|
|
||||||
try {
|
|
||||||
File tmpFile = File.createTempFile("sdkupload", ".bin"); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
tmpFileToDelete = tmpFile;
|
|
||||||
|
|
||||||
String name = archive.getParentPackage().getShortDescription();
|
|
||||||
String desc = String.format("Downloading %1$s", name);
|
|
||||||
monitor.setDescription(desc);
|
|
||||||
|
|
||||||
String link = archive.getUrl();
|
|
||||||
if (!link.startsWith("http://") //$NON-NLS-1$
|
|
||||||
&& !link.startsWith("https://") //$NON-NLS-1$
|
|
||||||
&& !link.startsWith("ftp://")) { //$NON-NLS-1$
|
|
||||||
// Make the URL absolute by prepending the source
|
|
||||||
Package pkg = archive.getParentPackage();
|
|
||||||
RepoSource src = pkg.getParentSource();
|
|
||||||
if (src == null) {
|
|
||||||
monitor.setResult("Internal error: no source for archive %1$s", name);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// take the URL to the repository.xml and remove the last component
|
|
||||||
// to get the base
|
|
||||||
String repoXml = src.getUrl();
|
|
||||||
int pos = repoXml.lastIndexOf('/');
|
|
||||||
String base = repoXml.substring(0, pos + 1);
|
|
||||||
|
|
||||||
link = base + link;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fetchUrl(tmpFile, archive, link, desc, monitor)) {
|
|
||||||
// Fetching was successful, don't delete the temp file here!
|
|
||||||
tmpFileToDelete = null;
|
|
||||||
return tmpFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
monitor.setResult(e.getMessage());
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
deleteFileOrFolder(tmpFileToDelete);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actually performs the download.
|
|
||||||
* Also computes the SHA1 of the file on the fly.
|
|
||||||
* <p/>
|
|
||||||
* Success is defined as downloading as many bytes as was expected and having the same
|
|
||||||
* SHA1 as expected. Returns true on success or false if any of those checks fail.
|
|
||||||
* <p/>
|
|
||||||
* Increments the monitor by {@link #NUM_MONITOR_INC}.
|
|
||||||
*/
|
|
||||||
private boolean fetchUrl(File tmpFile,
|
|
||||||
Archive archive,
|
|
||||||
String urlString,
|
|
||||||
String description,
|
|
||||||
ITaskMonitor monitor) {
|
|
||||||
URL url;
|
|
||||||
|
|
||||||
description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";
|
|
||||||
|
|
||||||
FileOutputStream os = null;
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
url = new URL(urlString);
|
|
||||||
is = url.openStream();
|
|
||||||
os = new FileOutputStream(tmpFile);
|
|
||||||
|
|
||||||
MessageDigest digester = archive.getChecksumType().getMessageDigest();
|
|
||||||
|
|
||||||
byte[] buf = new byte[65536];
|
|
||||||
int n;
|
|
||||||
|
|
||||||
long total = 0;
|
|
||||||
long size = archive.getSize();
|
|
||||||
long inc = size / NUM_MONITOR_INC;
|
|
||||||
long next_inc = inc;
|
|
||||||
|
|
||||||
long startMs = System.currentTimeMillis();
|
|
||||||
long nextMs = startMs + 2000; // start update after 2 seconds
|
|
||||||
|
|
||||||
while ((n = is.read(buf)) >= 0) {
|
|
||||||
if (n > 0) {
|
|
||||||
os.write(buf, 0, n);
|
|
||||||
digester.update(buf, 0, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
long timeMs = System.currentTimeMillis();
|
|
||||||
|
|
||||||
total += n;
|
|
||||||
if (total >= next_inc) {
|
|
||||||
monitor.incProgress(1);
|
|
||||||
next_inc += inc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeMs > nextMs) {
|
|
||||||
long delta = timeMs - startMs;
|
|
||||||
if (total > 0 && delta > 0) {
|
|
||||||
// percent left to download
|
|
||||||
int percent = (int) (100 * total / size);
|
|
||||||
// speed in KiB/s
|
|
||||||
float speed = (float)total / (float)delta * (1000.f / 1024.f);
|
|
||||||
// time left to download the rest at the current KiB/s rate
|
|
||||||
int timeLeft = (speed > 1e-3) ?
|
|
||||||
(int)(((size - total) / 1024.0f) / speed) :
|
|
||||||
0;
|
|
||||||
String timeUnit = "seconds";
|
|
||||||
if (timeLeft > 120) {
|
|
||||||
timeUnit = "minutes";
|
|
||||||
timeLeft /= 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor.setDescription(description, percent, speed, timeLeft, timeUnit);
|
|
||||||
}
|
|
||||||
nextMs = timeMs + 1000; // update every second
|
|
||||||
}
|
|
||||||
|
|
||||||
if (monitor.isCancelRequested()) {
|
|
||||||
monitor.setResult("Download aborted by user at %1$d bytes.", total);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total != size) {
|
|
||||||
monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
|
|
||||||
size, total);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an hex string from the digest
|
|
||||||
byte[] digest = digester.digest();
|
|
||||||
n = digest.length;
|
|
||||||
String hex = "0123456789abcdef"; //$NON-NLS-1$
|
|
||||||
char[] hexDigest = new char[n * 2];
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
int b = digest[i] & 0x0FF;
|
|
||||||
hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
|
|
||||||
hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
String expected = archive.getChecksum();
|
|
||||||
String actual = new String(hexDigest);
|
|
||||||
if (!actual.equalsIgnoreCase(expected)) {
|
|
||||||
monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",
|
|
||||||
expected, actual);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
monitor.setResult(e.getMessage());
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (os != null) {
|
|
||||||
try {
|
|
||||||
os.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is != null) {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Install the given archive in the given folder.
|
|
||||||
*/
|
|
||||||
private boolean installArchive(Archive archive, File archiveFile, ITaskMonitor monitor) {
|
|
||||||
String name = archive.getParentPackage().getShortDescription();
|
|
||||||
String desc = String.format("Installing %1$s", name);
|
|
||||||
monitor.setDescription(desc);
|
|
||||||
|
|
||||||
File destFolder = archive.getParentPackage().getInstallFolder(mUpdaterData.getOsSdkRoot());
|
|
||||||
|
|
||||||
File unzipDestFolder = destFolder;
|
|
||||||
File renamedDestFolder = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// If this folder already exists, unzip in a temporary folder and then move/unlink.
|
|
||||||
if (destFolder.exists()) {
|
|
||||||
// Find a new temp folder that doesn't exist yet
|
|
||||||
unzipDestFolder = findTempFolder(destFolder, "new"); //$NON-NLS-1$
|
|
||||||
|
|
||||||
if (unzipDestFolder == null) {
|
|
||||||
// this should not seriously happen.
|
|
||||||
monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",
|
|
||||||
destFolder.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!unzipDestFolder.mkdirs()) {
|
|
||||||
monitor.setResult("Failed to create temp directory %1$s",
|
|
||||||
unzipDestFolder.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!unzipFolder(archiveFile, archive.getSize(), unzipDestFolder, desc, monitor)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unzipDestFolder != destFolder) {
|
|
||||||
// Swap the old folder by the new one.
|
|
||||||
// Both original folders will be deleted in the finally clause below.
|
|
||||||
renamedDestFolder = findTempFolder(destFolder, "old"); //$NON-NLS-1$
|
|
||||||
if (renamedDestFolder == null) {
|
|
||||||
// this should not seriously happen.
|
|
||||||
monitor.setResult("Failed to find a suitable temp directory similar to %1$s.",
|
|
||||||
destFolder.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destFolder.renameTo(renamedDestFolder)) {
|
|
||||||
monitor.setResult("Failed to rename directory %1$s to %2$s",
|
|
||||||
destFolder.getPath(), renamedDestFolder.getPath());
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!unzipDestFolder.renameTo(destFolder)) {
|
|
||||||
monitor.setResult("Failed to rename directory %1$s to %2$s",
|
|
||||||
unzipDestFolder.getPath(), destFolder.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// Cleanup if the unzip folder is still set.
|
|
||||||
deleteFileOrFolder(renamedDestFolder);
|
|
||||||
if (unzipDestFolder != destFolder) {
|
|
||||||
deleteFileOrFolder(unzipDestFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean unzipFolder(File archiveFile,
|
|
||||||
long compressedSize,
|
|
||||||
File unzipDestFolder,
|
|
||||||
String description,
|
|
||||||
ITaskMonitor monitor) {
|
|
||||||
|
|
||||||
description += " (%1$d%%)";
|
|
||||||
|
|
||||||
FileInputStream fis = null;
|
|
||||||
ZipInputStream zis = null;
|
|
||||||
try {
|
|
||||||
fis = new FileInputStream(archiveFile);
|
|
||||||
zis = new ZipInputStream(fis);
|
|
||||||
|
|
||||||
// To advance the percent and the progress bar, we don't know the number of
|
|
||||||
// items left to unzip. However we know the size of the archive and the size of
|
|
||||||
// each uncompressed item. The zip file format overhead is negligible so that's
|
|
||||||
// a good approximation.
|
|
||||||
long incStep = compressedSize / NUM_MONITOR_INC;
|
|
||||||
long incTotal = 0;
|
|
||||||
long incCurr = 0;
|
|
||||||
int lastPercent = 0;
|
|
||||||
|
|
||||||
byte[] buf = new byte[65536];
|
|
||||||
|
|
||||||
ZipEntry entry;
|
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
|
||||||
|
|
||||||
String name = entry.getName();
|
|
||||||
|
|
||||||
// ZipFile entries should have forward slashes, but not all Zip
|
|
||||||
// implementations can be expected to do that.
|
|
||||||
name = name.replace('\\', '/');
|
|
||||||
|
|
||||||
File destFile = new File(unzipDestFolder, name);
|
|
||||||
|
|
||||||
if (name.endsWith("/")) { //$NON-NLS-1$
|
|
||||||
// Create directory if it doesn't exist yet. This allows us to create
|
|
||||||
// empty directories.
|
|
||||||
if (!destFile.isDirectory() && !destFile.mkdirs()) {
|
|
||||||
monitor.setResult("Failed to create temp directory %1$s",
|
|
||||||
destFile.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else if (name.indexOf('/') != -1) {
|
|
||||||
// Otherwise it's a file in a sub-directory.
|
|
||||||
// Make sure the parent directory has been created.
|
|
||||||
File parentDir = destFile.getParentFile();
|
|
||||||
if (!parentDir.isDirectory()) {
|
|
||||||
if (!parentDir.mkdirs()) {
|
|
||||||
monitor.setResult("Failed to create temp directory %1$s",
|
|
||||||
parentDir.getPath());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileOutputStream fos = null;
|
|
||||||
try {
|
|
||||||
fos = new FileOutputStream(destFile);
|
|
||||||
int n;
|
|
||||||
while ((n = zis.read(buf)) != -1) {
|
|
||||||
if (n > 0) {
|
|
||||||
fos.write(buf, 0, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (fos != null) {
|
|
||||||
fos.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment progress bar to match. We update only between files.
|
|
||||||
for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
|
|
||||||
monitor.incProgress(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int percent = (int) (100 * incTotal / compressedSize);
|
|
||||||
if (percent != lastPercent) {
|
|
||||||
monitor.setDescription(description, percent);
|
|
||||||
lastPercent = percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (monitor.isCancelRequested()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
monitor.setResult("Unzip failed: %1$s", e.getMessage());
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (zis != null) {
|
|
||||||
try {
|
|
||||||
zis.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fis != null) {
|
|
||||||
try {
|
|
||||||
fis.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a temp folder which name is similar to the one of the ideal folder
|
|
||||||
* and with a ".tmpN" appended.
|
|
||||||
* <p/>
|
|
||||||
* This operation is not atomic so there's no guarantee the folder can't get
|
|
||||||
* created in between. This is however unlikely and the caller can assume the
|
|
||||||
* returned folder does not exist yet.
|
|
||||||
* <p/>
|
|
||||||
* Returns null if no such folder can be found (e.g. if all candidates exist),
|
|
||||||
* which is rather unlikely.
|
|
||||||
*/
|
|
||||||
private File findTempFolder(File idealFolder, String suffix) {
|
|
||||||
String basePath = idealFolder.getPath();
|
|
||||||
|
|
||||||
for (int i = 1; i < 100; i++) {
|
|
||||||
File folder = new File(String.format("%1$s.%2$s%3$02d", basePath, suffix, i)); //$NON-NLS-1$
|
|
||||||
if (!folder.exists()) {
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a file or a directory.
|
|
||||||
* Directories are deleted recursively.
|
|
||||||
* The argument can be null.
|
|
||||||
*/
|
|
||||||
private void deleteFileOrFolder(File fileOrFolder) {
|
|
||||||
if (fileOrFolder != null) {
|
|
||||||
if (fileOrFolder.isDirectory()) {
|
|
||||||
// Must delete content recursively first
|
|
||||||
for (File item : fileOrFolder.listFiles()) {
|
|
||||||
deleteFileOrFolder(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fileOrFolder.delete()) {
|
|
||||||
fileOrFolder.deleteOnExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of hiding from SWT Designer
|
// End of hiding from SWT Designer
|
||||||
//$hide<<$
|
//$hide<<$
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user