PageRenderTime 70ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/android/sdklib/internal/repository/Archive.java

https://gitlab.com/math4youbyusgroupillinois/terminal-ide
Java | 1086 lines | 669 code | 168 blank | 249 comment | 150 complexity | 8073d8655c8c08163597234264e8d015 MD5 | raw file
  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. package com.android.sdklib.internal.repository;
  17. import com.android.sdklib.SdkConstants;
  18. import com.android.sdklib.SdkManager;
  19. import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
  20. import org.apache.commons.compress.archivers.zip.ZipFile;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileNotFoundException;
  24. import java.io.FileOutputStream;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.net.URL;
  28. import java.security.MessageDigest;
  29. import java.security.NoSuchAlgorithmException;
  30. import java.util.Enumeration;
  31. import java.util.Properties;
  32. /**
  33. * A {@link Archive} is the base class for "something" that can be downloaded from
  34. * the SDK repository.
  35. * <p/>
  36. * A package has some attributes (revision, description) and a list of archives
  37. * which represent the downloadable bits.
  38. * <p/>
  39. * Packages are offered by a {@link RepoSource} (a download site).
  40. */
  41. public class Archive implements IDescription {
  42. public static final int NUM_MONITOR_INC = 100;
  43. private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$
  44. private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
  45. /** The checksum type. */
  46. public enum ChecksumType {
  47. /** A SHA1 checksum, represented as a 40-hex string. */
  48. SHA1("SHA-1"); //$NON-NLS-1$
  49. private final String mAlgorithmName;
  50. /**
  51. * Constructs a {@link ChecksumType} with the algorigth name
  52. * suitable for {@link MessageDigest#getInstance(String)}.
  53. * <p/>
  54. * These names are officially documented at
  55. * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
  56. */
  57. private ChecksumType(String algorithmName) {
  58. mAlgorithmName = algorithmName;
  59. }
  60. /**
  61. * Returns a new {@link MessageDigest} instance for this checksum type.
  62. * @throws NoSuchAlgorithmException if this algorithm is not available.
  63. */
  64. public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
  65. return MessageDigest.getInstance(mAlgorithmName);
  66. }
  67. }
  68. /** The OS that this archive can be downloaded on. */
  69. public enum Os {
  70. ANY("Any"),
  71. LINUX("Linux"),
  72. MACOSX("MacOS X"),
  73. WINDOWS("Windows");
  74. private final String mUiName;
  75. private Os(String uiName) {
  76. mUiName = uiName;
  77. }
  78. /** Returns the UI name of the OS. */
  79. public String getUiName() {
  80. return mUiName;
  81. }
  82. /** Returns the XML name of the OS. */
  83. public String getXmlName() {
  84. return toString().toLowerCase();
  85. }
  86. /**
  87. * Returns the current OS as one of the {@link Os} enum values or null.
  88. */
  89. public static Os getCurrentOs() {
  90. String os = System.getProperty("os.name"); //$NON-NLS-1$
  91. if (os.startsWith("Mac")) { //$NON-NLS-1$
  92. return Os.MACOSX;
  93. } else if (os.startsWith("Windows")) { //$NON-NLS-1$
  94. return Os.WINDOWS;
  95. } else if (os.startsWith("Linux")) { //$NON-NLS-1$
  96. return Os.LINUX;
  97. }
  98. return null;
  99. }
  100. /** Returns true if this OS is compatible with the current one. */
  101. public boolean isCompatible() {
  102. if (this == ANY) {
  103. return true;
  104. }
  105. Os os = getCurrentOs();
  106. return this == os;
  107. }
  108. }
  109. /** The Architecture that this archive can be downloaded on. */
  110. public enum Arch {
  111. ANY("Any"),
  112. PPC("PowerPC"),
  113. X86("x86"),
  114. X86_64("x86_64");
  115. private final String mUiName;
  116. private Arch(String uiName) {
  117. mUiName = uiName;
  118. }
  119. /** Returns the UI name of the architecture. */
  120. public String getUiName() {
  121. return mUiName;
  122. }
  123. /** Returns the XML name of the architecture. */
  124. public String getXmlName() {
  125. return toString().toLowerCase();
  126. }
  127. /**
  128. * Returns the current architecture as one of the {@link Arch} enum values or null.
  129. */
  130. public static Arch getCurrentArch() {
  131. // Values listed from http://lopica.sourceforge.net/os.html
  132. String arch = System.getProperty("os.arch");
  133. if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) {
  134. return Arch.X86_64;
  135. } else if (arch.equalsIgnoreCase("x86")
  136. || arch.equalsIgnoreCase("i386")
  137. || arch.equalsIgnoreCase("i686")) {
  138. return Arch.X86;
  139. } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) {
  140. return Arch.PPC;
  141. }
  142. return null;
  143. }
  144. /** Returns true if this architecture is compatible with the current one. */
  145. public boolean isCompatible() {
  146. if (this == ANY) {
  147. return true;
  148. }
  149. Arch arch = getCurrentArch();
  150. return this == arch;
  151. }
  152. }
  153. private final Os mOs;
  154. private final Arch mArch;
  155. private final String mUrl;
  156. private final long mSize;
  157. private final String mChecksum;
  158. private final ChecksumType mChecksumType = ChecksumType.SHA1;
  159. private final Package mPackage;
  160. private final String mLocalOsPath;
  161. private final boolean mIsLocal;
  162. /**
  163. * Creates a new remote archive.
  164. */
  165. Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
  166. mPackage = pkg;
  167. mOs = os;
  168. mArch = arch;
  169. mUrl = url;
  170. mLocalOsPath = null;
  171. mSize = size;
  172. mChecksum = checksum;
  173. mIsLocal = false;
  174. }
  175. /**
  176. * Creates a new local archive.
  177. * Uses the properties from props first, if possible. Props can be null.
  178. */
  179. Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) {
  180. mPackage = pkg;
  181. mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString()));
  182. mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString()));
  183. mUrl = null;
  184. mLocalOsPath = localOsPath;
  185. mSize = 0;
  186. mChecksum = "";
  187. mIsLocal = true;
  188. }
  189. /**
  190. * Save the properties of the current archive in the give {@link Properties} object.
  191. * These properties will later be give the constructor that takes a {@link Properties} object.
  192. */
  193. void saveProperties(Properties props) {
  194. props.setProperty(PROP_OS, mOs.toString());
  195. props.setProperty(PROP_ARCH, mArch.toString());
  196. }
  197. /**
  198. * Returns true if this is a locally installed archive.
  199. * Returns false if this is a remote archive that needs to be downloaded.
  200. */
  201. public boolean isLocal() {
  202. return mIsLocal;
  203. }
  204. /**
  205. * Returns the package that created and owns this archive.
  206. * It should generally not be null.
  207. */
  208. public Package getParentPackage() {
  209. return mPackage;
  210. }
  211. /**
  212. * Returns the archive size, an int > 0.
  213. * Size will be 0 if this a local installed folder of unknown size.
  214. */
  215. public long getSize() {
  216. return mSize;
  217. }
  218. /**
  219. * Returns the SHA1 archive checksum, as a 40-char hex.
  220. * Can be empty but not null for local installed folders.
  221. */
  222. public String getChecksum() {
  223. return mChecksum;
  224. }
  225. /**
  226. * Returns the checksum type, always {@link ChecksumType#SHA1} right now.
  227. */
  228. public ChecksumType getChecksumType() {
  229. return mChecksumType;
  230. }
  231. /**
  232. * Returns the download archive URL, either absolute or relative to the repository xml.
  233. * Always return null for a local installed folder.
  234. * @see #getLocalOsPath()
  235. */
  236. public String getUrl() {
  237. return mUrl;
  238. }
  239. /**
  240. * Returns the local OS folder where a local archive is installed.
  241. * Always return null for remote archives.
  242. * @see #getUrl()
  243. */
  244. public String getLocalOsPath() {
  245. return mLocalOsPath;
  246. }
  247. /**
  248. * Returns the archive {@link Os} enum.
  249. * Can be null for a local installed folder on an unknown OS.
  250. */
  251. public Os getOs() {
  252. return mOs;
  253. }
  254. /**
  255. * Returns the archive {@link Arch} enum.
  256. * Can be null for a local installed folder on an unknown architecture.
  257. */
  258. public Arch getArch() {
  259. return mArch;
  260. }
  261. /**
  262. * Generates a description for this archive of the OS/Arch supported by this archive.
  263. */
  264. public String getOsDescription() {
  265. String os;
  266. if (mOs == null) {
  267. os = "unknown OS";
  268. } else if (mOs == Os.ANY) {
  269. os = "any OS";
  270. } else {
  271. os = mOs.getUiName();
  272. }
  273. String arch = ""; //$NON-NLS-1$
  274. if (mArch != null && mArch != Arch.ANY) {
  275. arch = mArch.getUiName();
  276. }
  277. return String.format("%1$s%2$s%3$s",
  278. os,
  279. arch.length() > 0 ? " " : "", //$NON-NLS-2$
  280. arch);
  281. }
  282. /**
  283. * Generates a short description for this archive.
  284. */
  285. public String getShortDescription() {
  286. return String.format("Archive for %1$s", getOsDescription());
  287. }
  288. /**
  289. * Generates a longer description for this archive.
  290. */
  291. public String getLongDescription() {
  292. return String.format("%1$s\nSize: %2$d MiB\nSHA1: %3$s",
  293. getShortDescription(),
  294. Math.round(getSize() / (1024*1024)),
  295. getChecksum());
  296. }
  297. /**
  298. * Returns true if this archive can be installed on the current platform.
  299. */
  300. public boolean isCompatible() {
  301. return getOs().isCompatible() && getArch().isCompatible();
  302. }
  303. /**
  304. * Delete the archive folder if this is a local archive.
  305. */
  306. public void deleteLocal() {
  307. if (isLocal()) {
  308. deleteFileOrFolder(new File(getLocalOsPath()));
  309. }
  310. }
  311. /**
  312. * Install this {@link Archive}s.
  313. * The archive will be skipped if it is incompatible.
  314. *
  315. * @return True if the archive was installed, false otherwise.
  316. */
  317. public boolean install(String osSdkRoot,
  318. boolean forceHttp,
  319. SdkManager sdkManager,
  320. ITaskMonitor monitor) {
  321. Package pkg = getParentPackage();
  322. File archiveFile = null;
  323. String name = pkg.getShortDescription();
  324. if (pkg instanceof ExtraPackage && !((ExtraPackage) pkg).isPathValid()) {
  325. monitor.setResult("Skipping %1$s: %2$s is not a valid install path.",
  326. name,
  327. ((ExtraPackage) pkg).getPath());
  328. return false;
  329. }
  330. if (isLocal()) {
  331. // This should never happen.
  332. monitor.setResult("Skipping already installed archive: %1$s for %2$s",
  333. name,
  334. getOsDescription());
  335. return false;
  336. }
  337. if (!isCompatible()) {
  338. monitor.setResult("Skipping incompatible archive: %1$s for %2$s",
  339. name,
  340. getOsDescription());
  341. return false;
  342. }
  343. archiveFile = downloadFile(osSdkRoot, monitor, forceHttp);
  344. if (archiveFile != null) {
  345. // Unarchive calls the pre/postInstallHook methods.
  346. if (unarchive(osSdkRoot, archiveFile, sdkManager, monitor)) {
  347. monitor.setResult("Installed %1$s", name);
  348. // Delete the temp archive if it exists, only on success
  349. deleteFileOrFolder(archiveFile);
  350. return true;
  351. }
  352. }
  353. return false;
  354. }
  355. /**
  356. * Downloads an archive and returns the temp file with it.
  357. * Caller is responsible with deleting the temp file when done.
  358. */
  359. private File downloadFile(String osSdkRoot, ITaskMonitor monitor, boolean forceHttp) {
  360. String name = getParentPackage().getShortDescription();
  361. String desc = String.format("Downloading %1$s", name);
  362. monitor.setDescription(desc);
  363. monitor.setResult(desc);
  364. String link = getUrl();
  365. if (!link.startsWith("http://") //$NON-NLS-1$
  366. && !link.startsWith("https://") //$NON-NLS-1$
  367. && !link.startsWith("ftp://")) { //$NON-NLS-1$
  368. // Make the URL absolute by prepending the source
  369. Package pkg = getParentPackage();
  370. RepoSource src = pkg.getParentSource();
  371. if (src == null) {
  372. monitor.setResult("Internal error: no source for archive %1$s", name);
  373. return null;
  374. }
  375. // take the URL to the repository.xml and remove the last component
  376. // to get the base
  377. String repoXml = src.getUrl();
  378. int pos = repoXml.lastIndexOf('/');
  379. String base = repoXml.substring(0, pos + 1);
  380. link = base + link;
  381. }
  382. if (forceHttp) {
  383. link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
  384. }
  385. // Get the basename of the file we're downloading, i.e. the last component
  386. // of the URL
  387. int pos = link.lastIndexOf('/');
  388. String base = link.substring(pos + 1);
  389. // Rather than create a real temp file in the system, we simply use our
  390. // temp folder (in the SDK base folder) and use the archive name for the
  391. // download. This allows us to reuse or continue downloads.
  392. File tmpFolder = getTempFolder(osSdkRoot);
  393. if (!tmpFolder.isDirectory()) {
  394. if (tmpFolder.isFile()) {
  395. deleteFileOrFolder(tmpFolder);
  396. }
  397. if (!tmpFolder.mkdirs()) {
  398. monitor.setResult("Failed to create directory %1$s", tmpFolder.getPath());
  399. return null;
  400. }
  401. }
  402. File tmpFile = new File(tmpFolder, base);
  403. // if the file exists, check if its checksum & size. Use it if complete
  404. if (tmpFile.exists()) {
  405. if (tmpFile.length() == getSize() &&
  406. fileChecksum(tmpFile, monitor).equalsIgnoreCase(getChecksum())) {
  407. // File is good, let's use it.
  408. return tmpFile;
  409. }
  410. // Existing file is either of different size or content.
  411. // TODO: continue download when we support continue mode.
  412. // Right now, let's simply remove the file and start over.
  413. deleteFileOrFolder(tmpFile);
  414. }
  415. if (fetchUrl(tmpFile, link, desc, monitor)) {
  416. // Fetching was successful, let's use this file.
  417. return tmpFile;
  418. } else {
  419. // Delete the temp file if we aborted the download
  420. // TODO: disable this when we want to support partial downloads!
  421. deleteFileOrFolder(tmpFile);
  422. return null;
  423. }
  424. }
  425. /**
  426. * Computes the SHA-1 checksum of the content of the given file.
  427. * Returns an empty string on error (rather than null).
  428. */
  429. private String fileChecksum(File tmpFile, ITaskMonitor monitor) {
  430. InputStream is = null;
  431. try {
  432. is = new FileInputStream(tmpFile);
  433. MessageDigest digester = getChecksumType().getMessageDigest();
  434. byte[] buf = new byte[65536];
  435. int n;
  436. while ((n = is.read(buf)) >= 0) {
  437. if (n > 0) {
  438. digester.update(buf, 0, n);
  439. }
  440. }
  441. return getDigestChecksum(digester);
  442. } catch (FileNotFoundException e) {
  443. // The FNF message is just the URL. Make it a bit more useful.
  444. monitor.setResult("File not found: %1$s", e.getMessage());
  445. } catch (Exception e) {
  446. monitor.setResult(e.getMessage());
  447. } finally {
  448. if (is != null) {
  449. try {
  450. is.close();
  451. } catch (IOException e) {
  452. // pass
  453. }
  454. }
  455. }
  456. return ""; //$NON-NLS-1$
  457. }
  458. /**
  459. * Returns the SHA-1 from a {@link MessageDigest} as an hex string
  460. * that can be compared with {@link #getChecksum()}.
  461. */
  462. private String getDigestChecksum(MessageDigest digester) {
  463. int n;
  464. // Create an hex string from the digest
  465. byte[] digest = digester.digest();
  466. n = digest.length;
  467. String hex = "0123456789abcdef"; //$NON-NLS-1$
  468. char[] hexDigest = new char[n * 2];
  469. for (int i = 0; i < n; i++) {
  470. int b = digest[i] & 0x0FF;
  471. hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
  472. hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
  473. }
  474. return new String(hexDigest);
  475. }
  476. /**
  477. * Actually performs the download.
  478. * Also computes the SHA1 of the file on the fly.
  479. * <p/>
  480. * Success is defined as downloading as many bytes as was expected and having the same
  481. * SHA1 as expected. Returns true on success or false if any of those checks fail.
  482. * <p/>
  483. * Increments the monitor by {@link #NUM_MONITOR_INC}.
  484. */
  485. private boolean fetchUrl(File tmpFile,
  486. String urlString,
  487. String description,
  488. ITaskMonitor monitor) {
  489. URL url;
  490. description += " (%1$d%%, %2$.0f KiB/s, %3$d %4$s left)";
  491. FileOutputStream os = null;
  492. InputStream is = null;
  493. try {
  494. url = new URL(urlString);
  495. is = url.openStream();
  496. os = new FileOutputStream(tmpFile);
  497. MessageDigest digester = getChecksumType().getMessageDigest();
  498. byte[] buf = new byte[65536];
  499. int n;
  500. long total = 0;
  501. long size = getSize();
  502. long inc = size / NUM_MONITOR_INC;
  503. long next_inc = inc;
  504. long startMs = System.currentTimeMillis();
  505. long nextMs = startMs + 2000; // start update after 2 seconds
  506. while ((n = is.read(buf)) >= 0) {
  507. if (n > 0) {
  508. os.write(buf, 0, n);
  509. digester.update(buf, 0, n);
  510. }
  511. long timeMs = System.currentTimeMillis();
  512. total += n;
  513. if (total >= next_inc) {
  514. monitor.incProgress(1);
  515. next_inc += inc;
  516. }
  517. if (timeMs > nextMs) {
  518. long delta = timeMs - startMs;
  519. if (total > 0 && delta > 0) {
  520. // percent left to download
  521. int percent = (int) (100 * total / size);
  522. // speed in KiB/s
  523. float speed = (float)total / (float)delta * (1000.f / 1024.f);
  524. // time left to download the rest at the current KiB/s rate
  525. int timeLeft = (speed > 1e-3) ?
  526. (int)(((size - total) / 1024.0f) / speed) :
  527. 0;
  528. String timeUnit = "seconds";
  529. if (timeLeft > 120) {
  530. timeUnit = "minutes";
  531. timeLeft /= 60;
  532. }
  533. monitor.setDescription(description, percent, speed, timeLeft, timeUnit);
  534. }
  535. nextMs = timeMs + 1000; // update every second
  536. }
  537. if (monitor.isCancelRequested()) {
  538. monitor.setResult("Download aborted by user at %1$d bytes.", total);
  539. return false;
  540. }
  541. }
  542. if (total != size) {
  543. monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
  544. size, total);
  545. return false;
  546. }
  547. // Create an hex string from the digest
  548. String actual = getDigestChecksum(digester);
  549. String expected = getChecksum();
  550. if (!actual.equalsIgnoreCase(expected)) {
  551. monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",
  552. expected, actual);
  553. return false;
  554. }
  555. return true;
  556. } catch (FileNotFoundException e) {
  557. // The FNF message is just the URL. Make it a bit more useful.
  558. monitor.setResult("File not found: %1$s", e.getMessage());
  559. } catch (Exception e) {
  560. monitor.setResult(e.getMessage());
  561. } finally {
  562. if (os != null) {
  563. try {
  564. os.close();
  565. } catch (IOException e) {
  566. // pass
  567. }
  568. }
  569. if (is != null) {
  570. try {
  571. is.close();
  572. } catch (IOException e) {
  573. // pass
  574. }
  575. }
  576. }
  577. return false;
  578. }
  579. /**
  580. * Install the given archive in the given folder.
  581. */
  582. private boolean unarchive(String osSdkRoot,
  583. File archiveFile,
  584. SdkManager sdkManager,
  585. ITaskMonitor monitor) {
  586. boolean success = false;
  587. Package pkg = getParentPackage();
  588. String pkgName = pkg.getShortDescription();
  589. String pkgDesc = String.format("Installing %1$s", pkgName);
  590. monitor.setDescription(pkgDesc);
  591. monitor.setResult(pkgDesc);
  592. // We always unzip in a temp folder which name depends on the package type
  593. // (e.g. addon, tools, etc.) and then move the folder to the destination folder.
  594. // If the destination folder exists, it will be renamed and deleted at the very
  595. // end if everything succeeded.
  596. String pkgKind = pkg.getClass().getSimpleName();
  597. File destFolder = null;
  598. File unzipDestFolder = null;
  599. File oldDestFolder = null;
  600. try {
  601. // Find a new temp folder that doesn't exist yet
  602. unzipDestFolder = createTempFolder(osSdkRoot, pkgKind, "new"); //$NON-NLS-1$
  603. if (unzipDestFolder == null) {
  604. // this should not seriously happen.
  605. monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
  606. return false;
  607. }
  608. if (!unzipDestFolder.mkdirs()) {
  609. monitor.setResult("Failed to create directory %1$s", unzipDestFolder.getPath());
  610. return false;
  611. }
  612. String[] zipRootFolder = new String[] { null };
  613. if (!unzipFolder(archiveFile, getSize(),
  614. unzipDestFolder, pkgDesc,
  615. zipRootFolder, monitor)) {
  616. return false;
  617. }
  618. if (!generateSourceProperties(unzipDestFolder)) {
  619. return false;
  620. }
  621. // Compute destination directory
  622. destFolder = pkg.getInstallFolder(osSdkRoot, zipRootFolder[0], sdkManager);
  623. if (destFolder == null) {
  624. // this should not seriously happen.
  625. monitor.setResult("Failed to compute installation directory for %1$s.", pkgName);
  626. return false;
  627. }
  628. if (!pkg.preInstallHook(this, monitor, osSdkRoot, destFolder)) {
  629. monitor.setResult("Skipping archive: %1$s", pkgName);
  630. return false;
  631. }
  632. // Swap the old folder by the new one.
  633. // We have 2 "folder rename" (aka moves) to do.
  634. // They must both succeed in the right order.
  635. boolean move1done = false;
  636. boolean move2done = false;
  637. while (!move1done || !move2done) {
  638. File renameFailedForDir = null;
  639. // Case where the dest dir already exists
  640. if (!move1done) {
  641. if (destFolder.isDirectory()) {
  642. // Create a new temp/old dir
  643. if (oldDestFolder == null) {
  644. oldDestFolder = createTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
  645. }
  646. if (oldDestFolder == null) {
  647. // this should not seriously happen.
  648. monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
  649. return false;
  650. }
  651. // try to move the current dest dir to the temp/old one
  652. if (!destFolder.renameTo(oldDestFolder)) {
  653. monitor.setResult("Failed to rename directory %1$s to %2$s.",
  654. destFolder.getPath(), oldDestFolder.getPath());
  655. renameFailedForDir = destFolder;
  656. }
  657. }
  658. move1done = (renameFailedForDir == null);
  659. }
  660. // Case where there's no dest dir or we successfully moved it to temp/old
  661. // We now try to move the temp/unzip to the dest dir
  662. if (move1done && !move2done) {
  663. if (renameFailedForDir == null && !unzipDestFolder.renameTo(destFolder)) {
  664. monitor.setResult("Failed to rename directory %1$s to %2$s",
  665. unzipDestFolder.getPath(), destFolder.getPath());
  666. renameFailedForDir = unzipDestFolder;
  667. }
  668. move2done = (renameFailedForDir == null);
  669. }
  670. if (renameFailedForDir != null) {
  671. if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
  672. String msg = String.format(
  673. "-= Warning ! =-\n" +
  674. "A folder failed to be renamed or moved. On Windows this " +
  675. "typically means that a program is using that folder (for example " +
  676. "Windows Explorer or your anti-virus software.)\n" +
  677. "Please momentarily deactivate your anti-virus software.\n" +
  678. "Please also close any running programs that may be accessing " +
  679. "the directory '%1$s'.\n" +
  680. "When ready, press YES to try again.",
  681. renameFailedForDir.getPath());
  682. if (monitor.displayPrompt("SDK Manager: failed to install", msg)) {
  683. // loop, trying to rename the temp dir into the destination
  684. continue;
  685. }
  686. }
  687. return false;
  688. }
  689. break;
  690. }
  691. unzipDestFolder = null;
  692. success = true;
  693. pkg.postInstallHook(this, monitor, destFolder);
  694. return true;
  695. } finally {
  696. // Cleanup if the unzip folder is still set.
  697. deleteFileOrFolder(oldDestFolder);
  698. deleteFileOrFolder(unzipDestFolder);
  699. // In case of failure, we call the postInstallHool with a null directory
  700. if (!success) {
  701. pkg.postInstallHook(this, monitor, null /*installDir*/);
  702. }
  703. }
  704. }
  705. /**
  706. * Unzips a zip file into the given destination directory.
  707. *
  708. * The archive file MUST have a unique "root" folder. This root folder is skipped when
  709. * unarchiving. However we return that root folder name to the caller, as it can be used
  710. * as a template to know what destination directory to use in the Add-on case.
  711. */
  712. @SuppressWarnings("unchecked")
  713. private boolean unzipFolder(File archiveFile,
  714. long compressedSize,
  715. File unzipDestFolder,
  716. String description,
  717. String[] outZipRootFolder,
  718. ITaskMonitor monitor) {
  719. description += " (%1$d%%)";
  720. ZipFile zipFile = null;
  721. try {
  722. zipFile = new ZipFile(archiveFile);
  723. // figure if we'll need to set the unix permission
  724. boolean usingUnixPerm = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
  725. SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
  726. // To advance the percent and the progress bar, we don't know the number of
  727. // items left to unzip. However we know the size of the archive and the size of
  728. // each uncompressed item. The zip file format overhead is negligible so that's
  729. // a good approximation.
  730. long incStep = compressedSize / NUM_MONITOR_INC;
  731. long incTotal = 0;
  732. long incCurr = 0;
  733. int lastPercent = 0;
  734. byte[] buf = new byte[65536];
  735. Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
  736. while (entries.hasMoreElements()) {
  737. ZipArchiveEntry entry = entries.nextElement();
  738. String name = entry.getName();
  739. // ZipFile entries should have forward slashes, but not all Zip
  740. // implementations can be expected to do that.
  741. name = name.replace('\\', '/');
  742. // Zip entries are always packages in a top-level directory
  743. // (e.g. docs/index.html). However we want to use our top-level
  744. // directory so we drop the first segment of the path name.
  745. int pos = name.indexOf('/');
  746. if (pos < 0 || pos == name.length() - 1) {
  747. continue;
  748. } else {
  749. if (outZipRootFolder[0] == null && pos > 0) {
  750. outZipRootFolder[0] = name.substring(0, pos);
  751. }
  752. name = name.substring(pos + 1);
  753. }
  754. File destFile = new File(unzipDestFolder, name);
  755. if (name.endsWith("/")) { //$NON-NLS-1$
  756. // Create directory if it doesn't exist yet. This allows us to create
  757. // empty directories.
  758. if (!destFile.isDirectory() && !destFile.mkdirs()) {
  759. monitor.setResult("Failed to create temp directory %1$s",
  760. destFile.getPath());
  761. return false;
  762. }
  763. continue;
  764. } else if (name.indexOf('/') != -1) {
  765. // Otherwise it's a file in a sub-directory.
  766. // Make sure the parent directory has been created.
  767. File parentDir = destFile.getParentFile();
  768. if (!parentDir.isDirectory()) {
  769. if (!parentDir.mkdirs()) {
  770. monitor.setResult("Failed to create temp directory %1$s",
  771. parentDir.getPath());
  772. return false;
  773. }
  774. }
  775. }
  776. FileOutputStream fos = null;
  777. try {
  778. fos = new FileOutputStream(destFile);
  779. int n;
  780. InputStream entryContent = zipFile.getInputStream(entry);
  781. while ((n = entryContent.read(buf)) != -1) {
  782. if (n > 0) {
  783. fos.write(buf, 0, n);
  784. }
  785. }
  786. } finally {
  787. if (fos != null) {
  788. fos.close();
  789. }
  790. }
  791. // if needed set the permissions.
  792. if (usingUnixPerm && destFile.isFile()) {
  793. // get the mode and test if it contains the executable bit
  794. int mode = entry.getUnixMode();
  795. if ((mode & 0111) != 0) {
  796. setExecutablePermission(destFile);
  797. }
  798. }
  799. // Increment progress bar to match. We update only between files.
  800. for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
  801. monitor.incProgress(1);
  802. }
  803. int percent = (int) (100 * incTotal / compressedSize);
  804. if (percent != lastPercent) {
  805. monitor.setDescription(description, percent);
  806. lastPercent = percent;
  807. }
  808. if (monitor.isCancelRequested()) {
  809. return false;
  810. }
  811. }
  812. return true;
  813. } catch (IOException e) {
  814. monitor.setResult("Unzip failed: %1$s", e.getMessage());
  815. } finally {
  816. if (zipFile != null) {
  817. try {
  818. zipFile.close();
  819. } catch (IOException e) {
  820. // pass
  821. }
  822. }
  823. }
  824. return false;
  825. }
  826. /**
  827. * Creates a temp folder in the form of osBasePath/temp/prefix.suffixNNN.
  828. * <p/>
  829. * This operation is not atomic so there's no guarantee the folder can't get
  830. * created in between. This is however unlikely and the caller can assume the
  831. * returned folder does not exist yet.
  832. * <p/>
  833. * Returns null if no such folder can be found (e.g. if all candidates exist,
  834. * which is rather unlikely) or if the base temp folder cannot be created.
  835. */
  836. private File createTempFolder(String osBasePath, String prefix, String suffix) {
  837. File baseTempFolder = getTempFolder(osBasePath);
  838. if (!baseTempFolder.isDirectory()) {
  839. if (baseTempFolder.isFile()) {
  840. deleteFileOrFolder(baseTempFolder);
  841. }
  842. if (!baseTempFolder.mkdirs()) {
  843. return null;
  844. }
  845. }
  846. for (int i = 1; i < 100; i++) {
  847. File folder = new File(baseTempFolder,
  848. String.format("%1$s.%2$s%3$02d", prefix, suffix, i)); //$NON-NLS-1$
  849. if (!folder.exists()) {
  850. return folder;
  851. }
  852. }
  853. return null;
  854. }
  855. /**
  856. * Returns the temp folder used by the SDK Manager.
  857. * This folder is always at osBasePath/temp.
  858. */
  859. private File getTempFolder(String osBasePath) {
  860. File baseTempFolder = new File(osBasePath, "temp"); //$NON-NLS-1$
  861. return baseTempFolder;
  862. }
  863. /**
  864. * Deletes a file or a directory.
  865. * Directories are deleted recursively.
  866. * The argument can be null.
  867. */
  868. private void deleteFileOrFolder(File fileOrFolder) {
  869. if (fileOrFolder != null) {
  870. if (fileOrFolder.isDirectory()) {
  871. // Must delete content recursively first
  872. for (File item : fileOrFolder.listFiles()) {
  873. deleteFileOrFolder(item);
  874. }
  875. }
  876. if (!fileOrFolder.delete()) {
  877. fileOrFolder.deleteOnExit();
  878. }
  879. }
  880. }
  881. /**
  882. * Generates a source.properties in the destination folder that contains all the infos
  883. * relevant to this archive, this package and the source so that we can reload them
  884. * locally later.
  885. */
  886. private boolean generateSourceProperties(File unzipDestFolder) {
  887. Properties props = new Properties();
  888. saveProperties(props);
  889. mPackage.saveProperties(props);
  890. FileOutputStream fos = null;
  891. try {
  892. File f = new File(unzipDestFolder, SdkConstants.FN_SOURCE_PROP);
  893. fos = new FileOutputStream(f);
  894. props.store( fos, "## Android Tool: Source of this archive."); //$NON-NLS-1$
  895. return true;
  896. } catch (IOException e) {
  897. e.printStackTrace();
  898. } finally {
  899. if (fos != null) {
  900. try {
  901. fos.close();
  902. } catch (IOException e) {
  903. }
  904. }
  905. }
  906. return false;
  907. }
  908. /**
  909. * Sets the executable Unix permission (0777) on a file or folder.
  910. * @param file The file to set permissions on.
  911. * @throws IOException If an I/O error occurs
  912. */
  913. private void setExecutablePermission(File file) throws IOException {
  914. Runtime.getRuntime().exec(new String[] {
  915. "chmod", "777", file.getAbsolutePath()
  916. });
  917. }
  918. }