1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.sdklib.internal.repository; 18 19 import com.android.annotations.VisibleForTesting; 20 import com.android.annotations.VisibleForTesting.Visibility; 21 import com.android.sdklib.io.FileOp; 22 23 import java.io.File; 24 import java.security.MessageDigest; 25 import java.security.NoSuchAlgorithmException; 26 import java.util.Properties; 27 28 29 /** 30 * A {@link Archive} is the base class for "something" that can be downloaded from 31 * the SDK repository. 32 * <p/> 33 * A package has some attributes (revision, description) and a list of archives 34 * which represent the downloadable bits. 35 * <p/> 36 * Packages are offered by a {@link SdkSource} (a download site). 37 * The {@link ArchiveInstaller} takes care of downloading, unpacking and installing an archive. 38 */ 39 public class Archive implements IDescription, Comparable<Archive> { 40 41 private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$ 42 private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$ 43 44 /** The checksum type. */ 45 public enum ChecksumType { 46 /** A SHA1 checksum, represented as a 40-hex string. */ 47 SHA1("SHA-1"); //$NON-NLS-1$ 48 49 private final String mAlgorithmName; 50 51 /** 52 * Constructs a {@link ChecksumType} with the algorigth name 53 * suitable for {@link MessageDigest#getInstance(String)}. 54 * <p/> 55 * These names are officially documented at 56 * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest 57 */ 58 private ChecksumType(String algorithmName) { 59 mAlgorithmName = algorithmName; 60 } 61 62 /** 63 * Returns a new {@link MessageDigest} instance for this checksum type. 64 * @throws NoSuchAlgorithmException if this algorithm is not available. 65 */ 66 public MessageDigest getMessageDigest() throws NoSuchAlgorithmException { 67 return MessageDigest.getInstance(mAlgorithmName); 68 } 69 } 70 71 /** The OS that this archive can be downloaded on. */ 72 public enum Os { 73 ANY("Any"), 74 LINUX("Linux"), 75 MACOSX("MacOS X"), 76 WINDOWS("Windows"); 77 78 private final String mUiName; 79 80 private Os(String uiName) { 81 mUiName = uiName; 82 } 83 84 /** Returns the UI name of the OS. */ 85 public String getUiName() { 86 return mUiName; 87 } 88 89 /** Returns the XML name of the OS. */ 90 public String getXmlName() { 91 return toString().toLowerCase(); 92 } 93 94 /** 95 * Returns the current OS as one of the {@link Os} enum values or null. 96 */ 97 public static Os getCurrentOs() { 98 String os = System.getProperty("os.name"); //$NON-NLS-1$ 99 if (os.startsWith("Mac")) { //$NON-NLS-1$ 100 return Os.MACOSX; 101 102 } else if (os.startsWith("Windows")) { //$NON-NLS-1$ 103 return Os.WINDOWS; 104 105 } else if (os.startsWith("Linux")) { //$NON-NLS-1$ 106 return Os.LINUX; 107 } 108 109 return null; 110 } 111 112 /** Returns true if this OS is compatible with the current one. */ 113 public boolean isCompatible() { 114 if (this == ANY) { 115 return true; 116 } 117 118 Os os = getCurrentOs(); 119 return this == os; 120 } 121 } 122 123 /** The Architecture that this archive can be downloaded on. */ 124 public enum Arch { 125 ANY("Any"), 126 PPC("PowerPC"), 127 X86("x86"), 128 X86_64("x86_64"); 129 130 private final String mUiName; 131 132 private Arch(String uiName) { 133 mUiName = uiName; 134 } 135 136 /** Returns the UI name of the architecture. */ 137 public String getUiName() { 138 return mUiName; 139 } 140 141 /** Returns the XML name of the architecture. */ 142 public String getXmlName() { 143 return toString().toLowerCase(); 144 } 145 146 /** 147 * Returns the current architecture as one of the {@link Arch} enum values or null. 148 */ 149 public static Arch getCurrentArch() { 150 // Values listed from http://lopica.sourceforge.net/os.html 151 String arch = System.getProperty("os.arch"); 152 153 if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) { 154 return Arch.X86_64; 155 156 } else if (arch.equalsIgnoreCase("x86") 157 || arch.equalsIgnoreCase("i386") 158 || arch.equalsIgnoreCase("i686")) { 159 return Arch.X86; 160 161 } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) { 162 return Arch.PPC; 163 } 164 165 return null; 166 } 167 168 /** Returns true if this architecture is compatible with the current one. */ 169 public boolean isCompatible() { 170 if (this == ANY) { 171 return true; 172 } 173 174 Arch arch = getCurrentArch(); 175 return this == arch; 176 } 177 } 178 179 private final Os mOs; 180 private final Arch mArch; 181 private final String mUrl; 182 private final long mSize; 183 private final String mChecksum; 184 private final ChecksumType mChecksumType = ChecksumType.SHA1; 185 private final Package mPackage; 186 private final String mLocalOsPath; 187 private final boolean mIsLocal; 188 189 /** 190 * Creates a new remote archive. 191 */ 192 Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) { 193 mPackage = pkg; 194 mOs = os; 195 mArch = arch; 196 mUrl = url == null ? null : url.trim(); 197 mLocalOsPath = null; 198 mSize = size; 199 mChecksum = checksum; 200 mIsLocal = false; 201 } 202 203 /** 204 * Creates a new local archive. 205 * Uses the properties from props first, if possible. Props can be null. 206 */ 207 @VisibleForTesting(visibility=Visibility.PACKAGE) 208 protected Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) { 209 mPackage = pkg; 210 211 mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString())); 212 mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString())); 213 214 mUrl = null; 215 mLocalOsPath = localOsPath; 216 mSize = 0; 217 mChecksum = ""; 218 mIsLocal = localOsPath != null; 219 } 220 221 /** 222 * Save the properties of the current archive in the give {@link Properties} object. 223 * These properties will later be give the constructor that takes a {@link Properties} object. 224 */ 225 void saveProperties(Properties props) { 226 props.setProperty(PROP_OS, mOs.toString()); 227 props.setProperty(PROP_ARCH, mArch.toString()); 228 } 229 230 /** 231 * Returns true if this is a locally installed archive. 232 * Returns false if this is a remote archive that needs to be downloaded. 233 */ 234 public boolean isLocal() { 235 return mIsLocal; 236 } 237 238 /** 239 * Returns the package that created and owns this archive. 240 * It should generally not be null. 241 */ 242 public Package getParentPackage() { 243 return mPackage; 244 } 245 246 /** 247 * Returns the archive size, an int > 0. 248 * Size will be 0 if this a local installed folder of unknown size. 249 */ 250 public long getSize() { 251 return mSize; 252 } 253 254 /** 255 * Returns the SHA1 archive checksum, as a 40-char hex. 256 * Can be empty but not null for local installed folders. 257 */ 258 public String getChecksum() { 259 return mChecksum; 260 } 261 262 /** 263 * Returns the checksum type, always {@link ChecksumType#SHA1} right now. 264 */ 265 public ChecksumType getChecksumType() { 266 return mChecksumType; 267 } 268 269 /** 270 * Returns the download archive URL, either absolute or relative to the repository xml. 271 * Always return null for a local installed folder. 272 * @see #getLocalOsPath() 273 */ 274 public String getUrl() { 275 return mUrl; 276 } 277 278 /** 279 * Returns the local OS folder where a local archive is installed. 280 * Always return null for remote archives. 281 * @see #getUrl() 282 */ 283 public String getLocalOsPath() { 284 return mLocalOsPath; 285 } 286 287 /** 288 * Returns the archive {@link Os} enum. 289 * Can be null for a local installed folder on an unknown OS. 290 */ 291 public Os getOs() { 292 return mOs; 293 } 294 295 /** 296 * Returns the archive {@link Arch} enum. 297 * Can be null for a local installed folder on an unknown architecture. 298 */ 299 public Arch getArch() { 300 return mArch; 301 } 302 303 /** 304 * Generates a description for this archive of the OS/Arch supported by this archive. 305 */ 306 public String getOsDescription() { 307 String os; 308 if (mOs == null) { 309 os = "unknown OS"; 310 } else if (mOs == Os.ANY) { 311 os = "any OS"; 312 } else { 313 os = mOs.getUiName(); 314 } 315 316 String arch = ""; //$NON-NLS-1$ 317 if (mArch != null && mArch != Arch.ANY) { 318 arch = mArch.getUiName(); 319 } 320 321 return String.format("%1$s%2$s%3$s", 322 os, 323 arch.length() > 0 ? " " : "", //$NON-NLS-2$ 324 arch); 325 } 326 327 /** 328 * Returns the short description of the source, if not null. 329 * Otherwise returns the default Object toString result. 330 * <p/> 331 * This is mostly helpful for debugging. 332 * For UI display, use the {@link IDescription} interface. 333 */ 334 @Override 335 public String toString() { 336 String s = getShortDescription(); 337 if (s != null) { 338 return s; 339 } 340 return super.toString(); 341 } 342 343 /** 344 * Generates a short description for this archive. 345 */ 346 public String getShortDescription() { 347 return String.format("Archive for %1$s", getOsDescription()); 348 } 349 350 /** 351 * Generates a longer description for this archive. 352 */ 353 public String getLongDescription() { 354 long size = getSize(); 355 String sizeStr; 356 if (size < 1024) { 357 sizeStr = String.format("%d Bytes", size); 358 } else if (size < 1024 * 1024) { 359 sizeStr = String.format("%d KiB", Math.round(size / 1024.0)); 360 } else if (size < 1024 * 1024 * 1024) { 361 sizeStr = String.format("%.1f MiB", 362 Math.round(10.0 * size / (1024 * 1024.0))/ 10.0); 363 } else { 364 sizeStr = String.format("%.1f GiB", 365 Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0); 366 } 367 368 return String.format("%1$s\nSize: %2$s\nSHA1: %3$s", 369 getShortDescription(), sizeStr, getChecksum()); 370 } 371 372 /** 373 * Returns true if this archive can be installed on the current platform. 374 */ 375 public boolean isCompatible() { 376 return getOs().isCompatible() && getArch().isCompatible(); 377 } 378 379 /** 380 * Delete the archive folder if this is a local archive. 381 */ 382 public void deleteLocal() { 383 if (isLocal()) { 384 new FileOp().deleteFileOrFolder(new File(getLocalOsPath())); 385 } 386 } 387 388 /** 389 * Archives are compared using their {@link Package} ordering. 390 * 391 * @see Package#compareTo(Package) 392 */ 393 public int compareTo(Archive rhs) { 394 if (mPackage != null && rhs != null) { 395 return mPackage.compareTo(rhs.getParentPackage()); 396 } 397 return 0; 398 } 399 400 /** 401 * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code. 402 * <p/> 403 * {@inheritDoc} 404 */ 405 @Override 406 public int hashCode() { 407 final int prime = 31; 408 int result = 1; 409 result = prime * result + ((mArch == null) ? 0 : mArch.hashCode()); 410 result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode()); 411 result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode()); 412 result = prime * result + (mIsLocal ? 1231 : 1237); 413 result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode()); 414 result = prime * result + ((mOs == null) ? 0 : mOs.hashCode()); 415 result = prime * result + (int) (mSize ^ (mSize >>> 32)); 416 result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode()); 417 return result; 418 } 419 420 /** 421 * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality. 422 * <p/> 423 * {@inheritDoc} 424 */ 425 @Override 426 public boolean equals(Object obj) { 427 if (this == obj) { 428 return true; 429 } 430 if (obj == null) { 431 return false; 432 } 433 if (!(obj instanceof Archive)) { 434 return false; 435 } 436 Archive other = (Archive) obj; 437 if (mArch == null) { 438 if (other.mArch != null) { 439 return false; 440 } 441 } else if (!mArch.equals(other.mArch)) { 442 return false; 443 } 444 if (mChecksum == null) { 445 if (other.mChecksum != null) { 446 return false; 447 } 448 } else if (!mChecksum.equals(other.mChecksum)) { 449 return false; 450 } 451 if (mChecksumType == null) { 452 if (other.mChecksumType != null) { 453 return false; 454 } 455 } else if (!mChecksumType.equals(other.mChecksumType)) { 456 return false; 457 } 458 if (mIsLocal != other.mIsLocal) { 459 return false; 460 } 461 if (mLocalOsPath == null) { 462 if (other.mLocalOsPath != null) { 463 return false; 464 } 465 } else if (!mLocalOsPath.equals(other.mLocalOsPath)) { 466 return false; 467 } 468 if (mOs == null) { 469 if (other.mOs != null) { 470 return false; 471 } 472 } else if (!mOs.equals(other.mOs)) { 473 return false; 474 } 475 if (mSize != other.mSize) { 476 return false; 477 } 478 if (mUrl == null) { 479 if (other.mUrl != null) { 480 return false; 481 } 482 } else if (!mUrl.equals(other.mUrl)) { 483 return false; 484 } 485 return true; 486 } 487 } 488