The install phase is still work in progress.
The local part needs to display descriptions.
Buttons callback are generally not implemented yet.
/** 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" */
/** 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) ?
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.
/** 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 */
/** 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 =
"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$
/**
/** 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$
}
/** 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". */
\r
package com.android.sdklib.internal.repository;\r
\r
+import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
import com.android.sdklib.repository.SdkRepository;\r
\r
import org.w3c.dom.Node;\r
\r
+import java.io.File;\r
import java.util.ArrayList;\r
\r
/**\r
* <p/>\r
* This constructor should throw an exception if the package cannot be created.\r
*/\r
- AddonPackage(Node packageNode) {\r
- super(packageNode);\r
+ AddonPackage(RepoSource source, Node packageNode) {\r
+ super(source, packageNode);\r
mVendor = getXmlString(packageNode, SdkRepository.NODE_VENDOR);\r
mName = getXmlString(packageNode, SdkRepository.NODE_NAME);\r
mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
mLibs = parseLibs(getFirstChild(packageNode, SdkRepository.NODE_LIBS));\r
}\r
\r
+ /**\r
+ * Creates a new platform package based on an actual {@link IAndroidTarget} (with\r
+ * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.\r
+ * This is used to list local SDK folders.\r
+ */\r
+ AddonPackage(IAndroidTarget target) {\r
+ super( null, //source\r
+ 0, //revision\r
+ target.getDescription(), //description\r
+ null, //descUrl\r
+ Os.getCurrentOs(), //archiveOs\r
+ Arch.getCurrentArch(), //archiveArch\r
+ "", //archiveUrl //$NON-NLS-1$\r
+ 0, //archiveSize\r
+ null //archiveChecksum\r
+ );\r
+\r
+ mApiLevel = target.getApiVersionNumber();\r
+ mName = target.getName();\r
+ mVendor = target.getVendor();\r
+\r
+ IOptionalLibrary[] optLibs = target.getOptionalLibraries();\r
+ if (optLibs == null || optLibs.length == 0) {\r
+ mLibs = new Lib[0];\r
+ } else {\r
+ mLibs = new Lib[optLibs.length];\r
+ for (int i = 0; i < optLibs.length; i++) {\r
+ mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription());\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Parses a <libs> element.\r
+ */\r
private Lib[] parseLibs(Node libsNode) {\r
ArrayList<Lib> libs = new ArrayList<Lib>();\r
\r
return libs.toArray(new Lib[libs.size()]);\r
}\r
\r
+ /**\r
+ * Parses a <lib> element from a <libs> container.\r
+ */\r
private Lib parseLib(Node libNode) {\r
return new Lib(getXmlString(libNode, SdkRepository.NODE_NAME),\r
getXmlString(libNode, SdkRepository.NODE_DESCRIPTION));\r
getShortDescription(),\r
super.getLongDescription());\r
}\r
+\r
+ /**\r
+ * Computes a potential installation folder if an archive of this package were\r
+ * to be installed right away in the given SDK root.\r
+ * <p/>\r
+ * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level".\r
+ * The name needs to be sanitized to be acceptable as a directory name.\r
+ * However if we can find a different directory under SDK/add-ons that already\r
+ * has this add-ons installed, we'll use that one.\r
+ *\r
+ * @param osSdkRoot The OS path of the SDK root folder.\r
+ * @return A new {@link File} corresponding to the directory to use to install this package.\r
+ */\r
+ @Override\r
+ public File getInstallFolder(String osSdkRoot) {\r
+ File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);\r
+\r
+ String name = String.format("%s-%d", getName(), getApiLevel()); // $NON-NLS-1$\r
+\r
+ name = name.replaceAll("[^a-zA-Z0-9_-]+", "_"); // $NON-NLS-1$\r
+ name = name.replaceAll("_+", "_"); // $NON-NLS-1$\r
+\r
+ File folder = new File(addons, name);\r
+\r
+ // TODO find similar existing addon in addons folder\r
+ return folder;\r
+ }\r
}\r
\r
package com.android.sdklib.internal.repository;\r
\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
\r
/**\r
* A {@link Archive} is the base class for "something" that can be downloaded from\r
/** The checksum type. */\r
public enum ChecksumType {\r
/** A SHA1 checksum, represented as a 40-hex string. */\r
- SHA1\r
+ SHA1("SHA-1"); //$NON-NLS-1$\r
+\r
+ private final String mAlgorithmName;\r
+\r
+ /**\r
+ * Constructs a {@link ChecksumType} with the algorigth name\r
+ * suitable for {@link MessageDigest#getInstance(String)}.\r
+ * <p/>\r
+ * These names are officially documented at\r
+ * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest\r
+ */\r
+ private ChecksumType(String algorithmName) {\r
+ mAlgorithmName = algorithmName;\r
+ }\r
+\r
+ /**\r
+ * Returns a new {@link MessageDigest} instance for this checksum type.\r
+ * @throws NoSuchAlgorithmException if this algorithm is not available.\r
+ */\r
+ public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {\r
+ return MessageDigest.getInstance(mAlgorithmName);\r
+ }\r
}\r
\r
/** The OS that this archive can be downloaded on. */\r
public enum Os {\r
- ANY,\r
- LINUX,\r
- MACOSX,\r
- WINDOWS\r
+ ANY("Any"),\r
+ LINUX("Linux"),\r
+ MACOSX("MacOS X"),\r
+ WINDOWS("Windows");\r
+\r
+ private final String mUiName;\r
+\r
+ private Os(String uiName) {\r
+ mUiName = uiName;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return mUiName;\r
+ }\r
+\r
+ /**\r
+ * Returns the current OS as one of the {@link Os} enum values or null.\r
+ */\r
+ public static Os getCurrentOs() {\r
+ String os = System.getProperty("os.name"); //$NON-NLS-1$\r
+ if (os.startsWith("Mac OS")) { //$NON-NLS-1$\r
+ return Os.MACOSX;\r
+\r
+ } else if (os.startsWith("Windows")) { //$NON-NLS-1$\r
+ return Os.WINDOWS;\r
+\r
+ } else if (os.startsWith("Linux")) { //$NON-NLS-1$\r
+ return Os.LINUX;\r
+ }\r
+\r
+ return null;\r
+ }\r
}\r
\r
- /** The Architecture that this archvie can be downloaded on. */\r
+ /** The Architecture that this archive can be downloaded on. */\r
public enum Arch {\r
- ANY,\r
- PPC,\r
- X86,\r
- X86_64\r
+ ANY("Any"),\r
+ PPC("PowerPC"),\r
+ X86("x86"),\r
+ X86_64("x86_64");\r
+\r
+ private final String mUiName;\r
+\r
+ private Arch(String uiName) {\r
+ mUiName = uiName;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return mUiName;\r
+ }\r
+\r
+ /**\r
+ * Returns the current architecture as one of the {@link Arch} enum values or null.\r
+ */\r
+ public static Arch getCurrentArch() {\r
+ // Values listed from http://lopica.sourceforge.net/os.html\r
+ String arch = System.getProperty("os.arch");\r
+\r
+ if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) {\r
+ return Arch.X86_64;\r
+\r
+ } else if (arch.equalsIgnoreCase("x86")\r
+ || arch.equalsIgnoreCase("i386")\r
+ || arch.equalsIgnoreCase("i686")) {\r
+ return Arch.X86;\r
+\r
+ } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) {\r
+ return Arch.PPC;\r
+ }\r
+\r
+ return null;\r
+ }\r
}\r
\r
private final Os mOs;\r
private final long mSize;\r
private final String mChecksum;\r
private final ChecksumType mChecksumType = ChecksumType.SHA1;\r
+ private final Package mPackage;\r
\r
/**\r
* Creates a new archive.\r
*/\r
- Archive(Os os, Arch arch, String url, long size, String checksum) {\r
+ Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {\r
+ mPackage = pkg;\r
mOs = os;\r
mArch = arch;\r
mUrl = url;\r
mChecksum = checksum;\r
}\r
\r
- /** Returns the archive size, an int > 0. */\r
+ /**\r
+ * Returns the package that created and owns this archive.\r
+ * It should generally not be null.\r
+ */\r
+ public Package getParentPackage() {\r
+ return mPackage;\r
+ }\r
+\r
+ /**\r
+ * Returns the archive size, an int > 0.\r
+ * Size will be 0 if this a local installed folder of unknown size.\r
+ */\r
public long getSize() {\r
return mSize;\r
}\r
\r
- /** Returns the SHA1 archive checksum, as a 40-char hex. */\r
+ /**\r
+ * Returns the SHA1 archive checksum, as a 40-char hex.\r
+ * Can be empty but not null for local installed folders.\r
+ */\r
public String getChecksum() {\r
return mChecksum;\r
}\r
\r
- /** Returns the checksum type, always {@link ChecksumType#SHA1} right now. */\r
+ /**\r
+ * Returns the checksum type, always {@link ChecksumType#SHA1} right now.\r
+ */\r
public ChecksumType getChecksumType() {\r
return mChecksumType;\r
}\r
\r
- /** Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
- * Can be empty but not null. */\r
- public String getDescUrl() {\r
+ /**\r
+ * Returns the download archive URL, either absolute or relative to the repository xml.\r
+ * For a local installed folder, an URL is frabricated from the folder path.\r
+ */\r
+ public String getUrl() {\r
return mUrl;\r
}\r
\r
- /** Returns the archive {@link Os} enum. */\r
+ /**\r
+ * Returns the archive {@link Os} enum.\r
+ * Can be null for a local installed folder on an unknown OS.\r
+ */\r
public Os getOs() {\r
return mOs;\r
}\r
\r
- /** Returns the archive {@link Arch} enum. */\r
+ /**\r
+ * Returns the archive {@link Arch} enum.\r
+ * Can be null for a local installed folder on an unknown architecture.\r
+ */\r
public Arch getArch() {\r
return mArch;\r
}\r
\r
+ /**\r
+ * Generates a short description for this archive.\r
+ */\r
public String getShortDescription() {\r
- String os = "any OS";\r
- if (mOs != Os.ANY) {\r
- os = capitalize(mOs.toString());\r
+ String os;\r
+ if (mOs == null) {\r
+ os = "unknown OS";\r
+ } else if (mOs == Os.ANY) {\r
+ os = "any OS";\r
+ } else {\r
+ os = mOs.toString();\r
}\r
\r
String arch = "";\r
- if (mArch != Arch.ANY) {\r
- arch = mArch.toString().toLowerCase();\r
+ if (mArch != null && mArch != Arch.ANY) {\r
+ arch = mArch.toString();\r
}\r
\r
return String.format("Archive for %1$s %2$s", os, arch);\r
}\r
\r
- private String capitalize(String string) {\r
- if (string.length() > 1) {\r
- return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();\r
- } else {\r
- return string.toUpperCase();\r
- }\r
- }\r
-\r
+ /**\r
+ * Generates a longer description for this archive.\r
+ */\r
public String getLongDescription() {\r
return String.format("%1$s\nSize: %2$d MiB\nSHA1: %3$s",\r
getShortDescription(),\r
Math.round(getSize() / (1024*1024)),\r
getChecksum());\r
}\r
+\r
+ /**\r
+ * Returns true if this archive can be installed on the current platform.\r
+ */\r
+ public boolean isCompatible() {\r
+ // Check OS\r
+ Os os = getOs();\r
+\r
+ if (os != Os.ANY) {\r
+ Os os2 = Os.getCurrentOs();\r
+ if (os2 != os) {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ // Check Arch\r
+ Arch arch = getArch();\r
+\r
+ if (arch != Arch.ANY) {\r
+ Arch arch2 = Arch.getCurrentArch();\r
+ if (arch2 != arch) {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
}\r
+\r
\r
package com.android.sdklib.internal.repository;\r
\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
import com.android.sdklib.repository.SdkRepository;\r
\r
import org.w3c.dom.Node;\r
\r
+import java.io.File;\r
+\r
/**\r
* Represents a doc XML node in an SDK repository.\r
*/\r
* <p/>\r
* This constructor should throw an exception if the package cannot be created.\r
*/\r
- DocPackage(Node packageNode) {\r
- super(packageNode);\r
+ DocPackage(RepoSource source, Node packageNode) {\r
+ super(source, packageNode);\r
mApiLevel = getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
}\r
\r
- /** Returns the api-level, an int > 0, for platform, add-on and doc packages. */\r
+ /**\r
+ * Manually create a new package with one archive and the given attributes.\r
+ * This is used to create packages from local directories.\r
+ */\r
+ DocPackage(RepoSource source,\r
+ int apiLevel,\r
+ int revision,\r
+ String description,\r
+ String descUrl,\r
+ Os archiveOs,\r
+ Arch archiveArch,\r
+ String archiveUrl,\r
+ long archiveSize,\r
+ String archiveChecksum) {\r
+ super(source,\r
+ revision,\r
+ description,\r
+ descUrl,\r
+ archiveOs,\r
+ archiveArch,\r
+ archiveUrl,\r
+ archiveSize,\r
+ archiveChecksum);\r
+ mApiLevel = apiLevel;\r
+ }\r
+\r
+ /** Returns the api-level, an int > 0, for platform, add-on and doc packages.\r
+ * Can be 0 if this is a local package of unknown api-level. */\r
public int getApiLevel() {\r
return mApiLevel;\r
}\r
/** Returns a short description for an {@link IDescription}. */\r
@Override\r
public String getShortDescription() {\r
- return String.format("Documentation for SDK Android API %1$d", getApiLevel());\r
+ if (mApiLevel != 0) {\r
+ return String.format("Documentation for Android SDK, API %1$d", mApiLevel);\r
+ } else {\r
+ return String.format("Documentation for Android SDK");\r
+ }\r
}\r
\r
/** Returns a long description for an {@link IDescription}. */\r
getShortDescription(),\r
super.getLongDescription());\r
}\r
+\r
+ /**\r
+ * Computes a potential installation folder if an archive of this package were\r
+ * to be installed right away in the given SDK root.\r
+ * <p/>\r
+ * A "doc" package should always be located in SDK/docs.\r
+ *\r
+ * @param osSdkRoot The OS path of the SDK root folder.\r
+ * @return A new {@link File} corresponding to the directory to use to install this package.\r
+ */\r
+ @Override\r
+ public File getInstallFolder(String osSdkRoot) {\r
+ return new File(osSdkRoot, SdkConstants.FD_DOCS);\r
+ }\r
}\r
* Sets the description in the current task dialog.\r
* This method can be invoked from a non-UI thread.\r
*/\r
- public void setDescription(String description);\r
+ public void setDescription(String descriptionFormat, Object...args);\r
\r
/**\r
* Sets the result text in the current task dialog.\r
* This method can be invoked from a non-UI thread.\r
*/\r
- public void setResult(String result);\r
+ public void setResult(String resultFormat, Object...args);\r
\r
/**\r
* Sets the max value of the progress bar.\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdklib.internal.repository;\r
+\r
+import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.ISdkLog;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
+import com.android.sdklib.repository.SdkRepository;\r
+\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Node;\r
+import org.xml.sax.InputSource;\r
+import org.xml.sax.SAXException;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileReader;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.StringReader;\r
+import java.net.MalformedURLException;\r
+import java.util.ArrayList;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import javax.xml.XMLConstants;\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import javax.xml.parsers.ParserConfigurationException;\r
+import javax.xml.transform.stream.StreamSource;\r
+import javax.xml.validation.Schema;\r
+import javax.xml.validation.SchemaFactory;\r
+import javax.xml.validation.Validator;\r
+\r
+/**\r
+ * Scans a local SDK to find which packages are currently installed.\r
+ */\r
+public class LocalSdkParser {\r
+\r
+ private static final String SOURCE_XML = "source.xml"; //$NON-NLS-1$ // TODO move to global constants\r
+ private Package[] mPackages;\r
+\r
+ public LocalSdkParser() {\r
+ // TODO Auto-generated constructor stub\r
+ }\r
+\r
+ /**\r
+ * Returns the packages found by the last call to {@link #parseSdk(String)}.\r
+ */\r
+ public Package[] getPackages() {\r
+ return mPackages;\r
+ }\r
+\r
+ /**\r
+ * Clear the internal packages list. After this call, {@link #getPackages()} will return\r
+ * null till {@link #parseSdk(String)} is called.\r
+ */\r
+ public void clearPackages() {\r
+ mPackages = null;\r
+ }\r
+\r
+ /**\r
+ * Scan the give SDK to find all the packages already installed at this location.\r
+ * <p/>\r
+ * Store the packages internally. You can use {@link #getPackages()} to retrieve them\r
+ * at any time later.\r
+ *\r
+ * @param osSdkRoot The path to the SDK folder.\r
+ * @return The packages found. Can be retrieved later using {@link #getPackages()}.\r
+ */\r
+ public Package[] parseSdk(String osSdkRoot) {\r
+ ArrayList<Package> packages = new ArrayList<Package>();\r
+\r
+ Package pkg = scanDoc(new File(osSdkRoot, SdkConstants.FD_DOCS));\r
+ if (pkg != null) {\r
+ packages.add(pkg);\r
+ }\r
+\r
+ pkg = scanTools(new File(osSdkRoot, SdkConstants.FD_TOOLS));\r
+ if (pkg != null) {\r
+ packages.add(pkg);\r
+ }\r
+\r
+ // for platforms and add-ons, rely on the SdkManager parser\r
+ SdkManager sdkman = SdkManager.createManager(osSdkRoot, new ISdkLog() {\r
+ // A dummy sdk logger that doesn't log anything.\r
+ public void error(Throwable t, String errorFormat, Object... args) {\r
+ // pass\r
+ }\r
+ public void printf(String msgFormat, Object... args) {\r
+ // pass\r
+ }\r
+ public void warning(String warningFormat, Object... args) {\r
+ // pass\r
+ }\r
+ });\r
+\r
+ for(IAndroidTarget target : sdkman.getTargets()) {\r
+ pkg = null;\r
+\r
+ if (target.isPlatform()) {\r
+ pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
+ SdkRepository.NODE_PLATFORM);\r
+ if (pkg == null) {\r
+ pkg = new PlatformPackage(target);\r
+ }\r
+\r
+ } else {\r
+ pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
+ SdkRepository.NODE_ADD_ON);\r
+\r
+ if (pkg == null) {\r
+ pkg = new AddonPackage(target);\r
+ }\r
+ }\r
+\r
+ if (pkg != null) {\r
+ packages.add(pkg);\r
+ }\r
+ }\r
+\r
+ mPackages = packages.toArray(new Package[packages.size()]);\r
+ return mPackages;\r
+ }\r
+\r
+ /**\r
+ * Try to find a tools package at the given location.\r
+ * Returns null if not found.\r
+ */\r
+ private Package scanTools(File toolFolder) {\r
+ // Can we find a source.xml?\r
+ Package pkg = parseXml(new File(toolFolder, SOURCE_XML), SdkRepository.NODE_TOOL);\r
+\r
+ // We're not going to check that all tools are present. At the very least\r
+ // we should expect to find adb, android and an emulator adapted to the current OS.\r
+ Set<String> names = new HashSet<String>();\r
+ for (File file : toolFolder.listFiles()) {\r
+ names.add(file.getName());\r
+ }\r
+ if (!names.contains(SdkConstants.FN_ADB) ||\r
+ !names.contains(SdkConstants.androidCmdName()) ||\r
+ !names.contains(SdkConstants.FN_EMULATOR)) {\r
+ return null;\r
+ }\r
+\r
+ // if we don't have the package info, make one up\r
+ if (pkg == null) {\r
+ pkg = new ToolPackage(\r
+ null, //source\r
+ 0, //revision\r
+ "Tools", //description\r
+ null, //descUrl\r
+ Os.getCurrentOs(), //archiveOs\r
+ Arch.getCurrentArch(), //archiveArch\r
+ "", //archiveUrl //$NON-NLS-1$\r
+ 0, //archiveSize\r
+ null //archiveChecksum\r
+ );\r
+ }\r
+\r
+ return pkg;\r
+ }\r
+\r
+ /**\r
+ * Try to find a docs package at the given location.\r
+ * Returns null if not found.\r
+ */\r
+ private Package scanDoc(File docFolder) {\r
+ // Can we find a source.xml?\r
+ Package pkg = parseXml(new File(docFolder, SOURCE_XML), SdkRepository.NODE_DOC);\r
+\r
+ // To start with, a doc folder should have an "index.html" to be acceptable.\r
+ String html = readFile(new File(docFolder, "index.html"));\r
+ if (html != null) {\r
+ // Try to find something that looks like this line:\r
+ // <a href="./sdk/1.5_r1/index.html">\r
+ // We should find one or more of these and we want the highest version\r
+ // and release numbers. Note that unfortunately that doesn't give us\r
+ // the api-level we care about for the doc package.\r
+\r
+ String found = null;\r
+ Pattern re = Pattern.compile(\r
+ "<a\\s+href=\"./sdk/(\\d\\.\\d_r\\d)/index.html\">",\r
+ Pattern.DOTALL);\r
+ Matcher m = re.matcher(html);\r
+ while(m.find()) {\r
+ String v = m.group(1);\r
+ if (found == null || v.compareTo(found) == 1) {\r
+ found = v;\r
+ }\r
+ }\r
+\r
+ if (found == null) {\r
+ // That doesn't look like a doc folder.\r
+ return null;\r
+ }\r
+\r
+ // We found the line, so it seems like an SDK doc.\r
+ // Create a pkg if we don't have one yet.\r
+\r
+ if (pkg == null) {\r
+ String url = null;\r
+ try {\r
+ url = docFolder.toURI().toURL().toString();\r
+ } catch (MalformedURLException e) {\r
+ // ignore\r
+ }\r
+\r
+ pkg = new DocPackage(\r
+ null, //source\r
+ 0, //apiLevel\r
+ 0, //revision\r
+ String.format("Documentation for %1$s", found), //description\r
+ null, //descUrl\r
+ Os.getCurrentOs(), //archiveOs\r
+ Arch.getCurrentArch(), //archiveArch\r
+ url, //archiveUrl\r
+ 0, //archiveSize\r
+ null //archiveChecksum\r
+ );\r
+ }\r
+ }\r
+\r
+ return pkg;\r
+ }\r
+\r
+ /**\r
+ * Parses the given XML file for the specific element filter.\r
+ * The element must one of the package type local names: doc, tool, platform or addon.\r
+ * Returns null if no such package was found.\r
+ */\r
+ private Package parseXml(File sourceXmlFile, String elementFilter) {\r
+\r
+ String xml = readFile(sourceXmlFile);\r
+ if (xml != null) {\r
+ if (validateXml(xml)) {\r
+ return parsePackages(xml, elementFilter);\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Parses the given XML to find the specific element filter.\r
+ * The element must one of the package type local names: doc, tool, platform or addon.\r
+ * Returns null if no such package was found.\r
+ */\r
+ private Package parsePackages(String xml, String elementFilter) {\r
+\r
+ try {\r
+ Document doc = getDocument(xml);\r
+\r
+ Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY);\r
+ if (root != null) {\r
+\r
+ for (Node child = root.getFirstChild();\r
+ child != null;\r
+ child = child.getNextSibling()) {\r
+ if (child.getNodeType() == Node.ELEMENT_NODE &&\r
+ SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&\r
+ elementFilter.equals(child.getLocalName())) {\r
+ String name = child.getLocalName();\r
+ Package p = null;\r
+\r
+ try {\r
+ if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
+ return new AddonPackage(null /*source*/, child);\r
+\r
+ } else if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
+ return new PlatformPackage(null /*source*/, child);\r
+\r
+ } else if (SdkRepository.NODE_DOC.equals(name)) {\r
+ return new DocPackage(null /*source*/, child);\r
+\r
+ } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
+ return new ToolPackage(null /*source*/, child);\r
+ }\r
+ } catch (Exception e) {\r
+ // Ignore invalid packages\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ } catch (Exception e) {\r
+ // ignore\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Reads a file as a string.\r
+ * Returns null if the file could not be read.\r
+ */\r
+ private String readFile(File sourceXmlFile) {\r
+ FileReader fr = null;\r
+ try {\r
+ fr = new FileReader(sourceXmlFile);\r
+ BufferedReader br = new BufferedReader(fr);\r
+ StringBuilder dest = new StringBuilder();\r
+ char[] buf = new char[65536];\r
+ int n;\r
+ while ((n = br.read(buf)) > 0) {\r
+ if (n > 0) {\r
+ dest.append(buf, 0, n);\r
+ }\r
+ }\r
+ return dest.toString();\r
+\r
+ } catch (IOException e) {\r
+ // ignore\r
+\r
+ } finally {\r
+ if (fr != null) {\r
+ try {\r
+ fr.close();\r
+ } catch (IOException e) {\r
+ // ignore\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Validates this XML against the SDK Repository schema.\r
+ * Returns true if the XML was correctly validated.\r
+ */\r
+ private boolean validateXml(String xml) {\r
+\r
+ try {\r
+ Validator validator = getValidator();\r
+ validator.validate(new StreamSource(new StringReader(xml)));\r
+ return true;\r
+\r
+ } catch (SAXException e) {\r
+ // ignore\r
+\r
+ } catch (IOException e) {\r
+ // ignore\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Helper method that returns a validator for our XSD\r
+ */\r
+ private Validator getValidator() throws SAXException {\r
+ InputStream xsdStream = SdkRepository.getXsdStream();\r
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
+\r
+ // This may throw a SAX Exception if the schema itself is not a valid XSD\r
+ Schema schema = factory.newSchema(new StreamSource(xsdStream));\r
+\r
+ Validator validator = schema.newValidator();\r
+\r
+ return validator;\r
+ }\r
+\r
+ /**\r
+ * Returns the first child element with the given XML local name.\r
+ * If xmlLocalName is null, returns the very first child element.\r
+ */\r
+ private Node getFirstChild(Node node, String xmlLocalName) {\r
+\r
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
+ if (child.getNodeType() == Node.ELEMENT_NODE &&\r
+ SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) {\r
+ if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {\r
+ return child;\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Takes an XML document as a string as parameter and returns a DOM for it.\r
+ */\r
+ private Document getDocument(String xml)\r
+ throws ParserConfigurationException, SAXException, IOException {\r
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
+ factory.setIgnoringComments(true);\r
+ factory.setNamespaceAware(true);\r
+\r
+ DocumentBuilder builder = factory.newDocumentBuilder();\r
+ Document doc = builder.parse(new InputSource(new StringReader(xml)));\r
+\r
+ return doc;\r
+ }\r
+}\r
\r
import org.w3c.dom.Node;\r
\r
+import java.io.File;\r
import java.util.ArrayList;\r
\r
/**\r
private final String mDescription;\r
private final String mDescUrl;\r
private final Archive[] mArchives;\r
+ private final RepoSource mSource;\r
\r
/**\r
* Creates a new package from the attributes and elements of the given XML node.\r
* <p/>\r
* This constructor should throw an exception if the package cannot be created.\r
*/\r
- Package(Node packageNode) {\r
+ Package(RepoSource source, Node packageNode) {\r
+ mSource = source;\r
mRevision = getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0);\r
mDescription = getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);\r
mDescUrl = getXmlString(packageNode, SdkRepository.NODE_DESC_URL);\r
mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES));\r
}\r
\r
+ /**\r
+ * Manually create a new package with one archive and the given attributes.\r
+ * This is used to create packages from local directories.\r
+ */\r
+ public Package(RepoSource source,\r
+ int revision,\r
+ String description,\r
+ String descUrl,\r
+ Os archiveOs,\r
+ Arch archiveArch,\r
+ String archiveUrl,\r
+ long archiveSize,\r
+ String archiveChecksum) {\r
+ mSource = source;\r
+ mRevision = revision;\r
+ mDescription = description;\r
+ mDescUrl = descUrl;\r
+ mArchives = new Archive[1];\r
+ mArchives[0] = new Archive(this,\r
+ archiveOs,\r
+ archiveArch,\r
+ archiveUrl,\r
+ archiveSize,\r
+ archiveChecksum);\r
+ }\r
+\r
+ /**\r
+ * Parses an XML node to process the <archives> element.\r
+ */\r
private Archive[] parseArchives(Node archivesNode) {\r
ArrayList<Archive> archives = new ArrayList<Archive>();\r
\r
return archives.toArray(new Archive[archives.size()]);\r
}\r
\r
+ /**\r
+ * Parses one <archive> element from an <archives> container.\r
+ */\r
private Archive parseArchive(Node archiveNode) {\r
Archive a = new Archive(\r
+ this,\r
(Os) getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,\r
Os.values(), null),\r
(Arch) getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,\r
Arch.values(), Arch.ANY),\r
getXmlString(archiveNode, SdkRepository.NODE_URL),\r
- getXmlInt(archiveNode, SdkRepository.NODE_SIZE, 0),\r
+ getXmlLong(archiveNode, SdkRepository.NODE_SIZE, 0),\r
getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)\r
);\r
\r
return a;\r
}\r
\r
- /** Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). */\r
+ /**\r
+ * Returns the source that created (and owns) this package. Can be null.\r
+ */\r
+ public RepoSource getParentSource() {\r
+ return mSource;\r
+ }\r
+\r
+ /**\r
+ * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).\r
+ * Can be 0 if this is a local package of unknown revision.\r
+ */\r
public int getRevision() {\r
return mRevision;\r
}\r
\r
- /** Returns the optional description for all packages (platform, add-on, tool, doc) or\r
- * for a lib. */\r
+ /**\r
+ * Returns the optional description for all packages (platform, add-on, tool, doc) or\r
+ * for a lib. Can be empty but not null.\r
+ */\r
public String getDescription() {\r
return mDescription;\r
}\r
\r
- /** Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
- * Can be empty but not null. */\r
+ /**\r
+ * Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
+ * Can be empty but not null.\r
+ */\r
public String getDescUrl() {\r
return mDescUrl;\r
}\r
\r
- /** Returns the archives defined in this package. Can be an empty array but not null. */\r
+ /**\r
+ * Returns the archives defined in this package.\r
+ * Can be an empty array but not null.\r
+ */\r
public Archive[] getArchives() {\r
return mArchives;\r
}\r
\r
- /** Returns a short description for an {@link IDescription}. */\r
+ /**\r
+ * Returns a short description for an {@link IDescription}.\r
+ * Can be empty but not null.\r
+ */\r
public abstract String getShortDescription();\r
\r
- /** Returns a long description for an {@link IDescription}. */\r
+ /**\r
+ * Returns a long description for an {@link IDescription}.\r
+ * Can be empty but not null.\r
+ */\r
public String getLongDescription() {\r
return String.format("%1$s\nRevision %2$d", getDescription(), getRevision());\r
}\r
\r
+ /**\r
+ * Computes a potential installation folder if an archive of this package were\r
+ * to be installed right away in the given SDK root.\r
+ * <p/>\r
+ * Some types of packages install in a fix location, for example docs and tools.\r
+ * In this case the returned folder may already exist with a different archive installed\r
+ * at the desired location.\r
+ * For other packages types, such as add-on or platform, the folder name is only partially\r
+ * relevant to determine the content and thus a real check will be done to provide an\r
+ * existing or new folder depending on the current content of the SDK.\r
+ *\r
+ * @param osSdkRoot The OS path of the SDK root folder.\r
+ * @return A new {@link File} corresponding to the directory to use to install this package.\r
+ */\r
+ public abstract File getInstallFolder(String osSdkRoot);\r
+\r
//---\r
\r
+ /**\r
+ * Returns the first child element with the given XML local name.\r
+ * If xmlLocalName is null, returns the very first child element.\r
+ */\r
protected static Node getFirstChild(Node node, String xmlLocalName) {\r
\r
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
}\r
\r
/**\r
+ * Retrieves the value of that XML element as a long.\r
+ * Returns the default value when the element is missing or is not an integer.\r
+ */\r
+ protected static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {\r
+ String s = getXmlString(node, xmlLocalName);\r
+ try {\r
+ return Long.parseLong(s);\r
+ } catch (NumberFormatException e) {\r
+ return defaultValue;\r
+ }\r
+ }\r
+\r
+ /**\r
* Retrieve an attribute which value must match one of the given enums using a\r
* case-insensitive name match.\r
*\r
\r
package com.android.sdklib.internal.repository;\r
\r
+import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
import com.android.sdklib.repository.SdkRepository;\r
\r
import org.w3c.dom.Node;\r
\r
+import java.io.File;\r
+\r
/**\r
* Represents a platform XML node in an SDK repository.\r
*/\r
* <p/>\r
* This constructor should throw an exception if the package cannot be created.\r
*/\r
- PlatformPackage(Node packageNode) {\r
- super(packageNode);\r
+ PlatformPackage(RepoSource source, Node packageNode) {\r
+ super(source, packageNode);\r
mVersion = getXmlString(packageNode, SdkRepository.NODE_VERSION);\r
mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
}\r
\r
+ /**\r
+ * Creates a new platform package based on an actual {@link IAndroidTarget} (with\r
+ * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.\r
+ * This is used to list local SDK folders.\r
+ */\r
+ PlatformPackage(IAndroidTarget target) {\r
+ super( null, //source\r
+ 0, //revision\r
+ target.getDescription(), //description\r
+ null, //descUrl\r
+ Os.getCurrentOs(), //archiveOs\r
+ Arch.getCurrentArch(), //archiveArch\r
+ "", //archiveUrl //$NON-NLS-1$\r
+ 0, //archiveSize\r
+ null //archiveChecksum\r
+ );\r
+\r
+ mApiLevel = target.getApiVersionNumber();\r
+ mVersion = target.getApiVersionName();\r
+ }\r
+\r
/** Returns the version, a string, for platform packages. */\r
public String getVersion() {\r
return mVersion;\r
getShortDescription(),\r
super.getLongDescription());\r
}\r
+\r
+ /**\r
+ * Computes a potential installation folder if an archive of this package were\r
+ * to be installed right away in the given SDK root.\r
+ * <p/>\r
+ * A platform package is typically installed in SDK/platforms/android-"version".\r
+ * However if we can find a different directory under SDK/platform that already\r
+ * has this platform version installed, we'll use that one.\r
+ *\r
+ * @param osSdkRoot The OS path of the SDK root folder.\r
+ * @return A new {@link File} corresponding to the directory to use to install this package.\r
+ */\r
+ @Override\r
+ public File getInstallFolder(String osSdkRoot) {\r
+ File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);\r
+ File folder = new File(platforms, String.format("android-%s", getVersion())); //$NON-NLS-1$\r
+ // TODO find similar existing platform in platforms folder\r
+ return folder;\r
+ }\r
}\r
}\r
\r
/**\r
- * Returns the list of known packages. This is null when the source hasn't been loaded yet.\r
+ * Returns the list of known packages found by the last call to {@link #load(ITaskFactory)}.\r
+ * This is null when the source hasn't been loaded yet.\r
*/\r
public Package[] getPackages() {\r
return mPackages;\r
}\r
\r
+ /**\r
+ * Clear the internal packages list. After this call, {@link #getPackages()} will return\r
+ * null till {@link #load(ITaskFactory)} is called.\r
+ */\r
+ public void clearPackages() {\r
+ mPackages = null;\r
+ }\r
+\r
public String getShortDescription() {\r
return mUrl;\r
}\r
\r
setDefaultDescription();\r
\r
- monitor.setDescription(String.format("Fetching %1$s", mUrl));\r
+ monitor.setDescription("Fetching %1$s", mUrl);\r
monitor.incProgress(1);\r
\r
String xml = fetchUrl(mUrl, monitor);\r
}\r
}\r
\r
- /*\r
+ /**\r
+ * Fetches the document at the given URL and returns it as a string.\r
+ * Returns null if anything wrong happens and write errors to the monitor.\r
+ *\r
* References:\r
* Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html\r
* Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html\r
return null;\r
}\r
\r
+ /**\r
+ * Validates this XML against the SDK Repository schema.\r
+ * Returns true if the XML was correctly validated.\r
+ */\r
private boolean validateXml(String xml, ITaskMonitor monitor) {\r
\r
try {\r
return false;\r
}\r
\r
- /** Helper method that returns a validator for our XSD */\r
+ /**\r
+ * Helper method that returns a validator for our XSD\r
+ */\r
private Validator getValidator() throws SAXException {\r
InputStream xsdStream = SdkRepository.getXsdStream();\r
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
}\r
\r
\r
+ /**\r
+ * Parse all packages defined in the SDK Repository XML and creates\r
+ * a new mPackages array with them.\r
+ */\r
private boolean parsePackages(String xml, ITaskMonitor monitor) {\r
\r
try {\r
\r
try {\r
if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
- p = new AddonPackage(child);\r
+ p = new AddonPackage(this, child);\r
\r
} else if (!mAddonOnly) {\r
if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
- p = new PlatformPackage(child);\r
+ p = new PlatformPackage(this, child);\r
} else if (SdkRepository.NODE_DOC.equals(name)) {\r
- p = new DocPackage(child);\r
+ p = new DocPackage(this, child);\r
} else if (SdkRepository.NODE_TOOL.equals(name)) {\r
- p = new ToolPackage(child);\r
+ p = new ToolPackage(this, child);\r
}\r
}\r
\r
if (p != null) {\r
packages.add(p);\r
- monitor.setDescription(\r
- String.format("Found %1$s", p.getShortDescription()));\r
+ monitor.setDescription("Found %1$s", p.getShortDescription());\r
}\r
} catch (Exception e) {\r
// Ignore invalid packages\r
return false;\r
}\r
\r
+ /**\r
+ * Returns the first child element with the given XML local name.\r
+ * If xmlLocalName is null, returns the very first child element.\r
+ */\r
private Node getFirstChild(Node node, String xmlLocalName) {\r
\r
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
return null;\r
}\r
\r
+ /**\r
+ * Takes an XML document as a string as parameter and returns a DOM for it.\r
+ */\r
private Document getDocument(String xml)\r
throws ParserConfigurationException, SAXException, IOException {\r
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdklib.internal.repository;\r
+\r
+import java.util.ArrayList;\r
+\r
+/**\r
+ * A list of sdk-repository sources.\r
+ */\r
+public class RepoSources {\r
+\r
+ private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();\r
+ private ITaskFactory mTaskFactory;\r
+\r
+ public RepoSources() {\r
+ }\r
+\r
+ public void setTaskFactory(ITaskFactory taskFactory) {\r
+ mTaskFactory = taskFactory;\r
+ }\r
+\r
+ public ITaskFactory getTaskFactory() {\r
+ return mTaskFactory;\r
+ }\r
+\r
+ public void add(RepoSource source) {\r
+ mSources.add(source);\r
+ }\r
+\r
+ public ArrayList<RepoSource> getSources() {\r
+ return mSources;\r
+ }\r
+}\r
\r
package com.android.sdklib.internal.repository;\r
\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
+\r
import org.w3c.dom.Node;\r
\r
+import java.io.File;\r
+\r
/**\r
* Represents a tool XML node in an SDK repository.\r
*/\r
* <p/>\r
* This constructor should throw an exception if the package cannot be created.\r
*/\r
- ToolPackage(Node packageNode) {\r
- super(packageNode);\r
+ ToolPackage(RepoSource source, Node packageNode) {\r
+ super(source, packageNode);\r
+ }\r
+\r
+ /**\r
+ * Manually create a new package with one archive and the given attributes.\r
+ * This is used to create packages from local directories.\r
+ */\r
+ ToolPackage(RepoSource source,\r
+ int revision,\r
+ String description,\r
+ String descUrl,\r
+ Os archiveOs,\r
+ Arch archiveArch,\r
+ String archiveUrl,\r
+ long archiveSize,\r
+ String archiveChecksum) {\r
+ super(source,\r
+ revision,\r
+ description,\r
+ descUrl,\r
+ archiveOs,\r
+ archiveArch,\r
+ archiveUrl,\r
+ archiveSize,\r
+ archiveChecksum);\r
}\r
\r
/** Returns a short description for an {@link IDescription}. */\r
getRevision(),\r
super.getLongDescription());\r
}\r
+\r
+ /**\r
+ * Computes a potential installation folder if an archive of this package were\r
+ * to be installed right away in the given SDK root.\r
+ * <p/>\r
+ * A "tool" package should always be located in SDK/tools.\r
+ *\r
+ * @param osSdkRoot The OS path of the SDK root folder.\r
+ * @return A new {@link File} corresponding to the directory to use to install this package.\r
+ */\r
+ @Override\r
+ public File getInstallFolder(String osSdkRoot) {\r
+ return new File(osSdkRoot, SdkConstants.FD_TOOLS);\r
+ }\r
}\r
\r
package com.android.sdklib.repository;\r
\r
+\r
import java.io.InputStream;\r
\r
/**\r
*/\r
public class SdkRepository {\r
\r
+ /** The URL of the official Google sdk-repository site. */\r
+ public static final String URL_GOOGLE_SDK_REPO_SITE =\r
+ "https://dl.google.com/android/eclipse/repository/index.xml"; //$NON-NLS-1$\r
+\r
/** The XML namespace of the sdk-repository XML. */\r
public static final String NS_SDK_REPOSITORY =\r
"http://schemas.android.com/sdk/android/repository/1"; //$NON-NLS-1$\r
\r
import org.eclipse.jface.viewers.TableViewer;\r
import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.ControlAdapter;\r
+import org.eclipse.swt.events.ControlEvent;\r
import org.eclipse.swt.events.SelectionAdapter;\r
import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.graphics.Rectangle;\r
import org.eclipse.swt.layout.GridData;\r
import org.eclipse.swt.layout.GridLayout;\r
import org.eclipse.swt.widgets.Button;\r
* - refresh callback\r
*/\r
\r
-public class InstalledPackagesPage extends Composite {\r
+public class LocalPackagesPage extends Composite {\r
private UpdaterData mUpdaterData;\r
\r
private Label mSdkLocLabel;\r
private Text mSdkLocText;\r
private Button mSdkLocBrowse;\r
- private Label mInstalledPkgLabel;\r
- private TableViewer mTableViewerInstPkg;\r
- private Table mTableInstPkg;\r
- private TableColumn mColumnInstSummary;\r
- private TableColumn mColumnInstApiLevel;\r
- private TableColumn mColumnInstRevision;\r
+ private TableViewer mTableViewerPackages;\r
+ private Table mTablePackages;\r
+ private TableColumn mColumnPackages;\r
private Group mDescriptionContainer;\r
- private Composite mInstButtons;\r
- private Button mInstUpdate;\r
+ private Composite mContainerButtons;\r
+ private Button mUpdateButton;\r
private Label mPlaceholder1;\r
- private Button mInstDelete;\r
+ private Button mDeleteButton;\r
private Label mPlaceholder2;\r
- private Button mInstHomePage;\r
+ private Button mHomePageButton;\r
private Label mDescriptionLabel;\r
\r
/**\r
* @param updaterData An instance of {@link UpdaterData}. If null, a local\r
* one will be allocated just to help with the SWT Designer.\r
*/\r
- public InstalledPackagesPage(Composite parent, UpdaterData updaterData) {\r
+ public LocalPackagesPage(Composite parent, UpdaterData updaterData) {\r
super(parent, SWT.BORDER);\r
\r
mUpdaterData = updaterData != null ? updaterData : new UpdaterData();\r
\r
createContents(this);\r
+ postCreate(); //$hide$\r
}\r
\r
private void createContents(Composite parent) {\r
\r
createSdkLocation(parent);\r
\r
- mInstalledPkgLabel = new Label(parent, SWT.NONE);\r
- mInstalledPkgLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));\r
- mInstalledPkgLabel.setText("Installed Packages:");\r
+ mTableViewerPackages = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION);\r
+ mTablePackages = mTableViewerPackages.getTable();\r
+ mTablePackages.setHeaderVisible(true);\r
+ mTablePackages.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));\r
\r
- mTableViewerInstPkg = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION);\r
- mTableInstPkg = mTableViewerInstPkg.getTable();\r
- mTableInstPkg.setHeaderVisible(true);\r
- mTableInstPkg.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));\r
-\r
- mColumnInstSummary = new TableColumn(mTableInstPkg, SWT.NONE);\r
- mColumnInstSummary.setWidth(377);\r
- mColumnInstSummary.setText("Summary");\r
-\r
- mColumnInstApiLevel = new TableColumn(mTableInstPkg, SWT.NONE);\r
- mColumnInstApiLevel.setWidth(100);\r
- mColumnInstApiLevel.setText("API Level");\r
-\r
- mColumnInstRevision = new TableColumn(mTableInstPkg, SWT.NONE);\r
- mColumnInstRevision.setWidth(100);\r
- mColumnInstRevision.setText("Revision");\r
+ mColumnPackages = new TableColumn(mTablePackages, SWT.NONE);\r
+ mColumnPackages.setWidth(377);\r
+ mColumnPackages.setText("Installed Packages");\r
\r
mDescriptionContainer = new Group(parent, SWT.NONE);\r
mDescriptionContainer.setLayout(new GridLayout(1, false));\r
mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true, 1, 1));\r
mDescriptionLabel.setText("Line1\nLine2\nLine3");\r
\r
- mInstButtons = new Composite(parent, SWT.NONE);\r
- mInstButtons.setLayout(new GridLayout(5, false));\r
- mInstButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));\r
+ mContainerButtons = new Composite(parent, SWT.NONE);\r
+ mContainerButtons.setLayout(new GridLayout(5, false));\r
+ mContainerButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));\r
\r
- mInstUpdate = new Button(mInstButtons, SWT.NONE);\r
- mInstUpdate.addSelectionListener(new SelectionAdapter() {\r
+ mUpdateButton = new Button(mContainerButtons, SWT.NONE);\r
+ mUpdateButton.addSelectionListener(new SelectionAdapter() {\r
@Override\r
public void widgetSelected(SelectionEvent e) {\r
onUpdateInstalledPackage(); //$hide$ (hide from SWT designer)\r
}\r
});\r
- mInstUpdate.setText("Update...");\r
+ mUpdateButton.setText("Update...");\r
\r
- mPlaceholder1 = new Label(mInstButtons, SWT.NONE);\r
+ mPlaceholder1 = new Label(mContainerButtons, SWT.NONE);\r
mPlaceholder1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));\r
\r
- mInstDelete = new Button(mInstButtons, SWT.NONE);\r
- mInstDelete.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));\r
- mInstDelete.setText("Delete...");\r
+ mDeleteButton = new Button(mContainerButtons, SWT.NONE);\r
+ mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));\r
+ mDeleteButton.setText("Delete...");\r
\r
- mPlaceholder2 = new Label(mInstButtons, SWT.NONE);\r
+ mPlaceholder2 = new Label(mContainerButtons, SWT.NONE);\r
mPlaceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));\r
\r
- mInstHomePage = new Button(mInstButtons, SWT.NONE);\r
- mInstHomePage.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
- mInstHomePage.setText("Home Page...");\r
+ mHomePageButton = new Button(mContainerButtons, SWT.NONE);\r
+ mHomePageButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
+ mHomePageButton.setText("Home Page...");\r
}\r
\r
private void createSdkLocation(Composite parent) {\r
// Hide everything down-below from SWT designer\r
//$hide>>$\r
\r
+ private void postCreate() {\r
+ adjustColumnsWidth();\r
+ }\r
+\r
+\r
+ /**\r
+ * Adds a listener to adjust the columns width when the parent is resized.\r
+ * <p/>\r
+ * If we need something more fancy, we might want to use this:\r
+ * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co\r
+ */\r
+ private void adjustColumnsWidth() {\r
+ // Add a listener to resize the column to the full width of the table\r
+ mTablePackages.addControlListener(new ControlAdapter() {\r
+ @Override\r
+ public void controlResized(ControlEvent e) {\r
+ Rectangle r = mTablePackages.getClientArea();\r
+ mColumnPackages.setWidth(r.width);\r
+ }\r
+ });\r
+ }\r
+\r
+ public void setInput(LocalSdkAdapter localSdkAdapter) {\r
+ mTableViewerPackages.setLabelProvider( localSdkAdapter.getLabelProvider());\r
+ mTableViewerPackages.setContentProvider(localSdkAdapter.getContentProvider());\r
+ mTableViewerPackages.setInput(localSdkAdapter);\r
+ }\r
+\r
protected void onUpdateInstalledPackage() {\r
+ // TODO just a test, needs to be removed later.\r
ProgressTask.start(getShell(), "Test", new ITask() {\r
public void run(ITaskMonitor monitor) {\r
monitor.setDescription("Test");\r
try {\r
Thread.sleep(5);\r
} catch (InterruptedException e) {\r
+ // ignore\r
}\r
}\r
}\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdkuilib.internal.repository;\r
+\r
+import com.android.sdklib.internal.repository.IDescription;\r
+import com.android.sdklib.internal.repository.LocalSdkParser;\r
+import com.android.sdklib.internal.repository.Package;\r
+import com.android.sdklib.internal.repository.RepoSource;\r
+\r
+import org.eclipse.jface.viewers.IContentProvider;\r
+import org.eclipse.jface.viewers.ILabelProvider;\r
+import org.eclipse.jface.viewers.IStructuredContentProvider;\r
+import org.eclipse.jface.viewers.LabelProvider;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.swt.graphics.Image;\r
+\r
+/**\r
+ * Table adapters to use the local SDK list.\r
+ */\r
+class LocalSdkAdapter {\r
+\r
+ private final LocalSdkParser mLocalSdkParser;\r
+ private String mOsSdkRoot;\r
+\r
+ public LocalSdkAdapter(LocalSdkParser localSdkParser) {\r
+ mLocalSdkParser = localSdkParser;\r
+ }\r
+\r
+ public void setSdkRoot(String osSdkRoot) {\r
+ mOsSdkRoot = osSdkRoot;\r
+ mLocalSdkParser.clearPackages();\r
+ }\r
+\r
+ public ILabelProvider getLabelProvider() {\r
+ return new ViewerLabelProvider();\r
+ }\r
+\r
+\r
+ public IContentProvider getContentProvider() {\r
+ return new TableContentProvider();\r
+ }\r
+\r
+ // ------------\r
+\r
+ public static class ViewerLabelProvider extends LabelProvider {\r
+ /** Returns null by default */\r
+ @Override\r
+ public Image getImage(Object element) {\r
+ return super.getImage(element);\r
+ }\r
+\r
+ /** Returns the toString of the element. */\r
+ @Override\r
+ public String getText(Object element) {\r
+ if (element instanceof IDescription) {\r
+ return ((IDescription) element).getShortDescription();\r
+ }\r
+ return super.getText(element);\r
+ }\r
+ }\r
+\r
+ // ------------\r
+\r
+ private static class TableContentProvider implements IStructuredContentProvider {\r
+\r
+ // Called when the viewer is disposed\r
+ public void dispose() {\r
+ // pass\r
+ }\r
+\r
+ // Called when the input is set or changed on the provider\r
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+ // pass\r
+ }\r
+\r
+ /**\r
+ * Called to collect the root elements for the given input.\r
+ * The input here is a {@link LocalSdkAdapter} object, this returns an array\r
+ * of {@link RepoSource}.\r
+ */\r
+ public Object[] getElements(Object inputElement) {\r
+ if (inputElement instanceof LocalSdkAdapter) {\r
+ LocalSdkAdapter adapter = (LocalSdkAdapter) inputElement;\r
+ LocalSdkParser parser = adapter.mLocalSdkParser;\r
+\r
+ Package[] packages = parser.getPackages();\r
+\r
+ if (packages == null) {\r
+ // load on demand the first time\r
+ packages = parser.parseSdk(adapter.mOsSdkRoot);\r
+ }\r
+\r
+ if (packages != null) {\r
+ return packages;\r
+ }\r
+ }\r
+\r
+ return new Object[0];\r
+ }\r
+ }\r
+\r
+}\r
* Sets the description in the current task dialog.\r
* This method can be invoke from a non-UI thread.\r
*/\r
- public void setDescription(final String description) {\r
+ public void setDescription(final String descriptionFormat, final Object...args) {\r
mDialogShell.getDisplay().asyncExec(new Runnable() {\r
public void run() {\r
if (!mLabel.isDisposed()) {\r
- mLabel.setText(description);\r
+ mLabel.setText(String.format(descriptionFormat, args));\r
}\r
}\r
});\r
* Sets the description in the current task dialog.\r
* This method can be invoke from a non-UI thread.\r
*/\r
- public void setResult(final String result) {\r
+ public void setResult(final String resultFormat, final Object...args) {\r
mAutomaticallyCloseOnTaskCompletion = false;\r
if (!mDialogShell.isDisposed()) {\r
mDialogShell.getDisplay().asyncExec(new Runnable() {\r
public void run() {\r
if (!mResultText.isDisposed()) {\r
mResultText.setVisible(true);\r
- mResultText.setText(result);\r
+ mResultText.setText(String.format(resultFormat, args));\r
}\r
}\r
});\r
package com.android.sdkuilib.internal.repository;\r
\r
\r
+import com.android.sdklib.internal.repository.Archive;\r
import com.android.sdklib.internal.repository.IDescription;\r
\r
+import org.eclipse.jface.viewers.CheckStateChangedEvent;\r
import org.eclipse.jface.viewers.CheckboxTreeViewer;\r
+import org.eclipse.jface.viewers.DoubleClickEvent;\r
+import org.eclipse.jface.viewers.ICheckStateListener;\r
+import org.eclipse.jface.viewers.IDoubleClickListener;\r
import org.eclipse.jface.viewers.ISelection;\r
import org.eclipse.jface.viewers.ITreeSelection;\r
import org.eclipse.swt.SWT;\r
import org.eclipse.swt.widgets.Label;\r
import org.eclipse.swt.widgets.Tree;\r
import org.eclipse.swt.widgets.TreeColumn;\r
-import org.eclipse.jface.viewers.ICheckStateListener;\r
-import org.eclipse.jface.viewers.CheckStateChangedEvent;\r
-import org.eclipse.jface.viewers.IDoubleClickListener;\r
-import org.eclipse.jface.viewers.DoubleClickEvent;\r
+\r
+import java.util.ArrayList;\r
\r
/*\r
* TODO list\r
* - install selected callback\r
*/\r
\r
-public class AvailablePackagesPage extends Composite {\r
+public class RemotePackagesPage extends Composite {\r
\r
+ private final UpdaterWindowImpl mUpdaterWindow;\r
private final UpdaterData mUpdaterData;\r
\r
private CheckboxTreeViewer mTreeViewerSources;\r
private Label mDescriptionLabel;\r
\r
\r
+\r
/**\r
* Create the composite.\r
* @param parent The parent of the composite.\r
* @param updaterData An instance of {@link UpdaterData}. If null, a local\r
* one will be allocated just to help with the SWT Designer.\r
*/\r
- public AvailablePackagesPage(Composite parent, UpdaterData updaterData) {\r
+ public RemotePackagesPage(UpdaterWindowImpl updaterWindow,\r
+ Composite parent,\r
+ UpdaterData updaterData) {\r
super(parent, SWT.BORDER);\r
+ mUpdaterWindow = updaterWindow;\r
\r
mUpdaterData = updaterData != null ? updaterData : new UpdaterData();\r
\r
mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);\r
mTreeViewerSources.addDoubleClickListener(new IDoubleClickListener() {\r
public void doubleClick(DoubleClickEvent event) {\r
- doTreeDoubleClick(event); //$hide$\r
+ onTreeDoubleClick(event); //$hide$\r
}\r
});\r
mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {\r
public void checkStateChanged(CheckStateChangedEvent event) {\r
- doTreeCeckStateChanged(event); //$hide$\r
+ onTreeCheckStateChanged(event); //$hide$\r
}\r
});\r
- mTreeViewerSources.setContentProvider(mUpdaterData.getSources().getContentProvider());\r
- mTreeViewerSources.setLabelProvider(mUpdaterData.getSources().getLabelProvider());\r
mTreeSources = mTreeViewerSources.getTree();\r
mTreeSources.addSelectionListener(new SelectionAdapter() {\r
@Override\r
mRefreshButton.setText("Refresh");\r
\r
mInstallSelectedButton = new Button(parent, SWT.NONE);\r
+ mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {\r
+ @Override\r
+ public void widgetSelected(SelectionEvent e) {\r
+ onInstallSelectedArchives(); //$hide$\r
+ }\r
+ });\r
mInstallSelectedButton.setText("Install Selected");\r
}\r
\r
});\r
}\r
\r
- public void setInput(RepoSources sources) {\r
+ public void setInput(RepoSourcesAdapter sources) {\r
+ mTreeViewerSources.setContentProvider(sources.getContentProvider());\r
+ mTreeViewerSources.setLabelProvider( sources.getLabelProvider());\r
mTreeViewerSources.setInput(sources);\r
onTreeSelected();\r
}\r
mDescriptionLabel.setText(""); //$NON-NLS1-$\r
}\r
\r
- private void doTreeCeckStateChanged(CheckStateChangedEvent event) {\r
+ private void onTreeCheckStateChanged(CheckStateChangedEvent event) {\r
boolean b = event.getChecked();\r
- Object elem = event.getElement();\r
+ Object elem = event.getElement(); // Will be Archive or Package or RepoSource\r
Object src = event.getSource();\r
+ // TODO\r
+ }\r
+\r
+ private void onTreeDoubleClick(DoubleClickEvent event) {\r
+ // TODO\r
}\r
\r
- private void doTreeDoubleClick(DoubleClickEvent event) {\r
+ private void onInstallSelectedArchives() {\r
+\r
+ ArrayList<Archive> archives = new ArrayList<Archive>();\r
+ for (Object element : mTreeViewerSources.getCheckedElements()) {\r
+ if (element instanceof Archive) {\r
+ archives.add((Archive) element);\r
+ }\r
+ }\r
+\r
+ mUpdaterWindow.installArchives(archives);\r
}\r
\r
// End of hiding from SWT Designer\r
\r
import com.android.sdklib.internal.repository.Archive;\r
import com.android.sdklib.internal.repository.IDescription;\r
-import com.android.sdklib.internal.repository.ITaskFactory;\r
import com.android.sdklib.internal.repository.Package;\r
import com.android.sdklib.internal.repository.RepoSource;\r
+import com.android.sdklib.internal.repository.RepoSources;\r
\r
import org.eclipse.jface.viewers.IContentProvider;\r
import org.eclipse.jface.viewers.ILabelProvider;\r
import org.eclipse.jface.viewers.Viewer;\r
import org.eclipse.swt.graphics.Image;\r
\r
-import java.util.ArrayList;\r
-\r
/**\r
* A list of sdk-repository sources.\r
*\r
* This implementation is UI dependent.\r
*/\r
-class RepoSources {\r
-\r
- private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();\r
- private ITaskFactory mTaskFactory;\r
-\r
- public RepoSources() {\r
- }\r
+class RepoSourcesAdapter {\r
\r
- public void setTaskFactory(ITaskFactory taskFactory) {\r
- mTaskFactory = taskFactory;\r
- }\r
+ private final RepoSources mRepoSources;\r
\r
- public void add(RepoSource source) {\r
- mSources.add(source);\r
+ public RepoSourcesAdapter(RepoSources repoSources) {\r
+ mRepoSources = repoSources;\r
}\r
\r
public ILabelProvider getLabelProvider() {\r
\r
// ------------\r
\r
- public class ViewerLabelProvider extends LabelProvider {\r
+ public static class ViewerLabelProvider extends LabelProvider {\r
/** Returns null by default */\r
@Override\r
public Image getImage(Object element) {\r
\r
// ------------\r
\r
- private class TreeContentProvider implements ITreeContentProvider {\r
+ private static class TreeContentProvider implements ITreeContentProvider {\r
\r
- private Object mInput;\r
+ private RepoSourcesAdapter mInput;\r
\r
// Called when the viewer is disposed\r
public void dispose() {\r
\r
// Called when the input is set or changed on the provider\r
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
- mInput = newInput;\r
+ assert newInput == null || newInput instanceof RepoSourcesAdapter;\r
+ mInput = (RepoSourcesAdapter) newInput;\r
// pass\r
}\r
\r
/**\r
* Called to collect the root elements for the given input.\r
- * The input here is a {@link RepoSources} object, this returns an array\r
+ * The input here is a {@link RepoSourcesAdapter} object, this returns an array\r
* of {@link RepoSource}.\r
*/\r
public Object[] getElements(Object inputElement) {\r
* Get the children of the given parent. This is requested on-demand as\r
* nodes are expanded.\r
*\r
- * For a {@link RepoSources} object, returns an array of {@link RepoSource}s.\r
+ * For a {@link RepoSourcesAdapter} object, returns an array of {@link RepoSource}s.\r
* For a {@link RepoSource}, returns an array of {@link Package}s.\r
* For a {@link Package}, returns an array of {@link Archive}s.\r
*/\r
public Object[] getChildren(Object parentElement) {\r
- if (parentElement instanceof RepoSources) {\r
- return ((RepoSources) parentElement).mSources.toArray();\r
+ if (parentElement instanceof RepoSourcesAdapter) {\r
+ return ((RepoSourcesAdapter) parentElement).mRepoSources.getSources().toArray();\r
\r
} else if (parentElement instanceof RepoSource) {\r
RepoSource source = (RepoSource) parentElement;\r
Package[] packages = source.getPackages();\r
\r
if (packages == null) {\r
- source.load(mTaskFactory);\r
+ source.load(mInput.mRepoSources.getTaskFactory());\r
packages = source.getPackages();\r
}\r
if (packages != null) {\r
return ((Package) parentElement).getArchives();\r
}\r
\r
-\r
return new Object[0];\r
}\r
\r
/**\r
* Returns the parent of a given element.\r
- * The input {@link RepoSources} is the parent of all {@link RepoSource} elements.\r
+ * The input {@link RepoSourcesAdapter} is the parent of all {@link RepoSource} elements.\r
*/\r
public Object getParent(Object element) {\r
+\r
if (element instanceof RepoSource) {\r
return mInput;\r
+\r
+ } else if (element instanceof Package) {\r
+ return ((Package) element).getParentSource();\r
}\r
return null;\r
}\r
/**\r
* Returns true if a given element has children, which is used to display a\r
* "+/expand" box next to the tree node.\r
- * All {@link RepoSource} are expandable, whether they actually have any childre or not.\r
+ * All {@link RepoSource} and {@link Package} are expandable, whether they actually\r
+ * have any children or not.\r
*/\r
public boolean hasChildren(Object element) {\r
return element instanceof RepoSource || element instanceof Package;\r
\r
package com.android.sdkuilib.internal.repository;\r
\r
+import com.android.sdklib.internal.repository.LocalSdkParser;\r
+import com.android.sdklib.internal.repository.RepoSources;\r
+\r
/**\r
* Data shared between {@link UpdaterWindowImpl} and its pages.\r
*/\r
class UpdaterData {\r
private String mOsSdkRoot;\r
private boolean mUserCanChangeSdkRoot;\r
- private RepoSources mSources = new RepoSources();\r
+\r
+ private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();\r
+ private final RepoSources mSources = new RepoSources();\r
+\r
+ private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(mLocalSdkParser);\r
+ private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(mSources);\r
\r
public void setOsSdkRoot(String osSdkRoot) {\r
mOsSdkRoot = osSdkRoot;\r
return mUserCanChangeSdkRoot;\r
}\r
\r
- public void setSources(RepoSources sources) {\r
- mSources = sources;\r
- }\r
-\r
public RepoSources getSources() {\r
return mSources;\r
}\r
\r
+ public RepoSourcesAdapter getSourcesAdapter() {\r
+ return mSourcesAdapter;\r
+ }\r
+\r
+ public LocalSdkParser getLocalSdkParser() {\r
+ return mLocalSdkParser;\r
+ }\r
+\r
+ public LocalSdkAdapter getLocalSdkAdapter() {\r
+ return mLocalSdkAdapter;\r
+ }\r
+\r
}\r
package com.android.sdkuilib.internal.repository;\r
\r
\r
+import com.android.sdklib.internal.repository.Archive;\r
+import com.android.sdklib.internal.repository.ITask;\r
+import com.android.sdklib.internal.repository.ITaskMonitor;\r
+import com.android.sdklib.internal.repository.Package;\r
import com.android.sdklib.internal.repository.RepoSource;\r
+import com.android.sdklib.repository.SdkRepository;\r
\r
import org.eclipse.swt.SWT;\r
import org.eclipse.swt.SWTException;\r
import org.eclipse.swt.widgets.List;\r
import org.eclipse.swt.widgets.Shell;\r
\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
import java.io.InputStream;\r
+import java.net.URL;\r
+import java.security.MessageDigest;\r
import java.util.ArrayList;\r
+import java.util.Collection;\r
\r
/**\r
* This is the private implementation of the UpdateWindow.\r
*/\r
public class UpdaterWindowImpl {\r
\r
+ private static final int NUM_FETCH_URL_MONITOR_INC = 10;\r
+\r
private final UpdaterData mUpdaterData = new UpdaterData();\r
private ArrayList<Composite> mPages = new ArrayList<Composite>();\r
private boolean mInternalPageChange;\r
private SashForm mSashForm;\r
private List mPageList;\r
private Composite mPagesRootComposite;\r
- private InstalledPackagesPage mInstalledPackagePage;\r
- private AvailablePackagesPage mAvailablePackagesPage;\r
+ private LocalPackagesPage mLocalPackagePage;\r
+ private RemotePackagesPage mRemotePackagesPage;\r
private StackLayout mStackLayout;\r
private Image mIconImage;\r
+ private ProgressTaskFactory mTaskFactory;\r
\r
public UpdaterWindowImpl(String osSdkRoot, boolean userCanChangeSdkRoot) {\r
mUpdaterData.setOsSdkRoot(osSdkRoot);\r
mStackLayout = new StackLayout();\r
mPagesRootComposite.setLayout(mStackLayout);\r
\r
- mInstalledPackagePage = new InstalledPackagesPage(mPagesRootComposite, mUpdaterData);\r
- mAvailablePackagesPage = new AvailablePackagesPage(mPagesRootComposite, mUpdaterData);\r
+ mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);\r
+ mRemotePackagesPage = new RemotePackagesPage(this, mPagesRootComposite, mUpdaterData);\r
mSashForm.setWeights(new int[] {150, 576});\r
}\r
\r
* Once the UI has been created, initialize the content\r
*/\r
private void firstInit() {\r
- addPage(mInstalledPackagePage, "Installed Packages");\r
- addPage(mAvailablePackagesPage, "Available Packages");\r
+ mTaskFactory = new ProgressTaskFactory(getShell());\r
+\r
+ addPage(mLocalPackagePage, "Installed Packages");\r
+ addPage(mRemotePackagesPage, "Available Packages");\r
displayPage(0);\r
mPageList.setSelection(0);\r
\r
}\r
\r
private void setupSources() {\r
- mUpdaterData.getSources().setTaskFactory(new ProgressTaskFactory(getShell()));\r
+ mUpdaterData.getSources().setTaskFactory(mTaskFactory);\r
\r
- mUpdaterData.getSources().add(new RepoSource(\r
- "https://dl.google.com/android/eclipse/repository/index.xml", //$NON-NLS-1$\r
- false /* addonOnly */));\r
+ mUpdaterData.getSources().add(\r
+ new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /* addonOnly */));\r
\r
String url = System.getenv("TEMP_SDK_URL"); // TODO STOPSHIP temporary remove before shipping\r
if (url != null) {\r
mUpdaterData.getSources().add(new RepoSource(url, false /* addonOnly */));\r
}\r
\r
- mAvailablePackagesPage.setInput(mUpdaterData.getSources());\r
+ mRemotePackagesPage.setInput(mUpdaterData.getSourcesAdapter());\r
}\r
\r
private void scanLocalSdkFolders() {\r
- // TODO Auto-generated method stub\r
+ mUpdaterData.getLocalSdkAdapter().setSdkRoot(mUpdaterData.getOsSdkRoot());\r
+\r
+ mLocalPackagePage.setInput(mUpdaterData.getLocalSdkAdapter());\r
+ }\r
+\r
+ public void installArchives(final Collection<Archive> archives) {\r
+ // TODO move most parts to SdkLib, maybe as part of Archive, making archives self-installing.\r
+ mTaskFactory.start("Installing Archives", new ITask() {\r
+ public void run(ITaskMonitor monitor) {\r
+\r
+ monitor.setProgressMax(archives.size() * (NUM_FETCH_URL_MONITOR_INC + 3));\r
+ monitor.setDescription("Preparing to install archives");\r
+\r
+ int num_installed = 0;\r
+ for (Archive archive : archives) {\r
+\r
+ if (!archive.isCompatible()) {\r
+ monitor.setResult("Skipping incompatible archive: %1$s",\r
+ archive.getShortDescription());\r
+ monitor.incProgress(3);\r
+ continue;\r
+ }\r
+\r
+ File archiveFile = null;\r
+ try {\r
+ archiveFile = downloadArchive(archive, monitor);\r
+ monitor.incProgress(1);\r
+ if (archiveFile != null) {\r
+ if (installArchive(archive, archiveFile, monitor)) {\r
+ num_installed++;\r
+ }\r
+ }\r
+ monitor.incProgress(1);\r
+ } finally {\r
+ if (archiveFile != null) {\r
+ if (!archiveFile.delete()) {\r
+ archiveFile.deleteOnExit();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (num_installed == 0) {\r
+ monitor.setResult("Nothing was installed.");\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ /**\r
+ * Downloads an archive and returns the temp file with it.\r
+ * Caller is responsible with deleting the temp file when done.\r
+ */\r
+ private File downloadArchive(Archive archive, ITaskMonitor monitor) {\r
+\r
+ try {\r
+ File tmpFile = File.createTempFile("sdkupload", "bin"); //$NON-NLS-1$ //$NON-NLS-2$\r
+\r
+ monitor.setDescription("Downloading %1$s", archive.getShortDescription());\r
+\r
+ String link = archive.getUrl();\r
+ if (!link.startsWith("http://") //$NON-NLS-1$\r
+ && !link.startsWith("https://") //$NON-NLS-1$\r
+ && !link.startsWith("ftp://")) { //$NON-NLS-1$\r
+ // Make the URL absolute by prepending the source\r
+ Package pkg = archive.getParentPackage();\r
+ RepoSource src = pkg.getParentSource();\r
+ if (src == null) {\r
+ monitor.setResult("Internal error: no source for archive %1$s",\r
+ archive.getShortDescription());\r
+ return null;\r
+ }\r
+\r
+ String base = src.getUrl();\r
+ if (!base.endsWith("/") && !link.startsWith("/")) { //$NON-NLS-1$ //$NON-NLS-2$\r
+ base += "/"; //$NON-NLS-1$\r
+ }\r
+\r
+ link = base + link;\r
+ }\r
+\r
+ fetchUrl(tmpFile, archive, link, monitor);\r
+\r
+ } catch (IOException e) {\r
+ monitor.setResult(e.getMessage());\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Actually performs the download.\r
+ * Also computes the SHA1 of the file on the fly.\r
+ * <p/>\r
+ * Success is defined as downloading as many bytes as was expected and having the same\r
+ * SHA1 as expected. Returns true on success or false if any of those checks fail.\r
+ * <p/>\r
+ * Increments the monitor by {@link #NUM_FETCH_URL_MONITOR_INC} (which is 10).\r
+ */\r
+ private boolean fetchUrl(File tmpFile, Archive archive, String urlString, ITaskMonitor monitor) {\r
+ URL url;\r
+\r
+ FileOutputStream os = null;\r
+ InputStream is = null;\r
+ try {\r
+ url = new URL(urlString);\r
+ is = url.openStream();\r
+ os = new FileOutputStream(tmpFile);\r
+\r
+ MessageDigest digester = archive.getChecksumType().getMessageDigest();\r
+\r
+ byte[] buf = new byte[65536];\r
+ int n;\r
+\r
+ long total = 0;\r
+ long size = archive.getSize();\r
+ long inc = size / NUM_FETCH_URL_MONITOR_INC;\r
+ long next_inc = inc;\r
+\r
+ while ((n = is.read(buf)) >= 0) {\r
+ if (n > 0) {\r
+ os.write(buf, 0, n);\r
+ digester.update(buf, 0, n);\r
+ }\r
+\r
+ total += n;\r
+ if (total >= next_inc) {\r
+ monitor.incProgress(1);\r
+ next_inc += inc;\r
+ }\r
+\r
+ if (monitor.cancelRequested()) {\r
+ monitor.setResult("Download aborted by user at %1$d bytes.", total);\r
+ return false;\r
+ }\r
+\r
+ }\r
+\r
+ if (total != size) {\r
+ monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",\r
+ size, total);\r
+ return false;\r
+ }\r
+\r
+ // Create an hex string from the digest\r
+ byte[] digest = digester.digest();\r
+ n = digest.length;\r
+ String hex = "0123456789abcdef"; //$NON-NLS-1$\r
+ char[] hexDigest = new char[n * 2];\r
+ for (int i = 0; i < n; i++) {\r
+ byte b = digest[i];\r
+ hexDigest[i*2 + 0] = hex.charAt(b >>> 4);\r
+ hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);\r
+ }\r
+\r
+ String expected = archive.getChecksum();\r
+ String actual = new String(hexDigest);\r
+ if (!actual.equalsIgnoreCase(expected)) {\r
+ monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",\r
+ expected, actual);\r
+ return false;\r
+ }\r
+\r
+ return true;\r
+\r
+ } catch (Exception e) {\r
+ monitor.setResult(e.getMessage());\r
+\r
+ } finally {\r
+ if (os != null) {\r
+ try {\r
+ os.close();\r
+ } catch (IOException e) {\r
+ // pass\r
+ }\r
+ }\r
+\r
+ if (is != null) {\r
+ try {\r
+ is.close();\r
+ } catch (IOException e) {\r
+ // pass\r
+ }\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ private boolean installArchive(Archive archive, File archiveFile, ITaskMonitor monitor) {\r
+ monitor.setDescription("Installing %1$s", archive.getShortDescription());\r
+\r
+ File destFolder = archive.getParentPackage().getInstallFolder(mUpdaterData.getOsSdkRoot());\r
\r
+ return false;\r
}\r
\r
// End of hiding from SWT Designer\r