Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / Command / Remote.php
1 <?php
2 /**
3  * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download,
4  * clear-cache commands)
5  *
6  * PHP versions 4 and 5
7  *
8  * @category   pear
9  * @package    PEAR
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  * @link       http://pear.php.net/package/PEAR
15  * @since      File available since Release 0.1
16  */
17
18 /**
19  * base class
20  */
21 require_once 'PEAR/Command/Common.php';
22 require_once 'PEAR/REST.php';
23
24 /**
25  * PEAR commands for remote server querying
26  *
27  * @category   pear
28  * @package    PEAR
29  * @author     Stig Bakken <ssb@php.net>
30  * @author     Greg Beaver <cellog@php.net>
31  * @copyright  1997-2009 The Authors
32  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
33  * @version    Release: 1.10.1
34  * @link       http://pear.php.net/package/PEAR
35  * @since      Class available since Release 0.1
36  */
37 class PEAR_Command_Remote extends PEAR_Command_Common
38 {
39     var $commands = array(
40         'remote-info' => array(
41             'summary' => 'Information About Remote Packages',
42             'function' => 'doRemoteInfo',
43             'shortcut' => 'ri',
44             'options' => array(),
45             'doc' => '<package>
46 Get details on a package from the server.',
47             ),
48         'list-upgrades' => array(
49             'summary' => 'List Available Upgrades',
50             'function' => 'doListUpgrades',
51             'shortcut' => 'lu',
52             'options' => array(
53                 'channelinfo' => array(
54                     'shortopt' => 'i',
55                     'doc' => 'output fully channel-aware data, even on failure',
56                     ),
57             ),
58             'doc' => '[preferred_state]
59 List releases on the server of packages you have installed where
60 a newer version is available with the same release state (stable etc.)
61 or the state passed as the second parameter.'
62             ),
63         'remote-list' => array(
64             'summary' => 'List Remote Packages',
65             'function' => 'doRemoteList',
66             'shortcut' => 'rl',
67             'options' => array(
68                 'channel' =>
69                     array(
70                     'shortopt' => 'c',
71                     'doc' => 'specify a channel other than the default channel',
72                     'arg' => 'CHAN',
73                     )
74                 ),
75             'doc' => '
76 Lists the packages available on the configured server along with the
77 latest stable release of each package.',
78             ),
79         'search' => array(
80             'summary' => 'Search remote package database',
81             'function' => 'doSearch',
82             'shortcut' => 'sp',
83             'options' => array(
84                 'channel' =>
85                     array(
86                     'shortopt' => 'c',
87                     'doc' => 'specify a channel other than the default channel',
88                     'arg' => 'CHAN',
89                     ),
90                 'allchannels' => array(
91                     'shortopt' => 'a',
92                     'doc' => 'search packages from all known channels',
93                     ),
94                 'channelinfo' => array(
95                     'shortopt' => 'i',
96                     'doc' => 'output fully channel-aware data, even on failure',
97                     ),
98                 ),
99             'doc' => '[packagename] [packageinfo]
100 Lists all packages which match the search parameters.  The first
101 parameter is a fragment of a packagename.  The default channel
102 will be used unless explicitly overridden.  The second parameter
103 will be used to match any portion of the summary/description',
104             ),
105         'list-all' => array(
106             'summary' => 'List All Packages',
107             'function' => 'doListAll',
108             'shortcut' => 'la',
109             'options' => array(
110                 'channel' =>
111                     array(
112                     'shortopt' => 'c',
113                     'doc' => 'specify a channel other than the default channel',
114                     'arg' => 'CHAN',
115                     ),
116                 'channelinfo' => array(
117                     'shortopt' => 'i',
118                     'doc' => 'output fully channel-aware data, even on failure',
119                     ),
120                 ),
121             'doc' => '
122 Lists the packages available on the configured server along with the
123 latest stable release of each package.',
124             ),
125         'download' => array(
126             'summary' => 'Download Package',
127             'function' => 'doDownload',
128             'shortcut' => 'd',
129             'options' => array(
130                 'nocompress' => array(
131                     'shortopt' => 'Z',
132                     'doc' => 'download an uncompressed (.tar) file',
133                     ),
134                 ),
135             'doc' => '<package>...
136 Download package tarballs.  The files will be named as suggested by the
137 server, for example if you download the DB package and the latest stable
138 version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.',
139             ),
140         'clear-cache' => array(
141             'summary' => 'Clear Web Services Cache',
142             'function' => 'doClearCache',
143             'shortcut' => 'cc',
144             'options' => array(),
145             'doc' => '
146 Clear the REST cache. See also the cache_ttl configuration
147 parameter.
148 ',
149             ),
150         );
151
152     /**
153      * PEAR_Command_Remote constructor.
154      *
155      * @access public
156      */
157     function __construct(&$ui, &$config)
158     {
159         parent::__construct($ui, $config);
160     }
161
162     function _checkChannelForStatus($channel, $chan)
163     {
164         if (PEAR::isError($chan)) {
165             $this->raiseError($chan);
166         }
167         if (!is_a($chan, 'PEAR_ChannelFile')) {
168             return $this->raiseError('Internal corruption error: invalid channel "' .
169                 $channel . '"');
170         }
171         $rest = new PEAR_REST($this->config);
172         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
173         $mirror = $this->config->get('preferred_mirror', null,
174                                      $channel);
175         $a = $rest->downloadHttp('http://' . $channel .
176             '/channel.xml', $chan->lastModified());
177         PEAR::staticPopErrorHandling();
178         if (!PEAR::isError($a) && $a) {
179             $this->ui->outputData('WARNING: channel "' . $channel . '" has ' .
180                 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel .
181                 '" to update');
182         }
183     }
184
185     function doRemoteInfo($command, $options, $params)
186     {
187         if (sizeof($params) != 1) {
188             return $this->raiseError("$command expects one param: the remote package name");
189         }
190         $savechannel = $channel = $this->config->get('default_channel');
191         $reg = &$this->config->getRegistry();
192         $package = $params[0];
193         $parsed = $reg->parsePackageName($package, $channel);
194         if (PEAR::isError($parsed)) {
195             return $this->raiseError('Invalid package name "' . $package . '"');
196         }
197
198         $channel = $parsed['channel'];
199         $this->config->set('default_channel', $channel);
200         $chan = $reg->getChannel($channel);
201         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
202             return $e;
203         }
204
205         $mirror = $this->config->get('preferred_mirror');
206         if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) {
207             $rest = &$this->config->getREST('1.0', array());
208             $info = $rest->packageInfo($base, $parsed['package'], $channel);
209         }
210
211         if (!isset($info)) {
212             return $this->raiseError('No supported protocol was found');
213         }
214
215         if (PEAR::isError($info)) {
216             $this->config->set('default_channel', $savechannel);
217             return $this->raiseError($info);
218         }
219
220         if (!isset($info['name'])) {
221             return $this->raiseError('No remote package "' . $package . '" was found');
222         }
223
224         $installed = $reg->packageInfo($info['name'], null, $channel);
225         $info['installed'] = $installed['version'] ? $installed['version'] : '- no -';
226         if (is_array($info['installed'])) {
227             $info['installed'] = $info['installed']['release'];
228         }
229
230         $this->ui->outputData($info, $command);
231         $this->config->set('default_channel', $savechannel);
232
233         return true;
234     }
235
236     function doRemoteList($command, $options, $params)
237     {
238         $savechannel = $channel = $this->config->get('default_channel');
239         $reg = &$this->config->getRegistry();
240         if (isset($options['channel'])) {
241             $channel = $options['channel'];
242             if (!$reg->channelExists($channel)) {
243                 return $this->raiseError('Channel "' . $channel . '" does not exist');
244             }
245
246             $this->config->set('default_channel', $channel);
247         }
248
249         $chan = $reg->getChannel($channel);
250         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
251             return $e;
252         }
253
254         $list_options = false;
255         if ($this->config->get('preferred_state') == 'stable') {
256             $list_options = true;
257         }
258
259         $available = array();
260         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
261               $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))
262         ) {
263             // use faster list-all if available
264             $rest = &$this->config->getREST('1.1', array());
265             $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
266         } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
267               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
268             $rest = &$this->config->getREST('1.0', array());
269             $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
270         }
271
272         if (PEAR::isError($available)) {
273             $this->config->set('default_channel', $savechannel);
274             return $this->raiseError($available);
275         }
276
277         $i = $j = 0;
278         $data = array(
279             'caption' => 'Channel ' . $channel . ' Available packages:',
280             'border' => true,
281             'headline' => array('Package', 'Version'),
282             'channel' => $channel
283             );
284
285         if (count($available) == 0) {
286             $data = '(no packages available yet)';
287         } else {
288             foreach ($available as $name => $info) {
289                 $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-';
290                 $data['data'][] = array($name, $version);
291             }
292         }
293         $this->ui->outputData($data, $command);
294         $this->config->set('default_channel', $savechannel);
295         return true;
296     }
297
298     function doListAll($command, $options, $params)
299     {
300         $savechannel = $channel = $this->config->get('default_channel');
301         $reg = &$this->config->getRegistry();
302         if (isset($options['channel'])) {
303             $channel = $options['channel'];
304             if (!$reg->channelExists($channel)) {
305                 return $this->raiseError("Channel \"$channel\" does not exist");
306             }
307
308             $this->config->set('default_channel', $channel);
309         }
310
311         $list_options = false;
312         if ($this->config->get('preferred_state') == 'stable') {
313             $list_options = true;
314         }
315
316         $chan = $reg->getChannel($channel);
317         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
318             return $e;
319         }
320
321         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
322               $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) {
323             // use faster list-all if available
324             $rest = &$this->config->getREST('1.1', array());
325             $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
326         } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
327               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
328             $rest = &$this->config->getREST('1.0', array());
329             $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
330         }
331
332         if (PEAR::isError($available)) {
333             $this->config->set('default_channel', $savechannel);
334             return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")');
335         }
336
337         $data = array(
338             'caption' => 'All packages [Channel ' . $channel . ']:',
339             'border' => true,
340             'headline' => array('Package', 'Latest', 'Local'),
341             'channel' => $channel,
342             );
343
344         if (isset($options['channelinfo'])) {
345             // add full channelinfo
346             $data['caption'] = 'Channel ' . $channel . ' All packages:';
347             $data['headline'] = array('Channel', 'Package', 'Latest', 'Local',
348                 'Description', 'Dependencies');
349         }
350         $local_pkgs = $reg->listPackages($channel);
351
352         foreach ($available as $name => $info) {
353             $installed = $reg->packageInfo($name, null, $channel);
354             if (is_array($installed['version'])) {
355                 $installed['version'] = $installed['version']['release'];
356             }
357             $desc = $info['summary'];
358             if (isset($params[$name])) {
359                 $desc .= "\n\n".$info['description'];
360             }
361             if (isset($options['mode']))
362             {
363                 if ($options['mode'] == 'installed' && !isset($installed['version'])) {
364                     continue;
365                 }
366                 if ($options['mode'] == 'notinstalled' && isset($installed['version'])) {
367                     continue;
368                 }
369                 if ($options['mode'] == 'upgrades'
370                       && (!isset($installed['version']) || version_compare($installed['version'],
371                       $info['stable'], '>='))) {
372                     continue;
373                 }
374             }
375             $pos = array_search(strtolower($name), $local_pkgs);
376             if ($pos !== false) {
377                 unset($local_pkgs[$pos]);
378             }
379
380             if (isset($info['stable']) && !$info['stable']) {
381                 $info['stable'] = null;
382             }
383
384             if (isset($options['channelinfo'])) {
385                 // add full channelinfo
386                 if ($info['stable'] === $info['unstable']) {
387                     $state = $info['state'];
388                 } else {
389                     $state = 'stable';
390                 }
391                 $latest = $info['stable'].' ('.$state.')';
392                 $local = '';
393                 if (isset($installed['version'])) {
394                     $inst_state = $reg->packageInfo($name, 'release_state', $channel);
395                     $local = $installed['version'].' ('.$inst_state.')';
396                 }
397
398                 $packageinfo = array(
399                     $channel,
400                     $name,
401                     $latest,
402                     $local,
403                     isset($desc) ? $desc : null,
404                     isset($info['deps']) ? $info['deps'] : null,
405                 );
406             } else {
407                 $packageinfo = array(
408                     $reg->channelAlias($channel) . '/' . $name,
409                     isset($info['stable']) ? $info['stable'] : null,
410                     isset($installed['version']) ? $installed['version'] : null,
411                     isset($desc) ? $desc : null,
412                     isset($info['deps']) ? $info['deps'] : null,
413                 );
414             }
415             $data['data'][$info['category']][] = $packageinfo;
416         }
417
418         if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) {
419             $this->config->set('default_channel', $savechannel);
420             $this->ui->outputData($data, $command);
421             return true;
422         }
423
424         foreach ($local_pkgs as $name) {
425             $info = &$reg->getPackage($name, $channel);
426             $data['data']['Local'][] = array(
427                 $reg->channelAlias($channel) . '/' . $info->getPackage(),
428                 '',
429                 $info->getVersion(),
430                 $info->getSummary(),
431                 $info->getDeps()
432                 );
433         }
434
435         $this->config->set('default_channel', $savechannel);
436         $this->ui->outputData($data, $command);
437         return true;
438     }
439
440     function doSearch($command, $options, $params)
441     {
442         if ((!isset($params[0]) || empty($params[0]))
443             && (!isset($params[1]) || empty($params[1])))
444         {
445             return $this->raiseError('no valid search string supplied');
446         }
447
448         $channelinfo = isset($options['channelinfo']);
449         $reg = &$this->config->getRegistry();
450         if (isset($options['allchannels'])) {
451             // search all channels
452             unset($options['allchannels']);
453             $channels = $reg->getChannels();
454             $errors = array();
455             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
456             foreach ($channels as $channel) {
457                 if ($channel->getName() != '__uri') {
458                     $options['channel'] = $channel->getName();
459                     $ret = $this->doSearch($command, $options, $params);
460                     if (PEAR::isError($ret)) {
461                         $errors[] = $ret;
462                     }
463                 }
464             }
465
466             PEAR::staticPopErrorHandling();
467             if (count($errors) !== 0) {
468                 // for now, only give first error
469                 return PEAR::raiseError($errors[0]);
470             }
471
472             return true;
473         }
474
475         $savechannel = $channel = $this->config->get('default_channel');
476         $package = strtolower($params[0]);
477         $summary = isset($params[1]) ? $params[1] : false;
478         if (isset($options['channel'])) {
479             $reg = &$this->config->getRegistry();
480             $channel = $options['channel'];
481             if (!$reg->channelExists($channel)) {
482                 return $this->raiseError('Channel "' . $channel . '" does not exist');
483             }
484
485             $this->config->set('default_channel', $channel);
486         }
487
488         $chan = $reg->getChannel($channel);
489         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
490             return $e;
491         }
492
493         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
494               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
495             $rest = &$this->config->getREST('1.0', array());
496             $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName());
497         }
498
499         if (PEAR::isError($available)) {
500             $this->config->set('default_channel', $savechannel);
501             return $this->raiseError($available);
502         }
503
504         if (!$available && !$channelinfo) {
505             // clean exit when not found, no error !
506             $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.';
507             $this->ui->outputData($data);
508             $this->config->set('default_channel', $channel);
509             return true;
510         }
511
512         if ($channelinfo) {
513             $data = array(
514                 'caption' => 'Matched packages, channel ' . $channel . ':',
515                 'border' => true,
516                 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'),
517                 'channel' => $channel
518                 );
519         } else {
520             $data = array(
521                 'caption' => 'Matched packages, channel ' . $channel . ':',
522                 'border' => true,
523                 'headline' => array('Package', 'Stable/(Latest)', 'Local'),
524                 'channel' => $channel
525                 );
526         }
527
528         if (!$available && $channelinfo) {
529             unset($data['headline']);
530             $data['data'] = 'No packages found that match pattern "' . $package . '".';
531             $available = array();
532         }
533
534         foreach ($available as $name => $info) {
535             $installed = $reg->packageInfo($name, null, $channel);
536             $desc = $info['summary'];
537             if (isset($params[$name]))
538                 $desc .= "\n\n".$info['description'];
539
540             if (!isset($info['stable']) || !$info['stable']) {
541                 $version_remote = 'none';
542             } else {
543                 if ($info['unstable']) {
544                     $version_remote = $info['unstable'];
545                 } else {
546                     $version_remote = $info['stable'];
547                 }
548                 $version_remote .= ' ('.$info['state'].')';
549             }
550             $version = is_array($installed['version']) ? $installed['version']['release'] :
551                 $installed['version'];
552             if ($channelinfo) {
553                 $packageinfo = array(
554                     $channel,
555                     $name,
556                     $version_remote,
557                     $version,
558                     $desc,
559                 );
560             } else {
561                 $packageinfo = array(
562                     $name,
563                     $version_remote,
564                     $version,
565                     $desc,
566                 );
567             }
568             $data['data'][$info['category']][] = $packageinfo;
569         }
570
571         $this->ui->outputData($data, $command);
572         $this->config->set('default_channel', $channel);
573         return true;
574     }
575
576     function &getDownloader($options)
577     {
578         if (!class_exists('PEAR_Downloader')) {
579             require_once 'PEAR/Downloader.php';
580         }
581         $a = new PEAR_Downloader($this->ui, $options, $this->config);
582         return $a;
583     }
584
585     function doDownload($command, $options, $params)
586     {
587         // make certain that dependencies are ignored
588         $options['downloadonly'] = 1;
589
590         // eliminate error messages for preferred_state-related errors
591         /* TODO: Should be an option, but until now download does respect
592            prefered state */
593         /* $options['ignorepreferred_state'] = 1; */
594         // eliminate error messages for preferred_state-related errors
595
596         $downloader = &$this->getDownloader($options);
597         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
598         $e = $downloader->setDownloadDir(getcwd());
599         PEAR::staticPopErrorHandling();
600         if (PEAR::isError($e)) {
601             return $this->raiseError('Current directory is not writeable, cannot download');
602         }
603
604         $errors = array();
605         $downloaded = array();
606         $err = $downloader->download($params);
607         if (PEAR::isError($err)) {
608             return $err;
609         }
610
611         $errors = $downloader->getErrorMsgs();
612         if (count($errors)) {
613             foreach ($errors as $error) {
614                 if ($error !== null) {
615                     $this->ui->outputData($error);
616                 }
617             }
618
619             return $this->raiseError("$command failed");
620         }
621
622         $downloaded = $downloader->getDownloadedPackages();
623         foreach ($downloaded as $pkg) {
624             $this->ui->outputData("File $pkg[file] downloaded", $command);
625         }
626
627         return true;
628     }
629
630     function downloadCallback($msg, $params = null)
631     {
632         if ($msg == 'done') {
633             $this->bytes_downloaded = $params;
634         }
635     }
636
637     function doListUpgrades($command, $options, $params)
638     {
639         require_once 'PEAR/Common.php';
640         if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) {
641             return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"');
642         }
643
644         $savechannel = $channel = $this->config->get('default_channel');
645         $reg = &$this->config->getRegistry();
646         foreach ($reg->listChannels() as $channel) {
647             $inst = array_flip($reg->listPackages($channel));
648             if (!count($inst)) {
649                 continue;
650             }
651
652             if ($channel == '__uri') {
653                 continue;
654             }
655
656             $this->config->set('default_channel', $channel);
657             $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0];
658
659             $caption = $channel . ' Available Upgrades';
660             $chan = $reg->getChannel($channel);
661             if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
662                 return $e;
663             }
664
665             $latest = array();
666             $base2  = false;
667             $preferred_mirror = $this->config->get('preferred_mirror');
668             if ($chan->supportsREST($preferred_mirror) &&
669                 (
670                    ($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
671                    || ($base  = $chan->getBaseURL('REST1.0', $preferred_mirror))
672                 )
673
674             ) {
675                 if ($base2) {
676                     $rest = &$this->config->getREST('1.3', array());
677                     $base = $base2;
678                 } else {
679                     $rest = &$this->config->getREST('1.0', array());
680                 }
681
682                 if (empty($state) || $state == 'any') {
683                     $state = false;
684                 } else {
685                     $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
686                 }
687
688                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
689                 $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg);
690                 PEAR::staticPopErrorHandling();
691             }
692
693             if (PEAR::isError($latest)) {
694                 $this->ui->outputData($latest->getMessage());
695                 continue;
696             }
697
698             $caption .= ':';
699             if (PEAR::isError($latest)) {
700                 $this->config->set('default_channel', $savechannel);
701                 return $latest;
702             }
703
704             $data = array(
705                 'caption' => $caption,
706                 'border' => 1,
707                 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'),
708                 'channel' => $channel
709                 );
710
711             foreach ((array)$latest as $pkg => $info) {
712                 $package = strtolower($pkg);
713                 if (!isset($inst[$package])) {
714                     // skip packages we don't have installed
715                     continue;
716                 }
717
718                 extract($info);
719                 $inst_version = $reg->packageInfo($package, 'version', $channel);
720                 $inst_state   = $reg->packageInfo($package, 'release_state', $channel);
721                 if (version_compare("$version", "$inst_version", "le")) {
722                     // installed version is up-to-date
723                     continue;
724                 }
725
726                 if ($filesize >= 20480) {
727                     $filesize += 1024 - ($filesize % 1024);
728                     $fs = sprintf("%dkB", $filesize / 1024);
729                 } elseif ($filesize > 0) {
730                     $filesize += 103 - ($filesize % 103);
731                     $fs = sprintf("%.1fkB", $filesize / 1024.0);
732                 } else {
733                     $fs = "  -"; // XXX center instead
734                 }
735
736                 $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
737             }
738
739             if (isset($options['channelinfo'])) {
740                 if (empty($data['data'])) {
741                     unset($data['headline']);
742                     if (count($inst) == 0) {
743                         $data['data'] = '(no packages installed)';
744                     } else {
745                         $data['data'] = '(no upgrades available)';
746                     }
747                 }
748                 $this->ui->outputData($data, $command);
749             } else {
750                 if (empty($data['data'])) {
751                     $this->ui->outputData('Channel ' . $channel . ': No upgrades available');
752                 } else {
753                     $this->ui->outputData($data, $command);
754                 }
755             }
756         }
757
758         $this->config->set('default_channel', $savechannel);
759         return true;
760     }
761
762     function doClearCache($command, $options, $params)
763     {
764         $cache_dir = $this->config->get('cache_dir');
765         $verbose   = $this->config->get('verbose');
766         $output = '';
767         if (!file_exists($cache_dir) || !is_dir($cache_dir)) {
768             return $this->raiseError("$cache_dir does not exist or is not a directory");
769         }
770
771         if (!($dp = @opendir($cache_dir))) {
772             return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
773         }
774
775         if ($verbose >= 1) {
776             $output .= "reading directory $cache_dir\n";
777         }
778
779         $num = 0;
780         while ($ent = readdir($dp)) {
781             if (preg_match('/rest.cache(file|id)\\z/', $ent)) {
782                 $path = $cache_dir . DIRECTORY_SEPARATOR . $ent;
783                 if (file_exists($path)) {
784                     $ok = @unlink($path);
785                 } else {
786                     $ok = false;
787                     $php_errormsg = '';
788                 }
789
790                 if ($ok) {
791                     if ($verbose >= 2) {
792                         $output .= "deleted $path\n";
793                     }
794                     $num++;
795                 } elseif ($verbose >= 1) {
796                     $output .= "failed to delete $path $php_errormsg\n";
797                 }
798             }
799         }
800
801         closedir($dp);
802         if ($verbose >= 1) {
803             $output .= "$num cache entries cleared\n";
804         }
805
806         $this->ui->outputData(rtrim($output), $command);
807         return $num;
808     }
809 }