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