Merge branch 'cache'

main
Jordi Boggiano 12 years ago
commit dc315cc768

@ -13,6 +13,8 @@
namespace Composer; namespace Composer;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Symfony\Component\Finder\Finder;
/** /**
* Reads/writes to a filesystem cache * Reads/writes to a filesystem cache
@ -24,11 +26,21 @@ class Cache
private $io; private $io;
private $root; private $root;
private $enabled = true; private $enabled = true;
private $whitelist;
private $filesystem;
public function __construct(IOInterface $io, $cacheDir) /**
* @param IOInterface $io
* @param string $cacheDir location of the cache
* @param string $whitelist List of characters that are allowed in path names (used in a regex character class)
* @param Filesystem $filesystem optional filesystem instance
*/
public function __construct(IOInterface $io, $cacheDir, $whitelist = 'a-z0-9.', Filesystem $filesystem = null)
{ {
$this->io = $io; $this->io = $io;
$this->root = rtrim($cacheDir, '/\\') . '/'; $this->root = rtrim($cacheDir, '/\\') . '/';
$this->whitelist = $whitelist;
$this->filesystem = $filesystem ?: new Filesystem();
if (!is_dir($this->root)) { if (!is_dir($this->root)) {
if (!@mkdir($this->root, 0777, true)) { if (!@mkdir($this->root, 0777, true)) {
@ -44,33 +56,79 @@ class Cache
public function read($file) public function read($file)
{ {
$file = preg_replace('{[^a-z0-9.]}i', '-', $file); $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
if ($this->enabled && file_exists($this->root . $file)) { if ($this->enabled && file_exists($this->root . $file)) {
return file_get_contents($this->root . $file); return file_get_contents($this->root . $file);
} }
return false;
} }
public function write($file, $contents) public function write($file, $contents)
{ {
if ($this->enabled) { if ($this->enabled) {
$file = preg_replace('{[^a-z0-9.]}i', '-', $file); $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
file_put_contents($this->root . $file, $contents);
return file_put_contents($this->root . $file, $contents);
} }
return false;
}
public function copyFrom($file, $source)
{
if ($this->enabled) {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file));
return copy($source, $this->root . $file);
}
return false;
}
public function copyTo($file, $target)
{
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
if ($this->enabled && file_exists($this->root . $file)) {
touch($this->root . $file);
return copy($this->root . $file, $target);
}
return false;
}
public function gc($ttl)
{
$expire = new \DateTime();
$expire->modify('-'.$ttl.' seconds');
$finder = Finder::create()->files()->in($this->root)->date('until '.$expire->format('Y-m-d H:i:s'));
foreach ($finder as $file) {
unlink($file->getRealPath());
}
return true;
} }
public function sha1($file) public function sha1($file)
{ {
$file = preg_replace('{[^a-z0-9.]}i', '-', $file); $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
if ($this->enabled && file_exists($this->root . $file)) { if ($this->enabled && file_exists($this->root . $file)) {
return sha1_file($this->root . $file); return sha1_file($this->root . $file);
} }
return false;
} }
public function sha256($file) public function sha256($file)
{ {
$file = preg_replace('{[^a-z0-9.]}i', '-', $file); $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
if ($this->enabled && file_exists($this->root . $file)) { if ($this->enabled && file_exists($this->root . $file)) {
return hash_file('sha256', $this->root . $file); return hash_file('sha256', $this->root . $file);
} }
return false;
} }
} }

@ -21,6 +21,7 @@ class Config
{ {
public static $defaultConfig = array( public static $defaultConfig = array(
'process-timeout' => 300, 'process-timeout' => 300,
'cache-ttl' => 15552000, // 6 months
'vendor-dir' => 'vendor', 'vendor-dir' => 'vendor',
'bin-dir' => '{$vendor-dir}/bin', 'bin-dir' => '{$vendor-dir}/bin',
'notify-on-install' => true, 'notify-on-install' => true,

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Config; use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
@ -29,25 +30,34 @@ use Composer\Util\RemoteFilesystem;
*/ */
class FileDownloader implements DownloaderInterface class FileDownloader implements DownloaderInterface
{ {
private static $cacheCollected = false;
protected $io; protected $io;
protected $config; protected $config;
protected $rfs; protected $rfs;
protected $filesystem; protected $filesystem;
protected $cache;
/** /**
* Constructor. * Constructor.
* *
* @param IOInterface $io The IO instance * @param IOInterface $io The IO instance
* @param Config $config The config * @param Config $config The config
* @param Cache $cache Optional cache instance
* @param RemoteFilesystem $rfs The remote filesystem * @param RemoteFilesystem $rfs The remote filesystem
* @param Filesystem $filesystem The filesystem * @param Filesystem $filesystem The filesystem
*/ */
public function __construct(IOInterface $io, Config $config, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) public function __construct(IOInterface $io, Config $config, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
{ {
$this->io = $io; $this->io = $io;
$this->config = $config; $this->config = $config;
$this->rfs = $rfs ?: new RemoteFilesystem($io); $this->rfs = $rfs ?: new RemoteFilesystem($io);
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
$this->cache = $cache;
if ($this->cache && !self::$cacheCollected && !rand(0, 50)) {
$this->cache->gc($config->get('cache-ttl'));
}
self::$cacheCollected = true;
} }
/** /**
@ -78,7 +88,12 @@ class FileDownloader implements DownloaderInterface
try { try {
try { try {
$this->rfs->copy(parse_url($processUrl, PHP_URL_HOST), $processUrl, $fileName); if (!$this->cache || !$this->cache->copyTo($this->getCacheKey($package), $fileName)) {
$this->rfs->copy(parse_url($processUrl, PHP_URL_HOST), $processUrl, $fileName);
if ($this->cache) {
$this->cache->copyFrom($this->getCacheKey($package), $fileName);
}
}
} catch (TransportException $e) { } catch (TransportException $e) {
if (404 === $e->getCode() && 'github.com' === parse_url($processUrl, PHP_URL_HOST)) { if (404 === $e->getCode() && 'github.com' === parse_url($processUrl, PHP_URL_HOST)) {
$message = "\n".'Could not fetch '.$processUrl.', enter your GitHub credentials to access private repos'; $message = "\n".'Could not fetch '.$processUrl.', enter your GitHub credentials to access private repos';
@ -159,4 +174,13 @@ class FileDownloader implements DownloaderInterface
return $url; return $url;
} }
private function getCacheKey(PackageInterface $package)
{
if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) {
return $package->getName().'/'.$package->getDistReference().'.'.$package->getDistType();
}
return $package->getName().'/'.$package->getVersion().'-'.$package->getDistReference().'.'.$package->getDistType();
}
} }

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Config; use Composer\Config;
use Composer\Cache;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use ZipArchive; use ZipArchive;
@ -24,10 +25,10 @@ class ZipDownloader extends ArchiveDownloader
{ {
protected $process; protected $process;
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null) public function __construct(IOInterface $io, Config $config, Cache $cache = null, ProcessExecutor $process = null)
{ {
$this->process = $process ?: new ProcessExecutor; $this->process = $process ?: new ProcessExecutor;
parent::__construct($io, $config); parent::__construct($io, $config, $cache);
} }
protected function extract($file, $path) protected function extract($file, $path)

@ -240,14 +240,16 @@ class Factory
*/ */
public function createDownloadManager(IOInterface $io, Config $config) public function createDownloadManager(IOInterface $io, Config $config)
{ {
$cache = new Cache($io, $config->get('home').'/cache.files/', 'a-z0-9_./');
$dm = new Downloader\DownloadManager(); $dm = new Downloader\DownloadManager();
$dm->setDownloader('git', new Downloader\GitDownloader($io, $config)); $dm->setDownloader('git', new Downloader\GitDownloader($io, $config));
$dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config)); $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config));
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config)); $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config));
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config)); $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $cache));
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config)); $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $cache));
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config)); $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $cache));
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config)); $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $cache));
return $dm; return $dm;
} }

@ -23,7 +23,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
$config = $config ?: $this->getMock('Composer\Config'); $config = $config ?: $this->getMock('Composer\Config');
$rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); $rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock();
return new FileDownloader($io, $config, $rfs); return new FileDownloader($io, $config, null, $rfs);
} }
/** /**
@ -56,8 +56,11 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
$downloader->download($packageMock, $path); $downloader->download($packageMock, $path);
$this->fail(); $this->fail();
} catch (\Exception $e) { } catch (\Exception $e) {
if (file_exists($path)) { if (is_dir($path)) {
unset($path); $fs = new Filesystem();
$fs->removeDirectory($path);
} elseif (is_file($path)) {
unlink($path);
} }
$this->assertInstanceOf('RuntimeException', $e); $this->assertInstanceOf('RuntimeException', $e);
$this->assertContains('exists and is not a directory', $e->getMessage()); $this->assertContains('exists and is not a directory', $e->getMessage());
@ -112,7 +115,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
$fs = new Filesystem(); $fs = new Filesystem();
$fs->removeDirectory($path); $fs->removeDirectory($path);
} elseif (is_file($path)) { } elseif (is_file($path)) {
unset($path); unlink($path);
} }
$this->assertInstanceOf('UnexpectedValueException', $e); $this->assertInstanceOf('UnexpectedValueException', $e);
@ -150,7 +153,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
$fs = new Filesystem(); $fs = new Filesystem();
$fs->removeDirectory($path); $fs->removeDirectory($path);
} elseif (is_file($path)) { } elseif (is_file($path)) {
unset($path); unlink($path);
} }
$this->assertInstanceOf('UnexpectedValueException', $e); $this->assertInstanceOf('UnexpectedValueException', $e);

Loading…
Cancel
Save