PageRenderTime 24ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Composer/Downloader/DownloadManager.php

https://gitlab.com/tigefa/composer
PHP | 322 lines | 179 code | 41 blank | 102 comment | 27 complexity | 7cf3d2447e5f0bac374193214d1a23de MD5 | raw file
  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 $packagePreferences = array();
  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 fine tuned preference settings for package level source/dist selection.
  65. *
  66. * @param array $preferences array of preferences by package patterns
  67. * @return DownloadManager
  68. */
  69. public function setPreferences(array $preferences)
  70. {
  71. $this->packagePreferences = $preferences;
  72. return $this;
  73. }
  74. /**
  75. * Sets whether to output download progress information for all registered
  76. * downloaders
  77. *
  78. * @param bool $outputProgress
  79. * @return DownloadManager
  80. */
  81. public function setOutputProgress($outputProgress)
  82. {
  83. foreach ($this->downloaders as $downloader) {
  84. $downloader->setOutputProgress($outputProgress);
  85. }
  86. return $this;
  87. }
  88. /**
  89. * Sets installer downloader for a specific installation type.
  90. *
  91. * @param string $type installation type
  92. * @param DownloaderInterface $downloader downloader instance
  93. * @return DownloadManager
  94. */
  95. public function setDownloader($type, DownloaderInterface $downloader)
  96. {
  97. $type = strtolower($type);
  98. $this->downloaders[$type] = $downloader;
  99. return $this;
  100. }
  101. /**
  102. * Returns downloader for a specific installation type.
  103. *
  104. * @param string $type installation type
  105. * @throws \InvalidArgumentException if downloader for provided type is not registered
  106. * @return DownloaderInterface
  107. */
  108. public function getDownloader($type)
  109. {
  110. $type = strtolower($type);
  111. if (!isset($this->downloaders[$type])) {
  112. throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
  113. }
  114. return $this->downloaders[$type];
  115. }
  116. /**
  117. * Returns downloader for already installed package.
  118. *
  119. * @param PackageInterface $package package instance
  120. * @throws \InvalidArgumentException if package has no installation source specified
  121. * @throws \LogicException if specific downloader used to load package with
  122. * wrong type
  123. * @return DownloaderInterface|null
  124. */
  125. public function getDownloaderForInstalledPackage(PackageInterface $package)
  126. {
  127. $installationSource = $package->getInstallationSource();
  128. if ('metapackage' === $package->getType()) {
  129. return;
  130. }
  131. if ('dist' === $installationSource) {
  132. $downloader = $this->getDownloader($package->getDistType());
  133. } elseif ('source' === $installationSource) {
  134. $downloader = $this->getDownloader($package->getSourceType());
  135. } else {
  136. throw new \InvalidArgumentException(
  137. 'Package '.$package.' seems not been installed properly'
  138. );
  139. }
  140. if ($installationSource !== $downloader->getInstallationSource()) {
  141. throw new \LogicException(sprintf(
  142. 'Downloader "%s" is a %s type downloader and can not be used to download %s',
  143. get_class($downloader), $downloader->getInstallationSource(), $installationSource
  144. ));
  145. }
  146. return $downloader;
  147. }
  148. /**
  149. * Downloads package into target dir.
  150. *
  151. * @param PackageInterface $package package instance
  152. * @param string $targetDir target dir
  153. * @param bool $preferSource prefer installation from source
  154. *
  155. * @throws \InvalidArgumentException if package have no urls to download from
  156. * @throws \RuntimeException
  157. */
  158. public function download(PackageInterface $package, $targetDir, $preferSource = null)
  159. {
  160. $preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
  161. $sourceType = $package->getSourceType();
  162. $distType = $package->getDistType();
  163. $sources = array();
  164. if ($sourceType) {
  165. $sources[] = 'source';
  166. }
  167. if ($distType) {
  168. $sources[] = 'dist';
  169. }
  170. if (empty($sources)) {
  171. throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
  172. }
  173. if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
  174. $sources = array_reverse($sources);
  175. }
  176. $this->filesystem->ensureDirectoryExists($targetDir);
  177. foreach ($sources as $i => $source) {
  178. if (isset($e)) {
  179. $this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
  180. }
  181. $package->setInstallationSource($source);
  182. try {
  183. $downloader = $this->getDownloaderForInstalledPackage($package);
  184. if ($downloader) {
  185. $downloader->download($package, $targetDir);
  186. }
  187. break;
  188. } catch (\RuntimeException $e) {
  189. if ($i === count($sources) - 1) {
  190. throw $e;
  191. }
  192. $this->io->writeError(
  193. ' <warning>Failed to download '.
  194. $package->getPrettyName().
  195. ' from ' . $source . ': '.
  196. $e->getMessage().'</warning>'
  197. );
  198. }
  199. }
  200. }
  201. /**
  202. * Updates package from initial to target version.
  203. *
  204. * @param PackageInterface $initial initial package version
  205. * @param PackageInterface $target target package version
  206. * @param string $targetDir target dir
  207. *
  208. * @throws \InvalidArgumentException if initial package is not installed
  209. */
  210. public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
  211. {
  212. $downloader = $this->getDownloaderForInstalledPackage($initial);
  213. if (!$downloader) {
  214. return;
  215. }
  216. $installationSource = $initial->getInstallationSource();
  217. if ('dist' === $installationSource) {
  218. $initialType = $initial->getDistType();
  219. $targetType = $target->getDistType();
  220. } else {
  221. $initialType = $initial->getSourceType();
  222. $targetType = $target->getSourceType();
  223. }
  224. // upgrading from a dist stable package to a dev package, force source reinstall
  225. if ($target->isDev() && 'dist' === $installationSource) {
  226. $downloader->remove($initial, $targetDir);
  227. $this->download($target, $targetDir);
  228. return;
  229. }
  230. if ($initialType === $targetType) {
  231. $target->setInstallationSource($installationSource);
  232. try {
  233. $downloader->update($initial, $target, $targetDir);
  234. return;
  235. } catch (\RuntimeException $e) {
  236. if (!$this->io->isInteractive()) {
  237. throw $e;
  238. }
  239. $this->io->writeError('<error> Update failed ('.$e->getMessage().')</error>');
  240. if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [<comment>yes</comment>]? ', true)) {
  241. throw $e;
  242. }
  243. }
  244. }
  245. $downloader->remove($initial, $targetDir);
  246. $this->download($target, $targetDir, 'source' === $installationSource);
  247. }
  248. /**
  249. * Removes package from target dir.
  250. *
  251. * @param PackageInterface $package package instance
  252. * @param string $targetDir target dir
  253. */
  254. public function remove(PackageInterface $package, $targetDir)
  255. {
  256. $downloader = $this->getDownloaderForInstalledPackage($package);
  257. if ($downloader) {
  258. $downloader->remove($package, $targetDir);
  259. }
  260. }
  261. /**
  262. * Determines the install preference of a package
  263. *
  264. * @param PackageInterface $package package instance
  265. *
  266. * @return string
  267. */
  268. protected function resolvePackageInstallPreference(PackageInterface $package)
  269. {
  270. foreach ($this->packagePreferences as $pattern => $preference) {
  271. $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i';
  272. if (preg_match($pattern, $package->getName())) {
  273. if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) {
  274. return 'dist';
  275. }
  276. return 'source';
  277. }
  278. }
  279. return $package->isDev() ? 'source' : 'dist';
  280. }
  281. }