aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2010-12-29 14:57:38 -0800
committerRaphael Moll <ralf@android.com>2011-01-03 21:56:58 -0800
commit2b53c912359ac3016f20b7b19cf893acbddb82cc (patch)
tree0b653df61c3439a4885963d9f9e4ca27439ef557
parent7cb26434691539a9091e618e623766be5b57dc26 (diff)
downloadsdk-2b53c912359ac3016f20b7b19cf893acbddb82cc.zip
sdk-2b53c912359ac3016f20b7b19cf893acbddb82cc.tar.gz
sdk-2b53c912359ac3016f20b7b19cf893acbddb82cc.tar.bz2
SDK Manager: Rework install logic.
This should get rid of the annoying behavior on Windows that prevent the "folder swap" operation due to the folders being locked. Cf public issue 4410. High level summary of the issue and the fix: the old behavior was to unzip in a temp folder, then rename the old folder to another temp file and finally rename the new folder at the desired location. This fails typically when there is a file indexer (e.g. anti-virus) scanning the new folder so we can't move that folder. The new logic is to try to move the old folder first into a temp folder. If the fail move, we have a lock on the old folder and ask the user to fix it manually. They probably have a file opened and it's a legit issue to report. Once that succeeded we can directly unzip the archive into the final destination without using a temp unzip location, thus avoiding the common "indexer in progress" issue. In case the unzip operation fails, we try to copy (not move) the old folder back. Change-Id: I5ed67ff626532fe7cc48a45e87d1dbaf6954f28a
-rw-r--r--changes.txt3
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java (renamed from sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java)3
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java2
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java370
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java138
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java8
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java25
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java12
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java8
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java1
14 files changed, 408 insertions, 178 deletions
diff --git a/changes.txt b/changes.txt
index b06aa68..1173d60 100644
--- a/changes.txt
+++ b/changes.txt
@@ -6,9 +6,10 @@ Revision 10:
* ignore classes extending android.app.backup.BackupAgentHelper
- Ant lib rules now allow for overriding java.encoding, java.source, and java.target
- Default encoding for Ant javac is now UTF-8
+- Fix "folder locked" errors when installing packages in SDK Manager on Windows.
Revision 9:
-- fix packaging issue that broke draw9patch
+- Fix packaging issue that broke draw9patch
- Ant build rules will now check the Ant version
Revision 8:
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java
index 241f30f..3fab9ce 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdbWrapper.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.sdkuilib.internal.repository;
+package com.android.sdklib.internal.repository;
import com.android.sdklib.SdkConstants;
-import com.android.sdklib.internal.repository.ITaskMonitor;
import java.io.BufferedReader;
import java.io.File;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
index 39750d4..2a58a89 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
@@ -242,13 +242,11 @@ public class AddonPackage extends Package
* has this add-ons installed, we'll use that one.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
// First find if this add-on is already installed. If so, reuse the same directory.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
index 7739f8c..ce3372c 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
@@ -364,7 +364,7 @@ public class Archive implements IDescription, Comparable<Archive> {
*/
public void deleteLocal() {
if (isLocal()) {
- new ArchiveInstaller().deleteFileOrFolder(new File(getLocalOsPath()));
+ OsHelper.deleteFileOrFolder(new File(getLocalOsPath()));
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java
index b98f731..f3fd347 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,9 @@ import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Properties;
+import java.util.Set;
/**
@@ -88,7 +90,7 @@ public class ArchiveInstaller {
if (unarchive(archive, osSdkRoot, archiveFile, sdkManager, monitor)) {
monitor.setResult("Installed %1$s", name);
// Delete the temp archive if it exists, only on success
- deleteFileOrFolder(archiveFile);
+ OsHelper.deleteFileOrFolder(archiveFile);
return true;
}
}
@@ -147,7 +149,7 @@ public class ArchiveInstaller {
File tmpFolder = getTempFolder(osSdkRoot);
if (!tmpFolder.isDirectory()) {
if (tmpFolder.isFile()) {
- deleteFileOrFolder(tmpFolder);
+ OsHelper.deleteFileOrFolder(tmpFolder);
}
if (!tmpFolder.mkdirs()) {
monitor.setResult("Failed to create directory %1$s", tmpFolder.getPath());
@@ -176,7 +178,7 @@ public class ArchiveInstaller {
// Existing file is either of different size or content.
// TODO: continue download when we support continue mode.
// Right now, let's simply remove the file and start over.
- deleteFileOrFolder(tmpFile);
+ OsHelper.deleteFileOrFolder(tmpFile);
}
if (fetchUrl(archive, tmpFile, link, desc, monitor)) {
@@ -184,8 +186,8 @@ public class ArchiveInstaller {
return tmpFile;
} else {
// Delete the temp file if we aborted the download
- // TODO: disable this when we want to support partial downloads!
- deleteFileOrFolder(tmpFile);
+ // TODO: disable this when we want to support partial downloads.
+ OsHelper.deleteFileOrFolder(tmpFile);
return null;
}
}
@@ -391,45 +393,42 @@ public class ArchiveInstaller {
monitor.setDescription(pkgDesc);
monitor.setResult(pkgDesc);
- // We always unzip in a temp folder which name depends on the package type
- // (e.g. addon, tools, etc.) and then move the folder to the destination folder.
+ // Ideally we want to always unzip in a temp folder which name depends on the package
+ // type (e.g. addon, tools, etc.) and then move the folder to the destination folder.
// If the destination folder exists, it will be renamed and deleted at the very
- // end if everything succeeded.
+ // end if everything succeeded. This provides a nice atomic swap and should leave the
+ // original folder untouched in case something wrong (e.g. program crash) in the
+ // middle of the unzip operation.
+ //
+ // However that doesn't work on Windows, we always end up not being able to move the
+ // new folder. There are actually 2 cases:
+ // A- A process such as a the explorer is locking the *old* folder or a file inside
+ // (e.g. adb.exe)
+ // In this case we really shouldn't be tried to work around it and we need to let
+ // the user know and let it close apps that access that folder.
+ // B- A process is locking the *new* folder. Very often this turns to be a file indexer
+ // or an anti-virus that is busy scanning the new folder that we just unzipped.
+ //
+ // So we're going to change the strategy:
+ // 1- Try to move the old folder to a temp/old folder. This might fail in case of issue A.
+ // Note: for platform-tools, we can try killing adb first.
+ // If it still fails, we do nothing and ask the user to terminate apps that can be
+ // locking that folder.
+ // 2- Once the old folder is out of the way, we unzip the archive directly into the
+ // optimal new location. We no longer unzip it in a temp folder and move it since we
+ // know that's what fails in most of the cases.
+ // 3- If the unzip fails, remove everything and try to restore the old folder by doing
+ // a *copy* in place and not a folder move (which will likely fail too).
String pkgKind = pkg.getClass().getSimpleName();
File destFolder = null;
- File unzipDestFolder = null;
File oldDestFolder = null;
try {
- // Find a new temp folder that doesn't exist yet
- unzipDestFolder = createTempFolder(osSdkRoot, pkgKind, "new"); //$NON-NLS-1$
+ // -0- Compute destination directory and check install pre-conditions
- if (unzipDestFolder == null) {
- // this should not seriously happen.
- monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
- return false;
- }
-
- if (!unzipDestFolder.mkdirs()) {
- monitor.setResult("Failed to create directory %1$s", unzipDestFolder.getPath());
- return false;
- }
-
- String[] zipRootFolder = new String[] { null };
- if (!unzipFolder(archiveFile, archive.getSize(),
- unzipDestFolder, pkgDesc,
- zipRootFolder, monitor)) {
- return false;
- }
-
- if (!generateSourceProperties(archive, unzipDestFolder)) {
- return false;
- }
-
- // Compute destination directory
- destFolder = pkg.getInstallFolder(osSdkRoot, zipRootFolder[0], sdkManager);
+ destFolder = pkg.getInstallFolder(osSdkRoot, sdkManager);
if (destFolder == null) {
// this should not seriously happen.
@@ -442,105 +441,142 @@ public class ArchiveInstaller {
return false;
}
- // Swap the old folder by the new one.
- // We have 2 "folder rename" (aka moves) to do.
- // They must both succeed in the right order.
- boolean move1done = false;
- boolean move2done = false;
- while (!move1done || !move2done) {
- File renameFailedForDir = null;
-
- // Case where the dest dir already exists
- if (!move1done) {
- if (destFolder.isDirectory()) {
- // Create a new temp/old dir
- if (oldDestFolder == null) {
- oldDestFolder = createTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
- }
- if (oldDestFolder == null) {
- // this should not seriously happen.
- monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
- return false;
- }
+ // -1- move old folder.
- // try to move the current dest dir to the temp/old one
- if (!destFolder.renameTo(oldDestFolder)) {
- monitor.setResult("Failed to rename directory %1$s to %2$s.",
- destFolder.getPath(), oldDestFolder.getPath());
- renameFailedForDir = destFolder;
+ if (destFolder.exists()) {
+ // Create a new temp/old dir
+ if (oldDestFolder == null) {
+ oldDestFolder = getNewTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
+ }
+ if (oldDestFolder == null) {
+ // this should not seriously happen.
+ monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
+ return false;
+ }
+
+ // Try to move the current dest dir to the temp/old one. Tell the user if it failed.
+ while(true) {
+ if (!moveFolder(destFolder, oldDestFolder)) {
+ monitor.setResult("Failed to rename directory %1$s to %2$s.",
+ destFolder.getPath(), oldDestFolder.getPath());
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ String msg = String.format(
+ "-= Warning ! =-\n" +
+ "A folder failed to be moved. On Windows this " +
+ "typically means that a program is using that folder (for " +
+ "example Windows Explorer or your anti-virus software.)\n" +
+ "Please momentarily deactivate your anti-virus software or " +
+ "close any running programs that may be accessing the " +
+ "directory '%1$s'.\n" +
+ "When ready, press YES to try again.",
+ destFolder.getPath());
+
+ if (monitor.displayPrompt("SDK Manager: failed to install", msg)) {
+ // loop, trying to rename the temp dir into the destination
+ continue;
+ } else {
+ return false;
+ }
}
}
-
- move1done = (renameFailedForDir == null);
+ break;
}
+ }
- // Case where there's no dest dir or we successfully moved it to temp/old
- // We now try to move the temp/unzip to the dest dir
- if (move1done && !move2done) {
- if (renameFailedForDir == null && !unzipDestFolder.renameTo(destFolder)) {
- monitor.setResult("Failed to rename directory %1$s to %2$s",
- unzipDestFolder.getPath(), destFolder.getPath());
- renameFailedForDir = unzipDestFolder;
- }
+ assert !destFolder.exists();
- move2done = (renameFailedForDir == null);
- }
+ // -2- Unzip new content directly in place.
- if (renameFailedForDir != null) {
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
-
- String msg = String.format(
- "-= Warning ! =-\n" +
- "A folder failed to be renamed or moved. On Windows this " +
- "typically means that a program is using that folder (for example " +
- "Windows Explorer or your anti-virus software.)\n" +
- "Please momentarily deactivate your anti-virus software.\n" +
- "Please also close any running programs that may be accessing " +
- "the directory '%1$s'.\n" +
- "When ready, press YES to try again.",
- renameFailedForDir.getPath());
-
- if (monitor.displayPrompt("SDK Manager: failed to install", msg)) {
- // loop, trying to rename the temp dir into the destination
- continue;
- }
+ if (!destFolder.mkdirs()) {
+ monitor.setResult("Failed to create directory %1$s", destFolder.getPath());
+ return false;
+ }
- }
- return false;
- }
- break;
+ if (!unzipFolder(archiveFile, archive.getSize(), destFolder, pkgDesc, monitor)) {
+ return false;
+ }
+
+ if (!generateSourceProperties(archive, destFolder)) {
+ monitor.setResult("Failed to generate source.properties in directory %1$s",
+ destFolder.getPath());
+ return false;
}
- unzipDestFolder = null;
success = true;
pkg.postInstallHook(archive, monitor, destFolder);
return true;
} finally {
- // Cleanup if the unzip folder is still set.
- deleteFileOrFolder(oldDestFolder);
- deleteFileOrFolder(unzipDestFolder);
-
- // In case of failure, we call the postInstallHool with a null directory
if (!success) {
+ // In case of failure, we try to restore the old folder content.
+ if (oldDestFolder != null) {
+ restoreFolder(oldDestFolder, destFolder);
+ }
+
+ // We also call the postInstallHool with a null directory to give a chance
+ // to the archive to cleanup after preInstallHook.
pkg.postInstallHook(archive, monitor, null /*installDir*/);
}
+
+ // Cleanup if the unzip folder is still set.
+ OsHelper.deleteFileOrFolder(oldDestFolder);
+ }
+ }
+
+ /**
+ * Tries to rename/move a folder.
+ * <p/>
+ * Contract:
+ * <ul>
+ * <li> When we start, oldDir must exist and be a directory. newDir must not exist. </li>
+ * <li> On successful completion, oldDir must not exists.
+ * newDir must exist and have the same content. </li>
+ * <li> On failure completion, oldDir must have the same content as before.
+ * newDir must not exist. </li>
+ * </ul>
+ * <p/>
+ * The simple "rename" operation on a folder can typically fail on Windows for a variety
+ * of reason, in fact as soon as a single process holds a reference on a directory. The
+ * most common case are the Explorer, the system's file indexer, Tortoise SVN cache or
+ * an anti-virus that are busy indexing a new directory having been created.
+ *
+ * @param oldDir The old location to move. It must exist and be a directory.
+ * @param newDir The new location where to move. It must not exist.
+ * @return True if the move succeeded. On failure, we try hard to not have touched the old
+ * directory in order not to loose its content.
+ */
+ private boolean moveFolder(File oldDir, File newDir) {
+ // This is a simple folder rename that works on Linux/Mac all the time.
+ //
+ // On Windows this might fail if an indexer is busy looking at a new directory
+ // (e.g. right after we unzip our archive), so it fails let's be nice and give
+ // it a bit of time to succeed.
+ for (int i = 0; i < 5; i++) {
+ if (oldDir.renameTo(newDir)) {
+ return true;
+ }
+ try {
+ Thread.sleep(500 /*ms*/);
+ } catch (InterruptedException e) {
+ // ignore
+ }
}
+
+ return false;
}
/**
* Unzips a zip file into the given destination directory.
*
- * The archive file MUST have a unique "root" folder. This root folder is skipped when
- * unarchiving. However we return that root folder name to the caller, as it can be used
- * as a template to know what destination directory to use in the Add-on case.
+ * The archive file MUST have a unique "root" folder.
+ * This root folder is skipped when unarchiving.
*/
@SuppressWarnings("unchecked")
private boolean unzipFolder(File archiveFile,
long compressedSize,
File unzipDestFolder,
String description,
- String[] outZipRootFolder,
ITaskMonitor monitor) {
description += " (%1$d%%)";
@@ -549,8 +585,9 @@ public class ArchiveInstaller {
try {
zipFile = new ZipFile(archiveFile);
- // figure if we'll need to set the unix permission
- boolean usingUnixPerm = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
+ // figure if we'll need to set the unix permissions
+ boolean usingUnixPerm =
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
// To advance the percent and the progress bar, we don't know the number of
@@ -581,9 +618,6 @@ public class ArchiveInstaller {
if (pos < 0 || pos == name.length() - 1) {
continue;
} else {
- if (outZipRootFolder[0] == null && pos > 0) {
- outZipRootFolder[0] = name.substring(0, pos);
- }
name = name.substring(pos + 1);
}
@@ -632,7 +666,7 @@ public class ArchiveInstaller {
// get the mode and test if it contains the executable bit
int mode = entry.getUnixMode();
if ((mode & 0111) != 0) {
- setExecutablePermission(destFile);
+ OsHelper.setExecutablePermission(destFile);
}
}
@@ -671,7 +705,10 @@ public class ArchiveInstaller {
}
/**
- * Creates a temp folder in the form of osBasePath/temp/prefix.suffixNNN.
+ * Returns an unused temp folder path in the form of osBasePath/temp/prefix.suffixNNN.
+ * <p/>
+ * This does not actually <em>create</em> the folder. It just scan the base path for
+ * a free folder name to use and returns the file to use to reference it.
* <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
@@ -680,12 +717,12 @@ public class ArchiveInstaller {
* Returns null if no such folder can be found (e.g. if all candidates exist,
* which is rather unlikely) or if the base temp folder cannot be created.
*/
- private File createTempFolder(String osBasePath, String prefix, String suffix) {
+ private File getNewTempFolder(String osBasePath, String prefix, String suffix) {
File baseTempFolder = getTempFolder(osBasePath);
if (!baseTempFolder.isDirectory()) {
if (baseTempFolder.isFile()) {
- deleteFileOrFolder(baseTempFolder);
+ OsHelper.deleteFileOrFolder(baseTempFolder);
}
if (!baseTempFolder.mkdirs()) {
return null;
@@ -703,8 +740,10 @@ public class ArchiveInstaller {
}
/**
- * Returns the temp folder used by the SDK Manager.
+ * Returns the single fixed "temp" folder used by the SDK Manager.
* This folder is always at osBasePath/temp.
+ * <p/>
+ * This does not actually <em>create</em> the folder.
*/
private File getTempFolder(String osBasePath) {
File baseTempFolder = new File(osBasePath, RepoConstants.FD_TEMP);
@@ -712,25 +751,6 @@ public class ArchiveInstaller {
}
/**
- * Deletes a file or a directory.
- * Directories are deleted recursively.
- * The argument can be null.
- */
- /*package*/ 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();
- }
- }
- }
-
- /**
* Generates a source.properties in the destination folder that contains all the infos
* relevant to this archive, this package and the source so that we can reload them
* locally later.
@@ -769,13 +789,81 @@ public class ArchiveInstaller {
}
/**
- * Sets the executable Unix permission (0777) on a file or folder.
- * @param file The file to set permissions on.
- * @throws IOException If an I/O error occurs
+ * Recursively restore srcFolder into destFolder by performing a copy of the file
+ * content rather than rename/moves.
+ *
+ * @param srcFolder The source folder to restore.
+ * @param destFolder The destination folder where to restore.
+ * @return True if the folder was successfully restored, false if it was not at all or
+ * only partially restored.
*/
- private void setExecutablePermission(File file) throws IOException {
- Runtime.getRuntime().exec(new String[] {
- "chmod", "777", file.getAbsolutePath()
- });
+ private boolean restoreFolder(File srcFolder, File destFolder) {
+ boolean result = true;
+
+ // Process sub-folders first
+ File[] srcFiles = srcFolder.listFiles();
+ if (srcFiles == null) {
+ // Source does not exist. That is quite odd.
+ return false;
+ }
+
+ if (destFolder.isFile()) {
+ if (!destFolder.delete()) {
+ // There's already a file in there where we want a directory and
+ // we can't delete it. This is rather unexpected. Just give up on
+ // that folder.
+ return false;
+ }
+ } else if (!destFolder.isDirectory()) {
+ destFolder.mkdirs();
+ }
+
+ // Get all the files and dirs of the current destination. We are not going
+ // to clean up the destination first. Instead we'll copy over and just remove
+ // any remaining files or directories.
+ File[] files = destFolder.listFiles();
+ Set<File> destDirs = new HashSet<File>();
+ Set<File> destFiles = new HashSet<File>();
+ for (File f : files) {
+ if (f.isDirectory()) {
+ destDirs.add(f);
+ } else {
+ destFiles.add(f);
+ }
+ }
+
+ // First restore all source directories.
+ for (File dir : srcFiles) {
+ if (dir.isDirectory()) {
+ File d = new File(destFolder, dir.getName());
+ destDirs.remove(d);
+ if (!restoreFolder(dir, d)) {
+ result = false;
+ }
+ }
+ }
+
+ // Remove any remaining directories not processed above.
+ for (File dir : destDirs) {
+ OsHelper.deleteFileOrFolder(dir);
+ }
+
+ // Copy any source files over to the destination.
+ for (File file : srcFiles) {
+ if (file.isFile()) {
+ File f = new File(destFolder, file.getName());
+ destFiles.remove(f);
+ if (!OsHelper.copyFile(file, f)) {
+ result = false;
+ }
+ }
+ }
+
+ // Remove any remaining files not processed above.
+ for (File file : destFiles) {
+ OsHelper.deleteFileOrFolder(file);
+ }
+
+ return result;
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index 3bfd639..5e0a767 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -153,13 +153,11 @@ public class DocPackage extends Package implements IPackageVersion {
* A "doc" package should always be located in SDK/docs.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
return new File(osSdkRoot, SdkConstants.FD_DOCS);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
index d3f3a9b..d06b08d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
@@ -257,13 +257,11 @@ public class ExtraPackage extends MinToolsPackage
* A "tool" package should always be located in SDK/tools.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
return new File(osSdkRoot, getPath());
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java
new file mode 100755
index 0000000..02cebe1
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.sdklib.SdkConstants;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Helper methods used when dealing with archives installation.
+ */
+abstract class OsHelper {
+
+ /**
+ * Helper to delete a file or a directory.
+ * For a directory, recursively deletes all of its content.
+ * Files that cannot be deleted right away are marked for deletion on exit.
+ * The argument can be null.
+ */
+ static void deleteFileOrFolder(File fileOrFolder) {
+ if (fileOrFolder != null) {
+ if (fileOrFolder.isDirectory()) {
+ // Must delete content recursively first
+ for (File item : fileOrFolder.listFiles()) {
+ deleteFileOrFolder(item);
+ }
+ }
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ // Trying to delete a resource on windows might fail if there's a file
+ // indexer locking the resource. Generally retrying will be enough to
+ // make it work.
+ //
+ // Try for half a second before giving up.
+
+ for (int i = 0; i < 5; i++) {
+ if (fileOrFolder.delete()) {
+ return;
+ }
+
+ try {
+ Thread.sleep(100 /*ms*/);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+
+ fileOrFolder.deleteOnExit();
+
+ } else {
+ // On Linux or Mac, just straight deleting it should just work.
+
+ if (!fileOrFolder.delete()) {
+ fileOrFolder.deleteOnExit();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the executable Unix permission (0777) on a file or folder.
+ * <p/>
+ * This invokes a chmod exec, so there is no guarantee of it being fast.
+ * Caller must make sure to not invoke this under Windows.
+ *
+ * @param file The file to set permissions on.
+ * @throws IOException If an I/O error occurs
+ */
+ static void setExecutablePermission(File file) throws IOException {
+ Runtime.getRuntime().exec(new String[] {
+ "chmod", "777", file.getAbsolutePath()
+ });
+ }
+
+ /**
+ * Copies a binary file.
+ *
+ * @param source the source file to copy
+ * @param dest the destination file to write
+ * @return True if the file was successfully copied. False otherwise.
+ */
+ static boolean copyFile(File source, File dest) {
+ byte[] buffer = new byte[8192];
+
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ try {
+ fis = new FileInputStream(source);
+ fos = new FileOutputStream(dest);
+
+ int read;
+ while ((read = fis.read(buffer)) != -1) {
+ fos.write(buffer, 0, read);
+ }
+
+ return true;
+
+ } catch (Exception e) {
+ // Ignore. Simply return false below.
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index 438b07b..12ca0e9 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -415,13 +415,10 @@ public abstract class Package implements IDescription, Comparable<Package> {
* existing or new folder depending on the current content of the SDK.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
- public abstract File getInstallFolder(
- String osSdkRoot, String suggestedDir, SdkManager sdkManager);
+ public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
/**
* Hook called right before an archive is installed. The archive has already
@@ -454,7 +451,8 @@ public abstract class Package implements IDescription, Comparable<Package> {
* @param archive The archive that has been installed.
* @param monitor The {@link ITaskMonitor} to display errors.
* @param installFolder The folder where the archive was successfully installed.
- * Null if the installation failed.
+ * Null if the installation failed, in case the archive needs to
+ * do some cleanup after <code>preInstallHook</code>.
*/
public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
// Nothing to do in base class.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index 1032e03..1e5e391 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -166,13 +166,11 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion
* has this platform version installed, we'll use that one.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
// First find if this platform is already installed. If so, reuse the same directory.
for (IAndroidTarget target : sdkManager.getTargets()) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java
index cfd88e9..717948e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java
@@ -107,13 +107,11 @@ public class PlatformToolPackage extends Package {
* A "tool" package should always be located in SDK/tools.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
return new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
}
@@ -123,4 +121,25 @@ public class PlatformToolPackage extends Package {
return pkg instanceof PlatformToolPackage;
}
+ /**
+ * Hook called right before an archive is installed.
+ * This is used here to stop ADB before trying to replace the platform-tool package.
+ *
+ * @param archive The archive that will be installed
+ * @param monitor The {@link ITaskMonitor} to display errors.
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param installFolder The folder where the archive will be installed. Note that this
+ * is <em>not</em> the folder where the archive was temporary
+ * unzipped. The installFolder, if it exists, contains the old
+ * archive that will soon be replaced by the new one.
+ * @return True if installing this archive shall continue, false if it should be skipped.
+ */
+ @Override
+ public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
+ String osSdkRoot, File installFolder) {
+ AdbWrapper aw = new AdbWrapper(osSdkRoot, monitor);
+ aw.stopAdb();
+ return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
+ }
+
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java
index d60cbaf..920a7e0 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java
@@ -209,13 +209,11 @@ public class SamplePackage extends MinToolsPackage
* version installed, we'll use that one.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
// The /samples dir at the root of the SDK
File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
@@ -326,12 +324,10 @@ public class SamplePackage extends MinToolsPackage
public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
super.postInstallHook(archive, monitor, installFolder);
- if (installFolder == null) {
- return;
+ if (installFolder != null) {
+ String h = computeContentHash(installFolder);
+ saveContentHash(installFolder, h);
}
-
- String h = computeContentHash(installFolder);
- saveContentHash(installFolder, h);
}
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index c8f1e66..c76de30 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -166,13 +166,11 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency
* A "tool" package should always be located in SDK/tools.
*
* @param osSdkRoot The OS path of the SDK root folder.
- * @param suggestedDir A suggestion for the installation folder name, based on the root
- * folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
- public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) {
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
return new File(osSdkRoot, SdkConstants.FD_TOOLS);
}
@@ -210,9 +208,9 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency
}
String scriptName = "post_tools_install"; //$NON-NLS-1$
- String shell = "";
+ String shell = ""; //$NON-NLS-1$
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- shell = "cmd.exe /c ";
+ shell = "cmd.exe /c "; //$NON-NLS-1$
scriptName += ".bat"; //$NON-NLS-1$
} else {
scriptName += ".sh"; //$NON-NLS-1$
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index 76ff7ba..e569eba 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -21,6 +21,7 @@ import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.repository.AdbWrapper;
import com.android.sdklib.internal.repository.AddonPackage;
import com.android.sdklib.internal.repository.AddonsListFetcher;
import com.android.sdklib.internal.repository.Archive;