Browse Source

Merge branch 'cache'

Jordi Boggiano 12 years ago
parent
commit
dc315cc768

+ 64 - 6
src/Composer/Cache.php

@@ -13,6 +13,8 @@
 namespace Composer;
 
 use Composer\IO\IOInterface;
+use Composer\Util\Filesystem;
+use Symfony\Component\Finder\Finder;
 
 /**
  * Reads/writes to a filesystem cache
@@ -24,11 +26,21 @@ class Cache
     private $io;
     private $root;
     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->root = rtrim($cacheDir, '/\\') . '/';
+        $this->whitelist = $whitelist;
+        $this->filesystem = $filesystem ?: new Filesystem();
 
         if (!is_dir($this->root)) {
             if (!@mkdir($this->root, 0777, true)) {
@@ -44,33 +56,79 @@ class Cache
 
     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)) {
             return file_get_contents($this->root . $file);
         }
+
+        return false;
     }
 
     public function write($file, $contents)
     {
         if ($this->enabled) {
-            $file = preg_replace('{[^a-z0-9.]}i', '-', $file);
-            file_put_contents($this->root . $file, $contents);
+            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
+
+            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)
     {
-        $file = preg_replace('{[^a-z0-9.]}i', '-', $file);
+        $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
         if ($this->enabled && file_exists($this->root . $file)) {
             return sha1_file($this->root . $file);
         }
+
+        return false;
     }
 
     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)) {
             return hash_file('sha256', $this->root . $file);
         }
+
+        return false;
     }
 }

+ 1 - 0
src/Composer/Config.php

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

+ 26 - 2
src/Composer/Downloader/FileDownloader.php

@@ -13,6 +13,7 @@
 namespace Composer\Downloader;
 
 use Composer\Config;
+use Composer\Cache;
 use Composer\IO\IOInterface;
 use Composer\Package\PackageInterface;
 use Composer\Package\Version\VersionParser;
@@ -29,25 +30,34 @@ use Composer\Util\RemoteFilesystem;
  */
 class FileDownloader implements DownloaderInterface
 {
+    private static $cacheCollected = false;
     protected $io;
     protected $config;
     protected $rfs;
     protected $filesystem;
+    protected $cache;
 
     /**
      * Constructor.
      *
      * @param IOInterface      $io         The IO instance
      * @param Config           $config     The config
+     * @param Cache            $cache      Optional cache instance
      * @param RemoteFilesystem $rfs        The remote 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->config = $config;
         $this->rfs = $rfs ?: new RemoteFilesystem($io);
         $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 {
-                $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) {
                 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';
@@ -159,4 +174,13 @@ class FileDownloader implements DownloaderInterface
 
         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();
+    }
 }

+ 3 - 2
src/Composer/Downloader/ZipDownloader.php

@@ -13,6 +13,7 @@
 namespace Composer\Downloader;
 
 use Composer\Config;
+use Composer\Cache;
 use Composer\Util\ProcessExecutor;
 use Composer\IO\IOInterface;
 use ZipArchive;
@@ -24,10 +25,10 @@ class ZipDownloader extends ArchiveDownloader
 {
     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;
-        parent::__construct($io, $config);
+        parent::__construct($io, $config, $cache);
     }
 
     protected function extract($file, $path)

+ 6 - 4
src/Composer/Factory.php

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

+ 8 - 5
tests/Composer/Test/Downloader/FileDownloaderTest.php

@@ -23,7 +23,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
         $config = $config ?: $this->getMock('Composer\Config');
         $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);
             $this->fail();
         } catch (\Exception $e) {
-            if (file_exists($path)) {
-                unset($path);
+            if (is_dir($path)) {
+                $fs = new Filesystem();
+                $fs->removeDirectory($path);
+            } elseif (is_file($path)) {
+                unlink($path);
             }
             $this->assertInstanceOf('RuntimeException', $e);
             $this->assertContains('exists and is not a directory', $e->getMessage());
@@ -112,7 +115,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
                 $fs = new Filesystem();
                 $fs->removeDirectory($path);
             } elseif (is_file($path)) {
-                unset($path);
+                unlink($path);
             }
 
             $this->assertInstanceOf('UnexpectedValueException', $e);
@@ -150,7 +153,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
                 $fs = new Filesystem();
                 $fs->removeDirectory($path);
             } elseif (is_file($path)) {
-                unset($path);
+                unlink($path);
             }
 
             $this->assertInstanceOf('UnexpectedValueException', $e);