/* * 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 algorigth 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; } }