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 }