diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java index ddfce4d4c..4464daacb 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java @@ -90,7 +90,7 @@ public class AndroidConstants { /** Name of the android sources directory */ public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ - + /** Resource java class filename, i.e. "R.java" */ public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$ /** Resource class file filename, i.e. "R.class" */ @@ -104,15 +104,11 @@ public class AndroidConstants { /** Temporary packaged resources file name for a specific set of configuration */ public final static String FN_RESOURCES_S_AP_ = "resources-%s.ap_"; //$NON-NLS-1$ public final static Pattern PATTERN_RESOURCES_S_AP_ = - Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE); + Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ - public final static String FN_ADB = - (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ? - "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$ + public final static String FN_ADB = SdkConstants.FN_ADB; - public final static String FN_EMULATOR = - (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ? - "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ + public final static String FN_EMULATOR = SdkConstants.FN_EMULATOR; public final static String FN_TRACEVIEW = (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ? @@ -128,8 +124,8 @@ public class AndroidConstants { public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS; /** Leaf of the javaDoc folder. Does not start with a separator. */ - public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/" + - SdkConstants.FD_DOCS_REFERENCE; //$NON-NLS-1$ + public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/" + //$NON-NLS-1$ + SdkConstants.FD_DOCS_REFERENCE; /** Path of the samples directory relative to the sdk folder. * This is an OS path, ending with a separator. @@ -159,10 +155,10 @@ public class AndroidConstants { /** aidl marker error. */ public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$ - + /** android marker error */ public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$ - + /** Name for the "type" marker attribute */ public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$ /** Name for the "class" marker attribute */ @@ -176,9 +172,9 @@ public class AndroidConstants { /** provider value for marker attribute "type" */ public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$ - public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ - public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ - public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ + public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ + public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ + public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$ public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$ public final static String CLASS_INSTRUMENTATION_RUNNER = @@ -202,7 +198,7 @@ public class AndroidConstants { "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$ public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$ public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$ - + public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$ /** @@ -219,6 +215,6 @@ public class AndroidConstants { /** The base URL where to find the Android class & manifest documentation */ public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$ - + public static final String LIBRARY_TEST_RUNNER = "android.test.runner"; // $NON-NLS-1$ } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java index 557f3542a..32b9a2e01 100644 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java @@ -82,18 +82,26 @@ public final class SdkConstants { /** dex.jar file */ public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$ - /** dx executable */ + /** dx executable (with extension for the current OS) */ public final static String FN_DX = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? "dx.bat" : "dx"; //$NON-NLS-1$ //$NON-NLS-2$ - /** aapt executable */ + /** aapt executable (with extension for the current OS) */ public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? "aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$ - /** aidl executable */ + /** aidl executable (with extension for the current OS) */ public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? "aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$ + /** adb executable (with extension for the current OS) */ + public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$ + + /** emulator executable (with extension for the current OS) */ + public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ + /* Folder Names for Android Projects . */ /** Resources folder name, i.e. "res". */ diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index becdd5727..bd76a4ce0 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -16,10 +16,17 @@ package com.android.sdklib.internal.repository; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.IAndroidTarget.IOptionalLibrary; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; import com.android.sdklib.repository.SdkRepository; import org.w3c.dom.Node; +import java.io.File; import java.util.ArrayList; /** @@ -57,8 +64,8 @@ public class AddonPackage extends Package { *

* This constructor should throw an exception if the package cannot be created. */ - AddonPackage(Node packageNode) { - super(packageNode); + AddonPackage(RepoSource source, Node packageNode) { + super(source, packageNode); mVendor = getXmlString(packageNode, SdkRepository.NODE_VENDOR); mName = getXmlString(packageNode, SdkRepository.NODE_NAME); mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0); @@ -66,6 +73,41 @@ public class AddonPackage extends Package { mLibs = parseLibs(getFirstChild(packageNode, SdkRepository.NODE_LIBS)); } + /** + * Creates a new platform package based on an actual {@link IAndroidTarget} (with + * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}. + * This is used to list local SDK folders. + */ + AddonPackage(IAndroidTarget target) { + super( null, //source + 0, //revision + target.getDescription(), //description + null, //descUrl + Os.getCurrentOs(), //archiveOs + Arch.getCurrentArch(), //archiveArch + "", //archiveUrl //$NON-NLS-1$ + 0, //archiveSize + null //archiveChecksum + ); + + mApiLevel = target.getApiVersionNumber(); + mName = target.getName(); + mVendor = target.getVendor(); + + IOptionalLibrary[] optLibs = target.getOptionalLibraries(); + if (optLibs == null || optLibs.length == 0) { + mLibs = new Lib[0]; + } else { + mLibs = new Lib[optLibs.length]; + for (int i = 0; i < optLibs.length; i++) { + mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription()); + } + } + } + + /** + * Parses a element. + */ private Lib[] parseLibs(Node libsNode) { ArrayList libs = new ArrayList(); @@ -85,6 +127,9 @@ public class AddonPackage extends Package { return libs.toArray(new Lib[libs.size()]); } + /** + * Parses a element from a container. + */ private Lib parseLib(Node libNode) { return new Lib(getXmlString(libNode, SdkRepository.NODE_NAME), getXmlString(libNode, SdkRepository.NODE_DESCRIPTION)); @@ -126,4 +171,31 @@ public class AddonPackage extends Package { getShortDescription(), super.getLongDescription()); } + + /** + * Computes a potential installation folder if an archive of this package were + * to be installed right away in the given SDK root. + *

+ * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level". + * The name needs to be sanitized to be acceptable as a directory name. + * However if we can find a different directory under SDK/add-ons that already + * has this add-ons installed, we'll use that one. + * + * @param osSdkRoot The OS path of the SDK root folder. + * @return A new {@link File} corresponding to the directory to use to install this package. + */ + @Override + public File getInstallFolder(String osSdkRoot) { + File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS); + + String name = String.format("%s-%d", getName(), getApiLevel()); // $NON-NLS-1$ + + name = name.replaceAll("[^a-zA-Z0-9_-]+", "_"); // $NON-NLS-1$ + name = name.replaceAll("_+", "_"); // $NON-NLS-1$ + + File folder = new File(addons, name); + + // TODO find similar existing addon in addons folder + return folder; + } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java index 267c17962..9686cbd58 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java @@ -16,6 +16,9 @@ package com.android.sdklib.internal.repository; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + /** * A {@link Archive} is the base class for "something" that can be downloaded from @@ -32,23 +35,106 @@ public class Archive implements IDescription { /** The checksum type. */ public enum ChecksumType { /** A SHA1 checksum, represented as a 40-hex string. */ - SHA1 + SHA1("SHA-1"); //$NON-NLS-1$ + + private final String mAlgorithmName; + + /** + * Constructs a {@link ChecksumType} with the algorigth name + * suitable for {@link MessageDigest#getInstance(String)}. + *

+ * These names are officially documented at + * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest + */ + private ChecksumType(String algorithmName) { + mAlgorithmName = algorithmName; + } + + /** + * Returns a new {@link MessageDigest} instance for this checksum type. + * @throws NoSuchAlgorithmException if this algorithm is not available. + */ + public MessageDigest getMessageDigest() throws NoSuchAlgorithmException { + return MessageDigest.getInstance(mAlgorithmName); + } } /** The OS that this archive can be downloaded on. */ public enum Os { - ANY, - LINUX, - MACOSX, - WINDOWS + ANY("Any"), + LINUX("Linux"), + MACOSX("MacOS X"), + WINDOWS("Windows"); + + private final String mUiName; + + private Os(String uiName) { + mUiName = uiName; + } + + @Override + public String toString() { + return mUiName; + } + + /** + * Returns the current OS as one of the {@link Os} enum values or null. + */ + public static Os getCurrentOs() { + String os = System.getProperty("os.name"); //$NON-NLS-1$ + if (os.startsWith("Mac OS")) { //$NON-NLS-1$ + return Os.MACOSX; + + } else if (os.startsWith("Windows")) { //$NON-NLS-1$ + return Os.WINDOWS; + + } else if (os.startsWith("Linux")) { //$NON-NLS-1$ + return Os.LINUX; + } + + return null; + } } - /** The Architecture that this archvie can be downloaded on. */ + /** The Architecture that this archive can be downloaded on. */ public enum Arch { - ANY, - PPC, - X86, - X86_64 + ANY("Any"), + PPC("PowerPC"), + X86("x86"), + X86_64("x86_64"); + + private final String mUiName; + + private Arch(String uiName) { + mUiName = uiName; + } + + @Override + public String toString() { + return mUiName; + } + + /** + * Returns the current architecture as one of the {@link Arch} enum values or null. + */ + public static Arch getCurrentArch() { + // Values listed from http://lopica.sourceforge.net/os.html + String arch = System.getProperty("os.arch"); + + if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) { + return Arch.X86_64; + + } else if (arch.equalsIgnoreCase("x86") + || arch.equalsIgnoreCase("i386") + || arch.equalsIgnoreCase("i686")) { + return Arch.X86; + + } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) { + return Arch.PPC; + } + + return null; + } } private final Os mOs; @@ -57,11 +143,13 @@ public class Archive implements IDescription { private final long mSize; private final String mChecksum; private final ChecksumType mChecksumType = ChecksumType.SHA1; + private final Package mPackage; /** * Creates a new archive. */ - Archive(Os os, Arch arch, String url, long size, String checksum) { + Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) { + mPackage = pkg; mOs = os; mArch = arch; mUrl = url; @@ -69,63 +157,117 @@ public class Archive implements IDescription { mChecksum = checksum; } - /** Returns the archive size, an int > 0. */ + /** + * Returns the package that created and owns this archive. + * It should generally not be null. + */ + public Package getParentPackage() { + return mPackage; + } + + /** + * Returns the archive size, an int > 0. + * Size will be 0 if this a local installed folder of unknown size. + */ public long getSize() { return mSize; } - /** Returns the SHA1 archive checksum, as a 40-char hex. */ + /** + * Returns the SHA1 archive checksum, as a 40-char hex. + * Can be empty but not null for local installed folders. + */ public String getChecksum() { return mChecksum; } - /** Returns the checksum type, always {@link ChecksumType#SHA1} right now. */ + /** + * Returns the checksum type, always {@link ChecksumType#SHA1} right now. + */ public ChecksumType getChecksumType() { return mChecksumType; } - /** Returns the optional description URL for all packages (platform, add-on, tool, doc). - * Can be empty but not null. */ - public String getDescUrl() { + /** + * Returns the download archive URL, either absolute or relative to the repository xml. + * For a local installed folder, an URL is frabricated from the folder path. + */ + public String getUrl() { return mUrl; } - /** Returns the archive {@link Os} enum. */ + /** + * Returns the archive {@link Os} enum. + * Can be null for a local installed folder on an unknown OS. + */ public Os getOs() { return mOs; } - /** Returns the archive {@link Arch} enum. */ + /** + * Returns the archive {@link Arch} enum. + * Can be null for a local installed folder on an unknown architecture. + */ public Arch getArch() { return mArch; } + /** + * Generates a short description for this archive. + */ public String getShortDescription() { - String os = "any OS"; - if (mOs != Os.ANY) { - os = capitalize(mOs.toString()); + String os; + if (mOs == null) { + os = "unknown OS"; + } else if (mOs == Os.ANY) { + os = "any OS"; + } else { + os = mOs.toString(); } String arch = ""; - if (mArch != Arch.ANY) { - arch = mArch.toString().toLowerCase(); + if (mArch != null && mArch != Arch.ANY) { + arch = mArch.toString(); } return String.format("Archive for %1$s %2$s", os, arch); } - private String capitalize(String string) { - if (string.length() > 1) { - return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase(); - } else { - return string.toUpperCase(); - } - } - + /** + * Generates a longer description for this archive. + */ public String getLongDescription() { return String.format("%1$s\nSize: %2$d MiB\nSHA1: %3$s", getShortDescription(), Math.round(getSize() / (1024*1024)), getChecksum()); } + + /** + * Returns true if this archive can be installed on the current platform. + */ + public boolean isCompatible() { + // Check OS + Os os = getOs(); + + if (os != Os.ANY) { + Os os2 = Os.getCurrentOs(); + if (os2 != os) { + return false; + } + } + + // Check Arch + Arch arch = getArch(); + + if (arch != Arch.ANY) { + Arch arch2 = Arch.getCurrentArch(); + if (arch2 != arch) { + return false; + } + } + + return true; + } } + diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java index 7601766d2..8f072552e 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java @@ -16,10 +16,15 @@ package com.android.sdklib.internal.repository; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; import com.android.sdklib.repository.SdkRepository; import org.w3c.dom.Node; +import java.io.File; + /** * Represents a doc XML node in an SDK repository. */ @@ -32,12 +37,39 @@ public class DocPackage extends Package { *

* This constructor should throw an exception if the package cannot be created. */ - DocPackage(Node packageNode) { - super(packageNode); + DocPackage(RepoSource source, Node packageNode) { + super(source, packageNode); mApiLevel = getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0); } - /** Returns the api-level, an int > 0, for platform, add-on and doc packages. */ + /** + * Manually create a new package with one archive and the given attributes. + * This is used to create packages from local directories. + */ + DocPackage(RepoSource source, + int apiLevel, + int revision, + String description, + String descUrl, + Os archiveOs, + Arch archiveArch, + String archiveUrl, + long archiveSize, + String archiveChecksum) { + super(source, + revision, + description, + descUrl, + archiveOs, + archiveArch, + archiveUrl, + archiveSize, + archiveChecksum); + mApiLevel = apiLevel; + } + + /** Returns the api-level, an int > 0, for platform, add-on and doc packages. + * Can be 0 if this is a local package of unknown api-level. */ public int getApiLevel() { return mApiLevel; } @@ -45,7 +77,11 @@ public class DocPackage extends Package { /** Returns a short description for an {@link IDescription}. */ @Override public String getShortDescription() { - return String.format("Documentation for SDK Android API %1$d", getApiLevel()); + if (mApiLevel != 0) { + return String.format("Documentation for Android SDK, API %1$d", mApiLevel); + } else { + return String.format("Documentation for Android SDK"); + } } /** Returns a long description for an {@link IDescription}. */ @@ -55,4 +91,18 @@ public class DocPackage extends Package { getShortDescription(), super.getLongDescription()); } + + /** + * Computes a potential installation folder if an archive of this package were + * to be installed right away in the given SDK root. + *

+ * A "doc" package should always be located in SDK/docs. + * + * @param osSdkRoot The OS path of the SDK root folder. + * @return A new {@link File} corresponding to the directory to use to install this package. + */ + @Override + public File getInstallFolder(String osSdkRoot) { + return new File(osSdkRoot, SdkConstants.FD_DOCS); + } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java index 832d246d2..05c982d62 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java @@ -34,13 +34,13 @@ public interface ITaskMonitor { * Sets the description in the current task dialog. * This method can be invoked from a non-UI thread. */ - public void setDescription(String description); + public void setDescription(String descriptionFormat, Object...args); /** * Sets the result text in the current task dialog. * This method can be invoked from a non-UI thread. */ - public void setResult(String result); + public void setResult(String resultFormat, Object...args); /** * Sets the max value of the progress bar. diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java new file mode 100755 index 000000000..f150510e0 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2009 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.IAndroidTarget; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; +import com.android.sdklib.repository.SdkRepository; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +/** + * Scans a local SDK to find which packages are currently installed. + */ +public class LocalSdkParser { + + private static final String SOURCE_XML = "source.xml"; //$NON-NLS-1$ // TODO move to global constants + private Package[] mPackages; + + public LocalSdkParser() { + // TODO Auto-generated constructor stub + } + + /** + * Returns the packages found by the last call to {@link #parseSdk(String)}. + */ + public Package[] getPackages() { + return mPackages; + } + + /** + * Clear the internal packages list. After this call, {@link #getPackages()} will return + * null till {@link #parseSdk(String)} is called. + */ + public void clearPackages() { + mPackages = null; + } + + /** + * Scan the give SDK to find all the packages already installed at this location. + *

+ * Store the packages internally. You can use {@link #getPackages()} to retrieve them + * at any time later. + * + * @param osSdkRoot The path to the SDK folder. + * @return The packages found. Can be retrieved later using {@link #getPackages()}. + */ + public Package[] parseSdk(String osSdkRoot) { + ArrayList packages = new ArrayList(); + + Package pkg = scanDoc(new File(osSdkRoot, SdkConstants.FD_DOCS)); + if (pkg != null) { + packages.add(pkg); + } + + pkg = scanTools(new File(osSdkRoot, SdkConstants.FD_TOOLS)); + if (pkg != null) { + packages.add(pkg); + } + + // for platforms and add-ons, rely on the SdkManager parser + SdkManager sdkman = SdkManager.createManager(osSdkRoot, new ISdkLog() { + // A dummy sdk logger that doesn't log anything. + public void error(Throwable t, String errorFormat, Object... args) { + // pass + } + public void printf(String msgFormat, Object... args) { + // pass + } + public void warning(String warningFormat, Object... args) { + // pass + } + }); + + for(IAndroidTarget target : sdkman.getTargets()) { + pkg = null; + + if (target.isPlatform()) { + pkg = parseXml(new File(target.getLocation(), SOURCE_XML), + SdkRepository.NODE_PLATFORM); + if (pkg == null) { + pkg = new PlatformPackage(target); + } + + } else { + pkg = parseXml(new File(target.getLocation(), SOURCE_XML), + SdkRepository.NODE_ADD_ON); + + if (pkg == null) { + pkg = new AddonPackage(target); + } + } + + if (pkg != null) { + packages.add(pkg); + } + } + + mPackages = packages.toArray(new Package[packages.size()]); + return mPackages; + } + + /** + * Try to find a tools package at the given location. + * Returns null if not found. + */ + private Package scanTools(File toolFolder) { + // Can we find a source.xml? + Package pkg = parseXml(new File(toolFolder, SOURCE_XML), SdkRepository.NODE_TOOL); + + // We're not going to check that all tools are present. At the very least + // we should expect to find adb, android and an emulator adapted to the current OS. + Set names = new HashSet(); + for (File file : toolFolder.listFiles()) { + names.add(file.getName()); + } + if (!names.contains(SdkConstants.FN_ADB) || + !names.contains(SdkConstants.androidCmdName()) || + !names.contains(SdkConstants.FN_EMULATOR)) { + return null; + } + + // if we don't have the package info, make one up + if (pkg == null) { + pkg = new ToolPackage( + null, //source + 0, //revision + "Tools", //description + null, //descUrl + Os.getCurrentOs(), //archiveOs + Arch.getCurrentArch(), //archiveArch + "", //archiveUrl //$NON-NLS-1$ + 0, //archiveSize + null //archiveChecksum + ); + } + + return pkg; + } + + /** + * Try to find a docs package at the given location. + * Returns null if not found. + */ + private Package scanDoc(File docFolder) { + // Can we find a source.xml? + Package pkg = parseXml(new File(docFolder, SOURCE_XML), SdkRepository.NODE_DOC); + + // To start with, a doc folder should have an "index.html" to be acceptable. + String html = readFile(new File(docFolder, "index.html")); + if (html != null) { + // Try to find something that looks like this line: + // + // We should find one or more of these and we want the highest version + // and release numbers. Note that unfortunately that doesn't give us + // the api-level we care about for the doc package. + + String found = null; + Pattern re = Pattern.compile( + "", + Pattern.DOTALL); + Matcher m = re.matcher(html); + while(m.find()) { + String v = m.group(1); + if (found == null || v.compareTo(found) == 1) { + found = v; + } + } + + if (found == null) { + // That doesn't look like a doc folder. + return null; + } + + // We found the line, so it seems like an SDK doc. + // Create a pkg if we don't have one yet. + + if (pkg == null) { + String url = null; + try { + url = docFolder.toURI().toURL().toString(); + } catch (MalformedURLException e) { + // ignore + } + + pkg = new DocPackage( + null, //source + 0, //apiLevel + 0, //revision + String.format("Documentation for %1$s", found), //description + null, //descUrl + Os.getCurrentOs(), //archiveOs + Arch.getCurrentArch(), //archiveArch + url, //archiveUrl + 0, //archiveSize + null //archiveChecksum + ); + } + } + + return pkg; + } + + /** + * Parses the given XML file for the specific element filter. + * The element must one of the package type local names: doc, tool, platform or addon. + * Returns null if no such package was found. + */ + private Package parseXml(File sourceXmlFile, String elementFilter) { + + String xml = readFile(sourceXmlFile); + if (xml != null) { + if (validateXml(xml)) { + return parsePackages(xml, elementFilter); + } + } + + return null; + } + + /** + * Parses the given XML to find the specific element filter. + * The element must one of the package type local names: doc, tool, platform or addon. + * Returns null if no such package was found. + */ + private Package parsePackages(String xml, String elementFilter) { + + try { + Document doc = getDocument(xml); + + Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY); + if (root != null) { + + for (Node child = root.getFirstChild(); + child != null; + child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && + SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) && + elementFilter.equals(child.getLocalName())) { + String name = child.getLocalName(); + Package p = null; + + try { + if (SdkRepository.NODE_ADD_ON.equals(name)) { + return new AddonPackage(null /*source*/, child); + + } else if (SdkRepository.NODE_PLATFORM.equals(name)) { + return new PlatformPackage(null /*source*/, child); + + } else if (SdkRepository.NODE_DOC.equals(name)) { + return new DocPackage(null /*source*/, child); + + } else if (SdkRepository.NODE_TOOL.equals(name)) { + return new ToolPackage(null /*source*/, child); + } + } catch (Exception e) { + // Ignore invalid packages + } + } + } + } + + } catch (Exception e) { + // ignore + } + + return null; + } + + /** + * Reads a file as a string. + * Returns null if the file could not be read. + */ + private String readFile(File sourceXmlFile) { + FileReader fr = null; + try { + fr = new FileReader(sourceXmlFile); + BufferedReader br = new BufferedReader(fr); + StringBuilder dest = new StringBuilder(); + char[] buf = new char[65536]; + int n; + while ((n = br.read(buf)) > 0) { + if (n > 0) { + dest.append(buf, 0, n); + } + } + return dest.toString(); + + } catch (IOException e) { + // ignore + + } finally { + if (fr != null) { + try { + fr.close(); + } catch (IOException e) { + // ignore + } + } + } + + return null; + } + + /** + * Validates this XML against the SDK Repository schema. + * Returns true if the XML was correctly validated. + */ + private boolean validateXml(String xml) { + + try { + Validator validator = getValidator(); + validator.validate(new StreamSource(new StringReader(xml))); + return true; + + } catch (SAXException e) { + // ignore + + } catch (IOException e) { + // ignore + } + + return false; + } + + /** + * Helper method that returns a validator for our XSD + */ + private Validator getValidator() throws SAXException { + InputStream xsdStream = SdkRepository.getXsdStream(); + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + // This may throw a SAX Exception if the schema itself is not a valid XSD + Schema schema = factory.newSchema(new StreamSource(xsdStream)); + + Validator validator = schema.newValidator(); + + return validator; + } + + /** + * Returns the first child element with the given XML local name. + * If xmlLocalName is null, returns the very first child element. + */ + private Node getFirstChild(Node node, String xmlLocalName) { + + for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && + SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) { + if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) { + return child; + } + } + } + + return null; + } + + /** + * Takes an XML document as a string as parameter and returns a DOM for it. + */ + private Document getDocument(String xml) + throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setIgnoringComments(true); + factory.setNamespaceAware(true); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(new InputSource(new StringReader(xml))); + + return doc; + } +} diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java index 061eb166a..55ecaef05 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java @@ -22,6 +22,7 @@ import com.android.sdklib.repository.SdkRepository; import org.w3c.dom.Node; +import java.io.File; import java.util.ArrayList; /** @@ -42,13 +43,15 @@ public abstract class Package implements IDescription { private final String mDescription; private final String mDescUrl; private final Archive[] mArchives; + private final RepoSource mSource; /** * Creates a new package from the attributes and elements of the given XML node. *

* This constructor should throw an exception if the package cannot be created. */ - Package(Node packageNode) { + Package(RepoSource source, Node packageNode) { + mSource = source; mRevision = getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0); mDescription = getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION); mDescUrl = getXmlString(packageNode, SdkRepository.NODE_DESC_URL); @@ -56,6 +59,35 @@ public abstract class Package implements IDescription { mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES)); } + /** + * Manually create a new package with one archive and the given attributes. + * This is used to create packages from local directories. + */ + public Package(RepoSource source, + int revision, + String description, + String descUrl, + Os archiveOs, + Arch archiveArch, + String archiveUrl, + long archiveSize, + String archiveChecksum) { + mSource = source; + mRevision = revision; + mDescription = description; + mDescUrl = descUrl; + mArchives = new Archive[1]; + mArchives[0] = new Archive(this, + archiveOs, + archiveArch, + archiveUrl, + archiveSize, + archiveChecksum); + } + + /** + * Parses an XML node to process the element. + */ private Archive[] parseArchives(Node archivesNode) { ArrayList archives = new ArrayList(); @@ -75,52 +107,99 @@ public abstract class Package implements IDescription { return archives.toArray(new Archive[archives.size()]); } + /** + * Parses one element from an container. + */ private Archive parseArchive(Node archiveNode) { Archive a = new Archive( + this, (Os) getEnumAttribute(archiveNode, SdkRepository.ATTR_OS, Os.values(), null), (Arch) getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH, Arch.values(), Arch.ANY), getXmlString(archiveNode, SdkRepository.NODE_URL), - getXmlInt(archiveNode, SdkRepository.NODE_SIZE, 0), + getXmlLong(archiveNode, SdkRepository.NODE_SIZE, 0), getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM) ); return a; } - /** Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). */ + /** + * Returns the source that created (and owns) this package. Can be null. + */ + public RepoSource getParentSource() { + return mSource; + } + + /** + * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). + * Can be 0 if this is a local package of unknown revision. + */ public int getRevision() { return mRevision; } - /** Returns the optional description for all packages (platform, add-on, tool, doc) or - * for a lib. */ + /** + * Returns the optional description for all packages (platform, add-on, tool, doc) or + * for a lib. Can be empty but not null. + */ public String getDescription() { return mDescription; } - /** Returns the optional description URL for all packages (platform, add-on, tool, doc). - * Can be empty but not null. */ + /** + * Returns the optional description URL for all packages (platform, add-on, tool, doc). + * Can be empty but not null. + */ public String getDescUrl() { return mDescUrl; } - /** Returns the archives defined in this package. Can be an empty array but not null. */ + /** + * Returns the archives defined in this package. + * Can be an empty array but not null. + */ public Archive[] getArchives() { return mArchives; } - /** Returns a short description for an {@link IDescription}. */ + /** + * Returns a short description for an {@link IDescription}. + * Can be empty but not null. + */ public abstract String getShortDescription(); - /** Returns a long description for an {@link IDescription}. */ + /** + * Returns a long description for an {@link IDescription}. + * Can be empty but not null. + */ public String getLongDescription() { return String.format("%1$s\nRevision %2$d", getDescription(), getRevision()); } + /** + * Computes a potential installation folder if an archive of this package were + * to be installed right away in the given SDK root. + *

+ * Some types of packages install in a fix location, for example docs and tools. + * In this case the returned folder may already exist with a different archive installed + * at the desired location. + * For other packages types, such as add-on or platform, the folder name is only partially + * relevant to determine the content and thus a real check will be done to provide an + * existing or new folder depending on the current content of the SDK. + * + * @param osSdkRoot The OS path of the SDK root folder. + * @return A new {@link File} corresponding to the directory to use to install this package. + */ + public abstract File getInstallFolder(String osSdkRoot); + //--- + /** + * Returns the first child element with the given XML local name. + * If xmlLocalName is null, returns the very first child element. + */ protected static Node getFirstChild(Node node, String xmlLocalName) { for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { @@ -158,6 +237,19 @@ public abstract class Package implements IDescription { } } + /** + * Retrieves the value of that XML element as a long. + * Returns the default value when the element is missing or is not an integer. + */ + protected static long getXmlLong(Node node, String xmlLocalName, long defaultValue) { + String s = getXmlString(node, xmlLocalName); + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + return defaultValue; + } + } + /** * Retrieve an attribute which value must match one of the given enums using a * case-insensitive name match. diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java index 8284c0871..0d51c58b4 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java @@ -16,10 +16,17 @@ package com.android.sdklib.internal.repository; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; import com.android.sdklib.repository.SdkRepository; import org.w3c.dom.Node; +import java.io.File; + /** * Represents a platform XML node in an SDK repository. */ @@ -33,12 +40,33 @@ public class PlatformPackage extends Package { *

* This constructor should throw an exception if the package cannot be created. */ - PlatformPackage(Node packageNode) { - super(packageNode); + PlatformPackage(RepoSource source, Node packageNode) { + super(source, packageNode); mVersion = getXmlString(packageNode, SdkRepository.NODE_VERSION); mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0); } + /** + * Creates a new platform package based on an actual {@link IAndroidTarget} (with + * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}. + * This is used to list local SDK folders. + */ + PlatformPackage(IAndroidTarget target) { + super( null, //source + 0, //revision + target.getDescription(), //description + null, //descUrl + Os.getCurrentOs(), //archiveOs + Arch.getCurrentArch(), //archiveArch + "", //archiveUrl //$NON-NLS-1$ + 0, //archiveSize + null //archiveChecksum + ); + + mApiLevel = target.getApiVersionNumber(); + mVersion = target.getApiVersionName(); + } + /** Returns the version, a string, for platform packages. */ public String getVersion() { return mVersion; @@ -64,4 +92,23 @@ public class PlatformPackage extends Package { getShortDescription(), super.getLongDescription()); } + + /** + * Computes a potential installation folder if an archive of this package were + * to be installed right away in the given SDK root. + *

+ * A platform package is typically installed in SDK/platforms/android-"version". + * However if we can find a different directory under SDK/platform that already + * has this platform version installed, we'll use that one. + * + * @param osSdkRoot The OS path of the SDK root folder. + * @return A new {@link File} corresponding to the directory to use to install this package. + */ + @Override + public File getInstallFolder(String osSdkRoot) { + File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS); + File folder = new File(platforms, String.format("android-%s", getVersion())); //$NON-NLS-1$ + // TODO find similar existing platform in platforms folder + return folder; + } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java index 20777c309..1fd880fff 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java @@ -68,12 +68,21 @@ public class RepoSource implements IDescription { } /** - * Returns the list of known packages. This is null when the source hasn't been loaded yet. + * Returns the list of known packages found by the last call to {@link #load(ITaskFactory)}. + * This is null when the source hasn't been loaded yet. */ public Package[] getPackages() { return mPackages; } + /** + * Clear the internal packages list. After this call, {@link #getPackages()} will return + * null till {@link #load(ITaskFactory)} is called. + */ + public void clearPackages() { + mPackages = null; + } + public String getShortDescription() { return mUrl; } @@ -93,7 +102,7 @@ public class RepoSource implements IDescription { setDefaultDescription(); - monitor.setDescription(String.format("Fetching %1$s", mUrl)); + monitor.setDescription("Fetching %1$s", mUrl); monitor.incProgress(1); String xml = fetchUrl(mUrl, monitor); @@ -136,7 +145,10 @@ public class RepoSource implements IDescription { } } - /* + /** + * Fetches the document at the given URL and returns it as a string. + * Returns null if anything wrong happens and write errors to the monitor. + * * References: * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html @@ -186,6 +198,10 @@ public class RepoSource implements IDescription { return null; } + /** + * Validates this XML against the SDK Repository schema. + * Returns true if the XML was correctly validated. + */ private boolean validateXml(String xml, ITaskMonitor monitor) { try { @@ -203,7 +219,9 @@ public class RepoSource implements IDescription { return false; } - /** Helper method that returns a validator for our XSD */ + /** + * Helper method that returns a validator for our XSD + */ private Validator getValidator() throws SAXException { InputStream xsdStream = SdkRepository.getXsdStream(); SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); @@ -217,6 +235,10 @@ public class RepoSource implements IDescription { } + /** + * Parse all packages defined in the SDK Repository XML and creates + * a new mPackages array with them. + */ private boolean parsePackages(String xml, ITaskMonitor monitor) { try { @@ -237,22 +259,21 @@ public class RepoSource implements IDescription { try { if (SdkRepository.NODE_ADD_ON.equals(name)) { - p = new AddonPackage(child); + p = new AddonPackage(this, child); } else if (!mAddonOnly) { if (SdkRepository.NODE_PLATFORM.equals(name)) { - p = new PlatformPackage(child); + p = new PlatformPackage(this, child); } else if (SdkRepository.NODE_DOC.equals(name)) { - p = new DocPackage(child); + p = new DocPackage(this, child); } else if (SdkRepository.NODE_TOOL.equals(name)) { - p = new ToolPackage(child); + p = new ToolPackage(this, child); } } if (p != null) { packages.add(p); - monitor.setDescription( - String.format("Found %1$s", p.getShortDescription())); + monitor.setDescription("Found %1$s", p.getShortDescription()); } } catch (Exception e) { // Ignore invalid packages @@ -278,6 +299,10 @@ public class RepoSource implements IDescription { return false; } + /** + * Returns the first child element with the given XML local name. + * If xmlLocalName is null, returns the very first child element. + */ private Node getFirstChild(Node node, String xmlLocalName) { for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { @@ -292,6 +317,9 @@ public class RepoSource implements IDescription { return null; } + /** + * Takes an XML document as a string as parameter and returns a DOM for it. + */ private Document getDocument(String xml) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java new file mode 100755 index 000000000..0a70953ed --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009 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 java.util.ArrayList; + +/** + * A list of sdk-repository sources. + */ +public class RepoSources { + + private ArrayList mSources = new ArrayList(); + private ITaskFactory mTaskFactory; + + public RepoSources() { + } + + public void setTaskFactory(ITaskFactory taskFactory) { + mTaskFactory = taskFactory; + } + + public ITaskFactory getTaskFactory() { + return mTaskFactory; + } + + public void add(RepoSource source) { + mSources.add(source); + } + + public ArrayList getSources() { + return mSources; + } +} diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java index 4416ea0be..71e35c4db 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java @@ -16,8 +16,14 @@ package com.android.sdklib.internal.repository; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; + import org.w3c.dom.Node; +import java.io.File; + /** * Represents a tool XML node in an SDK repository. */ @@ -28,8 +34,32 @@ public class ToolPackage extends Package { *

* This constructor should throw an exception if the package cannot be created. */ - ToolPackage(Node packageNode) { - super(packageNode); + ToolPackage(RepoSource source, Node packageNode) { + super(source, packageNode); + } + + /** + * Manually create a new package with one archive and the given attributes. + * This is used to create packages from local directories. + */ + ToolPackage(RepoSource source, + int revision, + String description, + String descUrl, + Os archiveOs, + Arch archiveArch, + String archiveUrl, + long archiveSize, + String archiveChecksum) { + super(source, + revision, + description, + descUrl, + archiveOs, + archiveArch, + archiveUrl, + archiveSize, + archiveChecksum); } /** Returns a short description for an {@link IDescription}. */ @@ -45,4 +75,18 @@ public class ToolPackage extends Package { getRevision(), super.getLongDescription()); } + + /** + * Computes a potential installation folder if an archive of this package were + * to be installed right away in the given SDK root. + *

+ * A "tool" package should always be located in SDK/tools. + * + * @param osSdkRoot The OS path of the SDK root folder. + * @return A new {@link File} corresponding to the directory to use to install this package. + */ + @Override + public File getInstallFolder(String osSdkRoot) { + return new File(osSdkRoot, SdkConstants.FD_TOOLS); + } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java index d0ea56c64..673e43fe3 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java @@ -16,6 +16,7 @@ package com.android.sdklib.repository; + import java.io.InputStream; /** @@ -23,6 +24,10 @@ import java.io.InputStream; */ public class SdkRepository { + /** The URL of the official Google sdk-repository site. */ + public static final String URL_GOOGLE_SDK_REPO_SITE = + "https://dl.google.com/android/eclipse/repository/index.xml"; //$NON-NLS-1$ + /** The XML namespace of the sdk-repository XML. */ public static final String NS_SDK_REPOSITORY = "http://schemas.android.com/sdk/android/repository/1"; //$NON-NLS-1$ diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/InstalledPackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java similarity index 60% rename from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/InstalledPackagesPage.java rename to tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java index 118760c7d..5bb9e8a38 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/InstalledPackagesPage.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java @@ -21,8 +21,11 @@ import com.android.sdklib.internal.repository.ITaskMonitor; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; @@ -44,25 +47,22 @@ import org.eclipse.swt.widgets.Text; * - refresh callback */ -public class InstalledPackagesPage extends Composite { +public class LocalPackagesPage extends Composite { private UpdaterData mUpdaterData; private Label mSdkLocLabel; private Text mSdkLocText; private Button mSdkLocBrowse; - private Label mInstalledPkgLabel; - private TableViewer mTableViewerInstPkg; - private Table mTableInstPkg; - private TableColumn mColumnInstSummary; - private TableColumn mColumnInstApiLevel; - private TableColumn mColumnInstRevision; + private TableViewer mTableViewerPackages; + private Table mTablePackages; + private TableColumn mColumnPackages; private Group mDescriptionContainer; - private Composite mInstButtons; - private Button mInstUpdate; + private Composite mContainerButtons; + private Button mUpdateButton; private Label mPlaceholder1; - private Button mInstDelete; + private Button mDeleteButton; private Label mPlaceholder2; - private Button mInstHomePage; + private Button mHomePageButton; private Label mDescriptionLabel; /** @@ -71,12 +71,13 @@ public class InstalledPackagesPage extends Composite { * @param updaterData An instance of {@link UpdaterData}. If null, a local * one will be allocated just to help with the SWT Designer. */ - public InstalledPackagesPage(Composite parent, UpdaterData updaterData) { + public LocalPackagesPage(Composite parent, UpdaterData updaterData) { super(parent, SWT.BORDER); mUpdaterData = updaterData != null ? updaterData : new UpdaterData(); createContents(this); + postCreate(); //$hide$ } private void createContents(Composite parent) { @@ -84,26 +85,14 @@ public class InstalledPackagesPage extends Composite { createSdkLocation(parent); - mInstalledPkgLabel = new Label(parent, SWT.NONE); - mInstalledPkgLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); - mInstalledPkgLabel.setText("Installed Packages:"); + mTableViewerPackages = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION); + mTablePackages = mTableViewerPackages.getTable(); + mTablePackages.setHeaderVisible(true); + mTablePackages.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); - mTableViewerInstPkg = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION); - mTableInstPkg = mTableViewerInstPkg.getTable(); - mTableInstPkg.setHeaderVisible(true); - mTableInstPkg.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); - - mColumnInstSummary = new TableColumn(mTableInstPkg, SWT.NONE); - mColumnInstSummary.setWidth(377); - mColumnInstSummary.setText("Summary"); - - mColumnInstApiLevel = new TableColumn(mTableInstPkg, SWT.NONE); - mColumnInstApiLevel.setWidth(100); - mColumnInstApiLevel.setText("API Level"); - - mColumnInstRevision = new TableColumn(mTableInstPkg, SWT.NONE); - mColumnInstRevision.setWidth(100); - mColumnInstRevision.setText("Revision"); + mColumnPackages = new TableColumn(mTablePackages, SWT.NONE); + mColumnPackages.setWidth(377); + mColumnPackages.setText("Installed Packages"); mDescriptionContainer = new Group(parent, SWT.NONE); mDescriptionContainer.setLayout(new GridLayout(1, false)); @@ -114,32 +103,32 @@ public class InstalledPackagesPage extends Composite { mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true, 1, 1)); mDescriptionLabel.setText("Line1\nLine2\nLine3"); - mInstButtons = new Composite(parent, SWT.NONE); - mInstButtons.setLayout(new GridLayout(5, false)); - mInstButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1)); + mContainerButtons = new Composite(parent, SWT.NONE); + mContainerButtons.setLayout(new GridLayout(5, false)); + mContainerButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1)); - mInstUpdate = new Button(mInstButtons, SWT.NONE); - mInstUpdate.addSelectionListener(new SelectionAdapter() { + mUpdateButton = new Button(mContainerButtons, SWT.NONE); + mUpdateButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onUpdateInstalledPackage(); //$hide$ (hide from SWT designer) } }); - mInstUpdate.setText("Update..."); + mUpdateButton.setText("Update..."); - mPlaceholder1 = new Label(mInstButtons, SWT.NONE); + mPlaceholder1 = new Label(mContainerButtons, SWT.NONE); mPlaceholder1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); - mInstDelete = new Button(mInstButtons, SWT.NONE); - mInstDelete.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1)); - mInstDelete.setText("Delete..."); + mDeleteButton = new Button(mContainerButtons, SWT.NONE); + mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1)); + mDeleteButton.setText("Delete..."); - mPlaceholder2 = new Label(mInstButtons, SWT.NONE); + mPlaceholder2 = new Label(mContainerButtons, SWT.NONE); mPlaceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); - mInstHomePage = new Button(mInstButtons, SWT.NONE); - mInstHomePage.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - mInstHomePage.setText("Home Page..."); + mHomePageButton = new Button(mContainerButtons, SWT.NONE); + mHomePageButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + mHomePageButton.setText("Home Page..."); } private void createSdkLocation(Composite parent) { @@ -175,7 +164,36 @@ public class InstalledPackagesPage extends Composite { // Hide everything down-below from SWT designer //$hide>>$ + private void postCreate() { + adjustColumnsWidth(); + } + + + /** + * Adds a listener to adjust the columns width when the parent is resized. + *

+ * If we need something more fancy, we might want to use this: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co + */ + private void adjustColumnsWidth() { + // Add a listener to resize the column to the full width of the table + mTablePackages.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = mTablePackages.getClientArea(); + mColumnPackages.setWidth(r.width); + } + }); + } + + public void setInput(LocalSdkAdapter localSdkAdapter) { + mTableViewerPackages.setLabelProvider( localSdkAdapter.getLabelProvider()); + mTableViewerPackages.setContentProvider(localSdkAdapter.getContentProvider()); + mTableViewerPackages.setInput(localSdkAdapter); + } + protected void onUpdateInstalledPackage() { + // TODO just a test, needs to be removed later. ProgressTask.start(getShell(), "Test", new ITask() { public void run(ITaskMonitor monitor) { monitor.setDescription("Test"); @@ -189,6 +207,7 @@ public class InstalledPackagesPage extends Composite { try { Thread.sleep(5); } catch (InterruptedException e) { + // ignore } } } diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java new file mode 100755 index 000000000..330be18a6 --- /dev/null +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009 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.sdkuilib.internal.repository; + +import com.android.sdklib.internal.repository.IDescription; +import com.android.sdklib.internal.repository.LocalSdkParser; +import com.android.sdklib.internal.repository.Package; +import com.android.sdklib.internal.repository.RepoSource; + +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; + +/** + * Table adapters to use the local SDK list. + */ +class LocalSdkAdapter { + + private final LocalSdkParser mLocalSdkParser; + private String mOsSdkRoot; + + public LocalSdkAdapter(LocalSdkParser localSdkParser) { + mLocalSdkParser = localSdkParser; + } + + public void setSdkRoot(String osSdkRoot) { + mOsSdkRoot = osSdkRoot; + mLocalSdkParser.clearPackages(); + } + + public ILabelProvider getLabelProvider() { + return new ViewerLabelProvider(); + } + + + public IContentProvider getContentProvider() { + return new TableContentProvider(); + } + + // ------------ + + public static class ViewerLabelProvider extends LabelProvider { + /** Returns null by default */ + @Override + public Image getImage(Object element) { + return super.getImage(element); + } + + /** Returns the toString of the element. */ + @Override + public String getText(Object element) { + if (element instanceof IDescription) { + return ((IDescription) element).getShortDescription(); + } + return super.getText(element); + } + } + + // ------------ + + private static class TableContentProvider implements IStructuredContentProvider { + + // Called when the viewer is disposed + public void dispose() { + // pass + } + + // Called when the input is set or changed on the provider + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // pass + } + + /** + * Called to collect the root elements for the given input. + * The input here is a {@link LocalSdkAdapter} object, this returns an array + * of {@link RepoSource}. + */ + public Object[] getElements(Object inputElement) { + if (inputElement instanceof LocalSdkAdapter) { + LocalSdkAdapter adapter = (LocalSdkAdapter) inputElement; + LocalSdkParser parser = adapter.mLocalSdkParser; + + Package[] packages = parser.getPackages(); + + if (packages == null) { + // load on demand the first time + packages = parser.parseSdk(adapter.mOsSdkRoot); + } + + if (packages != null) { + return packages; + } + } + + return new Object[0]; + } + } + +} diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java index 389709699..7667355fe 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java @@ -152,11 +152,11 @@ class ProgressTask extends Dialog * Sets the description in the current task dialog. * This method can be invoke from a non-UI thread. */ - public void setDescription(final String description) { + public void setDescription(final String descriptionFormat, final Object...args) { mDialogShell.getDisplay().asyncExec(new Runnable() { public void run() { if (!mLabel.isDisposed()) { - mLabel.setText(description); + mLabel.setText(String.format(descriptionFormat, args)); } } }); @@ -166,14 +166,14 @@ class ProgressTask extends Dialog * Sets the description in the current task dialog. * This method can be invoke from a non-UI thread. */ - public void setResult(final String result) { + public void setResult(final String resultFormat, final Object...args) { mAutomaticallyCloseOnTaskCompletion = false; if (!mDialogShell.isDisposed()) { mDialogShell.getDisplay().asyncExec(new Runnable() { public void run() { if (!mResultText.isDisposed()) { mResultText.setVisible(true); - mResultText.setText(result); + mResultText.setText(String.format(resultFormat, args)); } } }); diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvailablePackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java similarity index 79% rename from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvailablePackagesPage.java rename to tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java index 08aef2b22..7a8c62cd1 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvailablePackagesPage.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java @@ -17,9 +17,14 @@ package com.android.sdkuilib.internal.repository; +import com.android.sdklib.internal.repository.Archive; import com.android.sdklib.internal.repository.IDescription; +import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.swt.SWT; @@ -36,10 +41,8 @@ import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; -import org.eclipse.jface.viewers.ICheckStateListener; -import org.eclipse.jface.viewers.CheckStateChangedEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.DoubleClickEvent; + +import java.util.ArrayList; /* * TODO list @@ -54,8 +57,9 @@ import org.eclipse.jface.viewers.DoubleClickEvent; * - install selected callback */ -public class AvailablePackagesPage extends Composite { +public class RemotePackagesPage extends Composite { + private final UpdaterWindowImpl mUpdaterWindow; private final UpdaterData mUpdaterData; private CheckboxTreeViewer mTreeViewerSources; @@ -70,14 +74,18 @@ public class AvailablePackagesPage extends Composite { private Label mDescriptionLabel; + /** * Create the composite. * @param parent The parent of the composite. * @param updaterData An instance of {@link UpdaterData}. If null, a local * one will be allocated just to help with the SWT Designer. */ - public AvailablePackagesPage(Composite parent, UpdaterData updaterData) { + public RemotePackagesPage(UpdaterWindowImpl updaterWindow, + Composite parent, + UpdaterData updaterData) { super(parent, SWT.BORDER); + mUpdaterWindow = updaterWindow; mUpdaterData = updaterData != null ? updaterData : new UpdaterData(); @@ -91,16 +99,14 @@ public class AvailablePackagesPage extends Composite { mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER); mTreeViewerSources.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { - doTreeDoubleClick(event); //$hide$ + onTreeDoubleClick(event); //$hide$ } }); mTreeViewerSources.addCheckStateListener(new ICheckStateListener() { public void checkStateChanged(CheckStateChangedEvent event) { - doTreeCeckStateChanged(event); //$hide$ + onTreeCheckStateChanged(event); //$hide$ } }); - mTreeViewerSources.setContentProvider(mUpdaterData.getSources().getContentProvider()); - mTreeViewerSources.setLabelProvider(mUpdaterData.getSources().getLabelProvider()); mTreeSources = mTreeViewerSources.getTree(); mTreeSources.addSelectionListener(new SelectionAdapter() { @Override @@ -137,6 +143,12 @@ public class AvailablePackagesPage extends Composite { mRefreshButton.setText("Refresh"); mInstallSelectedButton = new Button(parent, SWT.NONE); + mInstallSelectedButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onInstallSelectedArchives(); //$hide$ + } + }); mInstallSelectedButton.setText("Install Selected"); } @@ -171,7 +183,9 @@ public class AvailablePackagesPage extends Composite { }); } - public void setInput(RepoSources sources) { + public void setInput(RepoSourcesAdapter sources) { + mTreeViewerSources.setContentProvider(sources.getContentProvider()); + mTreeViewerSources.setLabelProvider( sources.getLabelProvider()); mTreeViewerSources.setInput(sources); onTreeSelected(); } @@ -189,13 +203,27 @@ public class AvailablePackagesPage extends Composite { mDescriptionLabel.setText(""); //$NON-NLS1-$ } - private void doTreeCeckStateChanged(CheckStateChangedEvent event) { + private void onTreeCheckStateChanged(CheckStateChangedEvent event) { boolean b = event.getChecked(); - Object elem = event.getElement(); + Object elem = event.getElement(); // Will be Archive or Package or RepoSource Object src = event.getSource(); + // TODO } - private void doTreeDoubleClick(DoubleClickEvent event) { + private void onTreeDoubleClick(DoubleClickEvent event) { + // TODO + } + + private void onInstallSelectedArchives() { + + ArrayList archives = new ArrayList(); + for (Object element : mTreeViewerSources.getCheckedElements()) { + if (element instanceof Archive) { + archives.add((Archive) element); + } + } + + mUpdaterWindow.installArchives(archives); } // End of hiding from SWT Designer diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSources.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java similarity index 73% rename from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSources.java rename to tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java index 0e61b1e85..3f020d112 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSources.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java @@ -18,9 +18,9 @@ package com.android.sdkuilib.internal.repository; import com.android.sdklib.internal.repository.Archive; import com.android.sdklib.internal.repository.IDescription; -import com.android.sdklib.internal.repository.ITaskFactory; import com.android.sdklib.internal.repository.Package; import com.android.sdklib.internal.repository.RepoSource; +import com.android.sdklib.internal.repository.RepoSources; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ILabelProvider; @@ -29,27 +29,17 @@ import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.graphics.Image; -import java.util.ArrayList; - /** * A list of sdk-repository sources. * * This implementation is UI dependent. */ -class RepoSources { +class RepoSourcesAdapter { - private ArrayList mSources = new ArrayList(); - private ITaskFactory mTaskFactory; + private final RepoSources mRepoSources; - public RepoSources() { - } - - public void setTaskFactory(ITaskFactory taskFactory) { - mTaskFactory = taskFactory; - } - - public void add(RepoSource source) { - mSources.add(source); + public RepoSourcesAdapter(RepoSources repoSources) { + mRepoSources = repoSources; } public ILabelProvider getLabelProvider() { @@ -63,7 +53,7 @@ class RepoSources { // ------------ - public class ViewerLabelProvider extends LabelProvider { + public static class ViewerLabelProvider extends LabelProvider { /** Returns null by default */ @Override public Image getImage(Object element) { @@ -82,9 +72,9 @@ class RepoSources { // ------------ - private class TreeContentProvider implements ITreeContentProvider { + private static class TreeContentProvider implements ITreeContentProvider { - private Object mInput; + private RepoSourcesAdapter mInput; // Called when the viewer is disposed public void dispose() { @@ -93,13 +83,14 @@ class RepoSources { // Called when the input is set or changed on the provider public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - mInput = newInput; + assert newInput == null || newInput instanceof RepoSourcesAdapter; + mInput = (RepoSourcesAdapter) newInput; // pass } /** * Called to collect the root elements for the given input. - * The input here is a {@link RepoSources} object, this returns an array + * The input here is a {@link RepoSourcesAdapter} object, this returns an array * of {@link RepoSource}. */ public Object[] getElements(Object inputElement) { @@ -110,20 +101,20 @@ class RepoSources { * Get the children of the given parent. This is requested on-demand as * nodes are expanded. * - * For a {@link RepoSources} object, returns an array of {@link RepoSource}s. + * For a {@link RepoSourcesAdapter} object, returns an array of {@link RepoSource}s. * For a {@link RepoSource}, returns an array of {@link Package}s. * For a {@link Package}, returns an array of {@link Archive}s. */ public Object[] getChildren(Object parentElement) { - if (parentElement instanceof RepoSources) { - return ((RepoSources) parentElement).mSources.toArray(); + if (parentElement instanceof RepoSourcesAdapter) { + return ((RepoSourcesAdapter) parentElement).mRepoSources.getSources().toArray(); } else if (parentElement instanceof RepoSource) { RepoSource source = (RepoSource) parentElement; Package[] packages = source.getPackages(); if (packages == null) { - source.load(mTaskFactory); + source.load(mInput.mRepoSources.getTaskFactory()); packages = source.getPackages(); } if (packages != null) { @@ -134,17 +125,20 @@ class RepoSources { return ((Package) parentElement).getArchives(); } - return new Object[0]; } /** * Returns the parent of a given element. - * The input {@link RepoSources} is the parent of all {@link RepoSource} elements. + * The input {@link RepoSourcesAdapter} is the parent of all {@link RepoSource} elements. */ public Object getParent(Object element) { + if (element instanceof RepoSource) { return mInput; + + } else if (element instanceof Package) { + return ((Package) element).getParentSource(); } return null; } @@ -152,7 +146,8 @@ class RepoSources { /** * Returns true if a given element has children, which is used to display a * "+/expand" box next to the tree node. - * All {@link RepoSource} are expandable, whether they actually have any childre or not. + * All {@link RepoSource} and {@link Package} are expandable, whether they actually + * have any children or not. */ public boolean hasChildren(Object element) { return element instanceof RepoSource || element instanceof Package; diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java index 920769b40..384ff5717 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java @@ -16,13 +16,21 @@ package com.android.sdkuilib.internal.repository; +import com.android.sdklib.internal.repository.LocalSdkParser; +import com.android.sdklib.internal.repository.RepoSources; + /** * Data shared between {@link UpdaterWindowImpl} and its pages. */ class UpdaterData { private String mOsSdkRoot; private boolean mUserCanChangeSdkRoot; - private RepoSources mSources = new RepoSources(); + + private final LocalSdkParser mLocalSdkParser = new LocalSdkParser(); + private final RepoSources mSources = new RepoSources(); + + private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(mLocalSdkParser); + private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(mSources); public void setOsSdkRoot(String osSdkRoot) { mOsSdkRoot = osSdkRoot; @@ -40,12 +48,20 @@ class UpdaterData { return mUserCanChangeSdkRoot; } - public void setSources(RepoSources sources) { - mSources = sources; - } - public RepoSources getSources() { return mSources; } + public RepoSourcesAdapter getSourcesAdapter() { + return mSourcesAdapter; + } + + public LocalSdkParser getLocalSdkParser() { + return mLocalSdkParser; + } + + public LocalSdkAdapter getLocalSdkAdapter() { + return mLocalSdkAdapter; + } + } diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java index 50ebdb6c3..5613bfd59 100755 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java @@ -17,7 +17,12 @@ package com.android.sdkuilib.internal.repository; +import com.android.sdklib.internal.repository.Archive; +import com.android.sdklib.internal.repository.ITask; +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.repository.SdkRepository; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; @@ -36,14 +41,22 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Shell; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Collection; /** * This is the private implementation of the UpdateWindow. */ public class UpdaterWindowImpl { + private static final int NUM_FETCH_URL_MONITOR_INC = 10; + private final UpdaterData mUpdaterData = new UpdaterData(); private ArrayList mPages = new ArrayList(); private boolean mInternalPageChange; @@ -54,10 +67,11 @@ public class UpdaterWindowImpl { private SashForm mSashForm; private List mPageList; private Composite mPagesRootComposite; - private InstalledPackagesPage mInstalledPackagePage; - private AvailablePackagesPage mAvailablePackagesPage; + private LocalPackagesPage mLocalPackagePage; + private RemotePackagesPage mRemotePackagesPage; private StackLayout mStackLayout; private Image mIconImage; + private ProgressTaskFactory mTaskFactory; public UpdaterWindowImpl(String osSdkRoot, boolean userCanChangeSdkRoot) { mUpdaterData.setOsSdkRoot(osSdkRoot); @@ -114,8 +128,8 @@ public class UpdaterWindowImpl { mStackLayout = new StackLayout(); mPagesRootComposite.setLayout(mStackLayout); - mInstalledPackagePage = new InstalledPackagesPage(mPagesRootComposite, mUpdaterData); - mAvailablePackagesPage = new AvailablePackagesPage(mPagesRootComposite, mUpdaterData); + mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData); + mRemotePackagesPage = new RemotePackagesPage(this, mPagesRootComposite, mUpdaterData); mSashForm.setWeights(new int[] {150, 576}); } @@ -158,8 +172,10 @@ public class UpdaterWindowImpl { * Once the UI has been created, initialize the content */ private void firstInit() { - addPage(mInstalledPackagePage, "Installed Packages"); - addPage(mAvailablePackagesPage, "Available Packages"); + mTaskFactory = new ProgressTaskFactory(getShell()); + + addPage(mLocalPackagePage, "Installed Packages"); + addPage(mRemotePackagesPage, "Available Packages"); displayPage(0); mPageList.setSelection(0); @@ -199,23 +215,214 @@ public class UpdaterWindowImpl { } private void setupSources() { - mUpdaterData.getSources().setTaskFactory(new ProgressTaskFactory(getShell())); + mUpdaterData.getSources().setTaskFactory(mTaskFactory); - mUpdaterData.getSources().add(new RepoSource( - "https://dl.google.com/android/eclipse/repository/index.xml", //$NON-NLS-1$ - false /* addonOnly */)); + mUpdaterData.getSources().add( + new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /* addonOnly */)); String url = System.getenv("TEMP_SDK_URL"); // TODO STOPSHIP temporary remove before shipping if (url != null) { mUpdaterData.getSources().add(new RepoSource(url, false /* addonOnly */)); } - mAvailablePackagesPage.setInput(mUpdaterData.getSources()); + mRemotePackagesPage.setInput(mUpdaterData.getSourcesAdapter()); } private void scanLocalSdkFolders() { - // TODO Auto-generated method stub + mUpdaterData.getLocalSdkAdapter().setSdkRoot(mUpdaterData.getOsSdkRoot()); + mLocalPackagePage.setInput(mUpdaterData.getLocalSdkAdapter()); + } + + public void installArchives(final Collection archives) { + // TODO move most parts to SdkLib, maybe as part of Archive, making archives self-installing. + mTaskFactory.start("Installing Archives", new ITask() { + public void run(ITaskMonitor monitor) { + + monitor.setProgressMax(archives.size() * (NUM_FETCH_URL_MONITOR_INC + 3)); + monitor.setDescription("Preparing to install archives"); + + int num_installed = 0; + for (Archive archive : archives) { + + if (!archive.isCompatible()) { + monitor.setResult("Skipping incompatible archive: %1$s", + archive.getShortDescription()); + monitor.incProgress(3); + continue; + } + + File archiveFile = null; + try { + archiveFile = downloadArchive(archive, monitor); + monitor.incProgress(1); + if (archiveFile != null) { + if (installArchive(archive, archiveFile, monitor)) { + num_installed++; + } + } + monitor.incProgress(1); + } finally { + if (archiveFile != null) { + if (!archiveFile.delete()) { + archiveFile.deleteOnExit(); + } + } + } + } + + if (num_installed == 0) { + monitor.setResult("Nothing was installed."); + } + } + }); + } + + /** + * 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) { + + try { + File tmpFile = File.createTempFile("sdkupload", "bin"); //$NON-NLS-1$ //$NON-NLS-2$ + + monitor.setDescription("Downloading %1$s", archive.getShortDescription()); + + 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", + archive.getShortDescription()); + return null; + } + + String base = src.getUrl(); + if (!base.endsWith("/") && !link.startsWith("/")) { //$NON-NLS-1$ //$NON-NLS-2$ + base += "/"; //$NON-NLS-1$ + } + + link = base + link; + } + + fetchUrl(tmpFile, archive, link, monitor); + + } catch (IOException e) { + monitor.setResult(e.getMessage()); + } + return null; + } + + /** + * Actually performs the download. + * Also computes the SHA1 of the file on the fly. + *

+ * 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. + *

+ * Increments the monitor by {@link #NUM_FETCH_URL_MONITOR_INC} (which is 10). + */ + private boolean fetchUrl(File tmpFile, Archive archive, String urlString, ITaskMonitor monitor) { + URL url; + + 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_FETCH_URL_MONITOR_INC; + long next_inc = inc; + + while ((n = is.read(buf)) >= 0) { + if (n > 0) { + os.write(buf, 0, n); + digester.update(buf, 0, n); + } + + total += n; + if (total >= next_inc) { + monitor.incProgress(1); + next_inc += inc; + } + + if (monitor.cancelRequested()) { + 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++) { + byte b = digest[i]; + 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; + } + + private boolean installArchive(Archive archive, File archiveFile, ITaskMonitor monitor) { + monitor.setDescription("Installing %1$s", archive.getShortDescription()); + + File destFolder = archive.getParentPackage().getInstallFolder(mUpdaterData.getOsSdkRoot()); + + return false; } // End of hiding from SWT Designer