Downloader.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available through the world-wide-web at the following url: |
  11. // | http://www.php.net/license/3_0.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | [email protected] so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <[email protected]> |
  17. // | Tomas V.V.Cox <[email protected]> |
  18. // | Martin Jansen <[email protected]> |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Downloader.php,v 1.17.2.1 2004/10/22 22:54:03 cellog Exp $
  22. require_once 'PEAR/Common.php';
  23. require_once 'PEAR/Registry.php';
  24. require_once 'PEAR/Dependency.php';
  25. require_once 'PEAR/Remote.php';
  26. require_once 'System.php';
  27. define('PEAR_INSTALLER_OK', 1);
  28. define('PEAR_INSTALLER_FAILED', 0);
  29. define('PEAR_INSTALLER_SKIPPED', -1);
  30. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  31. /**
  32. * Administration class used to download PEAR packages and maintain the
  33. * installed package database.
  34. *
  35. * @since PEAR 1.4
  36. * @author Greg Beaver <[email protected]>
  37. */
  38. class PEAR_Downloader extends PEAR_Common
  39. {
  40. /**
  41. * @var PEAR_Config
  42. * @access private
  43. */
  44. var $_config;
  45. /**
  46. * @var PEAR_Registry
  47. * @access private
  48. */
  49. var $_registry;
  50. /**
  51. * @var PEAR_Remote
  52. * @access private
  53. */
  54. var $_remote;
  55. /**
  56. * Preferred Installation State (snapshot, devel, alpha, beta, stable)
  57. * @var string|null
  58. * @access private
  59. */
  60. var $_preferredState;
  61. /**
  62. * Options from command-line passed to Install.
  63. *
  64. * Recognized options:<br />
  65. * - onlyreqdeps : install all required dependencies as well
  66. * - alldeps : install all dependencies, including optional
  67. * - installroot : base relative path to install files in
  68. * - force : force a download even if warnings would prevent it
  69. * @see PEAR_Command_Install
  70. * @access private
  71. * @var array
  72. */
  73. var $_options;
  74. /**
  75. * Downloaded Packages after a call to download().
  76. *
  77. * Format of each entry:
  78. *
  79. * <code>
  80. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  81. * 'info' => array() // parsed package.xml
  82. * );
  83. * </code>
  84. * @access private
  85. * @var array
  86. */
  87. var $_downloadedPackages = array();
  88. /**
  89. * Packages slated for download.
  90. *
  91. * This is used to prevent downloading a package more than once should it be a dependency
  92. * for two packages to be installed.
  93. * Format of each entry:
  94. *
  95. * <pre>
  96. * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
  97. * );
  98. * </pre>
  99. * @access private
  100. * @var array
  101. */
  102. var $_toDownload = array();
  103. /**
  104. * Array of every package installed, with names lower-cased.
  105. *
  106. * Format:
  107. * <code>
  108. * array('package1' => 0, 'package2' => 1, );
  109. * </code>
  110. * @var array
  111. */
  112. var $_installed = array();
  113. /**
  114. * @var array
  115. * @access private
  116. */
  117. var $_errorStack = array();
  118. // {{{ PEAR_Downloader()
  119. function PEAR_Downloader(&$ui, $options, &$config)
  120. {
  121. $this->_options = $options;
  122. $this->_config = &$config;
  123. $this->_preferredState = $this->_config->get('preferred_state');
  124. $this->ui = &$ui;
  125. if (!$this->_preferredState) {
  126. // don't inadvertantly use a non-set preferred_state
  127. $this->_preferredState = null;
  128. }
  129. $php_dir = $this->_config->get('php_dir');
  130. if (isset($this->_options['installroot'])) {
  131. if (substr($this->_options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  132. $this->_options['installroot'] = substr($this->_options['installroot'], 0, -1);
  133. }
  134. $php_dir = $this->_prependPath($php_dir, $this->_options['installroot']);
  135. }
  136. $this->_registry = &new PEAR_Registry($php_dir);
  137. $this->_remote = &new PEAR_Remote($config);
  138. if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  139. $this->_installed = $this->_registry->listPackages();
  140. array_walk($this->_installed, create_function('&$v,$k','$v = strtolower($v);'));
  141. $this->_installed = array_flip($this->_installed);
  142. }
  143. parent::PEAR_Common();
  144. }
  145. // }}}
  146. // {{{ configSet()
  147. function configSet($key, $value, $layer = 'user')
  148. {
  149. $this->_config->set($key, $value, $layer);
  150. $this->_preferredState = $this->_config->get('preferred_state');
  151. if (!$this->_preferredState) {
  152. // don't inadvertantly use a non-set preferred_state
  153. $this->_preferredState = null;
  154. }
  155. }
  156. // }}}
  157. // {{{ setOptions()
  158. function setOptions($options)
  159. {
  160. $this->_options = $options;
  161. }
  162. // }}}
  163. // {{{ _downloadFile()
  164. /**
  165. * @param string filename to download
  166. * @param string version/state
  167. * @param string original value passed to command-line
  168. * @param string|null preferred state (snapshot/devel/alpha/beta/stable)
  169. * Defaults to configuration preferred state
  170. * @return null|PEAR_Error|string
  171. * @access private
  172. */
  173. function _downloadFile($pkgfile, $version, $origpkgfile, $state = null)
  174. {
  175. if (is_null($state)) {
  176. $state = $this->_preferredState;
  177. }
  178. // {{{ check the package filename, and whether it's already installed
  179. $need_download = false;
  180. if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  181. $need_download = true;
  182. } elseif (!@is_file($pkgfile)) {
  183. if ($this->validPackageName($pkgfile)) {
  184. if ($this->_registry->packageExists($pkgfile)) {
  185. if (empty($this->_options['upgrade']) && empty($this->_options['force'])) {
  186. $errors[] = "$pkgfile already installed";
  187. return;
  188. }
  189. }
  190. $pkgfile = $this->getPackageDownloadUrl($pkgfile, $version);
  191. $need_download = true;
  192. } else {
  193. if (strlen($pkgfile)) {
  194. $errors[] = "Could not open the package file: $pkgfile";
  195. } else {
  196. $errors[] = "No package file given";
  197. }
  198. return;
  199. }
  200. }
  201. // }}}
  202. // {{{ Download package -----------------------------------------------
  203. if ($need_download) {
  204. $downloaddir = $this->_config->get('download_dir');
  205. if (empty($downloaddir)) {
  206. if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
  207. return $downloaddir;
  208. }
  209. $this->log(3, '+ tmp dir created at ' . $downloaddir);
  210. }
  211. $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
  212. $this->pushErrorHandling(PEAR_ERROR_RETURN);
  213. $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
  214. $this->popErrorHandling();
  215. if (PEAR::isError($file)) {
  216. if ($this->validPackageName($origpkgfile)) {
  217. if (!PEAR::isError($info = $this->_remote->call('package.info',
  218. $origpkgfile))) {
  219. if (!count($info['releases'])) {
  220. return $this->raiseError('Package ' . $origpkgfile .
  221. ' has no releases');
  222. } else {
  223. return $this->raiseError('No releases of preferred state "'
  224. . $state . '" exist for package ' . $origpkgfile .
  225. '. Use ' . $origpkgfile . '-state to install another' .
  226. ' state (like ' . $origpkgfile .'-beta)',
  227. PEAR_INSTALLER_ERROR_NO_PREF_STATE);
  228. }
  229. } else {
  230. return $pkgfile;
  231. }
  232. } else {
  233. return $this->raiseError($file);
  234. }
  235. }
  236. $pkgfile = $file;
  237. }
  238. // }}}
  239. return $pkgfile;
  240. }
  241. // }}}
  242. // {{{ getPackageDownloadUrl()
  243. function getPackageDownloadUrl($package, $version = null)
  244. {
  245. if ($version) {
  246. $package .= "-$version";
  247. }
  248. if ($this === null || $this->_config === null) {
  249. $package = "http://pear.php.net/get/$package";
  250. } else {
  251. $package = "http://" . $this->_config->get('master_server') .
  252. "/get/$package";
  253. }
  254. if (!extension_loaded("zlib")) {
  255. $package .= '?uncompress=yes';
  256. }
  257. return $package;
  258. }
  259. // }}}
  260. // {{{ extractDownloadFileName($pkgfile, &$version)
  261. function extractDownloadFileName($pkgfile, &$version)
  262. {
  263. if (@is_file($pkgfile)) {
  264. return $pkgfile;
  265. }
  266. // regex defined in Common.php
  267. if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) {
  268. $version = (isset($m[3])) ? $m[3] : null;
  269. return $m[1];
  270. }
  271. $version = null;
  272. return $pkgfile;
  273. }
  274. // }}}
  275. // }}}
  276. // {{{ getDownloadedPackages()
  277. /**
  278. * Retrieve a list of downloaded packages after a call to {@link download()}.
  279. *
  280. * Also resets the list of downloaded packages.
  281. * @return array
  282. */
  283. function getDownloadedPackages()
  284. {
  285. $ret = $this->_downloadedPackages;
  286. $this->_downloadedPackages = array();
  287. $this->_toDownload = array();
  288. return $ret;
  289. }
  290. // }}}
  291. // {{{ download()
  292. /**
  293. * Download any files and their dependencies, if necessary
  294. *
  295. * BC-compatible method name
  296. * @param array a mixed list of package names, local files, or package.xml
  297. */
  298. function download($packages)
  299. {
  300. return $this->doDownload($packages);
  301. }
  302. // }}}
  303. // {{{ doDownload()
  304. /**
  305. * Download any files and their dependencies, if necessary
  306. *
  307. * @param array a mixed list of package names, local files, or package.xml
  308. */
  309. function doDownload($packages)
  310. {
  311. $mywillinstall = array();
  312. $state = $this->_preferredState;
  313. // {{{ download files in this list if necessary
  314. foreach($packages as $pkgfile) {
  315. $need_download = false;
  316. if (!is_file($pkgfile)) {
  317. if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  318. $need_download = true;
  319. }
  320. $pkgfile = $this->_downloadNonFile($pkgfile);
  321. if (PEAR::isError($pkgfile)) {
  322. return $pkgfile;
  323. }
  324. if ($pkgfile === false) {
  325. continue;
  326. }
  327. } // end is_file()
  328. $tempinfo = $this->infoFromAny($pkgfile);
  329. if ($need_download) {
  330. $this->_toDownload[] = $tempinfo['package'];
  331. }
  332. if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  333. // ignore dependencies if there are any errors
  334. if (!PEAR::isError($tempinfo)) {
  335. $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps'];
  336. }
  337. }
  338. $this->_downloadedPackages[] = array('pkg' => $tempinfo['package'],
  339. 'file' => $pkgfile, 'info' => $tempinfo);
  340. } // end foreach($packages)
  341. // }}}
  342. // {{{ extract dependencies from downloaded files and then download
  343. // them if necessary
  344. if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  345. $deppackages = array();
  346. foreach ($mywillinstall as $package => $alldeps) {
  347. if (!is_array($alldeps)) {
  348. // there are no dependencies
  349. continue;
  350. }
  351. $fail = false;
  352. foreach ($alldeps as $info) {
  353. if ($info['type'] != 'pkg') {
  354. continue;
  355. }
  356. $ret = $this->_processDependency($package, $info, $mywillinstall);
  357. if ($ret === false) {
  358. continue;
  359. }
  360. if ($ret === 0) {
  361. $fail = true;
  362. continue;
  363. }
  364. if (PEAR::isError($ret)) {
  365. return $ret;
  366. }
  367. $deppackages[] = $ret;
  368. } // foreach($alldeps
  369. if ($fail) {
  370. $deppackages = array();
  371. }
  372. }
  373. if (count($deppackages)) {
  374. $this->doDownload($deppackages);
  375. }
  376. } // }}} if --alldeps or --onlyreqdeps
  377. }
  378. // }}}
  379. // {{{ _downloadNonFile($pkgfile)
  380. /**
  381. * @return false|PEAR_Error|string false if loop should be broken out of,
  382. * string if the file was downloaded,
  383. * PEAR_Error on exception
  384. * @access private
  385. */
  386. function _downloadNonFile($pkgfile)
  387. {
  388. $origpkgfile = $pkgfile;
  389. $state = null;
  390. $pkgfile = $this->extractDownloadFileName($pkgfile, $version);
  391. if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  392. return $this->_downloadFile($pkgfile, $version, $origpkgfile);
  393. }
  394. if (!$this->validPackageName($pkgfile)) {
  395. return $this->raiseError("Package name '$pkgfile' not valid");
  396. }
  397. // ignore packages that are installed unless we are upgrading
  398. $curinfo = $this->_registry->packageInfo($pkgfile);
  399. if ($this->_registry->packageExists($pkgfile)
  400. && empty($this->_options['upgrade']) && empty($this->_options['force'])) {
  401. $this->log(0, "Package '{$curinfo['package']}' already installed, skipping");
  402. return false;
  403. }
  404. if (in_array($pkgfile, $this->_toDownload)) {
  405. return false;
  406. }
  407. $releases = $this->_remote->call('package.info', $pkgfile, 'releases', true);
  408. if (!count($releases)) {
  409. return $this->raiseError("No releases found for package '$pkgfile'");
  410. }
  411. // Want a specific version/state
  412. if ($version !== null) {
  413. // Passed Foo-1.2
  414. if ($this->validPackageVersion($version)) {
  415. if (!isset($releases[$version])) {
  416. return $this->raiseError("No release with version '$version' found for '$pkgfile'");
  417. }
  418. // Passed Foo-alpha
  419. } elseif (in_array($version, $this->getReleaseStates())) {
  420. $state = $version;
  421. $version = 0;
  422. foreach ($releases as $ver => $inf) {
  423. if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) {
  424. $version = $ver;
  425. break;
  426. }
  427. }
  428. if ($version == 0) {
  429. return $this->raiseError("No release with state '$state' found for '$pkgfile'");
  430. }
  431. // invalid suffix passed
  432. } else {
  433. return $this->raiseError("Invalid suffix '-$version', be sure to pass a valid PEAR ".
  434. "version number or release state");
  435. }
  436. // Guess what to download
  437. } else {
  438. $states = $this->betterStates($this->_preferredState, true);
  439. $possible = false;
  440. $version = 0;
  441. $higher_version = 0;
  442. $prev_hi_ver = 0;
  443. foreach ($releases as $ver => $inf) {
  444. if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) {
  445. $version = $ver;
  446. break;
  447. } else {
  448. $ver = (string)$ver;
  449. if (version_compare($prev_hi_ver, $ver) < 0) {
  450. $prev_hi_ver = $higher_version = $ver;
  451. }
  452. }
  453. }
  454. if ($version === 0 && !isset($this->_options['force'])) {
  455. return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) .
  456. "' found for '$pkgfile'");
  457. } elseif ($version === 0) {
  458. $this->log(0, "Warning: $pkgfile is state '" . $releases[$higher_version]['state'] . "' which is less stable " .
  459. "than state '$this->_preferredState'");
  460. }
  461. }
  462. // Check if we haven't already the version
  463. if (empty($this->_options['force']) && !is_null($curinfo)) {
  464. if ($curinfo['version'] == $version) {
  465. $this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping");
  466. return false;
  467. } elseif (version_compare("$version", "{$curinfo['version']}") < 0) {
  468. $this->log(0, "Package '{$curinfo['package']}' version '{$curinfo['version']}' " .
  469. " is installed and {$curinfo['version']} is > requested '$version', skipping");
  470. return false;
  471. }
  472. }
  473. $this->_toDownload[] = $pkgfile;
  474. return $this->_downloadFile($pkgfile, $version, $origpkgfile, $state);
  475. }
  476. // }}}
  477. // {{{ _processDependency($package, $info, $mywillinstall)
  478. /**
  479. * Process a dependency, download if necessary
  480. * @param array dependency information from PEAR_Remote call
  481. * @param array packages that will be installed in this iteration
  482. * @return false|string|PEAR_Error
  483. * @access private
  484. * @todo Add test for relation 'lt'/'le' -> make sure that the dependency requested is
  485. * in fact lower than the required value. This will be very important for BC dependencies
  486. */
  487. function _processDependency($package, $info, $mywillinstall)
  488. {
  489. $state = $this->_preferredState;
  490. if (!isset($this->_options['alldeps']) && isset($info['optional']) &&
  491. $info['optional'] == 'yes') {
  492. // skip optional deps
  493. $this->log(0, "skipping Package '$package' optional dependency '$info[name]'");
  494. return false;
  495. }
  496. // {{{ get releases
  497. $releases = $this->_remote->call('package.info', $info['name'], 'releases', true);
  498. if (PEAR::isError($releases)) {
  499. return $releases;
  500. }
  501. if (!count($releases)) {
  502. if (!isset($this->_installed[strtolower($info['name'])])) {
  503. $this->pushError("Package '$package' dependency '$info[name]' ".
  504. "has no releases");
  505. }
  506. return false;
  507. }
  508. $found = false;
  509. $save = $releases;
  510. while(count($releases) && !$found) {
  511. if (!empty($state) && $state != 'any') {
  512. list($release_version, $release) = each($releases);
  513. if ($state != $release['state'] &&
  514. !in_array($release['state'], $this->betterStates($state)))
  515. {
  516. // drop this release - it ain't stable enough
  517. array_shift($releases);
  518. } else {
  519. $found = true;
  520. }
  521. } else {
  522. $found = true;
  523. }
  524. }
  525. if (!count($releases) && !$found) {
  526. $get = array();
  527. foreach($save as $release) {
  528. $get = array_merge($get,
  529. $this->betterStates($release['state'], true));
  530. }
  531. $savestate = array_shift($get);
  532. $this->pushError( "Release for '$package' dependency '$info[name]' " .
  533. "has state '$savestate', requires '$state'");
  534. return 0;
  535. }
  536. if (in_array(strtolower($info['name']), $this->_toDownload) ||
  537. isset($mywillinstall[strtolower($info['name'])])) {
  538. // skip upgrade check for packages we will install
  539. return false;
  540. }
  541. if (!isset($this->_installed[strtolower($info['name'])])) {
  542. // check to see if we can install the specific version required
  543. if ($info['rel'] == 'eq') {
  544. return $info['name'] . '-' . $info['version'];
  545. }
  546. // skip upgrade check for packages we don't have installed
  547. return $info['name'];
  548. }
  549. // }}}
  550. // {{{ see if a dependency must be upgraded
  551. $inst_version = $this->_registry->packageInfo($info['name'], 'version');
  552. if (!isset($info['version'])) {
  553. // this is a rel='has' dependency, check against latest
  554. if (version_compare($release_version, $inst_version, 'le')) {
  555. return false;
  556. } else {
  557. return $info['name'];
  558. }
  559. }
  560. if (version_compare($info['version'], $inst_version, 'le')) {
  561. // installed version is up-to-date
  562. return false;
  563. }
  564. return $info['name'];
  565. }
  566. // }}}
  567. // {{{ _downloadCallback()
  568. function _downloadCallback($msg, $params = null)
  569. {
  570. switch ($msg) {
  571. case 'saveas':
  572. $this->log(1, "downloading $params ...");
  573. break;
  574. case 'done':
  575. $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  576. break;
  577. case 'bytesread':
  578. static $bytes;
  579. if (empty($bytes)) {
  580. $bytes = 0;
  581. }
  582. if (!($bytes % 10240)) {
  583. $this->log(1, '.', false);
  584. }
  585. $bytes += $params;
  586. break;
  587. case 'start':
  588. $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
  589. break;
  590. }
  591. if (method_exists($this->ui, '_downloadCallback'))
  592. $this->ui->_downloadCallback($msg, $params);
  593. }
  594. // }}}
  595. // {{{ _prependPath($path, $prepend)
  596. function _prependPath($path, $prepend)
  597. {
  598. if (strlen($prepend) > 0) {
  599. if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  600. $path = $prepend . substr($path, 2);
  601. } else {
  602. $path = $prepend . $path;
  603. }
  604. }
  605. return $path;
  606. }
  607. // }}}
  608. // {{{ pushError($errmsg, $code)
  609. /**
  610. * @param string
  611. * @param integer
  612. */
  613. function pushError($errmsg, $code = -1)
  614. {
  615. array_push($this->_errorStack, array($errmsg, $code));
  616. }
  617. // }}}
  618. // {{{ getErrorMsgs()
  619. function getErrorMsgs()
  620. {
  621. $msgs = array();
  622. $errs = $this->_errorStack;
  623. foreach ($errs as $err) {
  624. $msgs[] = $err[0];
  625. }
  626. $this->_errorStack = array();
  627. return $msgs;
  628. }
  629. // }}}
  630. }
  631. // }}}
  632. ?>