| /* |
| * 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.archives; |
| |
| import com.android.annotations.VisibleForTesting; |
| import com.android.annotations.VisibleForTesting.Visibility; |
| import com.android.sdklib.internal.repository.IDescription; |
| import com.android.sdklib.internal.repository.packages.Package; |
| import com.android.sdklib.internal.repository.sources.SdkSource; |
| import com.android.sdklib.io.FileOp; |
| |
| import java.io.File; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Locale; |
| import java.util.Properties; |
| |
| |
| /** |
| * A {@link Archive} is the base class for "something" that can be downloaded from |
| * the SDK repository. |
| * <p/> |
| * A package has some attributes (revision, description) and a list of archives |
| * which represent the downloadable bits. |
| * <p/> |
| * Packages are offered by a {@link SdkSource} (a download site). |
| * The {@link ArchiveInstaller} takes care of downloading, unpacking and installing an archive. |
| */ |
| public class Archive implements IDescription, Comparable<Archive> { |
| |
| private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$ |
| private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$ |
| |
| /** The checksum type. */ |
| public enum ChecksumType { |
| /** A SHA1 checksum, represented as a 40-hex string. */ |
| SHA1("SHA-1"); //$NON-NLS-1$ |
| |
| private final String mAlgorithmName; |
| |
| /** |
| * Constructs a {@link ChecksumType} with the algorithm name |
| * suitable for {@link MessageDigest#getInstance(String)}. |
| * <p/> |
| * 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("Any"), |
| LINUX("Linux"), |
| MACOSX("MacOS X"), |
| WINDOWS("Windows"); |
| |
| private final String mUiName; |
| |
| private Os(String uiName) { |
| mUiName = uiName; |
| } |
| |
| /** Returns the UI name of the OS. */ |
| public String getUiName() { |
| return mUiName; |
| } |
| |
| /** Returns the XML name of the OS. */ |
| public String getXmlName() { |
| return toString().toLowerCase(Locale.US); |
| } |
| |
| /** |
| * 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")) { //$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; |
| } |
| |
| /** Returns true if this OS is compatible with the current one. */ |
| public boolean isCompatible() { |
| if (this == ANY) { |
| return true; |
| } |
| |
| Os os = getCurrentOs(); |
| return this == os; |
| } |
| } |
| |
| /** The Architecture that this archive can be downloaded on. */ |
| public enum Arch { |
| ANY("Any"), |
| PPC("PowerPC"), |
| X86("x86"), |
| X86_64("x86_64"); |
| |
| private final String mUiName; |
| |
| private Arch(String uiName) { |
| mUiName = uiName; |
| } |
| |
| /** Returns the UI name of the architecture. */ |
| public String getUiName() { |
| return mUiName; |
| } |
| |
| /** Returns the XML name of the architecture. */ |
| public String getXmlName() { |
| return toString().toLowerCase(Locale.US); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** Returns true if this architecture is compatible with the current one. */ |
| public boolean isCompatible() { |
| if (this == ANY) { |
| return true; |
| } |
| |
| Arch arch = getCurrentArch(); |
| return this == arch; |
| } |
| } |
| |
| private final Os mOs; |
| private final Arch mArch; |
| private final String mUrl; |
| private final long mSize; |
| private final String mChecksum; |
| private final ChecksumType mChecksumType = ChecksumType.SHA1; |
| private final Package mPackage; |
| private final String mLocalOsPath; |
| private final boolean mIsLocal; |
| |
| /** |
| * Creates a new remote archive. |
| */ |
| public Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) { |
| mPackage = pkg; |
| mOs = os; |
| mArch = arch; |
| mUrl = url == null ? null : url.trim(); |
| mLocalOsPath = null; |
| mSize = size; |
| mChecksum = checksum; |
| mIsLocal = false; |
| } |
| |
| /** |
| * Creates a new local archive. |
| * Uses the properties from props first, if possible. Props can be null. |
| */ |
| @VisibleForTesting(visibility=Visibility.PACKAGE) |
| public Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) { |
| mPackage = pkg; |
| |
| mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString())); |
| mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString())); |
| |
| mUrl = null; |
| mLocalOsPath = localOsPath; |
| mSize = 0; |
| mChecksum = ""; |
| mIsLocal = localOsPath != null; |
| } |
| |
| /** |
| * Save the properties of the current archive in the give {@link Properties} object. |
| * These properties will later be give the constructor that takes a {@link Properties} object. |
| */ |
| void saveProperties(Properties props) { |
| props.setProperty(PROP_OS, mOs.toString()); |
| props.setProperty(PROP_ARCH, mArch.toString()); |
| } |
| |
| /** |
| * Returns true if this is a locally installed archive. |
| * Returns false if this is a remote archive that needs to be downloaded. |
| */ |
| public boolean isLocal() { |
| return mIsLocal; |
| } |
| |
| /** |
| * 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. |
| * 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. |
| */ |
| public ChecksumType getChecksumType() { |
| return mChecksumType; |
| } |
| |
| /** |
| * Returns the download archive URL, either absolute or relative to the repository xml. |
| * Always return null for a local installed folder. |
| * @see #getLocalOsPath() |
| */ |
| public String getUrl() { |
| return mUrl; |
| } |
| |
| /** |
| * Returns the local OS folder where a local archive is installed. |
| * Always return null for remote archives. |
| * @see #getUrl() |
| */ |
| public String getLocalOsPath() { |
| return mLocalOsPath; |
| } |
| |
| /** |
| * 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. |
| * Can be null for a local installed folder on an unknown architecture. |
| */ |
| public Arch getArch() { |
| return mArch; |
| } |
| |
| /** |
| * Generates a description for this archive of the OS/Arch supported by this archive. |
| */ |
| public String getOsDescription() { |
| String os; |
| if (mOs == null) { |
| os = "unknown OS"; |
| } else if (mOs == Os.ANY) { |
| os = "any OS"; |
| } else { |
| os = mOs.getUiName(); |
| } |
| |
| String arch = ""; //$NON-NLS-1$ |
| if (mArch != null && mArch != Arch.ANY) { |
| arch = mArch.getUiName(); |
| } |
| |
| return String.format("%1$s%2$s%3$s", |
| os, |
| arch.length() > 0 ? " " : "", //$NON-NLS-2$ |
| arch); |
| } |
| |
| /** |
| * Returns the short description of the source, if not null. |
| * Otherwise returns the default Object toString result. |
| * <p/> |
| * This is mostly helpful for debugging. |
| * For UI display, use the {@link IDescription} interface. |
| */ |
| @Override |
| public String toString() { |
| String s = getShortDescription(); |
| if (s != null) { |
| return s; |
| } |
| return super.toString(); |
| } |
| |
| /** |
| * Generates a short description for this archive. |
| */ |
| @Override |
| public String getShortDescription() { |
| return String.format("Archive for %1$s", getOsDescription()); |
| } |
| |
| /** |
| * Generates a longer description for this archive. |
| */ |
| @Override |
| public String getLongDescription() { |
| return String.format("%1$s\n%2$s\n%3$s", |
| getShortDescription(), |
| getSizeDescription(), |
| getSha1Description()); |
| } |
| |
| public String getSizeDescription() { |
| long size = getSize(); |
| String sizeStr; |
| if (size < 1024) { |
| sizeStr = String.format("%d Bytes", size); |
| } else if (size < 1024 * 1024) { |
| sizeStr = String.format("%d KiB", Math.round(size / 1024.0)); |
| } else if (size < 1024 * 1024 * 1024) { |
| sizeStr = String.format("%.1f MiB", |
| Math.round(10.0 * size / (1024 * 1024.0))/ 10.0); |
| } else { |
| sizeStr = String.format("%.1f GiB", |
| Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0); |
| } |
| |
| return String.format("Size: %1$s", sizeStr); |
| } |
| |
| public String getSha1Description() { |
| return String.format("SHA1: %1$s", getChecksum()); |
| } |
| |
| /** |
| * Returns true if this archive can be installed on the current platform. |
| */ |
| public boolean isCompatible() { |
| return getOs().isCompatible() && getArch().isCompatible(); |
| } |
| |
| /** |
| * Delete the archive folder if this is a local archive. |
| */ |
| public void deleteLocal() { |
| if (isLocal()) { |
| new FileOp().deleteFileOrFolder(new File(getLocalOsPath())); |
| } |
| } |
| |
| /** |
| * Archives are compared using their {@link Package} ordering. |
| * |
| * @see Package#compareTo(Package) |
| */ |
| @Override |
| public int compareTo(Archive rhs) { |
| if (mPackage != null && rhs != null) { |
| return mPackage.compareTo(rhs.getParentPackage()); |
| } |
| return 0; |
| } |
| |
| /** |
| * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code. |
| * <p/> |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((mArch == null) ? 0 : mArch.hashCode()); |
| result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode()); |
| result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode()); |
| result = prime * result + (mIsLocal ? 1231 : 1237); |
| result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode()); |
| result = prime * result + ((mOs == null) ? 0 : mOs.hashCode()); |
| result = prime * result + (int) (mSize ^ (mSize >>> 32)); |
| result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode()); |
| return result; |
| } |
| |
| /** |
| * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality. |
| * <p/> |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (!(obj instanceof Archive)) { |
| return false; |
| } |
| Archive other = (Archive) obj; |
| if (mArch == null) { |
| if (other.mArch != null) { |
| return false; |
| } |
| } else if (!mArch.equals(other.mArch)) { |
| return false; |
| } |
| if (mChecksum == null) { |
| if (other.mChecksum != null) { |
| return false; |
| } |
| } else if (!mChecksum.equals(other.mChecksum)) { |
| return false; |
| } |
| if (mChecksumType == null) { |
| if (other.mChecksumType != null) { |
| return false; |
| } |
| } else if (!mChecksumType.equals(other.mChecksumType)) { |
| return false; |
| } |
| if (mIsLocal != other.mIsLocal) { |
| return false; |
| } |
| if (mLocalOsPath == null) { |
| if (other.mLocalOsPath != null) { |
| return false; |
| } |
| } else if (!mLocalOsPath.equals(other.mLocalOsPath)) { |
| return false; |
| } |
| if (mOs == null) { |
| if (other.mOs != null) { |
| return false; |
| } |
| } else if (!mOs.equals(other.mOs)) { |
| return false; |
| } |
| if (mSize != other.mSize) { |
| return false; |
| } |
| if (mUrl == null) { |
| if (other.mUrl != null) { |
| return false; |
| } |
| } else if (!mUrl.equals(other.mUrl)) { |
| return false; |
| } |
| return true; |
| } |
| } |