File indexing completed on 2024-12-22 05:36:57

0001 <?php
0002 /**
0003  * LICENSE
0004  *
0005  * This source file is subject to the new BSD license that is bundled
0006  * with this package in the file LICENSE.txt.
0007  * It is also available through the world-wide-web at this URL:
0008  * http://framework.zend.com/license/new-bsd
0009  * If you did not receive a copy of the license and are unable to
0010  * obtain it through the world-wide-web, please send an email
0011  * to license@zend.com so we can send you a copy immediately.
0012  *
0013  * @category   Zend
0014  * @package    Zend_ProgressBar
0015  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0016  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0017  * @version    $Id$
0018  */
0019 
0020 /**
0021  * @see Zend_ProgressBar_Adapter
0022  */
0023 // require_once 'Zend/ProgressBar/Adapter.php';
0024 
0025 /**
0026  * @see Zend_Text_MultiByte
0027  */
0028 // require_once 'Zend/Text/MultiByte.php';
0029 
0030 /**
0031  * Zend_ProgressBar_Adapter_Console offers a text-based progressbar for console
0032  * applications
0033  *
0034  * @category  Zend
0035  * @package   Zend_ProgressBar
0036  * @uses      Zend_ProgressBar_Adapter_Interface
0037  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0038  * @license   http://framework.zend.com/license/new-bsd     New BSD License
0039  */
0040 class Zend_ProgressBar_Adapter_Console extends Zend_ProgressBar_Adapter
0041 {
0042     /**
0043      * Percentage value of the progress
0044      */
0045     const ELEMENT_PERCENT = 'ELEMENT_PERCENT';
0046 
0047     /**
0048      * Visual value of the progress
0049      */
0050     const ELEMENT_BAR = 'ELEMENT_BAR';
0051 
0052     /**
0053      * ETA of the progress
0054      */
0055     const ELEMENT_ETA = 'ELEMENT_ETA';
0056 
0057     /**
0058      * Text part of the progress
0059      */
0060     const ELEMENT_TEXT = 'ELEMENT_TEXT';
0061 
0062     /**
0063      * Finish action: End of Line
0064      */
0065     const FINISH_ACTION_EOL = 'FINISH_ACTION_EOL';
0066 
0067     /**
0068      * Finish action: Clear Line
0069      */
0070     const FINISH_ACTION_CLEAR_LINE = 'FINISH_ACTION_CLEAR_LINE';
0071 
0072     /**
0073      * Finish action: None
0074      */
0075     const FINISH_ACTION_NONE = 'FINISH_ACTION_NONE';
0076 
0077     /**
0078      * Width of the progressbar
0079      *
0080      * @var integer
0081      */
0082     protected $_width = null;
0083 
0084     /**
0085      * Elements to display
0086      *
0087      * @var array
0088      */
0089     protected $_elements = array(self::ELEMENT_PERCENT,
0090                                  self::ELEMENT_BAR,
0091                                  self::ELEMENT_ETA);
0092 
0093     /**
0094      * Which action to do at finish call
0095      *
0096      * @var string
0097      */
0098     protected $_finishAction = self::FINISH_ACTION_EOL;
0099 
0100     /**
0101      * Width of the bar element
0102      *
0103      * @var integer
0104      */
0105     protected $_barWidth;
0106 
0107     /**
0108      * Left character(s) within the bar
0109      *
0110      * @var string
0111      */
0112     protected $_barLeftChar = '#';
0113 
0114     /**
0115      * Indicator character(s) within the bar
0116      *
0117      * @var string
0118      */
0119     protected $_barIndicatorChar = '';
0120 
0121     /**
0122      * Right character(s) within the bar
0123      *
0124      * @var string
0125      */
0126     protected $_barRightChar = '-';
0127 
0128     /**
0129      * Output-stream, when STDOUT is not defined (e.g. in CGI) or set manually
0130      *
0131      * @var resource
0132      */
0133     protected $_outputStream = null;
0134 
0135     /**
0136      * Width of the text element
0137      *
0138      * @var string
0139      */
0140     protected $_textWidth = 20;
0141 
0142     /**
0143      * Wether the output started yet or not
0144      *
0145      * @var boolean
0146      */
0147     protected $_outputStarted = false;
0148 
0149     /**
0150      * Charset of text element
0151      *
0152      * @var string
0153      */
0154     protected $_charset = 'utf-8';
0155 
0156     /**
0157      * Defined by Zend_ProgressBar_Adapter
0158      *
0159      * @param null|array|Zend_Config $options
0160      */
0161     public function __construct($options = null)
0162     {
0163         // Call parent constructor with options
0164         parent::__construct($options);
0165 
0166         // Check if a width was set, else use auto width
0167         if ($this->_width === null) {
0168             $this->setWidth();
0169         }
0170     }
0171 
0172     /**
0173      * Close local stdout, when open
0174      */
0175     public function __destruct()
0176     {
0177         if ($this->_outputStream !== null) {
0178             fclose($this->_outputStream);
0179         }
0180     }
0181 
0182     /**
0183      * Set a different output-stream
0184      *
0185      * @param  string $resource
0186      * @return Zend_ProgressBar_Adapter_Console
0187      */
0188     public function setOutputStream($resource)
0189     {
0190        $stream = @fopen($resource, 'w');
0191 
0192        if ($stream === false) {
0193             // require_once 'Zend/ProgressBar/Adapter/Exception.php';
0194             throw new Zend_ProgressBar_Adapter_Exception('Unable to open stream');
0195        }
0196 
0197        if ($this->_outputStream !== null) {
0198            fclose($this->_outputStream);
0199        }
0200 
0201        $this->_outputStream = $stream;
0202     }
0203 
0204     /**
0205      * Get the current output stream
0206      *
0207      * @return resource
0208      */
0209     public function getOutputStream()
0210     {
0211         if ($this->_outputStream === null) {
0212             if (!defined('STDOUT')) {
0213                 $this->_outputStream = fopen('php://stdout', 'w');
0214             } else {
0215                 return STDOUT;
0216             }
0217         }
0218 
0219         return $this->_outputStream;
0220     }
0221 
0222     /**
0223      * Set the width of the progressbar
0224      *
0225      * @param  integer $width
0226      * @return Zend_ProgressBar_Adapter_Console
0227      */
0228     public function setWidth($width = null)
0229     {
0230         if ($width === null || !is_integer($width)) {
0231             if (substr(PHP_OS, 0, 3) === 'WIN') {
0232                 // We have to default to 79 on windows, because the windows
0233                 // terminal always has a fixed width of 80 characters and the
0234                 // cursor is counted to the line, else windows would line break
0235                 // after every update.
0236                 $this->_width = 79;
0237             } else {
0238                 // Set the default width of 80
0239                 $this->_width = 80;
0240 
0241                 // Try to determine the width through stty
0242                 if (preg_match('#\d+ (\d+)#', @shell_exec('stty size'), $match) === 1) {
0243                     $this->_width = (int) $match[1];
0244                 } else if (preg_match('#columns = (\d+);#', @shell_exec('stty'), $match) === 1) {
0245                     $this->_width = (int) $match[1];
0246                 }
0247             }
0248         } else {
0249             $this->_width = (int) $width;
0250         }
0251 
0252         $this->_calculateBarWidth();
0253 
0254         return $this;
0255     }
0256 
0257     /**
0258      * Set the elements to display with the progressbar
0259      *
0260      * @param  array $elements
0261      * @throws Zend_ProgressBar_Adapter_Exception When an invalid element is foudn in the array
0262      * @return Zend_ProgressBar_Adapter_Console
0263      */
0264     public function setElements(array $elements)
0265     {
0266         $allowedElements = array(self::ELEMENT_PERCENT,
0267                                  self::ELEMENT_BAR,
0268                                  self::ELEMENT_ETA,
0269                                  self::ELEMENT_TEXT);
0270 
0271         if (count(array_diff($elements, $allowedElements)) > 0) {
0272             // require_once 'Zend/ProgressBar/Adapter/Exception.php';
0273             throw new Zend_ProgressBar_Adapter_Exception('Invalid element found in $elements array');
0274         }
0275 
0276         $this->_elements = $elements;
0277 
0278         $this->_calculateBarWidth();
0279 
0280         return $this;
0281     }
0282 
0283     /**
0284      * Set the left-hand character for the bar
0285      *
0286      * @param  string $char
0287      * @throws Zend_ProgressBar_Adapter_Exception When character is empty
0288      * @return Zend_ProgressBar_Adapter_Console
0289      */
0290     public function setBarLeftChar($char)
0291     {
0292         if (empty($char)) {
0293             // require_once 'Zend/ProgressBar/Adapter/Exception.php';
0294             throw new Zend_ProgressBar_Adapter_Exception('Character may not be empty');
0295         }
0296 
0297         $this->_barLeftChar = (string) $char;
0298 
0299         return $this;
0300     }
0301 
0302     /**
0303      * Set the right-hand character for the bar
0304      *
0305      * @param  string $char
0306      * @throws Zend_ProgressBar_Adapter_Exception When character is empty
0307      * @return Zend_ProgressBar_Adapter_Console
0308      */
0309     public function setBarRightChar($char)
0310     {
0311         if (empty($char)) {
0312             // require_once 'Zend/ProgressBar/Adapter/Exception.php';
0313             throw new Zend_ProgressBar_Adapter_Exception('Character may not be empty');
0314         }
0315 
0316         $this->_barRightChar = (string) $char;
0317 
0318         return $this;
0319     }
0320 
0321     /**
0322      * Set the indicator character for the bar
0323      *
0324      * @param  string $char
0325      * @return Zend_ProgressBar_Adapter_Console
0326      */
0327     public function setBarIndicatorChar($char)
0328     {
0329         $this->_barIndicatorChar = (string) $char;
0330 
0331         return $this;
0332     }
0333 
0334     /**
0335      * Set the width of the text element
0336      *
0337      * @param  integer $width
0338      * @return Zend_ProgressBar_Adapter_Console
0339      */
0340     public function setTextWidth($width)
0341     {
0342         $this->_textWidth = (int) $width;
0343 
0344         $this->_calculateBarWidth();
0345 
0346         return $this;
0347     }
0348 
0349     /**
0350      * Set the charset of the text element
0351      *
0352      * @param string $charset
0353      */
0354     public function setCharset($charset)
0355     {
0356         $this->_charset = $charset;
0357     }
0358 
0359     /**
0360      * Set the finish action
0361      *
0362      * @param  string $action
0363      * @throws Zend_ProgressBar_Adapter_Exception When an invalid action is specified
0364      * @return Zend_ProgressBar_Adapter_Console
0365      */
0366     public function setFinishAction($action)
0367     {
0368         $allowedActions = array(self::FINISH_ACTION_CLEAR_LINE,
0369                                 self::FINISH_ACTION_EOL,
0370                                 self::FINISH_ACTION_NONE);
0371 
0372         if (!in_array($action, $allowedActions)) {
0373             // require_once 'Zend/ProgressBar/Adapter/Exception.php';
0374             throw new Zend_ProgressBar_Adapter_Exception('Invalid finish action specified');
0375         }
0376 
0377         $this->_finishAction = $action;
0378 
0379         return $this;
0380     }
0381 
0382     /**
0383      * Defined by Zend_ProgressBar_Adapter_Interface
0384      *
0385      * @param  float   $current       Current progress value
0386      * @param  float   $max           Max progress value
0387      * @param  float   $percent       Current percent value
0388      * @param  integer $timeTaken     Taken time in seconds
0389      * @param  integer $timeRemaining Remaining time in seconds
0390      * @param  string  $text          Status text
0391      * @return void
0392      */
0393     public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
0394     {
0395         // See if we must clear the line
0396         if ($this->_outputStarted) {
0397             $data = str_repeat("\x08", $this->_width);
0398         } else {
0399             $data = '';
0400             $this->_outputStarted = true;
0401         }
0402 
0403         // Build all elements
0404         $renderedElements = array();
0405 
0406         foreach ($this->_elements as $element) {
0407             switch ($element) {
0408                 case self::ELEMENT_BAR:
0409                     $visualWidth = $this->_barWidth - 2;
0410                     $bar         = '[';
0411 
0412                     $indicatorWidth = strlen($this->_barIndicatorChar);
0413 
0414                     $doneWidth = min($visualWidth - $indicatorWidth, round($visualWidth * $percent));
0415                     if ($doneWidth > 0) {
0416                         $bar .= substr(str_repeat($this->_barLeftChar, ceil($doneWidth / strlen($this->_barLeftChar))), 0, $doneWidth);
0417                     }
0418 
0419                     $bar .= $this->_barIndicatorChar;
0420 
0421                     $leftWidth = $visualWidth - $doneWidth - $indicatorWidth;
0422                     if ($leftWidth > 0) {
0423                         $bar .= substr(str_repeat($this->_barRightChar, ceil($leftWidth / strlen($this->_barRightChar))), 0, $leftWidth);
0424                     }
0425 
0426                     $bar .= ']';
0427 
0428                     $renderedElements[] = $bar;
0429                     break;
0430 
0431                 case self::ELEMENT_PERCENT:
0432                     $renderedElements[] = str_pad(round($percent * 100), 3, ' ', STR_PAD_LEFT) . '%';
0433                     break;
0434 
0435                 case self::ELEMENT_ETA:
0436                     // In the first 5 seconds we don't get accurate results,
0437                     // this skipping technique is found in many progressbar
0438                     // implementations.
0439                     if ($timeTaken < 5) {
0440                         $renderedElements[] = str_repeat(' ', 12);
0441                         break;
0442                     }
0443 
0444                     if ($timeRemaining === null || $timeRemaining > 86400) {
0445                         $etaFormatted = '??:??:??';
0446                     } else {
0447                         $hours   = floor($timeRemaining / 3600);
0448                         $minutes = floor(($timeRemaining % 3600) / 60);
0449                         $seconds = ($timeRemaining % 3600 % 60);
0450 
0451                         $etaFormatted = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
0452                     }
0453 
0454                     $renderedElements[] = 'ETA ' . $etaFormatted;
0455                     break;
0456 
0457                 case self::ELEMENT_TEXT:
0458                     $renderedElements[] = Zend_Text_MultiByte::strPad(substr($text, 0, $this->_textWidth), $this->_textWidth, ' ', STR_PAD_RIGHT, $this->_charset);
0459                     break;
0460             }
0461         }
0462 
0463         $data .= implode(' ', $renderedElements);
0464 
0465         // Output line data
0466         $this->_outputData($data);
0467     }
0468 
0469     /**
0470      * Defined by Zend_ProgressBar_Adapter_Interface
0471      *
0472      * @return void
0473      */
0474     public function finish()
0475     {
0476         switch ($this->_finishAction) {
0477             case self::FINISH_ACTION_EOL:
0478                 $this->_outputData(PHP_EOL);
0479                 break;
0480 
0481             case self::FINISH_ACTION_CLEAR_LINE:
0482                 if ($this->_outputStarted) {
0483                     $data = str_repeat("\x08", $this->_width)
0484                           . str_repeat(' ', $this->_width)
0485                           . str_repeat("\x08", $this->_width);
0486 
0487                     $this->_outputData($data);
0488                 }
0489                 break;
0490 
0491             case self::FINISH_ACTION_NONE:
0492                 break;
0493         }
0494     }
0495 
0496     /**
0497      * Calculate the bar width when other elements changed
0498      *
0499      * @return void
0500      */
0501     protected function _calculateBarWidth()
0502     {
0503         if (in_array(self::ELEMENT_BAR, $this->_elements)) {
0504             $barWidth = $this->_width;
0505 
0506             if (in_array(self::ELEMENT_PERCENT, $this->_elements)) {
0507                 $barWidth -= 4;
0508             }
0509 
0510             if (in_array(self::ELEMENT_ETA, $this->_elements)) {
0511                 $barWidth -= 12;
0512             }
0513 
0514             if (in_array(self::ELEMENT_TEXT, $this->_elements)) {
0515                 $barWidth -= $this->_textWidth;
0516             }
0517 
0518             $this->_barWidth = $barWidth - (count($this->_elements) - 1);
0519         }
0520     }
0521 
0522     /**
0523      * Outputs given data to STDOUT.
0524      *
0525      * This split-off is required for unit-testing.
0526      *
0527      * @param  string $data
0528      * @return void
0529      */
0530     protected function _outputData($data)
0531     {
0532         fwrite($this->getOutputStream(), $data);
0533     }
0534 }