Downloader.php 23 KB

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