DownloadManager.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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 Composer\IO\IOInterface;
  14. use Composer\Util\Filesystem;
  15. /**
  16. * Downloaders manager.
  17. *
  18. * @author Konstantin Kudryashov <ever.zet@gmail.com>
  19. */
  20. class DownloadManager
  21. {
  22. private $io;
  23. private $preferDist = false;
  24. private $preferSource = false;
  25. private $filesystem;
  26. private $downloaders = array();
  27. /**
  28. * Initializes download manager.
  29. *
  30. * @param IOInterface $io The Input Output Interface
  31. * @param bool $preferSource prefer downloading from source
  32. * @param Filesystem|null $filesystem custom Filesystem object
  33. */
  34. public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
  35. {
  36. $this->io = $io;
  37. $this->preferSource = $preferSource;
  38. $this->filesystem = $filesystem ?: new Filesystem();
  39. }
  40. /**
  41. * Makes downloader prefer source installation over the dist.
  42. *
  43. * @param bool $preferSource prefer downloading from source
  44. * @return DownloadManager
  45. */
  46. public function setPreferSource($preferSource)
  47. {
  48. $this->preferSource = $preferSource;
  49. return $this;
  50. }
  51. /**
  52. * Makes downloader prefer dist installation over the source.
  53. *
  54. * @param bool $preferDist prefer downloading from dist
  55. * @return DownloadManager
  56. */
  57. public function setPreferDist($preferDist)
  58. {
  59. $this->preferDist = $preferDist;
  60. return $this;
  61. }
  62. /**
  63. * Sets whether to output download progress information for all registered
  64. * downloaders
  65. *
  66. * @param bool $outputProgress
  67. * @return DownloadManager
  68. */
  69. public function setOutputProgress($outputProgress)
  70. {
  71. foreach ($this->downloaders as $downloader) {
  72. $downloader->setOutputProgress($outputProgress);
  73. }
  74. return $this;
  75. }
  76. /**
  77. * Sets installer downloader for a specific installation type.
  78. *
  79. * @param string $type installation type
  80. * @param DownloaderInterface $downloader downloader instance
  81. * @return DownloadManager
  82. */
  83. public function setDownloader($type, DownloaderInterface $downloader)
  84. {
  85. $type = strtolower($type);
  86. $this->downloaders[$type] = $downloader;
  87. return $this;
  88. }
  89. /**
  90. * Returns downloader for a specific installation type.
  91. *
  92. * @param string $type installation type
  93. * @return DownloaderInterface
  94. *
  95. * @throws \InvalidArgumentException if downloader for provided type is not registered
  96. */
  97. public function getDownloader($type)
  98. {
  99. $type = strtolower($type);
  100. if (!isset($this->downloaders[$type])) {
  101. throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
  102. }
  103. return $this->downloaders[$type];
  104. }
  105. /**
  106. * Returns downloader for already installed package.
  107. *
  108. * @param PackageInterface $package package instance
  109. * @return DownloaderInterface|null
  110. *
  111. * @throws \InvalidArgumentException if package has no installation source specified
  112. * @throws \LogicException if specific downloader used to load package with
  113. * wrong type
  114. */
  115. public function getDownloaderForInstalledPackage(PackageInterface $package)
  116. {
  117. $installationSource = $package->getInstallationSource();
  118. if ('metapackage' === $package->getType()) {
  119. return;
  120. }
  121. if ('dist' === $installationSource) {
  122. $downloader = $this->getDownloader($package->getDistType());
  123. } elseif ('source' === $installationSource) {
  124. $downloader = $this->getDownloader($package->getSourceType());
  125. } else {
  126. throw new \InvalidArgumentException(
  127. 'Package '.$package.' seems not been installed properly'
  128. );
  129. }
  130. if ($installationSource !== $downloader->getInstallationSource()) {
  131. throw new \LogicException(sprintf(
  132. 'Downloader "%s" is a %s type downloader and can not be used to download %s',
  133. get_class($downloader), $downloader->getInstallationSource(), $installationSource
  134. ));
  135. }
  136. return $downloader;
  137. }
  138. /**
  139. * Downloads package into target dir.
  140. *
  141. * @param PackageInterface $package package instance
  142. * @param string $targetDir target dir
  143. * @param bool $preferSource prefer installation from source
  144. *
  145. * @throws \InvalidArgumentException if package have no urls to download from
  146. */
  147. public function download(PackageInterface $package, $targetDir, $preferSource = null)
  148. {
  149. $preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
  150. $sourceType = $package->getSourceType();
  151. $distType = $package->getDistType();
  152. $sources = array();
  153. if ($sourceType) {
  154. $sources[] = 'source';
  155. }
  156. if ($distType) {
  157. $sources[] = 'dist';
  158. }
  159. if (empty($sources)) {
  160. throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
  161. }
  162. if ((!$package->isDev() || $this->preferDist) && !$preferSource) {
  163. $sources = array_reverse($sources);
  164. }
  165. $this->filesystem->ensureDirectoryExists($targetDir);
  166. foreach ($sources as $i => $source) {
  167. if (isset($e)) {
  168. $this->io->write('<warning>Now trying to download from ' . $source . '</warning>');
  169. }
  170. $package->setInstallationSource($source);
  171. try {
  172. $downloader = $this->getDownloaderForInstalledPackage($package);
  173. if ($downloader) {
  174. $downloader->download($package, $targetDir);
  175. }
  176. break;
  177. } catch (\RuntimeException $e) {
  178. if ($i == count($sources) - 1) {
  179. throw $e;
  180. }
  181. $this->io->write(
  182. '<warning>Failed to download '.
  183. $package->getPrettyName().
  184. ' from ' . $source . ': '.
  185. $e->getMessage().'</warning>'
  186. );
  187. }
  188. }
  189. }
  190. /**
  191. * Updates package from initial to target version.
  192. *
  193. * @param PackageInterface $initial initial package version
  194. * @param PackageInterface $target target package version
  195. * @param string $targetDir target dir
  196. *
  197. * @throws \InvalidArgumentException if initial package is not installed
  198. */
  199. public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
  200. {
  201. $downloader = $this->getDownloaderForInstalledPackage($initial);
  202. if (!$downloader) {
  203. return;
  204. }
  205. $installationSource = $initial->getInstallationSource();
  206. if ('dist' === $installationSource) {
  207. $initialType = $initial->getDistType();
  208. $targetType = $target->getDistType();
  209. } else {
  210. $initialType = $initial->getSourceType();
  211. $targetType = $target->getSourceType();
  212. }
  213. // upgrading from a dist stable package to a dev package, force source reinstall
  214. if ($target->isDev() && 'dist' === $installationSource) {
  215. $downloader->remove($initial, $targetDir);
  216. $this->download($target, $targetDir);
  217. return;
  218. }
  219. if ($initialType === $targetType) {
  220. $target->setInstallationSource($installationSource);
  221. $downloader->update($initial, $target, $targetDir);
  222. } else {
  223. $downloader->remove($initial, $targetDir);
  224. $this->download($target, $targetDir, 'source' === $installationSource);
  225. }
  226. }
  227. /**
  228. * Removes package from target dir.
  229. *
  230. * @param PackageInterface $package package instance
  231. * @param string $targetDir target dir
  232. */
  233. public function remove(PackageInterface $package, $targetDir)
  234. {
  235. $downloader = $this->getDownloaderForInstalledPackage($package);
  236. if ($downloader) {
  237. $downloader->remove($package, $targetDir);
  238. }
  239. }
  240. }