diff --git a/composer.lock b/composer.lock index 953c1a7a2..da880d14a 100644 --- a/composer.lock +++ b/composer.lock @@ -1,6 +1,10 @@ { "hash": "9c243b2c15fdc7c3e35c5200d704ba53", "packages": [ + { + "package": "symfony\/process", + "version": "2.1.0-dev" + }, { "package": "symfony\/finder", "version": "2.1.0-dev" @@ -8,10 +12,6 @@ { "package": "symfony\/console", "version": "2.1.0-dev" - }, - { - "package": "symfony\/process", - "version": "2.1.0-dev" } ] } \ No newline at end of file diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8ae89c93c..b830986cd 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -14,7 +14,7 @@ namespace Composer\Downloader; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; -use Composer\Util\StreamContextFactory; +use Composer\Util\RemoteFilesystem; /** * Base downloader for file packages @@ -26,7 +26,6 @@ use Composer\Util\StreamContextFactory; abstract class FileDownloader implements DownloaderInterface { protected $io; - private $bytesMax; /** * Constructor. @@ -51,9 +50,6 @@ abstract class FileDownloader implements DownloaderInterface */ public function download(PackageInterface $package, $path) { - // init the progress bar - $this->bytesMax = 0; - $url = $package->getDistUrl(); $checksum = $package->getDistSha1Checksum(); @@ -79,18 +75,9 @@ abstract class FileDownloader implements DownloaderInterface } } - $options = array(); - if ($this->io->hasAuthorization($package->getSourceUrl())) { - $auth = $this->io->getAuthorization($package->getSourceUrl()); - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $options['http']['header'] = "Authorization: Basic $authStr\r\n"; - } - - $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); - - $this->io->overwrite(" Downloading: connection...", false); - @copy($url, $fileName, $ctx); - $this->io->overwrite(" Downloading"); + $rfs = new RemoteFilesystem($this->io); + $rfs->copy($package->getSourceUrl(), $url, $fileName); + $this->io->write(''); if (!file_exists($fileName)) { throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' @@ -148,52 +135,6 @@ abstract class FileDownloader implements DownloaderInterface $fs->removeDirectory($path); } - /** - * Get notification action. - * - * @param integer $notificationCode The notification code - * @param integer $severity The severity level - * @param string $message The message - * @param integer $messageCode The message code - * @param integer $bytesTransferred The loaded size - * @param integer $bytesMax The total size - */ - protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) - { - switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: - throw new \LogicException("Authorization is required"); - break; - - case STREAM_NOTIFY_FAILURE: - throw new \LogicException("File not found"); - break; - - case STREAM_NOTIFY_FILE_SIZE_IS: - if ($this->bytesMax < $bytesMax) { - $this->bytesMax = $bytesMax; - } - break; - - case STREAM_NOTIFY_PROGRESS: - if ($this->bytesMax > 0) { - $progression = 0; - - if ($this->bytesMax > 0) { - $progression = round($bytesTransferred / $this->bytesMax * 100); - } - - if (0 === $progression % 5) { - $this->io->overwrite(" Downloading: $progression%", false); - } - } - break; - - default: - break; - } - } - /** * Extract file to directory * diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 6addf26e7..7008f41be 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -14,6 +14,7 @@ namespace Composer\Repository\Vcs; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; +use Composer\Util\RemoteFilesystem; /** * A driver implementation for driver with authorization interaction. @@ -25,9 +26,6 @@ abstract class VcsDriver protected $url; protected $io; protected $process; - private $firstCall; - private $contentUrl; - private $content; /** * Constructor. @@ -41,7 +39,6 @@ abstract class VcsDriver $this->url = $url; $this->io = $io; $this->process = $process ?: new ProcessExecutor; - $this->firstCall = true; } /** @@ -68,85 +65,7 @@ abstract class VcsDriver */ protected function getContents($url) { - $this->contentUrl = $url; - $auth = $this->io->getAuthorization($this->url); - $params = array(); - - // add authorization to curl options - if ($this->io->hasAuthorization($this->url)) { - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - } else if (null !== $this->io->getLastUsername()) { - $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); - $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - $this->io->setAuthorization($this->url, $this->io->getLastUsername(), $this->io->getLastPassword()); - } - - $ctx = stream_context_create($params); - stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); - - $content = @file_get_contents($url, false, $ctx); - - // content get after authorization - if (false === $content) { - $content = $this->content; - $this->content = null; - $this->contentUrl = null; - } - - return $content; - } - - /** - * Get notification action. - * - * @param integer $notificationCode The notification code - * @param integer $severity The severity level - * @param string $message The message - * @param integer $messageCode The message code - * @param integer $bytesTransferred The loaded size - * @param integer $bytesMax The total size - */ - protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) - { - switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: - case STREAM_NOTIFY_FAILURE: - // for private repository returning 404 error when the authorization is incorrect - $auth = $this->io->getAuthorization($this->url); - $ps = $this->firstCall && 404 === $messageCode - && null === $this->io->getLastUsername() - && null === $auth['username']; - - if (404 === $messageCode && !$this->firstCall) { - throw new \RuntimeException("The '" . $this->contentUrl . "' URL not found"); - } - - $this->firstCall = false; - - // get authorization informations - if (401 === $messageCode || $ps) { - if (!$this->io->isInteractive()) { - $mess = "The '" . $this->contentUrl . "' URL not found"; - - if (401 === $code || $ps) { - $mess = "The '" . $this->contentUrl . "' URL required the authorization.\nYou must be used the interactive console"; - } - - throw new \RuntimeException($mess); - } - - $this->io->write("Authorization for " . $this->contentUrl . ":"); - $username = $this->io->ask(' Username: '); - $password = $this->io->askAndHideAnswer(' Password: '); - $this->io->setAuthorization($this->url, $username, $password); - - $this->content = $this->getContents($this->contentUrl); - } - break; - - default: - break; - } + $rfs = new RemoteFilesystem($this->io); + return $rfs->getContents($this->url, $url, false); } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php new file mode 100644 index 000000000..2d4be57d6 --- /dev/null +++ b/src/Composer/Util/RemoteFilesystem.php @@ -0,0 +1,209 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; + +/** + * @author François Pluchino + */ +class RemoteFilesystem +{ + private $io; + private $firstCall; + private $bytesMax; + private $originUrl; + private $fileUrl; + private $fileName; + private $content; + private $progess; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + */ + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * Copy the remote file in local. + * + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param boolean $progess Display the progression + */ + public function copy($originUrl, $fileUrl, $fileName, $progess = true) + { + $this->get($originUrl, $fileUrl, $fileName, $progess); + } + + /** + * Get the content. + * + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param boolean $progess Display the progression + * + * @return string The content + */ + public function getContents($originUrl, $fileUrl, $progess = true) + { + $this->get($originUrl, $fileUrl, null, $progess); + + return $this->content; + } + + /** + * Get file content or copy action. + * + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param boolean $progess Display the progression + * + * @throws \RuntimeException When the file could not be downloaded + */ + protected function get($originUrl, $fileUrl, $fileName = null, $progess = true) + { + $this->firstCall = true; + $this->bytesMax = 0; + $this->content = null; + $this->originUrl = $originUrl; + $this->fileUrl = $fileUrl; + $this->fileName = $fileName; + $this->progress = $progess; + + // add authorization in context + $options = array(); + if ($this->io->hasAuthorization($originUrl)) { + $auth = $this->io->getAuthorization($originUrl); + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $options['http']['header'] = "Authorization: Basic $authStr\r\n"; + } else if (null !== $this->io->getLastUsername()) { + $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); + $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); + } + + $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); + + if ($this->progress) { + $this->io->overwrite(" Downloading: connection...", false); + } + + if (null !== $fileName) { + $result = @copy($fileUrl, $fileName, $ctx); + } else { + $result = @file_get_contents($fileUrl, false, $ctx); + $this->content = $result; + } + + if ($this->progress) { + $this->io->overwrite(" Downloading", false); + } + + if (false === $result) { + throw new \RuntimeException("the '$fileUrl' file could not be downloaded"); + } + } + + /** + * Get notification action. + * + * @param integer $notificationCode The notification code + * @param integer $severity The severity level + * @param string $message The message + * @param integer $messageCode The message code + * @param integer $bytesTransferred The loaded size + * @param integer $bytesMax The total size + */ + protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) + { + switch ($notificationCode) { + case STREAM_NOTIFY_AUTH_REQUIRED: + case STREAM_NOTIFY_FAILURE: + // for private repository returning 404 error when the authorization is incorrect + $auth = $this->io->getAuthorization($this->originUrl); + $ps = $this->firstCall && 404 === $messageCode && null === $auth['username']; + + if (404 === $messageCode && !$this->firstCall) { + throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); + } + + $this->firstCall = false; + + // get authorization informations + if (401 === $messageCode || $ps) { + if (!$this->io->isInteractive()) { + $mess = "The '" . $this->fileUrl . "' URL was not found"; + + if (401 === $code || $ps) { + $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be using the interactive console"; + } + + throw new \RuntimeException($mess); + } + + $this->io->overwrite(' Authorization required (' .$this->getHostname($this->fileUrl).'):'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthorization($this->originUrl, $username, $password); + + $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress); + } + break; + + case STREAM_NOTIFY_FILE_SIZE_IS: + if ($this->bytesMax < $bytesMax) { + $this->bytesMax = $bytesMax; + } + break; + + case STREAM_NOTIFY_PROGRESS: + if ($this->bytesMax > 0 && $this->progress) { + $progression = 0; + + if ($this->bytesMax > 0) { + $progression = round($bytesTransferred / $this->bytesMax * 100); + } + + if (0 === $progression % 5) { + $this->io->overwrite(" Downloading: $progression%", false); + } + } + break; + + default: + break; + } + } + + /** + * Get the hostname. + * + * @param string $url The file URL + * + * @return string The hostname + */ + protected function getHostname($url) + { + $host = substr($url, strpos($url, '://') + 3); + $host = substr($host, 0, strpos($host, '/')); + + return $host; + } +} \ No newline at end of file