3 * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download,
4 * clear-cache commands)
10 * @author Stig Bakken <ssb@php.net>
11 * @author Greg Beaver <cellog@php.net>
12 * @copyright 1997-2009 The Authors
13 * @license http://opensource.org/licenses/bsd-license.php New BSD License
14 * @version CVS: $Id: Remote.php 313023 2011-07-06 19:17:11Z dufuz $
15 * @link http://pear.php.net/package/PEAR
16 * @since File available since Release 0.1
22 require_once 'PEAR/Command/Common.php';
23 require_once 'PEAR/REST.php';
26 * PEAR commands for remote server querying
30 * @author Stig Bakken <ssb@php.net>
31 * @author Greg Beaver <cellog@php.net>
32 * @copyright 1997-2009 The Authors
33 * @license http://opensource.org/licenses/bsd-license.php New BSD License
34 * @version Release: 1.9.4
35 * @link http://pear.php.net/package/PEAR
36 * @since Class available since Release 0.1
38 class PEAR_Command_Remote extends PEAR_Command_Common
40 var $commands = array(
41 'remote-info' => array(
42 'summary' => 'Information About Remote Packages',
43 'function' => 'doRemoteInfo',
47 Get details on a package from the server.',
49 'list-upgrades' => array(
50 'summary' => 'List Available Upgrades',
51 'function' => 'doListUpgrades',
54 'channelinfo' => array(
56 'doc' => 'output fully channel-aware data, even on failure',
59 'doc' => '[preferred_state]
60 List releases on the server of packages you have installed where
61 a newer version is available with the same release state (stable etc.)
62 or the state passed as the second parameter.'
64 'remote-list' => array(
65 'summary' => 'List Remote Packages',
66 'function' => 'doRemoteList',
72 'doc' => 'specify a channel other than the default channel',
77 Lists the packages available on the configured server along with the
78 latest stable release of each package.',
81 'summary' => 'Search remote package database',
82 'function' => 'doSearch',
88 'doc' => 'specify a channel other than the default channel',
91 'allchannels' => array(
93 'doc' => 'search packages from all known channels',
95 'channelinfo' => array(
97 'doc' => 'output fully channel-aware data, even on failure',
100 'doc' => '[packagename] [packageinfo]
101 Lists all packages which match the search parameters. The first
102 parameter is a fragment of a packagename. The default channel
103 will be used unless explicitly overridden. The second parameter
104 will be used to match any portion of the summary/description',
107 'summary' => 'List All Packages',
108 'function' => 'doListAll',
114 'doc' => 'specify a channel other than the default channel',
117 'channelinfo' => array(
119 'doc' => 'output fully channel-aware data, even on failure',
123 Lists the packages available on the configured server along with the
124 latest stable release of each package.',
127 'summary' => 'Download Package',
128 'function' => 'doDownload',
131 'nocompress' => array(
133 'doc' => 'download an uncompressed (.tar) file',
136 'doc' => '<package>...
137 Download package tarballs. The files will be named as suggested by the
138 server, for example if you download the DB package and the latest stable
139 version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.',
141 'clear-cache' => array(
142 'summary' => 'Clear Web Services Cache',
143 'function' => 'doClearCache',
145 'options' => array(),
147 Clear the REST cache. See also the cache_ttl configuration
154 * PEAR_Command_Remote constructor.
158 function PEAR_Command_Remote(&$ui, &$config)
160 parent::PEAR_Command_Common($ui, $config);
163 function _checkChannelForStatus($channel, $chan)
165 if (PEAR::isError($chan)) {
166 $this->raiseError($chan);
168 if (!is_a($chan, 'PEAR_ChannelFile')) {
169 return $this->raiseError('Internal corruption error: invalid channel "' .
172 $rest = new PEAR_REST($this->config);
173 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
174 $mirror = $this->config->get('preferred_mirror', null,
176 $a = $rest->downloadHttp('http://' . $channel .
177 '/channel.xml', $chan->lastModified());
178 PEAR::staticPopErrorHandling();
179 if (!PEAR::isError($a) && $a) {
180 $this->ui->outputData('WARNING: channel "' . $channel . '" has ' .
181 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel .
186 function doRemoteInfo($command, $options, $params)
188 if (sizeof($params) != 1) {
189 return $this->raiseError("$command expects one param: the remote package name");
191 $savechannel = $channel = $this->config->get('default_channel');
192 $reg = &$this->config->getRegistry();
193 $package = $params[0];
194 $parsed = $reg->parsePackageName($package, $channel);
195 if (PEAR::isError($parsed)) {
196 return $this->raiseError('Invalid package name "' . $package . '"');
199 $channel = $parsed['channel'];
200 $this->config->set('default_channel', $channel);
201 $chan = $reg->getChannel($channel);
202 if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
206 $mirror = $this->config->get('preferred_mirror');
207 if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) {
208 $rest = &$this->config->getREST('1.0', array());
209 $info = $rest->packageInfo($base, $parsed['package'], $channel);
213 return $this->raiseError('No supported protocol was found');
216 if (PEAR::isError($info)) {
217 $this->config->set('default_channel', $savechannel);
218 return $this->raiseError($info);
221 if (!isset($info['name'])) {
222 return $this->raiseError('No remote package "' . $package . '" was found');
225 $installed = $reg->packageInfo($info['name'], null, $channel);
226 $info['installed'] = $installed['version'] ? $installed['version'] : '- no -';
227 if (is_array($info['installed'])) {
228 $info['installed'] = $info['installed']['release'];
231 $this->ui->outputData($info, $command);
232 $this->config->set('default_channel', $savechannel);
237 function doRemoteList($command, $options, $params)
239 $savechannel = $channel = $this->config->get('default_channel');
240 $reg = &$this->config->getRegistry();
241 if (isset($options['channel'])) {
242 $channel = $options['channel'];
243 if (!$reg->channelExists($channel)) {
244 return $this->raiseError('Channel "' . $channel . '" does not exist');
247 $this->config->set('default_channel', $channel);
250 $chan = $reg->getChannel($channel);
251 if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
255 $list_options = false;
256 if ($this->config->get('preferred_state') == 'stable') {
257 $list_options = true;
260 $available = array();
261 if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
262 $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))
264 // use faster list-all if available
265 $rest = &$this->config->getREST('1.1', array());
266 $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
267 } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
268 $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
269 $rest = &$this->config->getREST('1.0', array());
270 $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
273 if (PEAR::isError($available)) {
274 $this->config->set('default_channel', $savechannel);
275 return $this->raiseError($available);
280 'caption' => 'Channel ' . $channel . ' Available packages:',
282 'headline' => array('Package', 'Version'),
283 'channel' => $channel
286 if (count($available) == 0) {
287 $data = '(no packages available yet)';
289 foreach ($available as $name => $info) {
290 $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-';
291 $data['data'][] = array($name, $version);
294 $this->ui->outputData($data, $command);
295 $this->config->set('default_channel', $savechannel);
299 function doListAll($command, $options, $params)
301 $savechannel = $channel = $this->config->get('default_channel');
302 $reg = &$this->config->getRegistry();
303 if (isset($options['channel'])) {
304 $channel = $options['channel'];
305 if (!$reg->channelExists($channel)) {
306 return $this->raiseError("Channel \"$channel\" does not exist");
309 $this->config->set('default_channel', $channel);
312 $list_options = false;
313 if ($this->config->get('preferred_state') == 'stable') {
314 $list_options = true;
317 $chan = $reg->getChannel($channel);
318 if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
322 if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
323 $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) {
324 // use faster list-all if available
325 $rest = &$this->config->getREST('1.1', array());
326 $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
327 } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
328 $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
329 $rest = &$this->config->getREST('1.0', array());
330 $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
333 if (PEAR::isError($available)) {
334 $this->config->set('default_channel', $savechannel);
335 return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")');
339 'caption' => 'All packages [Channel ' . $channel . ']:',
341 'headline' => array('Package', 'Latest', 'Local'),
342 'channel' => $channel,
345 if (isset($options['channelinfo'])) {
346 // add full channelinfo
347 $data['caption'] = 'Channel ' . $channel . ' All packages:';
348 $data['headline'] = array('Channel', 'Package', 'Latest', 'Local',
349 'Description', 'Dependencies');
351 $local_pkgs = $reg->listPackages($channel);
353 foreach ($available as $name => $info) {
354 $installed = $reg->packageInfo($name, null, $channel);
355 if (is_array($installed['version'])) {
356 $installed['version'] = $installed['version']['release'];
358 $desc = $info['summary'];
359 if (isset($params[$name])) {
360 $desc .= "\n\n".$info['description'];
362 if (isset($options['mode']))
364 if ($options['mode'] == 'installed' && !isset($installed['version'])) {
367 if ($options['mode'] == 'notinstalled' && isset($installed['version'])) {
370 if ($options['mode'] == 'upgrades'
371 && (!isset($installed['version']) || version_compare($installed['version'],
372 $info['stable'], '>='))) {
376 $pos = array_search(strtolower($name), $local_pkgs);
377 if ($pos !== false) {
378 unset($local_pkgs[$pos]);
381 if (isset($info['stable']) && !$info['stable']) {
382 $info['stable'] = null;
385 if (isset($options['channelinfo'])) {
386 // add full channelinfo
387 if ($info['stable'] === $info['unstable']) {
388 $state = $info['state'];
392 $latest = $info['stable'].' ('.$state.')';
394 if (isset($installed['version'])) {
395 $inst_state = $reg->packageInfo($name, 'release_state', $channel);
396 $local = $installed['version'].' ('.$inst_state.')';
399 $packageinfo = array(
404 isset($desc) ? $desc : null,
405 isset($info['deps']) ? $info['deps'] : null,
408 $packageinfo = array(
409 $reg->channelAlias($channel) . '/' . $name,
410 isset($info['stable']) ? $info['stable'] : null,
411 isset($installed['version']) ? $installed['version'] : null,
412 isset($desc) ? $desc : null,
413 isset($info['deps']) ? $info['deps'] : null,
416 $data['data'][$info['category']][] = $packageinfo;
419 if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) {
420 $this->config->set('default_channel', $savechannel);
421 $this->ui->outputData($data, $command);
425 foreach ($local_pkgs as $name) {
426 $info = &$reg->getPackage($name, $channel);
427 $data['data']['Local'][] = array(
428 $reg->channelAlias($channel) . '/' . $info->getPackage(),
436 $this->config->set('default_channel', $savechannel);
437 $this->ui->outputData($data, $command);
441 function doSearch($command, $options, $params)
443 if ((!isset($params[0]) || empty($params[0]))
444 && (!isset($params[1]) || empty($params[1])))
446 return $this->raiseError('no valid search string supplied');
449 $channelinfo = isset($options['channelinfo']);
450 $reg = &$this->config->getRegistry();
451 if (isset($options['allchannels'])) {
452 // search all channels
453 unset($options['allchannels']);
454 $channels = $reg->getChannels();
456 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
457 foreach ($channels as $channel) {
458 if ($channel->getName() != '__uri') {
459 $options['channel'] = $channel->getName();
460 $ret = $this->doSearch($command, $options, $params);
461 if (PEAR::isError($ret)) {
467 PEAR::staticPopErrorHandling();
468 if (count($errors) !== 0) {
469 // for now, only give first error
470 return PEAR::raiseError($errors[0]);
476 $savechannel = $channel = $this->config->get('default_channel');
477 $package = strtolower($params[0]);
478 $summary = isset($params[1]) ? $params[1] : false;
479 if (isset($options['channel'])) {
480 $reg = &$this->config->getRegistry();
481 $channel = $options['channel'];
482 if (!$reg->channelExists($channel)) {
483 return $this->raiseError('Channel "' . $channel . '" does not exist');
486 $this->config->set('default_channel', $channel);
489 $chan = $reg->getChannel($channel);
490 if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
494 if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
495 $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
496 $rest = &$this->config->getREST('1.0', array());
497 $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName());
500 if (PEAR::isError($available)) {
501 $this->config->set('default_channel', $savechannel);
502 return $this->raiseError($available);
505 if (!$available && !$channelinfo) {
506 // clean exit when not found, no error !
507 $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.';
508 $this->ui->outputData($data);
509 $this->config->set('default_channel', $channel);
515 'caption' => 'Matched packages, channel ' . $channel . ':',
517 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'),
518 'channel' => $channel
522 'caption' => 'Matched packages, channel ' . $channel . ':',
524 'headline' => array('Package', 'Stable/(Latest)', 'Local'),
525 'channel' => $channel
529 if (!$available && $channelinfo) {
530 unset($data['headline']);
531 $data['data'] = 'No packages found that match pattern "' . $package . '".';
532 $available = array();
535 foreach ($available as $name => $info) {
536 $installed = $reg->packageInfo($name, null, $channel);
537 $desc = $info['summary'];
538 if (isset($params[$name]))
539 $desc .= "\n\n".$info['description'];
541 if (!isset($info['stable']) || !$info['stable']) {
542 $version_remote = 'none';
544 if ($info['unstable']) {
545 $version_remote = $info['unstable'];
547 $version_remote = $info['stable'];
549 $version_remote .= ' ('.$info['state'].')';
551 $version = is_array($installed['version']) ? $installed['version']['release'] :
552 $installed['version'];
554 $packageinfo = array(
562 $packageinfo = array(
569 $data['data'][$info['category']][] = $packageinfo;
572 $this->ui->outputData($data, $command);
573 $this->config->set('default_channel', $channel);
577 function &getDownloader($options)
579 if (!class_exists('PEAR_Downloader')) {
580 require_once 'PEAR/Downloader.php';
582 $a = &new PEAR_Downloader($this->ui, $options, $this->config);
586 function doDownload($command, $options, $params)
588 // make certain that dependencies are ignored
589 $options['downloadonly'] = 1;
591 // eliminate error messages for preferred_state-related errors
592 /* TODO: Should be an option, but until now download does respect
594 /* $options['ignorepreferred_state'] = 1; */
595 // eliminate error messages for preferred_state-related errors
597 $downloader = &$this->getDownloader($options);
598 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
599 $e = $downloader->setDownloadDir(getcwd());
600 PEAR::staticPopErrorHandling();
601 if (PEAR::isError($e)) {
602 return $this->raiseError('Current directory is not writeable, cannot download');
606 $downloaded = array();
607 $err = $downloader->download($params);
608 if (PEAR::isError($err)) {
612 $errors = $downloader->getErrorMsgs();
613 if (count($errors)) {
614 foreach ($errors as $error) {
615 if ($error !== null) {
616 $this->ui->outputData($error);
620 return $this->raiseError("$command failed");
623 $downloaded = $downloader->getDownloadedPackages();
624 foreach ($downloaded as $pkg) {
625 $this->ui->outputData("File $pkg[file] downloaded", $command);
631 function downloadCallback($msg, $params = null)
633 if ($msg == 'done') {
634 $this->bytes_downloaded = $params;
638 function doListUpgrades($command, $options, $params)
640 require_once 'PEAR/Common.php';
641 if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) {
642 return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"');
645 $savechannel = $channel = $this->config->get('default_channel');
646 $reg = &$this->config->getRegistry();
647 foreach ($reg->listChannels() as $channel) {
648 $inst = array_flip($reg->listPackages($channel));
653 if ($channel == '__uri') {
657 $this->config->set('default_channel', $channel);
658 $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0];
660 $caption = $channel . ' Available Upgrades';
661 $chan = $reg->getChannel($channel);
662 if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
668 $preferred_mirror = $this->config->get('preferred_mirror');
669 if ($chan->supportsREST($preferred_mirror) &&
671 //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
672 ($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
677 $rest = &$this->config->getREST('1.4', array());
680 $rest = &$this->config->getREST('1.0', array());
683 if (empty($state) || $state == 'any') {
686 $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
689 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
690 $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg);
691 PEAR::staticPopErrorHandling();
694 if (PEAR::isError($latest)) {
695 $this->ui->outputData($latest->getMessage());
700 if (PEAR::isError($latest)) {
701 $this->config->set('default_channel', $savechannel);
706 'caption' => $caption,
708 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'),
709 'channel' => $channel
712 foreach ((array)$latest as $pkg => $info) {
713 $package = strtolower($pkg);
714 if (!isset($inst[$package])) {
715 // skip packages we don't have installed
720 $inst_version = $reg->packageInfo($package, 'version', $channel);
721 $inst_state = $reg->packageInfo($package, 'release_state', $channel);
722 if (version_compare("$version", "$inst_version", "le")) {
723 // installed version is up-to-date
727 if ($filesize >= 20480) {
728 $filesize += 1024 - ($filesize % 1024);
729 $fs = sprintf("%dkB", $filesize / 1024);
730 } elseif ($filesize > 0) {
731 $filesize += 103 - ($filesize % 103);
732 $fs = sprintf("%.1fkB", $filesize / 1024.0);
734 $fs = " -"; // XXX center instead
737 $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
740 if (isset($options['channelinfo'])) {
741 if (empty($data['data'])) {
742 unset($data['headline']);
743 if (count($inst) == 0) {
744 $data['data'] = '(no packages installed)';
746 $data['data'] = '(no upgrades available)';
749 $this->ui->outputData($data, $command);
751 if (empty($data['data'])) {
752 $this->ui->outputData('Channel ' . $channel . ': No upgrades available');
754 $this->ui->outputData($data, $command);
759 $this->config->set('default_channel', $savechannel);
763 function doClearCache($command, $options, $params)
765 $cache_dir = $this->config->get('cache_dir');
766 $verbose = $this->config->get('verbose');
768 if (!file_exists($cache_dir) || !is_dir($cache_dir)) {
769 return $this->raiseError("$cache_dir does not exist or is not a directory");
772 if (!($dp = @opendir($cache_dir))) {
773 return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
777 $output .= "reading directory $cache_dir\n";
781 while ($ent = readdir($dp)) {
782 if (preg_match('/rest.cache(file|id)\\z/', $ent)) {
783 $path = $cache_dir . DIRECTORY_SEPARATOR . $ent;
784 if (file_exists($path)) {
785 $ok = @unlink($path);
793 $output .= "deleted $path\n";
796 } elseif ($verbose >= 1) {
797 $output .= "failed to delete $path $php_errormsg\n";
804 $output .= "$num cache entries cleared\n";
807 $this->ui->outputData(rtrim($output), $command);