Initial repo created
[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  * @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
17  */
18
19 /**
20  * base class
21  */
22 require_once 'PEAR/Command/Common.php';
23 require_once 'PEAR/REST.php';
24
25 /**
26  * PEAR commands for remote server querying
27  *
28  * @category   pear
29  * @package    PEAR
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
37  */
38 class PEAR_Command_Remote extends PEAR_Command_Common
39 {
40     var $commands = array(
41         'remote-info' => array(
42             'summary' => 'Information About Remote Packages',
43             'function' => 'doRemoteInfo',
44             'shortcut' => 'ri',
45             'options' => array(),
46             'doc' => '<package>
47 Get details on a package from the server.',
48             ),
49         'list-upgrades' => array(
50             'summary' => 'List Available Upgrades',
51             'function' => 'doListUpgrades',
52             'shortcut' => 'lu',
53             'options' => array(
54                 'channelinfo' => array(
55                     'shortopt' => 'i',
56                     'doc' => 'output fully channel-aware data, even on failure',
57                     ),
58             ),
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.'
63             ),
64         'remote-list' => array(
65             'summary' => 'List Remote Packages',
66             'function' => 'doRemoteList',
67             'shortcut' => 'rl',
68             'options' => array(
69                 'channel' =>
70                     array(
71                     'shortopt' => 'c',
72                     'doc' => 'specify a channel other than the default channel',
73                     'arg' => 'CHAN',
74                     )
75                 ),
76             'doc' => '
77 Lists the packages available on the configured server along with the
78 latest stable release of each package.',
79             ),
80         'search' => array(
81             'summary' => 'Search remote package database',
82             'function' => 'doSearch',
83             'shortcut' => 'sp',
84             'options' => array(
85                 'channel' =>
86                     array(
87                     'shortopt' => 'c',
88                     'doc' => 'specify a channel other than the default channel',
89                     'arg' => 'CHAN',
90                     ),
91                 'allchannels' => array(
92                     'shortopt' => 'a',
93                     'doc' => 'search packages from all known channels',
94                     ),
95                 'channelinfo' => array(
96                     'shortopt' => 'i',
97                     'doc' => 'output fully channel-aware data, even on failure',
98                     ),
99                 ),
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',
105             ),
106         'list-all' => array(
107             'summary' => 'List All Packages',
108             'function' => 'doListAll',
109             'shortcut' => 'la',
110             'options' => array(
111                 'channel' =>
112                     array(
113                     'shortopt' => 'c',
114                     'doc' => 'specify a channel other than the default channel',
115                     'arg' => 'CHAN',
116                     ),
117                 'channelinfo' => array(
118                     'shortopt' => 'i',
119                     'doc' => 'output fully channel-aware data, even on failure',
120                     ),
121                 ),
122             'doc' => '
123 Lists the packages available on the configured server along with the
124 latest stable release of each package.',
125             ),
126         'download' => array(
127             'summary' => 'Download Package',
128             'function' => 'doDownload',
129             'shortcut' => 'd',
130             'options' => array(
131                 'nocompress' => array(
132                     'shortopt' => 'Z',
133                     'doc' => 'download an uncompressed (.tar) file',
134                     ),
135                 ),
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.',
140             ),
141         'clear-cache' => array(
142             'summary' => 'Clear Web Services Cache',
143             'function' => 'doClearCache',
144             'shortcut' => 'cc',
145             'options' => array(),
146             'doc' => '
147 Clear the REST cache. See also the cache_ttl configuration
148 parameter.
149 ',
150             ),
151         );
152
153     /**
154      * PEAR_Command_Remote constructor.
155      *
156      * @access public
157      */
158     function PEAR_Command_Remote(&$ui, &$config)
159     {
160         parent::PEAR_Command_Common($ui, $config);
161     }
162
163     function _checkChannelForStatus($channel, $chan)
164     {
165         if (PEAR::isError($chan)) {
166             $this->raiseError($chan);
167         }
168         if (!is_a($chan, 'PEAR_ChannelFile')) {
169             return $this->raiseError('Internal corruption error: invalid channel "' .
170                 $channel . '"');
171         }
172         $rest = new PEAR_REST($this->config);
173         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
174         $mirror = $this->config->get('preferred_mirror', null,
175                                      $channel);
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 .
182                 '" to update');
183         }
184     }
185
186     function doRemoteInfo($command, $options, $params)
187     {
188         if (sizeof($params) != 1) {
189             return $this->raiseError("$command expects one param: the remote package name");
190         }
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 . '"');
197         }
198
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))) {
203             return $e;
204         }
205
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);
210         }
211
212         if (!isset($info)) {
213             return $this->raiseError('No supported protocol was found');
214         }
215
216         if (PEAR::isError($info)) {
217             $this->config->set('default_channel', $savechannel);
218             return $this->raiseError($info);
219         }
220
221         if (!isset($info['name'])) {
222             return $this->raiseError('No remote package "' . $package . '" was found');
223         }
224
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'];
229         }
230
231         $this->ui->outputData($info, $command);
232         $this->config->set('default_channel', $savechannel);
233
234         return true;
235     }
236
237     function doRemoteList($command, $options, $params)
238     {
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');
245             }
246
247             $this->config->set('default_channel', $channel);
248         }
249
250         $chan = $reg->getChannel($channel);
251         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
252             return $e;
253         }
254
255         $list_options = false;
256         if ($this->config->get('preferred_state') == 'stable') {
257             $list_options = true;
258         }
259
260         $available = array();
261         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
262               $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))
263         ) {
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());
271         }
272
273         if (PEAR::isError($available)) {
274             $this->config->set('default_channel', $savechannel);
275             return $this->raiseError($available);
276         }
277
278         $i = $j = 0;
279         $data = array(
280             'caption' => 'Channel ' . $channel . ' Available packages:',
281             'border' => true,
282             'headline' => array('Package', 'Version'),
283             'channel' => $channel
284             );
285
286         if (count($available) == 0) {
287             $data = '(no packages available yet)';
288         } else {
289             foreach ($available as $name => $info) {
290                 $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-';
291                 $data['data'][] = array($name, $version);
292             }
293         }
294         $this->ui->outputData($data, $command);
295         $this->config->set('default_channel', $savechannel);
296         return true;
297     }
298
299     function doListAll($command, $options, $params)
300     {
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");
307             }
308
309             $this->config->set('default_channel', $channel);
310         }
311
312         $list_options = false;
313         if ($this->config->get('preferred_state') == 'stable') {
314             $list_options = true;
315         }
316
317         $chan = $reg->getChannel($channel);
318         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
319             return $e;
320         }
321
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());
331         }
332
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() . '")');
336         }
337
338         $data = array(
339             'caption' => 'All packages [Channel ' . $channel . ']:',
340             'border' => true,
341             'headline' => array('Package', 'Latest', 'Local'),
342             'channel' => $channel,
343             );
344
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');
350         }
351         $local_pkgs = $reg->listPackages($channel);
352
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'];
357             }
358             $desc = $info['summary'];
359             if (isset($params[$name])) {
360                 $desc .= "\n\n".$info['description'];
361             }
362             if (isset($options['mode']))
363             {
364                 if ($options['mode'] == 'installed' && !isset($installed['version'])) {
365                     continue;
366                 }
367                 if ($options['mode'] == 'notinstalled' && isset($installed['version'])) {
368                     continue;
369                 }
370                 if ($options['mode'] == 'upgrades'
371                       && (!isset($installed['version']) || version_compare($installed['version'],
372                       $info['stable'], '>='))) {
373                     continue;
374                 }
375             }
376             $pos = array_search(strtolower($name), $local_pkgs);
377             if ($pos !== false) {
378                 unset($local_pkgs[$pos]);
379             }
380
381             if (isset($info['stable']) && !$info['stable']) {
382                 $info['stable'] = null;
383             }
384
385             if (isset($options['channelinfo'])) {
386                 // add full channelinfo
387                 if ($info['stable'] === $info['unstable']) {
388                     $state = $info['state'];
389                 } else {
390                     $state = 'stable';
391                 }
392                 $latest = $info['stable'].' ('.$state.')';
393                 $local = '';
394                 if (isset($installed['version'])) {
395                     $inst_state = $reg->packageInfo($name, 'release_state', $channel);
396                     $local = $installed['version'].' ('.$inst_state.')';
397                 }
398
399                 $packageinfo = array(
400                     $channel,
401                     $name,
402                     $latest,
403                     $local,
404                     isset($desc) ? $desc : null,
405                     isset($info['deps']) ? $info['deps'] : null,
406                 );
407             } else {
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,
414                 );
415             }
416             $data['data'][$info['category']][] = $packageinfo;
417         }
418
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);
422             return true;
423         }
424
425         foreach ($local_pkgs as $name) {
426             $info = &$reg->getPackage($name, $channel);
427             $data['data']['Local'][] = array(
428                 $reg->channelAlias($channel) . '/' . $info->getPackage(),
429                 '',
430                 $info->getVersion(),
431                 $info->getSummary(),
432                 $info->getDeps()
433                 );
434         }
435
436         $this->config->set('default_channel', $savechannel);
437         $this->ui->outputData($data, $command);
438         return true;
439     }
440
441     function doSearch($command, $options, $params)
442     {
443         if ((!isset($params[0]) || empty($params[0]))
444             && (!isset($params[1]) || empty($params[1])))
445         {
446             return $this->raiseError('no valid search string supplied');
447         }
448
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();
455             $errors = array();
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)) {
462                         $errors[] = $ret;
463                     }
464                 }
465             }
466
467             PEAR::staticPopErrorHandling();
468             if (count($errors) !== 0) {
469                 // for now, only give first error
470                 return PEAR::raiseError($errors[0]);
471             }
472
473             return true;
474         }
475
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');
484             }
485
486             $this->config->set('default_channel', $channel);
487         }
488
489         $chan = $reg->getChannel($channel);
490         if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
491             return $e;
492         }
493
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());
498         }
499
500         if (PEAR::isError($available)) {
501             $this->config->set('default_channel', $savechannel);
502             return $this->raiseError($available);
503         }
504
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);
510             return true;
511         }
512
513         if ($channelinfo) {
514             $data = array(
515                 'caption' => 'Matched packages, channel ' . $channel . ':',
516                 'border' => true,
517                 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'),
518                 'channel' => $channel
519                 );
520         } else {
521             $data = array(
522                 'caption' => 'Matched packages, channel ' . $channel . ':',
523                 'border' => true,
524                 'headline' => array('Package', 'Stable/(Latest)', 'Local'),
525                 'channel' => $channel
526                 );
527         }
528
529         if (!$available && $channelinfo) {
530             unset($data['headline']);
531             $data['data'] = 'No packages found that match pattern "' . $package . '".';
532             $available = array();
533         }
534
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'];
540
541             if (!isset($info['stable']) || !$info['stable']) {
542                 $version_remote = 'none';
543             } else {
544                 if ($info['unstable']) {
545                     $version_remote = $info['unstable'];
546                 } else {
547                     $version_remote = $info['stable'];
548                 }
549                 $version_remote .= ' ('.$info['state'].')';
550             }
551             $version = is_array($installed['version']) ? $installed['version']['release'] :
552                 $installed['version'];
553             if ($channelinfo) {
554                 $packageinfo = array(
555                     $channel,
556                     $name,
557                     $version_remote,
558                     $version,
559                     $desc,
560                 );
561             } else {
562                 $packageinfo = array(
563                     $name,
564                     $version_remote,
565                     $version,
566                     $desc,
567                 );
568             }
569             $data['data'][$info['category']][] = $packageinfo;
570         }
571
572         $this->ui->outputData($data, $command);
573         $this->config->set('default_channel', $channel);
574         return true;
575     }
576
577     function &getDownloader($options)
578     {
579         if (!class_exists('PEAR_Downloader')) {
580             require_once 'PEAR/Downloader.php';
581         }
582         $a = &new PEAR_Downloader($this->ui, $options, $this->config);
583         return $a;
584     }
585
586     function doDownload($command, $options, $params)
587     {
588         // make certain that dependencies are ignored
589         $options['downloadonly'] = 1;
590
591         // eliminate error messages for preferred_state-related errors
592         /* TODO: Should be an option, but until now download does respect
593            prefered state */
594         /* $options['ignorepreferred_state'] = 1; */
595         // eliminate error messages for preferred_state-related errors
596
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');
603         }
604
605         $errors = array();
606         $downloaded = array();
607         $err = $downloader->download($params);
608         if (PEAR::isError($err)) {
609             return $err;
610         }
611
612         $errors = $downloader->getErrorMsgs();
613         if (count($errors)) {
614             foreach ($errors as $error) {
615                 if ($error !== null) {
616                     $this->ui->outputData($error);
617                 }
618             }
619
620             return $this->raiseError("$command failed");
621         }
622
623         $downloaded = $downloader->getDownloadedPackages();
624         foreach ($downloaded as $pkg) {
625             $this->ui->outputData("File $pkg[file] downloaded", $command);
626         }
627
628         return true;
629     }
630
631     function downloadCallback($msg, $params = null)
632     {
633         if ($msg == 'done') {
634             $this->bytes_downloaded = $params;
635         }
636     }
637
638     function doListUpgrades($command, $options, $params)
639     {
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"');
643         }
644
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));
649             if (!count($inst)) {
650                 continue;
651             }
652
653             if ($channel == '__uri') {
654                 continue;
655             }
656
657             $this->config->set('default_channel', $channel);
658             $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0];
659
660             $caption = $channel . ' Available Upgrades';
661             $chan = $reg->getChannel($channel);
662             if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
663                 return $e;
664             }
665
666             $latest = array();
667             $base2  = false;
668             $preferred_mirror = $this->config->get('preferred_mirror');
669             if ($chan->supportsREST($preferred_mirror) &&
670                 (
671                    //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
672                    ($base  = $chan->getBaseURL('REST1.0', $preferred_mirror))
673                 )
674
675             ) {
676                 if ($base2) {
677                     $rest = &$this->config->getREST('1.4', array());
678                     $base = $base2;
679                 } else {
680                     $rest = &$this->config->getREST('1.0', array());
681                 }
682
683                 if (empty($state) || $state == 'any') {
684                     $state = false;
685                 } else {
686                     $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
687                 }
688
689                 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
690                 $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg);
691                 PEAR::staticPopErrorHandling();
692             }
693
694             if (PEAR::isError($latest)) {
695                 $this->ui->outputData($latest->getMessage());
696                 continue;
697             }
698
699             $caption .= ':';
700             if (PEAR::isError($latest)) {
701                 $this->config->set('default_channel', $savechannel);
702                 return $latest;
703             }
704
705             $data = array(
706                 'caption' => $caption,
707                 'border' => 1,
708                 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'),
709                 'channel' => $channel
710                 );
711
712             foreach ((array)$latest as $pkg => $info) {
713                 $package = strtolower($pkg);
714                 if (!isset($inst[$package])) {
715                     // skip packages we don't have installed
716                     continue;
717                 }
718
719                 extract($info);
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
724                     continue;
725                 }
726
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);
733                 } else {
734                     $fs = "  -"; // XXX center instead
735                 }
736
737                 $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
738             }
739
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)';
745                     } else {
746                         $data['data'] = '(no upgrades available)';
747                     }
748                 }
749                 $this->ui->outputData($data, $command);
750             } else {
751                 if (empty($data['data'])) {
752                     $this->ui->outputData('Channel ' . $channel . ': No upgrades available');
753                 } else {
754                     $this->ui->outputData($data, $command);
755                 }
756             }
757         }
758
759         $this->config->set('default_channel', $savechannel);
760         return true;
761     }
762
763     function doClearCache($command, $options, $params)
764     {
765         $cache_dir = $this->config->get('cache_dir');
766         $verbose   = $this->config->get('verbose');
767         $output = '';
768         if (!file_exists($cache_dir) || !is_dir($cache_dir)) {
769             return $this->raiseError("$cache_dir does not exist or is not a directory");
770         }
771
772         if (!($dp = @opendir($cache_dir))) {
773             return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
774         }
775
776         if ($verbose >= 1) {
777             $output .= "reading directory $cache_dir\n";
778         }
779
780         $num = 0;
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);
786                 } else {
787                     $ok = false;
788                     $php_errormsg = '';
789                 }
790
791                 if ($ok) {
792                     if ($verbose >= 2) {
793                         $output .= "deleted $path\n";
794                     }
795                     $num++;
796                 } elseif ($verbose >= 1) {
797                     $output .= "failed to delete $path $php_errormsg\n";
798                 }
799             }
800         }
801
802         closedir($dp);
803         if ($verbose >= 1) {
804             $output .= "$num cache entries cleared\n";
805         }
806
807         $this->ui->outputData(rtrim($output), $command);
808         return $num;
809     }
810 }