ArchiveDownloader.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Downloader;
  12. use Composer\Package\PackageInterface;
  13. use Symfony\Component\Finder\Finder;
  14. use Composer\IO\IOInterface;
  15. /**
  16. * Base downloader for archives
  17. *
  18. * @author Kirill chEbba Chebunin <iam@chebba.org>
  19. * @author Jordi Boggiano <j.boggiano@seld.be>
  20. * @author François Pluchino <francois.pluchino@opendisplay.com>
  21. */
  22. abstract class ArchiveDownloader extends FileDownloader
  23. {
  24. /**
  25. * {@inheritDoc}
  26. * @throws \RuntimeException
  27. * @throws \UnexpectedValueException
  28. */
  29. public function install(PackageInterface $package, $path, $output = true)
  30. {
  31. if ($output) {
  32. $this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): Extracting archive");
  33. } else {
  34. $this->io->writeError('Extracting archive', false);
  35. }
  36. $this->filesystem->emptyDirectory($path);
  37. $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
  38. $fileName = $this->getFileName($package, $path);
  39. try {
  40. $this->filesystem->ensureDirectoryExists($temporaryDir);
  41. try {
  42. $this->extract($package, $fileName, $temporaryDir);
  43. } catch (\Exception $e) {
  44. // remove cache if the file was corrupted
  45. parent::clearLastCacheWrite($package);
  46. throw $e;
  47. }
  48. $this->filesystem->unlink($fileName);
  49. $renameAsOne = false;
  50. if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
  51. $renameAsOne = true;
  52. }
  53. $contentDir = $this->getFolderContent($temporaryDir);
  54. $singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir));
  55. if ($renameAsOne) {
  56. // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
  57. if ($singleDirAtTopLevel) {
  58. $extractedDir = (string) reset($contentDir);
  59. } else {
  60. $extractedDir = $temporaryDir;
  61. }
  62. $this->filesystem->rename($extractedDir, $path);
  63. } else {
  64. // only one dir in the archive, extract its contents out of it
  65. if ($singleDirAtTopLevel) {
  66. $contentDir = $this->getFolderContent((string) reset($contentDir));
  67. }
  68. // move files back out of the temp dir
  69. foreach ($contentDir as $file) {
  70. $file = (string) $file;
  71. $this->filesystem->rename($file, $path . '/' . basename($file));
  72. }
  73. }
  74. $this->filesystem->removeDirectory($temporaryDir);
  75. if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
  76. $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
  77. }
  78. if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
  79. $this->filesystem->removeDirectory($this->config->get('vendor-dir'));
  80. }
  81. } catch (\Exception $e) {
  82. // clean up
  83. $this->filesystem->removeDirectory($path);
  84. $this->filesystem->removeDirectory($temporaryDir);
  85. if (file_exists($fileName)) {
  86. $this->filesystem->unlink($fileName);
  87. }
  88. throw $e;
  89. }
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. protected function getFileName(PackageInterface $package, $path)
  95. {
  96. return rtrim($path.'_'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
  97. }
  98. /**
  99. * Extract file to directory
  100. *
  101. * @param string $file Extracted file
  102. * @param string $path Directory
  103. *
  104. * @throws \UnexpectedValueException If can not extract downloaded file to path
  105. */
  106. abstract protected function extract(PackageInterface $package, $file, $path);
  107. /**
  108. * Returns the folder content, excluding dotfiles
  109. *
  110. * @param string $dir Directory
  111. * @return \SplFileInfo[]
  112. */
  113. private function getFolderContent($dir)
  114. {
  115. $finder = Finder::create()
  116. ->ignoreVCS(false)
  117. ->ignoreDotFiles(false)
  118. ->notName('.DS_Store')
  119. ->depth(0)
  120. ->in($dir);
  121. return iterator_to_array($finder);
  122. }
  123. }