From 56712b10c78a17f8305436c5a4ccdf3106107691 Mon Sep 17 00:00:00 2001 From: Manuel Pichler Date: Fri, 27 Feb 2015 16:07:36 +0100 Subject: [PATCH] Refs pdepend/pdepend#90: A simple file cache garbage collector implemented. --- composer.lock | 160 +++++++++++++++--- src/main/php/PDepend/Engine.php | 2 +- .../Driver/File/FileCacheGarbageCollector.php | 139 +++++++++++++++ .../Util/Cache/Driver/FileCacheDriver.php | 20 +++ .../File/FileCacheGarbageCollectorTest.php | 146 ++++++++++++++++ 5 files changed, 440 insertions(+), 27 deletions(-) create mode 100644 src/main/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollector.php create mode 100644 src/test/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollectorTest.php diff --git a/composer.lock b/composer.lock index a0f6b1a1f0..76c42a09da 100644 --- a/composer.lock +++ b/composer.lock @@ -8,17 +8,17 @@ "packages": [ { "name": "symfony/config", - "version": "v2.6.3", + "version": "v2.6.4", "target-dir": "Symfony/Component/Config", "source": { "type": "git", "url": "https://github.com/symfony/Config.git", - "reference": "d94f222eff99a22ce313555b78642b4873418d56" + "reference": "a9f781ba1221067d1f07c8cec0bc50f81b8d7408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/d94f222eff99a22ce313555b78642b4873418d56", - "reference": "d94f222eff99a22ce313555b78642b4873418d56", + "url": "https://api.github.com/repos/symfony/Config/zipball/a9f781ba1221067d1f07c8cec0bc50f81b8d7408", + "reference": "a9f781ba1221067d1f07c8cec0bc50f81b8d7408", "shasum": "" }, "require": { @@ -52,21 +52,21 @@ ], "description": "Symfony Config Component", "homepage": "http://symfony.com", - "time": "2015-01-03 08:01:59" + "time": "2015-01-21 20:57:55" }, { "name": "symfony/dependency-injection", - "version": "v2.6.3", + "version": "v2.6.4", "target-dir": "Symfony/Component/DependencyInjection", "source": { "type": "git", "url": "https://github.com/symfony/DependencyInjection.git", - "reference": "72db9adf2f6c42e773108038bcdaff263f325b4e" + "reference": "42bbb43fab66292a1865dc9616c299904c3d4d14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/72db9adf2f6c42e773108038bcdaff263f325b4e", - "reference": "72db9adf2f6c42e773108038bcdaff263f325b4e", + "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/42bbb43fab66292a1865dc9616c299904c3d4d14", + "reference": "42bbb43fab66292a1865dc9616c299904c3d4d14", "shasum": "" }, "require": { @@ -112,11 +112,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "http://symfony.com", - "time": "2015-01-05 17:41:06" + "time": "2015-01-25 04:39:26" }, { "name": "symfony/filesystem", - "version": "v2.6.3", + "version": "v2.6.4", "target-dir": "Symfony/Component/Filesystem", "source": { "type": "git", @@ -217,6 +217,114 @@ ], "time": "2014-10-13 12:58:55" }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "~1.0,>=1.0.2", + "phpdocumentor/reflection-docblock": "~2.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "http://phpspec.org", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2014-11-17 16:23:49" + }, { "name": "phpunit/php-code-coverage", "version": "2.0.15", @@ -463,16 +571,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.4.5", + "version": "4.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a" + "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2e8580deebb7d1ac92ac878595e6bffe01069c2a", - "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5b578d3865a9128b9c209b011fda6539ec06e7a5", + "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5", "shasum": "" }, "require": { @@ -482,17 +590,17 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", + "phpspec/prophecy": "~1.3.1", "phpunit/php-code-coverage": "~2.0", "phpunit/php-file-iterator": "~1.3.2", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "~1.0.2", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.0", + "sebastian/comparator": "~1.1", "sebastian/diff": "~1.1", - "sebastian/environment": "~1.1", - "sebastian/exporter": "~1.1", + "sebastian/environment": "~1.2", + "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", - "sebastian/recursion-context": "~1.0", "sebastian/version": "~1.0", "symfony/yaml": "~2.0" }, @@ -505,7 +613,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4.x-dev" + "dev-master": "4.5.x-dev" } }, "autoload": { @@ -531,7 +639,7 @@ "testing", "xunit" ], - "time": "2015-01-27 16:06:15" + "time": "2015-02-05 15:51:19" }, { "name": "phpunit/phpunit-mock-objects", @@ -1029,17 +1137,17 @@ }, { "name": "symfony/yaml", - "version": "v2.6.3", + "version": "v2.6.4", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "82462a90848a52c2533aa6b598b107d68076b018" + "reference": "60ed7751671113cf1ee7d7778e691642c2e9acd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/82462a90848a52c2533aa6b598b107d68076b018", - "reference": "82462a90848a52c2533aa6b598b107d68076b018", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/60ed7751671113cf1ee7d7778e691642c2e9acd8", + "reference": "60ed7751671113cf1ee7d7778e691642c2e9acd8", "shasum": "" }, "require": { @@ -1072,7 +1180,7 @@ ], "description": "Symfony Yaml Component", "homepage": "http://symfony.com", - "time": "2015-01-03 15:33:07" + "time": "2015-01-25 04:39:26" } ], "aliases": [], diff --git a/src/main/php/PDepend/Engine.php b/src/main/php/PDepend/Engine.php index 6675b21b49..425a078156 100644 --- a/src/main/php/PDepend/Engine.php +++ b/src/main/php/PDepend/Engine.php @@ -115,7 +115,7 @@ class Engine /** * Generated {@link \PDepend\Source\AST\ASTNamespace} objects. * - * @var Iterator + * @var \PDepend\Source\AST\ASTNamespace[] */ private $namespaces = null; diff --git a/src/main/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollector.php b/src/main/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollector.php new file mode 100644 index 0000000000..56df761168 --- /dev/null +++ b/src/main/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollector.php @@ -0,0 +1,139 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Manuel Pichler nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @copyright 2008-2015 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +namespace PDepend\Util\Cache\Driver\File; + +use PDepend\Util\Log; + +/** + * Simple garbage collector for PDepend's file cache. + * + * @copyright 2008-2015 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ +class FileCacheGarbageCollector +{ + /** + * @var string + */ + private $cacheDir; + + /** + * @var integer + */ + private $minTime; + + /** + * @param string $cacheDir + * @param integer $maxDays + */ + public function __construct($cacheDir, $maxDays = 30) + { + $this->cacheDir = $cacheDir; + $this->minTime = time() - ($maxDays * 86400); + } + + /** + * Removes all outdated cache files and returns the number of garbage + * collected files. + * + * @return integer + */ + public function garbageCollect() + { + if (false === file_exists($this->cacheDir)) { + return 0; + } + + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->cacheDir) + ); + + $count = 0; + foreach ($files as $file) { + if ($this->isCollectibleFile($file)) { + $this->garbageCollectFile($file); + $count += 1; + } + } + + return $count; + } + + /** + * Checks if the given file can be removed. + * + * @param \SplFileInfo $file + * @return boolean + */ + private function isCollectibleFile(\SplFileInfo $file) + { + if (false === $file->isFile()) { + return false; + } + + $time = $file->getATime(); + if ($time > $this->minTime) { + return false; + } + + $time = $file->getMTime(); + if ($time > $this->minTime) { + return false; + } + + return true; + } + + /** + * Removes the given cache file. + * + * @param \SplFileInfo $file + * @return void + */ + private function garbageCollectFile(\SplFileInfo $file) + { + Log::debug("Removing file '{$file->getPathname()}' from cache."); + + @unlink($file->getPathname()); + } +} diff --git a/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php b/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php index ae5200153d..2209700d97 100644 --- a/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php +++ b/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php @@ -45,6 +45,7 @@ use PDepend\Util\Cache\CacheDriver; use PDepend\Util\Cache\Driver\File\FileCacheDirectory; +use PDepend\Util\Cache\Driver\File\FileCacheGarbageCollector; /** * A file system based cache implementation. @@ -93,6 +94,11 @@ class FileCacheDriver implements CacheDriver */ private $cacheKey; + /** + * @var \PDepend\Util\Cache\Driver\File\FileCacheGarbageCollector + */ + private $garbageCollector; + /** * This method constructs a new file cache instance for the given root * directory. @@ -106,6 +112,8 @@ public function __construct($root, $cacheKey = null) $this->version = preg_replace('(^(\d+\.\d+).*)', '\\1', phpversion()); $this->cacheKey = $cacheKey; + + $this->garbageCollect($root); } /** @@ -274,4 +282,16 @@ protected function getCacheFileWithoutExtension($key) $path = $this->directory->createCacheDirectory($key); return "{$path}/{$key}"; } + + /** + * Cleans old cache files. + * + * @param string $root + * @return void + */ + protected function garbageCollect($root) + { + $garbageCollector = new FileCacheGarbageCollector($root); + $garbageCollector->garbageCollect(); + } } diff --git a/src/test/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollectorTest.php b/src/test/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollectorTest.php new file mode 100644 index 0000000000..28cc6f4765 --- /dev/null +++ b/src/test/php/PDepend/Util/Cache/Driver/File/FileCacheGarbageCollectorTest.php @@ -0,0 +1,146 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Manuel Pichler nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @copyright 2008-2015 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +namespace PDepend\Util\Cache\Driver\File; + +use PDepend\AbstractTest; + +/** + * Test case for the {@link \PDepend\Util\Cache\Driver\File\FileCacheGarbageCollector} class. + * + * @copyright 2008-2015 Manuel Pichler. All rights reserved. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * + * @covers \PDepend\Util\Cache\Driver\File\FileCacheGarbageCollector + * @group unittest + */ +class FileCacheGarbageCollectorTest extends AbstractTest +{ + /** + * Temporary cache directory. + * + * @var string + */ + protected $cacheDir; + + /** + * Initializes a temporary working directory. + * + * @return void + */ + protected function setUp() + { + parent::setUp(); + + $this->cacheDir = self::createRunResourceURI('cache') . '/'; + mkdir($this->cacheDir); + } + + /** + * @return void + */ + public function testKeepsRecentFiles() + { + $this->createFile(); + $this->createFile(); + + $garbageCollector = new FileCacheGarbageCollector($this->cacheDir); + $this->assertSame(0, $garbageCollector->garbageCollect()); + } + + /** + * @return void + */ + public function testRemovesOutdatedFiles() + { + $time = 31 * 86400; + + $this->createFile($time, $time); + $this->createFile($time, $time); + + $garbageCollector = new FileCacheGarbageCollector($this->cacheDir); + $this->assertSame(2, $garbageCollector->garbageCollect()); + } + + /** + * @return void + */ + public function testKeepsFilesWithRecentATime() + { + $time = 31 * 86400; + + $this->createFile($time, $time); + $this->createFile($time); + + $garbageCollector = new FileCacheGarbageCollector($this->cacheDir); + $this->assertSame(1, $garbageCollector->garbageCollect()); + } + + /** + * @return void + */ + public function testKeepsFilesWithRecentMTime() + { + $time = 31 * 86400; + + $this->createFile($time, $time); + $this->createFile(0, $time); + + $garbageCollector = new FileCacheGarbageCollector($this->cacheDir); + $this->assertSame(1, $garbageCollector->garbageCollect()); + } + + protected function createFile($mtime = 0, $atime = 0) + { + + $time = time(); + + $mtime = $time - $mtime; + $atime = $time - $atime; + + $file = uniqid($this->cacheDir); + + touch($file, $mtime, $atime); + + return $file; + } +}