run(); function notifier( $code, $severity, $msg, $xcode, $sofar, $fileSize ) { global $webDownloader; if ( $code == STREAM_NOTIFY_FILE_SIZE_IS ) { $webDownloader->setFileSize( $fileSize ); } } class ezcWebDownloader { protected $output; protected $input; protected $bandwidth; protected $user; protected $pass; protected $fileSize; function notifier( $code, $severity, $msg, $xcode, $sofar, $fileSize ) { if ( $code == STREAM_NOTIFY_FILE_SIZE_IS ) { $this->setFileSize( $fileSize ); } } public function run() { $this->init(); $this->output->outputLine( 'ezcWebDownloader version 1.0devel', 'info' ); $this->output->outputLine(); if ( sizeof( $this->input->getArguments() ) === 0 && sizeof( $this->input->getOptionValues() ) === 0 || $this->input->getOption( 'h' )->value === true ) { $this->printHelp(); exit(0); } if ( sizeof( ( $args = $this->input->getArguments() ) ) === 0 ) { $this->raiseError( 'At least 1 URL to download is required as an argument, but none not provided.' ); } else { foreach ( $args as $url ) { $finalUrl = $this->formUrl( $url ); if ( !$finalUrl ) { $this->output->outputLine( "Invalid URL skipped '$url'", 'info' ); break; } $this->getHTTPFile( $finalUrl ); } } } protected function formUrl( $url ) { $parsedUrl = parse_url( $url ); if ( $parsedUrl['host'] == '' && $parsedUrl['scheme'] == '' ) { $url = 'http://'.$url ; $parsedUrl = parse_url( $url ); } if ( !$parsedUrl ) { $this->output->outputLine( "Malformed URL: '$url'", 'error' ); return false; } if ( $parsedUrl['scheme'] != 'http' ) { if ( $parsedUrl['scheme'] == '' ) { $parsedUrl['scheme'] = 'http'; } else { $this->output->outputLine( "Only HTTP protocol supported but found '{$parsedUrl['scheme']}'", 'error' ); return false; } } if ( $parsedUrl['pass'] == '' && $this->pass !== false ) { $parsedUrl['pass'] = $this->pass; } if ( $parsedUrl['user'] == '' && $this->user !== false ) { $parsedUrl['user'] = $this->user; } $userString = ''; if ( $parsedUrl['user'] ) { $userString = $parsedUrl['user']; if ( $parsedUrl['pass'] ) { $userString .= ':'.$parsedUrl['pass']; } $userString .= '@'; } // form url including user and password if provided $url = $parsedUrl['scheme'].'://'. $userString. $parsedUrl['host']. ($parsedUrl['port']?':'.$parsedUrl['port']:''). $parsedUrl['path']. $parsedUrl['query']. $parsedUrl['fragment']; return $url; } //return name of the file where given url will be saved protected function getFileName( $url ) { $urlArray = parse_url( $url ); if ( !$urlArray ) { return 'index.html'; } if ( $urlPath = $urlArray['path'] ) { return basename( $urlPath ); } return 'index.html'; } // downloading and saving url with progress indication protected function getHTTPFile( $url ) { $fileToSave = $this->getFileName( $url ); $out = $this->output; $out->outputText( "Downloading file <$url>.\n" ); if ( !$handle = fopen( $url, "rb" ) ) { $out->outputLine( "Error: Can't get given URL '$url'." ); return; } if ( !$file = fopen( $fileToSave, 'wb' ) ) { $out->outputText( "Error: Can't open '$fileToSave' for writing.\n" ); return; } $progressThreshold = 0; $isHaveProgressBar = false; $this->fileSizeHTTP( $url ); if ( $this->fileSize ) { $progressThreshold = (int)($this->fileSize/100)+1; $out->outputText( "File size: ".$this->fileSize."\n" ); // Create progress bar $progress = new ezcConsoleProgressbar( $out, (int)$this->fileSize, array( 'step' => $progressThreshold ) ); $progress->options->emptyChar = '-'; $progress->options->progressChar = '#'; $progress->options->formatString = "Downloading:%act%/%max%kb [%bar%]%fraction%%"; $isHaveProgressBar = true; } else { $out->outputText( "File size: unknown.\n" ); } $downloadedTotal = 0; $lastProgressAdvance = 0; $totalTimeStart = microtime(true); //Download file by chunks and save while ( !feof( $handle ) ) { $portion = ''; $bytesInThisSecond = 0; $timeStart = microtime(true); $timeElapsed = 0; while ( $timeElapsed < 1.0 ) //Process speed limitation { $data = fread( $handle, 1024 ); $dataLength = strlen($data ); $portion .= $data; $bytesInThisSecond += $dataLength; $downloadedTotal += $dataLength; if ( ( $progressThreshold > 0 ) && ( $downloadedTotal - $lastProgressAdvance > $progressThreshold ) ) { //advance progress bar maybe several times if received portion is more than one threshold in length while ( $downloadedTotal - $lastProgressAdvance > $progressThreshold ) { $progress->advance(); $lastProgressAdvance += $progressThreshold; } } $timeElapsed = microtime(true) - $timeStart; if ( !$this->bandwidth ) //just no delays if there is no bandwidth limitations provided { break; } if ( $bytesInThisSecond >= $this->bandwidth*1024 && $timeElapsed < 1.0 && !feof( $handle ) ) { usleep( (int)((1.0 - $timeElapsed)*1000000) ); //sleep until the end of the second break; } } //Write portion to file if ( !fwrite( $file, $portion ) ) { $out->outputText( "Error: Can't write '$fileToSave' to the disk.\n" ); return; } $currentSpeed = sprintf( "%1.2f", $downloadedTotal / (( microtime(true) - $totalTimeStart )*1024) ); if ( !$isHaveProgressBar ) { $out->toPos(); $out->outputText(" $downloadedTotal $currentSpeed K/s"); } else { $progress->output(); $out->outputText(" $downloadedTotal $currentSpeed K/s"); } } fclose( $handle ); fclose( $file ); if ( !$isHaveProgressBar ) //set progress bar complete with already downloaded file now we know how much received. { $out->outputText(" "); $progress = new ezcConsoleProgressbar( $out, $downloadedTotal, array( 'step' => (int)($downloadedTotal/100)+1 ) ); $progress->advance(); } // Finish progress bar and jump to next line. $progress->finish(); $out->outputText( "\n" ); $out->outputText( "Successfully downloaded <$url>.\n", 'success' ); } protected function printHelp() { $table = new ezcConsoleTable( $this->output, 78 ); $table->options->widthType = ezcConsoleTable::WIDTH_FIXED; foreach ( $this->input->getOptions() as $id => $option ){ $table[$id][]->content = '-'.$option->short.'/--'.$option->long; $table[$id][]->content = $option->shorthelp; } $this->output->outputLine( 'Commandline help.' ); $this->output->outputLine( $this->input->getSynopsis() ); $table->outputTable(); $this->output->outputLine(); } protected function init() { // Output configuration $this->output = new ezcConsoleOutput(); $this->output->formats->error->color = 'red'; $this->output->formats->error->style = array( 'bold' ); $this->output->formats->info->color = 'blue'; $this->output->formats->info->style = array( 'bold' ); $this->output->formats->log->color = 'gray'; $this->output->formats->log->style = array( 'bold' ); // Option configuration $this->input = new ezcConsoleInput(); $helpOption = $this->input->registerOption( new ezcConsoleOption( 'h', 'help' ) ); $helpOption->shorthelp = 'Get help information.'; $banwidthOption = $this->input->registerOption( new ezcConsoleOption( 'b', 'bandwidth-limit', ezcConsoleInput::TYPE_INT ) ); $banwidthOption->shorthelp = 'Bandwidth limit in Kb per second.'; $userOption = $this->input->registerOption( new ezcConsoleOption( 'u', 'http-user', ezcConsoleInput::TYPE_STRING ) ); $userOption->shorthelp = 'Connect to server with user USER.'; $passOption = $this->input->registerOption( new ezcConsoleOption( 'p', 'http-password', ezcConsoleInput::TYPE_STRING ) ); $passOption->shorthelp = 'Connect to server with password PWD.'; try { $this->input->process(); } catch ( ezcConsoleOptionException $e ) { $this->raiseError( $e->getMessage() ); } $this->bandwidth = $this->input->getOption( 'bandwidth-limit' )->value; if ( $this->bandwidth === false ) { $this->bandwidth = 0; // 0 means unlimited download speed. } $this->user = $this->input->getOption( 'http-user' )->value; $this->pass = $this->input->getOption( 'http-password' )->value; } protected function raiseError( $msg ) { $this->output->outputLine( $msg, 'error' ); die( 255 ); } protected function fileSizeHTTP( $url ) { $ctxt = stream_context_create(); stream_context_set_params($ctxt, array( "notification" => array( $this, "notifier" ) ) ); $f = fopen ( $url, "rb", false, $ctxt ); fclose( $f ); } public function setFileSize( $fileSize ) { $this->fileSize = $fileSize; } } ?>