DownloadManager.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. * @throws \InvalidArgumentException if downloader for provided type is not registered
  94. * @return DownloaderInterface
  95. */
  96. public function getDownloader($type)
  97. {
  98. $type = strtolower($type);
  99. if (!isset($this->downloaders[$type])) {
  100. throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
  101. }
  102. return $this->downloaders[$type];
  103. }
  104. /**
  105. * Returns downloader for already installed package.
  106. *
  107. * @param PackageInterface $package package instance
  108. * @throws \InvalidArgumentException if package has no installation source specified
  109. * @throws \LogicException if specific downloader used to load package with
  110. * wrong type
  111. * @return DownloaderInterface|null
  112. */
  113. public function getDownloaderForInstalledPackage(PackageInterface $package)
  114. {
  115. $installationSource = $package->getInstallationSource();
  116. if ('metapackage' === $package->getType()) {
  117. return;
  118. }
  119. if ('dist' === $installationSource) {
  120. $downloader = $this->getDownloader($package->getDistType());
  121. } elseif ('source' === $installationSource) {
  122. $downloader = $this->getDownloader($package->getSourceType());
  123. } else {
  124. throw new \InvalidArgumentException(
  125. 'Package '.$package.' seems not been installed properly'
  126. );
  127. }
  128. if ($installationSource !== $downloader->getInstallationSource()) {
  129. throw new \LogicException(sprintf(
  130. 'Downloader "%s" is a %s type downloader and can not be used to download %s',
  131. get_class($downloader), $downloader->getInstallationSource(), $installationSource
  132. ));
  133. }
  134. return $downloader;
  135. }
  136. /**
  137. * Downloads package into target dir.
  138. *
  139. * @param PackageInterface $package package instance
  140. * @param string $targetDir target dir
  141. * @param bool $preferSource prefer installation from source
  142. *
  143. * @throws \InvalidArgumentException if package have no urls to download from
  144. * @throws \RuntimeException
  145. */
  146. public function download(PackageInterface $package, $targetDir, $preferSource = null)
  147. {
  148. $preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
  149. $sourceType = $package->getSourceType();
  150. $distType = $package->getDistType();
  151. $sources = array();
  152. if ($sourceType) {
  153. $sources[] = 'source';
  154. }
  155. if ($distType) {
  156. $sources[] = 'dist';
  157. }
  158. if (empty($sources)) {
  159. throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
  160. }
  161. if ((!$package->isDev() || $this->preferDist) && !$preferSource) {
  162. $sources = array_reverse($sources);
  163. }
  164. $this->filesystem->ensureDirectoryExists($targetDir);
  165. foreach ($sources as $i => $source) {
  166. if (isset($e)) {
  167. $this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
  168. }
  169. $package->setInstallationSource($source);
  170. try {
  171. $downloader = $this->getDownloaderForInstalledPackage($package);
  172. if ($downloader) {
  173. $downloader->download($package, $targetDir);
  174. }
  175. break;
  176. } catch (\RuntimeException $e) {
  177. if ($i === count($sources) - 1) {
  178. throw $e;
  179. }
  180. $this->io->writeError(
  181. ' <warning>Failed to download '.
  182. $package->getPrettyName().
  183. ' from ' . $source . ': '.
  184. $e->getMessage().'</warning>'
  185. );
  186. }
  187. }
  188. }
  189. /**
  190. * Updates package from initial to target version.
  191. *
  192. * @param PackageInterface $initial initial package version
  193. * @param PackageInterface $target target package version
  194. * @param string $targetDir target dir
  195. *
  196. * @throws \InvalidArgumentException if initial package is not installed
  197. */
  198. public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
  199. {
  200. $downloader = $this->getDownloaderForInstalledPackage($initial);
  201. if (!$downloader) {
  202. return;
  203. }
  204. $installationSource = $initial->getInstallationSource();
  205. if ('dist' === $installationSource) {
  206. $initialType = $initial->getDistType();
  207. $targetType = $target->getDistType();
  208. } else {
  209. $initialType = $initial->getSourceType();
  210. $targetType = $target->getSourceType();
  211. }
  212. // upgrading from a dist stable package to a dev package, force source reinstall
  213. if ($target->isDev() && 'dist' === $installationSource) {
  214. $downloader->remove($initial, $targetDir);
  215. $this->download($target, $targetDir);
  216. return;
  217. }
  218. if ($initialType === $targetType) {
  219. $target->setInstallationSource($installationSource);
  220. try {
  221. $downloader->update($initial, $target, $targetDir);
  222. return;
  223. } catch (\RuntimeException $ex) {
  224. if (!$this->io->isInteractive() ||
  225. !$this->io->askConfirmation(' Updating failed. Would you like to try reinstalling instead [<comment>yes</comment>]? ', true)) {
  226. throw $ex;
  227. }
  228. }
  229. }
  230. $downloader->remove($initial, $targetDir);
  231. $this->download($target, $targetDir, 'source' === $installationSource);
  232. }
  233. /**
  234. * Removes package from target dir.
  235. *
  236. * @param PackageInterface $package package instance
  237. * @param string $targetDir target dir
  238. */
  239. public function remove(PackageInterface $package, $targetDir)
  240. {
  241. $downloader = $this->getDownloaderForInstalledPackage($package);
  242. if ($downloader) {
  243. $downloader->remove($package, $targetDir);
  244. }
  245. }
  246. }