DownloadManager.php 8.5 KB

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