From 55cc03b30938b41f3a9609ba5cf91806109ec684 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:33:22 +0200 Subject: [PATCH 01/36] [ResourceWatcher] resource change event created --- .../Component/ResourceWatcher/Event/Event.php | 75 +++++++++++++++++++ .../ResourceWatcher/Event/EventTest.php | 27 +++++++ 2 files changed, 102 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Event/Event.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php diff --git a/src/Symfony/Component/ResourceWatcher/Event/Event.php b/src/Symfony/Component/ResourceWatcher/Event/Event.php new file mode 100644 index 0000000000000..88615635947a2 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Event/Event.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Event; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Resource change event. + * + * @author Konstantin Kudryashov + */ +class Event +{ + const CREATED = 1; + const MODIFIED = 2; + const DELETED = 4; + const ALL = 7; + + private $trackingId; + private $resource; + private $type; + + /** + * Initializes resource event. + * + * @param mixed $trackingId id of resource inside tracker + * @param ResourceInterface $resource resource instance + * @param integer $type event type bit + */ + public function __construct($trackingId, ResourceInterface $resource, $type) + { + $this->trackingId = $trackingId; + $this->resource = $resource; + $this->type = $type; + } + + /** + * Returns id of resource inside tracker. + * + * @return integer + */ + public function getTrackingId() + { + return $this->trackingId; + } + + /** + * Returns changed resource. + * + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * Returns event type. + * + * @return integer + */ + public function getType() + { + return $this->type; + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php new file mode 100644 index 0000000000000..c97126b32ad67 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Event; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\ResourceWatcher\Event\Event; + +class EventTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructAndGetters() + { + $event = new Event($id = 23, $res = new FileResource(__FILE__), $type = Event::MODIFIED); + + $this->assertEquals($id, $event->getTrackingId()); + $this->assertSame($res, $event->getResource()); + $this->assertSame($type, $event->getType()); + } +} From 9eec6a139760092ebc1447ba0cc3844015a08590 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:33:48 +0200 Subject: [PATCH 02/36] [ResourceWatcher] resource event listener interface described --- .../Event/EventListenerInterface.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php b/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php new file mode 100644 index 0000000000000..674404e2ee46d --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Event; + +/** + * Resource change listener interface. + * + * @author Konstantin Kudryashov + */ +interface EventListenerInterface +{ + /** + * Returns listening resource. + * + * @return ResourceInterface + */ + function getResource(); + + /** + * Returns callback. + * + * @return callable + */ + function getCallback(); + + /** + * Checks whether listener can handle provided resource event. + * + * @param Event $event + */ + function handles(Event $event); +} From 2435265914d80109e56e9692df60c1ebb4924e65 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:34:19 +0200 Subject: [PATCH 03/36] [ResourceWatcher] created basic resource event listener object --- .../ResourceWatcher/Event/EventListener.php | 76 +++++++++++++++++++ .../Event/EventListenerTest.php | 57 ++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Event/EventListener.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php new file mode 100644 index 0000000000000..9401e5411b34b --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Event; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Resource change listener. + * + * @author Konstantin Kudryashov + */ +class EventListener implements EventListenerInterface +{ + private $resource; + private $callback; + private $eventsMask; + + /** + * Initializes listener. + * + * @param ResourceInterface $resource resource to listen + * @param callable $callback callback to call on event + * @param integer $eventsMask event types to listen + */ + public function __construct(ResourceInterface $resource, $callback, $eventsMask) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'EventListener\'s second argument should be callable.' + ); + } + + $this->resource = $resource; + $this->callback = $callback; + $this->eventsMask = $eventsMask; + } + + /** + * Returns listening resource. + * + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * Returns callback. + * + * @return callable + */ + public function getCallback() + { + return $this->callback; + } + + /** + * Checks whether listener can handle provided resource event. + * + * @param Event $event + */ + public function handles(Event $event) + { + return 0 !== ($this->eventsMask & $event->getType()); + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php new file mode 100644 index 0000000000000..db11000da1ed2 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Event; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\ResourceWatcher\Event\EventListener; + +class EventListenerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructAndGetters() + { + $listener = new EventListener($res = new FileResource(__FILE__), $cb = function(){}, Event::CREATED); + + $this->assertSame($res, $listener->getResource()); + $this->assertSame($cb, $listener->getCallback()); + } + + public function testHandles() + { + $res = new FileResource(__FILE__); + $cb = function(){}; + + $listener = new EventListener($res, $cb, Event::CREATED); + + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::DELETED))); + + $listener = new EventListener($res, $cb, Event::CREATED | Event::DELETED); + + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + + $listener = new EventListener($res, $cb, Event::ALL); + + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + + $listener = new EventListener($res, $cb, Event::DELETED); + + $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + } +} From 27afac85241e8e62cae79329ab841db7d1ea35b8 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:35:05 +0200 Subject: [PATCH 04/36] [ResourceWatcher] resource state checker interface described --- .../StateChecker/StateCheckerInterface.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php new file mode 100644 index 0000000000000..a78dfb45b402c --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\StateChecker; + +/** + * Resource state checker interface. + * + * @author Konstantin Kudryashov + */ +interface StateCheckerInterface +{ + /** + * Is tracked resource still exists. + * + * @return Boolean + */ + function isResourceExists(); + + /** + * Returns tracked resource. + * + * @return ResourceInterface + */ + function getResource(); + + /** + * Check tracked resource for changes. + * + * @return array + */ + function checkChanges(); +} From d1249dfbfa3449c811449ff3ba0fa6dcbecb0dc2 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:36:21 +0200 Subject: [PATCH 05/36] [ResourceWatcher] created recursive iterator resource state checker this is a basic state checker, which checks all files and directories recursively for changes --- .../RecursiveIteratorStateChecker.php | 159 +++++++++++++++ .../RecursiveIteratorStateCheckerTest.php | 192 ++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php new file mode 100644 index 0000000000000..77fd01175c8a5 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +/** + * Recursive iterator resource state checker. + * + * @author Konstantin Kudryashov + */ +class RecursiveIteratorStateChecker implements StateCheckerInterface +{ + private $resource; + private $timestamp; + private $deleted = false; + private $subcheckers = array(); + + /** + * Initializes checker. + * + * @param ResourceInterface $resource + */ + public function __construct(ResourceInterface $resource) + { + $this->resource = $resource; + $this->timestamp = filemtime($this->getResource()->getResource()); + + if ($resource instanceof DirectoryResource) { + $subresources = $this->readResourcesFromDirectory($resource); + + foreach ($subresources as $subresource) { + $checker = new RecursiveIteratorStateChecker($subresource); + $this->subcheckers[(string) $subresource] = $checker; + } + } + } + + /** + * Returns tracked resource. + * + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * Is tracked resource still exists. + * + * @return Boolean + */ + public function isResourceExists() + { + return !$this->deleted; + } + + /** + * Check tracked resource for changes. + * + * @return array + */ + public function checkChanges() + { + if ($this->deleted) { + return array(); + } + + $changeset = array(); + if ($state = $this->getResourceChangeStateSince($this->getResource(), $this->timestamp + 1)) { + if ('deleted' === $state) { + $this->deleted = true; + } elseif ('modified' === $state) { + $this->timestamp = filemtime($this->getResource()->getResource()); + } + + $changeset = array($state => array($this->getResource())); + + if ('deleted' === $state) { + return $changeset; + } + } + + if ($this->getResource() instanceof DirectoryResource) { + foreach ($this->subcheckers as $path => $checker) { + $changeset = array_merge_recursive($changeset, $checker->checkChanges()); + } + + $subresources = $this->readResourcesFromDirectory($this->getResource()); + foreach ($subresources as $subresource) { + if (!isset($this->subcheckers[(string) $subresource])) { + $checker = new RecursiveIteratorStateChecker($subresource); + $this->subcheckers[(string) $subresource] = $checker; + $changeset = array_merge_recursive($changeset, array('created' => array($subresource))); + } + } + } + + return $changeset; + } + + /** + * Reads files and subdirectories on provided resource path and transform them to resources. + * + * @param DirectoryResource $resource + * + * @return array + */ + private function readResourcesFromDirectory(DirectoryResource $resource) + { + $iterator = new \DirectoryIterator($resource->getResource()); + $resources = array(); + + foreach ($iterator as $info) { + if ($info->isDot()) { + continue; + } + + if ($info->isDir()) { + $resources[] = new DirectoryResource($info->getPathname()); + } else { + $resources[] = new FileResource($info->getPathname()); + } + } + + return $resources; + } + + /** + * Checks resource change state since provided timestamp. + * + * @param ResourceInterface $resource + * @param integer $timestamp + * + * @return string|Boolean deleted|modified|false + */ + private function getResourceChangeStateSince(ResourceInterface $resource, $timestamp) + { + if (!file_exists($resource->getResource())) { + return 'deleted'; + } elseif (!$resource->isFresh($timestamp)) { + return 'modified'; + } + + return false; + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php new file mode 100644 index 0000000000000..ca9e317e25718 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\ResourceWatcher\StateChecker\RecursiveIteratorStateChecker; + +class RecursiveIteratorStateCheckerTest extends \PHPUnit_Framework_TestCase +{ + private $tempPath; + + protected function setUp() + { + $this->tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . + 'symfony_resource_watcher_recucrisve_statechecker'; + + if (!file_exists($this->tempPath)) { + mkdir($this->tempPath, 0777, true); + } + } + + protected function tearDown() + { + if (file_exists($this->tempPath)) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->tempPath), \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $path) { + if ($path->isDir()) { + rmdir((string) $path); + } else { + unlink((string) $path); + } + } + + rmdir($this->tempPath); + } + } + + public function testFileStatusChecks() + { + touch($path = $this->generateRandomPath(), time() - 10); + $resource = new FileResource($path); + $checker = new RecursiveIteratorStateChecker($resource); + + $this->assertTrue($checker->isResourceExists()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($path, time() - 2); + filemtime($path); + + $this->assertEquals(array('modified' => array($resource)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertEquals(0, count($checker->checkChanges())); + + file_put_contents($path, 'test2'); + filemtime($path); + + $this->assertEquals(array('modified' => array($resource)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + unlink($path); + + $this->assertTrue($checker->isResourceExists()); + $this->assertEquals(array('deleted' => array($resource)), $checker->checkChanges()); + $this->assertFalse($checker->isResourceExists()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertEquals(0, count($checker->checkChanges())); + } + + public function testDirectoryStatusChecks() + { + mkdir($path = $this->generateRandomPath(), 0777, true); + + $resource = new DirectoryResource($path); + $checker = new RecursiveIteratorStateChecker($resource); + + $this->assertTrue($checker->isResourceExists()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($filePath = $this->generateRandomPath($path), time() - 10); + $file1 = new FileResource($filePath); + filemtime($file1->getResource()); + + $this->assertEquals(array('created' => array($file1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($file1->getResource(), time() - 5); + filemtime($file1->getResource()); + + $this->assertEquals(array('modified' => array($file1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($filePath = $this->generateRandomPath($path), time() - 10); + $file2 = new FileResource($filePath); + filemtime($file2->getResource()); + + $this->assertEquals(array('created' => array($file2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + unlink($file1->getResource()); + + $this->assertEquals(array('deleted' => array($file1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertTrue($checker->isResourceExists()); + + touch($file2->getResource()); + filemtime($file2->getResource()); + + $this->assertEquals(array('modified' => array($file2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + unlink($file2->getResource()); + + $this->assertEquals(array('deleted' => array($file2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertTrue($checker->isResourceExists()); + + rmdir($resource->getResource()); + $this->assertEquals(array('deleted' => array($resource)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertFalse($checker->isResourceExists()); + } + + public function testDirectoryInsideDirectoryChecks() + { + mkdir($path = $this->generateRandomPath(), 0777, true); + $resource = new DirectoryResource($path); + $checker = new RecursiveIteratorStateChecker($resource); + + $this->assertTrue($checker->isResourceExists()); + $this->assertEquals(0, count($checker->checkChanges())); + + mkdir($dir1Path = $this->generateRandomPath($path), 0777, true); + $dir1 = new DirectoryResource($dir1Path); + + $this->assertEquals(array('created' => array($dir1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + mkdir($dir2Path = $this->generateRandomPath($dir1Path), 0777, true); + $dir2 = new DirectoryResource($dir2Path); + + $this->assertEquals(array('created' => array($dir2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($filePath = $this->generateRandomPath($dir1Path), time() - 10); + $file1 = new FileResource($filePath); + filemtime($file1->getResource()); + + $this->assertEquals(array('created' => array($file1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + touch($filePath = $this->generateRandomPath($dir2Path), time() - 10); + $file2 = new FileResource($filePath); + filemtime($file1->getResource()); + + $this->assertEquals(array('created' => array($file2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + + unlink($file1->getResource()); + + $this->assertEquals(array('deleted' => array($file1)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + $this->assertTrue($checker->isResourceExists()); + + touch($file2, time() - 5); + filemtime($file2->getResource()); + + $this->assertEquals(array('modified' => array($file2)), $checker->checkChanges()); + $this->assertEquals(0, count($checker->checkChanges())); + } + + private function generateRandomPath($prefix = null) + { + if (null === $prefix) { + $prefix = $this->tempPath; + } + + return $prefix . DIRECTORY_SEPARATOR . md5(microtime(true) . rand(1, 9999)); + } +} From c8bfa1830ee5161067b92550df87830fc2945715 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:36:58 +0200 Subject: [PATCH 06/36] [ResourceWatcher] resources tracker interface described --- .../Tracker/TrackerInterface.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php b/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php new file mode 100644 index 0000000000000..e93d9ebe05f02 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Tracker; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Resources tracker interface. + * + * @author Konstantin Kudryashov + */ +interface TrackerInterface +{ + /** + * Starts to track provided resource for changes. + * + * @param ResourceInterface $resource + */ + function track(ResourceInterface $resource); + + /** + * Checks whether provided resource is tracked by this tracker. + * + * @param ResourceInterface $resource + * + * @return Boolean + */ + function isResourceTracked(ResourceInterface $resource); + + /** + * Returns resource tracking ID. + * + * @param ResourceInterface $resource + * + * @return mixed + */ + function getResourceTrackingId(ResourceInterface $resource); + + /** + * Checks tracked resources for changes. + * + * @return array change events array + */ + function checkChanges(); +} From 73b3ea066bb81dd28a1c2fbdb61cdbeca0093e4e Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:38:08 +0200 Subject: [PATCH 07/36] [ResourceWatcher] recursive iterator resources tracker created this resources tracker uses recursive iterator state checker internally --- .../Tracker/RecursiveIteratorTracker.php | 104 ++++++++++++++++++ .../Tracker/RecursiveIteratorTrackerTest.php | 88 +++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php new file mode 100644 index 0000000000000..6213746c82ee2 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface; +use Symfony\Component\ResourceWatcher\StateChecker\RecursiveIteratorStateChecker; + +/** + * Recursive iterator resources tracker. + * + * @author Konstantin Kudryashov + */ +class RecursiveIteratorTracker implements TrackerInterface +{ + private $checkers = array(); + + /** + * Starts to track provided resource for changes. + * + * @param ResourceInterface $resource + */ + public function track(ResourceInterface $resource) + { + $this->addStateChecker(new RecursiveIteratorStateChecker($resource)); + } + + /** + * Adds state checker to tracker. + * + * @param StateCheckerInterface $checker + */ + public function addStateChecker(StateCheckerInterface $checker) + { + $this->checkers[$this->getResourceTrackingId($checker->getResource())] = $checker; + } + + /** + * Checks whether provided resource is tracked by this tracker. + * + * @param ResourceInterface $resource + * + * @return Boolean + */ + public function isResourceTracked(ResourceInterface $resource) + { + return isset($this->checkers[$this->getResourceTrackingId($resource)]); + } + + /** + * Returns resource tracking ID. + * + * @param ResourceInterface $resource + * + * @return mixed + */ + public function getResourceTrackingId(ResourceInterface $resource) + { + return md5((string) $resource); + } + + /** + * Checks tracked resources for changes. + * + * @return array change events array + */ + public function checkChanges() + { + $events = array(); + foreach ($this->checkers as $trackingId => $checker) { + $changeset = $checker->checkChanges(); + + foreach ($changeset as $type => $resources) { + switch ($type) { + case 'created': + $eventType = Event::CREATED; + break; + case 'modified': + $eventType = Event::MODIFIED; + break; + case 'deleted': + $eventType = Event::DELETED; + break; + } + + foreach ($resources as $resource) { + $events[] = new Event($trackingId, $resource, $eventType); + } + } + } + + return $events; + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php new file mode 100644 index 0000000000000..ade6d4ab486dc --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Tracker; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\ResourceWatcher\Tracker\RecursiveIteratorTracker; +use Symfony\Component\ResourceWatcher\Event\Event; + +class RecursiveIteratorTrackerTestTest extends \PHPUnit_Framework_TestCase +{ + public function testIsResourceTracked() + { + $tracker = new RecursiveIteratorTracker(); + $resource = new FileResource(__FILE__); + + $this->assertFalse($tracker->isResourceTracked($resource)); + + $checker = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface') + ->disableOriginalConstructor() + ->getMock(); + $checker + ->expects($this->once()) + ->method('getResource') + ->will($this->returnValue($resource)); + + $tracker->addStateChecker($checker); + + $this->assertTrue($tracker->isResourceTracked($resource)); + } + + public function testGetResourceTrackingId() + { + $tracker = new RecursiveIteratorTracker(); + $file = new FileResource(__FILE__); + $dir = new DirectoryResource(__DIR__); + + $this->assertNotNull($tracker->getResourceTrackingId($file)); + $this->assertNotNull($tracker->getResourceTrackingId($dir)); + + $this->assertNotEquals( + $tracker->getResourceTrackingId($file), $tracker->getResourceTrackingId($dir) + ); + } + + public function testCheckChanges() + { + $tracker = new RecursiveIteratorTracker(); + $resource = new FileResource(__FILE__); + $trackingId = $tracker->getResourceTrackingId($resource); + + $checker = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface') + ->disableOriginalConstructor() + ->getMock(); + $checker + ->expects($this->once()) + ->method('getResource') + ->will($this->returnValue($resource)); + + $tracker->addStateChecker($checker); + + $checker + ->expects($this->once()) + ->method('checkChanges') + ->will($this->returnValue(array( + 'created' => array($resource), + 'modified' => array($resource), + 'deleted' => array($resource), + ))); + + $this->assertEquals(array( + new Event($trackingId, $resource, Event::CREATED), + new Event($trackingId, $resource, Event::MODIFIED), + new Event($trackingId, $resource, Event::DELETED), + ), $tracker->checkChanges()); + } +} From 150bc5a738cbc2cf61fdd1ef154d0fe10ea9ac6b Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 20:38:52 +0200 Subject: [PATCH 08/36] [ResourceWatcher] resource watcher object created --- .../ResourceWatcher/ResourceWatcher.php | 151 +++++++++++++++++ .../ResourceWatcher/ResourceWatcherTest.php | 160 ++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/ResourceWatcher.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php new file mode 100644 index 0000000000000..bd24874a514ed --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\ResourceWatcher\Event\EventListener; +use Symfony\Component\ResourceWatcher\Event\EventListenerInterface; +use Symfony\Component\ResourceWatcher\Tracker\TrackerInterface; + +/** + * Resources changes watcher. + * + * @author Konstantin Kudryashov + */ +class ResourceWatcher +{ + private $tracker; + private $watching = true; + private $listeners = array(); + + /** + * Initializes path watcher. + * + * @param TrackerInterface $tracker + */ + public function __construct(TrackerInterface $tracker) + { + $this->tracker = $tracker; + } + + /** + * Track resource with watcher. + * + * @param ResourceInterface $resource resource to track + * @param callable $callback event callback + * @param integer $eventsMask event types bitmask + */ + public function track(ResourceInterface $resource, $callback, $eventsMask = Event::ALL) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Second argument to track() should be callable.'); + } + + $this->addListener(new EventListener($resource, $callback, $eventsMask)); + } + + /** + * Adds resource event listener to watcher. + * + * @param EventListenerInterface $listener resource event listener + */ + public function addListener(EventListenerInterface $listener) + { + if (!$this->getTracker()->isResourceTracked($listener->getResource())) { + $this->getTracker()->track($listener->getResource()); + } + + $trackingId = $this->getTracker()->getResourceTrackingId($listener->getResource()); + + if (!isset($this->listeners[$trackingId])) { + $this->listeners[$trackingId] = array(); + } + + $this->listeners[$trackingId][] = $listener; + } + + /** + * Returns true if watcher is currently watching on tracked resources (started). + * + * @return Boolean + */ + public function isWatching() + { + return $this->watching; + } + + /** + * Starts wathing on tracked resources. + * + * @param integer $checkInterval check interval in microseconds + * @param integer $timeLimit maximum watching time limit in microseconds + */ + public function start($checkInterval = 1000000, $timeLimit = null) + { + $totalTime = 0; + $this->watching = true; + + while ($this->watching) { + usleep($checkInterval); + $totalTime += $checkInterval; + + if (null !== $timeLimit && $totalTime > $timeLimit) { + break; + } + + if (count($events = $this->getTracker()->checkChanges())) { + $this->notifyListeners($events); + } + } + + $this->watching = false; + } + + /** + * Stop watching on tracked resources. + */ + public function stop() + { + $this->watching = false; + } + + /** + * Returns current tracker instance. + * + * @return TrackerInterface + */ + protected function getTracker() + { + return $this->tracker; + } + + /** + * Notifies all registered resource event listeners about their events. + * + * @param array $events array of resource events + */ + private function notifyListeners(array $events) + { + foreach ($events as $event) { + $trackingId = $event->getTrackingId(); + + if (isset($this->listeners[$trackingId])) { + foreach ($this->listeners[$trackingId] as $listener) { + if ($listener->handles($event)) { + call_user_func($listener->getCallback(), $event); + } + } + } + } + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php new file mode 100644 index 0000000000000..556c74f26ca5e --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher; + +use Symfony\Component\ResourceWatcher\ResourceWatcher; +use Symfony\Component\ResourceWatcher\Event\Event; + +class ResourceWatcherTest extends \PHPUnit_Framework_TestCase +{ + public function testUntrackedResourceTrack() + { + $tracker = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\Tracker\TrackerInterface') + ->getMock(); + + $resource = $this + ->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface') + ->getMock(); + + $tracker + ->expects($this->once()) + ->method('isResourceTracked') + ->with($resource) + ->will($this->returnValue(false)); + $tracker + ->expects($this->once()) + ->method('track') + ->with($resource) + ->will($this->returnValue(null)); + $tracker + ->expects($this->once()) + ->method('getResourceTrackingId') + ->with($resource); + + $watcher = new ResourceWatcher($tracker); + $watcher->track($resource, function(){}); + } + + public function testTrackedResourceTrack() + { + $tracker = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\Tracker\TrackerInterface') + ->getMock(); + + $resource = $this + ->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface') + ->getMock(); + + $tracker + ->expects($this->once()) + ->method('isResourceTracked') + ->with($resource) + ->will($this->returnValue(true)); + $tracker + ->expects($this->never()) + ->method('track'); + $tracker + ->expects($this->once()) + ->method('getResourceTrackingId') + ->with($resource); + + $watcher = new ResourceWatcher($tracker); + $watcher->track($resource, function(){}); + } + + public function testWatching() + { + $tracker = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\Tracker\TrackerInterface') + ->getMock(); + + $resourceMockBuilder = $this + ->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface'); + + $resource1 = $resourceMockBuilder->getMock(); + $resource2 = $resourceMockBuilder->getMock(); + + $listenerMockBuilder = $this + ->getMockBuilder('Symfony\Component\ResourceWatcher\Event\EventListenerInterface'); + + $listener1 = $listenerMockBuilder->getMock(); + $listener2 = $listenerMockBuilder->getMock(); + $listener3 = $listenerMockBuilder->getMock(); + + $listener1 + ->expects($this->exactly(3)) + ->method('getResource') + ->will($this->returnValue($resource1)); + $listener2 + ->expects($this->exactly(3)) + ->method('getResource') + ->will($this->returnValue($resource2)); + $listener3 + ->expects($this->exactly(2)) + ->method('getResource') + ->will($this->returnValue($resource2)); + + $tracker + ->expects($this->exactly(3)) + ->method('isResourceTracked') + ->will($this->onConsecutiveCalls(false, false, true)); + $tracker + ->expects($this->exactly(2)) + ->method('track'); + $tracker + ->expects($this->exactly(3)) + ->method('getResourceTrackingId') + ->will($this->onConsecutiveCalls(1, 2, 2)); + + $watcher = new ResourceWatcher($tracker); + $watcher->addListener($listener1); + $watcher->addListener($listener2); + $watcher->addListener($listener3); + + $listener1 + ->expects($this->once()) + ->method('handles') + ->will($this->returnValue(true)); + $listener2 + ->expects($this->exactly(2)) + ->method('handles') + ->will($this->onConsecutiveCalls(false, false)); + $listener3 + ->expects($this->exactly(2)) + ->method('handles') + ->will($this->onConsecutiveCalls(true, true)); + + $listener1 + ->expects($this->once()) + ->method('getCallback') + ->will($this->returnValue(function($e){})); + $listener2 + ->expects($this->never()) + ->method('getCallback'); + $listener3 + ->expects($this->exactly(2)) + ->method('getCallback') + ->will($this->returnValue(function($e){})); + + $tracker + ->expects($this->once()) + ->method('checkChanges') + ->will($this->returnValue(array( + new Event(1, $resource1, 1), + new Event(2, $resource2, 1), + new Event(2, $resource2, 1), + ))); + + $watcher->start(1, 1); + } +} From f78ff91f3a62b3a56b6dad02a56fe341587254a7 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 21:10:33 +0200 Subject: [PATCH 09/36] [ResourceWatcher] inotify tracker added --- .../Tracker/InotifyTracker.php | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php new file mode 100644 index 0000000000000..6a9e3265ac10d --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +/** + * Inotify events resources tracker. + * + * @author Konstantin Kudryashov + */ +class InotifyTracker implements TrackerInterface +{ + private $stream; + private $trackingIds = array(); + private $tracks = array(); + + /** + * Initializes tracker. + */ + public function __construct() + { + $this->stream = inotify_init(); + } + + /** + * Destructs tracker. + */ + public function __destruct() + { + fclose($this->stream); + } + + /** + * Starts to track provided resource for changes. + * + * @param ResourceInterface $resource + */ + public function track(ResourceInterface $resource) + { + $trackingId = inotify_add_watch( + $this->stream, $resource->getResource(), IN_CREATE | IN_MODIFY | IN_DELETE + ); + $this->trackingIds[$resource->getResource()] = $trackingId; + + $this->tracks[$trackingId] = $resource; + } + + /** + * Checks whether provided resource is tracked by this tracker. + * + * @param ResourceInterface $resource + * + * @return Boolean + */ + public function isResourceTracked(ResourceInterface $resource) + { + return null !== $this->getResourceTrackingId($resource); + } + + /** + * Returns resource tracking ID. + * + * @param ResourceInterface $resource + * + * @return mixed + */ + public function getResourceTrackingId(ResourceInterface $resource) + { + return isset($this->trackingIds[$resource->getResource()]) + ? $this->trackingIds[$resource->getResource()] + : null; + } + + /** + * Checks tracked resources for changes. + * + * @return array change events array + */ + public function checkChanges() + { + $events = array(); + + if ($iEvents = inotify_read($this->stream)) { + foreach ($iEvents as $iEvent) { + $trackingId = $iEvent['wd']; + + if (isset($this->tracks[$trackingId])) { + $resource = $this->tracks[$trackingId]; + } else { + if (is_dir($iEvent['name'])) { + $resource = new DirectoryResource($iEvent['name']); + } else { + $resource = new FileResource($iEvent['name']); + } + } + + if ($iEvent['mask'] & IN_CREATE) { + $event = Event::CREATED; + } elseif ($iEvent['mask'] & IN_MODIFY) { + $event = Event::MODIFIED; + } elseif ($iEvent['mask'] & IN_DELETE) { + $event = Event::DELETED; + } + + $events[] = new Event($trackingId, $resource, $event); + } + } + + return $events; + } +} From fe664a676515121ce0bef85b9f7b75542789a881 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 21:11:32 +0200 Subject: [PATCH 10/36] [ResourceWatcher] use best tracker by default if no tracker were specified in watcher constructor it will use best one (inotify if available and rec otherwise) --- .../Component/ResourceWatcher/ResourceWatcher.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index bd24874a514ed..de56d46dba224 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -16,6 +16,8 @@ use Symfony\Component\ResourceWatcher\Event\EventListener; use Symfony\Component\ResourceWatcher\Event\EventListenerInterface; use Symfony\Component\ResourceWatcher\Tracker\TrackerInterface; +use Symfony\Component\ResourceWatcher\Tracker\InotifyTracker; +use Symfony\Component\ResourceWatcher\Tracker\RecursiveIteratorTracker; /** * Resources changes watcher. @@ -33,9 +35,17 @@ class ResourceWatcher * * @param TrackerInterface $tracker */ - public function __construct(TrackerInterface $tracker) + public function __construct(TrackerInterface $tracker = null) { - $this->tracker = $tracker; + if (null !== $tracker) { + $this->tracker = $tracker; + } else { + if (function_exists('inotify_init')) { + $this->tracker = new InotifyTracker(); + } else { + $this->tracker = new RecursiveIteratorTracker(); + } + } } /** From d40d2e082c6b59fc12a88d2ed0cdc0bd94bc0ef2 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Mar 2011 21:41:40 +0200 Subject: [PATCH 11/36] [ResourceWatcher] renamed Event::handes to Event::supports to be consistent with other parts of the framework. --- .../ResourceWatcher/Event/EventListener.php | 4 ++-- .../Event/EventListenerInterface.php | 4 ++-- .../ResourceWatcher/ResourceWatcher.php | 2 +- .../Event/EventListenerTest.php | 24 +++++++++---------- .../ResourceWatcher/ResourceWatcherTest.php | 6 ++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php index 9401e5411b34b..62f329cb99405 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php @@ -65,11 +65,11 @@ public function getCallback() } /** - * Checks whether listener can handle provided resource event. + * Checks whether listener supports provided resource event. * * @param Event $event */ - public function handles(Event $event) + public function supports(Event $event) { return 0 !== ($this->eventsMask & $event->getType()); } diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php b/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php index 674404e2ee46d..ff4515dae4383 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListenerInterface.php @@ -33,9 +33,9 @@ function getResource(); function getCallback(); /** - * Checks whether listener can handle provided resource event. + * Checks whether listener supports provided resource event. * * @param Event $event */ - function handles(Event $event); + function supports(Event $event); } diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index de56d46dba224..a41e960b45738 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -151,7 +151,7 @@ private function notifyListeners(array $events) if (isset($this->listeners[$trackingId])) { foreach ($this->listeners[$trackingId] as $listener) { - if ($listener->handles($event)) { + if ($listener->supports($event)) { call_user_func($listener->getCallback(), $event); } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php index db11000da1ed2..c3a16a5ca8e74 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventListenerTest.php @@ -32,26 +32,26 @@ public function testHandles() $listener = new EventListener($res, $cb, Event::CREATED); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); - $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); - $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::DELETED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->supports(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertFalse($listener->supports(new Event(1, $res, $type = Event::DELETED))); $listener = new EventListener($res, $cb, Event::CREATED | Event::DELETED); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); - $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->supports(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::DELETED))); $listener = new EventListener($res, $cb, Event::ALL); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::CREATED))); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::CREATED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::DELETED))); $listener = new EventListener($res, $cb, Event::DELETED); - $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::CREATED))); - $this->assertFalse($listener->handles(new Event(1, $res, $type = Event::MODIFIED))); - $this->assertTrue($listener->handles(new Event(1, $res, $type = Event::DELETED))); + $this->assertFalse($listener->supports(new Event(1, $res, $type = Event::CREATED))); + $this->assertFalse($listener->supports(new Event(1, $res, $type = Event::MODIFIED))); + $this->assertTrue($listener->supports(new Event(1, $res, $type = Event::DELETED))); } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php index 556c74f26ca5e..1e95e4811ba5c 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php @@ -123,15 +123,15 @@ public function testWatching() $listener1 ->expects($this->once()) - ->method('handles') + ->method('supports') ->will($this->returnValue(true)); $listener2 ->expects($this->exactly(2)) - ->method('handles') + ->method('supports') ->will($this->onConsecutiveCalls(false, false)); $listener3 ->expects($this->exactly(2)) - ->method('handles') + ->method('supports') ->will($this->onConsecutiveCalls(true, true)); $listener1 From d661a16827ae433379ea34a0a002e39f0bc23af8 Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 20 Aug 2011 16:28:57 +0300 Subject: [PATCH 12/36] [ResourceWatcher] fixed non-empty directory events --- .../StateChecker/RecursiveIteratorStateChecker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php index 77fd01175c8a5..902fb934d34d9 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php @@ -35,7 +35,7 @@ class RecursiveIteratorStateChecker implements StateCheckerInterface public function __construct(ResourceInterface $resource) { $this->resource = $resource; - $this->timestamp = filemtime($this->getResource()->getResource()); + $this->timestamp = time(); if ($resource instanceof DirectoryResource) { $subresources = $this->readResourcesFromDirectory($resource); @@ -83,7 +83,7 @@ public function checkChanges() if ('deleted' === $state) { $this->deleted = true; } elseif ('modified' === $state) { - $this->timestamp = filemtime($this->getResource()->getResource()); + $this->timestamp = time(); } $changeset = array($state => array($this->getResource())); From dcb58faf3191f7db246471b51ab91220d5508755 Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 08:58:54 +0100 Subject: [PATCH 13/36] [Config] updated resources API to be more explicit --- .../Config/Resource/DirectoryResource.php | 39 ++++++++++++++++--- .../Config/Resource/FileResource.php | 26 ++++++++++++- .../Config/Resource/ResourceInterface.php | 14 +++++++ .../Config/Resource/DirectoryResourceTest.php | 12 ++++++ .../Config/Resource/FileResourceTest.php | 19 +++++++++ 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 5ccd204ef9334..021523963e705 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -59,14 +59,14 @@ public function getPattern() } /** - * Returns true if the resource has not been updated since the given timestamp. - * - * @param integer $timestamp The last time the resource was loaded + * Returns resource mtime. * - * @return Boolean true if the resource has not been updated, false otherwise + * @return integer */ - public function isFresh($timestamp) + public function getModificationTime() { + clearstatcache(true, $this->resource); + if (!is_dir($this->resource)) { return false; } @@ -84,10 +84,37 @@ public function isFresh($timestamp) continue; } + clearstatcache(true, (string) $file); $newestMTime = max($file->getMTime(), $newestMTime); } - return $newestMTime < $timestamp; + return $newestMTime; + } + + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param integer $timestamp The last time the resource was loaded + * + * @return Boolean true if the resource has not been updated, false otherwise + */ + public function isFresh($timestamp) + { + if (!$this->exists()) { + return false; + } + + return $this->getModificationTime() <= $timestamp; + } + + /** + * Returns true if the resource exists in the filesystem. + * + * @return Boolean + */ + public function exists() + { + return file_exists($this->resource); } public function serialize() diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 619f84bcef2ec..2499de68e5f8c 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -52,6 +52,18 @@ public function getResource() return $this->resource; } + /** + * Returns resource mtime. + * + * @return integer + */ + public function getModificationTime() + { + clearstatcache(true, $this->resource); + + return filemtime($this->resource); + } + /** * Returns true if the resource has not been updated since the given timestamp. * @@ -61,11 +73,21 @@ public function getResource() */ public function isFresh($timestamp) { - if (!file_exists($this->resource)) { + if (!$this->exists()) { return false; } - return filemtime($this->resource) < $timestamp; + return $this->getModificationTime() <= $timestamp; + } + + /** + * Returns true if the resource exists in the filesystem. + * + * @return Boolean + */ + public function exists() + { + return file_exists($this->resource); } public function serialize() diff --git a/src/Symfony/Component/Config/Resource/ResourceInterface.php b/src/Symfony/Component/Config/Resource/ResourceInterface.php index 024f2e95f95fc..e684fd6b09253 100644 --- a/src/Symfony/Component/Config/Resource/ResourceInterface.php +++ b/src/Symfony/Component/Config/Resource/ResourceInterface.php @@ -34,6 +34,20 @@ function __toString(); */ function isFresh($timestamp); + /** + * Returns resource mtime. + * + * @return integer + */ + function getModificationTime(); + + /** + * Returns true if the resource exists in the filesystem. + * + * @return Boolean + */ + function exists(); + /** * Returns the resource tied to this Resource. * diff --git a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php index 417a433adb8a4..d026b8ee4fa76 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php @@ -117,8 +117,20 @@ public function testIsFreshDeleteDirectory() $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the whole resource is removed'); } + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::exists + */ + public function testExists() + { + $this->assertTrue($this->resource->exists(), '->exists() returns true if the directory still exist'); + + $this->removeDirectory($this->directory); + $this->assertFalse($this->resource->exists(), '->exists() returns false if the directory does not exist'); + } + /** * @covers Symfony\Component\Config\Resource\DirectoryResource::isFresh + * @covers Symfony\Component\Config\Resource\DirectoryResource::getModificationTime */ public function testIsFreshCreateFileInSubdirectory() { diff --git a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php index ef8daf593678a..fd3c09036f668 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php @@ -49,4 +49,23 @@ public function testIsFresh() $resource = new FileResource('/____foo/foobar'.rand(1, 999999)); $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist'); } + + /** + * @covers Symfony\Component\Config\Resource\FileResource::exists + */ + public function testExists() + { + $this->assertTrue($this->resource->exists(), '->exists() returns true if the resource does exist'); + + $resource = new FileResource('/____foo/foobar'.rand(1, 999999)); + $this->assertFalse($resource->exists(), '->exists() returns false if the resource does not exist'); + } + + /** + * @covers Symfony\Component\Config\Resource\FileResource::getModificationTime + */ + public function testGetModificationTime() + { + $this->assertSame(filemtime($this->resource->getResource()), $this->resource->getModificationTime()); + } } From d9b88bda0256729cc4a0173a23579fdc5246c56a Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 10:50:13 +0100 Subject: [PATCH 14/36] [Config] moved DirectoryResource childs retrieving to the special getFilteredChilds method --- .../Config/Resource/DirectoryResource.php | 57 +++++++++++++------ .../Config/Resource/DirectoryResourceTest.php | 1 + 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 021523963e705..1818f6da9fbb1 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -33,6 +33,46 @@ public function __construct($resource, $pattern = null) $this->pattern = $pattern; } + /** + * Returns the list of filtered file and directory childs of directory resource. + * + * @param Boolean $recursive search for files recursive + * + * @return array An array of files + */ + public function getFilteredChilds($recursive = true) + { + $iterator = $recursive + ? new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) + : new \DirectoryIterator($this->resource); + + $childs = array(); + foreach ($iterator as $file) { + // if regex filtering is enabled only return matching files + if (isset($this->filterRegexList) && $file->isFile()) { + $regexMatched = false; + foreach ($this->filterRegexList as $regex) { + if (preg_match($regex, (string) $file)) { + $regexMatched = true; + } + } + if (!$regexMatched) { + continue; + } + } + + // always monitor directories for changes, except the .. entries + // (otherwise deleted files wouldn't get detected) + if ($file->isDir() && '/..' === substr($file, -3)) { + continue; + } + + $childs[] = $file; + } + + return $childs; + } + /** * Returns a string representation of the Resource. * @@ -66,24 +106,9 @@ public function getPattern() public function getModificationTime() { clearstatcache(true, $this->resource); - - if (!is_dir($this->resource)) { - return false; - } - $newestMTime = filemtime($this->resource); - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { - // if regex filtering is enabled only check matching files - if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) { - continue; - } - - // always monitor directories for changes, except the .. entries - // (otherwise deleted files wouldn't get detected) - if ($file->isDir() && '/..' === substr($file, -3)) { - continue; - } + foreach ($this->getFilteredChilds() as $file) { clearstatcache(true, (string) $file); $newestMTime = max($file->getMTime(), $newestMTime); } diff --git a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php index d026b8ee4fa76..f27369c208825 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php @@ -160,6 +160,7 @@ public function testIsFreshModifySubdirectory() /** * @covers Symfony\Component\Config\Resource\DirectoryResource::isFresh + * @covers Symfony\Component\Config\Resource\DirectoryResource::getFilteredChilds */ public function testFilterRegexListNoMatch() { From 62a116dc7c818c016c0a47700795bf051004be86 Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 23:30:06 +0100 Subject: [PATCH 15/36] [ResourceWatcher] cleaned code little bit --- src/Symfony/Component/ResourceWatcher/Event/Event.php | 8 ++++---- .../Component/ResourceWatcher/Event/EventListener.php | 6 +++--- src/Symfony/Component/ResourceWatcher/ResourceWatcher.php | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/Event/Event.php b/src/Symfony/Component/ResourceWatcher/Event/Event.php index 88615635947a2..e562454cb3b06 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/Event.php +++ b/src/Symfony/Component/ResourceWatcher/Event/Event.php @@ -20,10 +20,10 @@ */ class Event { - const CREATED = 1; - const MODIFIED = 2; - const DELETED = 4; - const ALL = 7; + const CREATED = 1; + const MODIFIED = 2; + const DELETED = 4; + const ALL = 7; private $trackingId; private $resource; diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php index 62f329cb99405..a8e8b38f3ba19 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php @@ -39,9 +39,9 @@ public function __construct(ResourceInterface $resource, $callback, $eventsMask) ); } - $this->resource = $resource; - $this->callback = $callback; - $this->eventsMask = $eventsMask; + $this->resource = $resource; + $this->callback = $callback; + $this->eventsMask = $eventsMask; } /** diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index a41e960b45738..0643d6dd135eb 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -27,8 +27,8 @@ class ResourceWatcher { private $tracker; - private $watching = true; - private $listeners = array(); + private $watching = true; + private $listeners = array(); /** * Initializes path watcher. From a714bede48bc4ec781c3bc7f699d3a8da9b6698d Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 23:31:15 +0100 Subject: [PATCH 16/36] [ResourceWatcher] refactored recursive state checkers into 2 separate classes --- .../StateChecker/DirectoryStateChecker.php | 91 +++++++++ .../StateChecker/FileStateChecker.php | 34 ++++ .../RecursiveIteratorStateChecker.php | 159 --------------- .../StateChecker/ResourceStateChecker.php | 82 ++++++++ .../StateChecker/StateCheckerInterface.php | 7 - .../DirectoryStateCheckerTest.php | 175 ++++++++++++++++ .../StateChecker/FileStateCheckerTest.php | 112 ++++++++++ .../RecursiveIteratorStateCheckerTest.php | 192 ------------------ 8 files changed, 494 insertions(+), 358 deletions(-) create mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php create mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/FileStateChecker.php delete mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php create mode 100644 src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php delete mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php new file mode 100644 index 0000000000000..0dafb4ba09016 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +/** + * Recursive directory state checker. + * + * @author Konstantin Kudryashov + */ +class DirectoryStateChecker extends ResourceStateChecker +{ + private $childs = array(); + + /** + * Initializes checker. + * + * @param DirectoryResource $resource + */ + public function __construct(DirectoryResource $resource) + { + parent::__construct($resource); + + foreach ($this->createDirectoryChildCheckers($resource) as $checker) { + $this->childs[(string) $checker->getResource()] = $checker; + } + } + + /** + * Check tracked resource for changes. + * + * @return array + */ + public function checkChanges() + { + $changeset = parent::checkChanges(); + if ((isset($changeset[0]) && $changeset[0]['event'] === Event::DELETED) || $this->isDeleted()) { + return $changeset; + } + + foreach ($this->childs as $path => $checker) { + foreach ($checker->checkChanges() as $change) { + $changeset[] = $change; + } + } + + foreach ($this->createDirectoryChildCheckers($this->getResource()) as $checker) { + if (!isset($this->childs[(string) $checker->getResource()])) { + $this->childs[(string) $checker->getResource()] = $checker; + $changeset[] = array( + 'event' => Event::CREATED, 'resource' => $checker->getResource() + ); + } + } + + return $changeset; + } + + /** + * Reads files and subdirectories on provided resource path and transform them to resources. + * + * @param DirectoryResource $resource + * + * @return array + */ + private function createDirectoryChildCheckers(DirectoryResource $resource) + { + $checkers = array(); + foreach ($resource->getFilteredChildResources() as $resource) { + if ($resource instanceof DirectoryResource) { + $checkers[] = new DirectoryStateChecker($resource); + } else { + $checkers[] = new FileStateChecker($resource); + } + } + + return $checkers; + } +} diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/FileStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/FileStateChecker.php new file mode 100644 index 0000000000000..57140a5816ea4 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/FileStateChecker.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\FileResource; + +/** + * File state checker. + * + * @author Konstantin Kudryashov + */ +class FileStateChecker extends ResourceStateChecker +{ + /** + * Initializes checker. + * + * @param FileResource $resource + */ + public function __construct(FileResource $resource) + { + parent::__construct($resource); + } +} diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php deleted file mode 100644 index 902fb934d34d9..0000000000000 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateChecker.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ResourceWatcher\StateChecker; - -use Symfony\Component\Config\Resource\ResourceInterface; -use Symfony\Component\Config\Resource\DirectoryResource; -use Symfony\Component\Config\Resource\FileResource; - -/** - * Recursive iterator resource state checker. - * - * @author Konstantin Kudryashov - */ -class RecursiveIteratorStateChecker implements StateCheckerInterface -{ - private $resource; - private $timestamp; - private $deleted = false; - private $subcheckers = array(); - - /** - * Initializes checker. - * - * @param ResourceInterface $resource - */ - public function __construct(ResourceInterface $resource) - { - $this->resource = $resource; - $this->timestamp = time(); - - if ($resource instanceof DirectoryResource) { - $subresources = $this->readResourcesFromDirectory($resource); - - foreach ($subresources as $subresource) { - $checker = new RecursiveIteratorStateChecker($subresource); - $this->subcheckers[(string) $subresource] = $checker; - } - } - } - - /** - * Returns tracked resource. - * - * @return ResourceInterface - */ - public function getResource() - { - return $this->resource; - } - - /** - * Is tracked resource still exists. - * - * @return Boolean - */ - public function isResourceExists() - { - return !$this->deleted; - } - - /** - * Check tracked resource for changes. - * - * @return array - */ - public function checkChanges() - { - if ($this->deleted) { - return array(); - } - - $changeset = array(); - if ($state = $this->getResourceChangeStateSince($this->getResource(), $this->timestamp + 1)) { - if ('deleted' === $state) { - $this->deleted = true; - } elseif ('modified' === $state) { - $this->timestamp = time(); - } - - $changeset = array($state => array($this->getResource())); - - if ('deleted' === $state) { - return $changeset; - } - } - - if ($this->getResource() instanceof DirectoryResource) { - foreach ($this->subcheckers as $path => $checker) { - $changeset = array_merge_recursive($changeset, $checker->checkChanges()); - } - - $subresources = $this->readResourcesFromDirectory($this->getResource()); - foreach ($subresources as $subresource) { - if (!isset($this->subcheckers[(string) $subresource])) { - $checker = new RecursiveIteratorStateChecker($subresource); - $this->subcheckers[(string) $subresource] = $checker; - $changeset = array_merge_recursive($changeset, array('created' => array($subresource))); - } - } - } - - return $changeset; - } - - /** - * Reads files and subdirectories on provided resource path and transform them to resources. - * - * @param DirectoryResource $resource - * - * @return array - */ - private function readResourcesFromDirectory(DirectoryResource $resource) - { - $iterator = new \DirectoryIterator($resource->getResource()); - $resources = array(); - - foreach ($iterator as $info) { - if ($info->isDot()) { - continue; - } - - if ($info->isDir()) { - $resources[] = new DirectoryResource($info->getPathname()); - } else { - $resources[] = new FileResource($info->getPathname()); - } - } - - return $resources; - } - - /** - * Checks resource change state since provided timestamp. - * - * @param ResourceInterface $resource - * @param integer $timestamp - * - * @return string|Boolean deleted|modified|false - */ - private function getResourceChangeStateSince(ResourceInterface $resource, $timestamp) - { - if (!file_exists($resource->getResource())) { - return 'deleted'; - } elseif (!$resource->isFresh($timestamp)) { - return 'modified'; - } - - return false; - } -} diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php new file mode 100644 index 0000000000000..5280cf4a24d5a --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\ResourceWatcher\Event\Event; + +/** + * Abstract resource state checker class. + * + * @author Konstantin Kudryashov + */ +abstract class ResourceStateChecker implements StateCheckerInterface +{ + private $resource; + private $timestamp; + private $deleted = false; + + /** + * Initializes checker. + * + * @param ResourceInterface $resource + */ + public function __construct(ResourceInterface $resource) + { + $this->resource = $resource; + $this->timestamp = $resource->getModificationTime(); + } + + /** + * Returns tracked resource. + * + * @return ResourceInterface + */ + public function getResource() + { + return $this->resource; + } + + /** + * Check tracked resource for changes. + * + * @return array + */ + public function checkChanges() + { + $changeset = array(); + + if ($this->isDeleted()) { + return $changeset; + } + + if (!$this->resource->exists()) { + $changeset[] = array('event' => Event::DELETED, 'resource' => $this->resource); + $this->deleted = true; + } elseif (!$this->resource->isFresh($this->timestamp)) { + $changeset[] = array('event' => Event::MODIFIED, 'resource' => $this->resource); + $this->timestamp = $this->resource->getModificationTime(); + } + + return $changeset; + } + + /** + * Checks whether resource have been previously deleted. + * + * @return Boolean + */ + protected function isDeleted() + { + return $this->deleted; + } +} diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php index a78dfb45b402c..b8155423e3b06 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php @@ -18,13 +18,6 @@ */ interface StateCheckerInterface { - /** - * Is tracked resource still exists. - * - * @return Boolean - */ - function isResourceExists(); - /** * Returns tracked resource. * diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php new file mode 100644 index 0000000000000..3aa6ffb85a46f --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\ResourceWatcher\StateChecker\DirectoryStateChecker; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +class DirectoryStateCheckerTest extends FileStateCheckerTest +{ + protected function setUp() + { + $this->resource = $this->createDirectoryResourceMock(); + $this->resource + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array())); + $this->resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + + $this->checker = new DirectoryStateChecker($this->resource); + } + + public function testDeepFileChanged() + { + $resource = $this->createDirectoryResourceMock(); + $resource + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foo = $this->createDirectoryResourceMock() + ))); + $resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + $foo + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foobar = $this->createFileResourceMock() + ))); + $foo + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(22)); + $foobar + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(33)); + + $checker = new DirectoryStateChecker($resource); + + $this->touchResource($resource, true, true); + $this->touchResource($foo, true, true); + $this->touchResource($foobar, true, false); + + $this->assertEquals(array( + array('event' => Event::MODIFIED, 'resource' => $foobar) + ), $checker->checkChanges()); + } + + public function testDeepFileDeleted() + { + $resource = $this->createDirectoryResourceMock(); + $resource + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foo = $this->createDirectoryResourceMock() + ))); + $resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + $foo + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foobar = $this->createFileResourceMock() + ))); + $foo + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(22)); + $foobar + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(33)); + + $checker = new DirectoryStateChecker($resource); + + $this->touchResource($resource, true, true); + $this->touchResource($foo, true, true); + $this->touchResource($foobar, false); + + $this->assertEquals(array( + array('event' => Event::DELETED, 'resource' => $foobar) + ), $checker->checkChanges()); + } + + public function testDeepFileCreated() + { + $resource = $this->createDirectoryResourceMock(); + $resource + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foo = $this->createDirectoryResourceMock() + ))); + $resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + $foo + ->expects($this->any()) + ->method('getFilteredChildResources') + ->will($this->returnValue(array( + $foobar = $this->createFileResourceMock() + ))); + $foo + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(22)); + $foobar + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(33)); + + $checker = new DirectoryStateChecker($resource); + + $this->touchResource($resource, true, true); + $this->touchResource($foo, true, true); + $this->touchResource($foobar, false); + + $this->assertEquals(array( + array('event' => Event::DELETED, 'resource' => $foobar) + ), $checker->checkChanges()); + } + + protected function touchResource(ResourceInterface $resource, $exists = true, $fresh = true) + { + $resource + ->expects($this->once()) + ->method('exists') + ->will($this->returnValue($exists)); + + if ($exists) { + $resource + ->expects($this->once()) + ->method('isFresh') + ->will($this->returnValue($fresh)); + } + } + + protected function createDirectoryResourceMock() + { + return $this->getMockBuilder('Symfony\Component\Config\Resource\DirectoryResource') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php new file mode 100644 index 0000000000000..a235f5dfa1627 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\StateChecker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\ResourceWatcher\StateChecker\FileStateChecker; + +class FileStateCheckerTest extends \PHPUnit_Framework_TestCase +{ + protected $resource; + + protected function setUp() + { + $this->resource = $this->createFileResourceMock(); + $this->resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + + $this->checker = new FileStateChecker($this->resource); + } + + public function testGetResource() + { + $this->assertSame($this->resource, $this->checker->getResource()); + } + + public function testNoChanges() + { + $this->resource + ->expects($this->once()) + ->method('exists') + ->will($this->returnValue(true)); + $this->resource + ->expects($this->once()) + ->method('isFresh') + ->with(11) + ->will($this->returnValue(true)); + + $this->assertEquals(array(), $this->checker->checkChanges()); + } + + public function testDeleted() + { + $this->resource + ->expects($this->once()) + ->method('exists') + ->will($this->returnValue(false)); + + $this->assertEquals( + array(array('event' => Event::DELETED, 'resource' => $this->resource)), + $this->checker->checkChanges() + ); + } + + public function testModified() + { + $this->resource + ->expects($this->once()) + ->method('exists') + ->will($this->returnValue(true)); + $this->resource + ->expects($this->once()) + ->method('isFresh') + ->with(11) + ->will($this->returnValue(false)); + + $this->assertEquals( + array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), + $this->checker->checkChanges() + ); + } + + public function testConsecutiveChecks() + { + $this->resource + ->expects($this->exactly(2)) + ->method('exists') + ->will($this->onConsecutiveCalls(true, false)); + $this->resource + ->expects($this->once()) + ->method('isFresh') + ->with(11) + ->will($this->returnValue(false)); + + $this->assertEquals( + array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), + $this->checker->checkChanges() + ); + $this->assertEquals( + array(array('event' => Event::DELETED, 'resource' => $this->resource)), + $this->checker->checkChanges() + ); + $this->assertEquals(array(), $this->checker->checkChanges()); + } + + protected function createFileResourceMock() + { + return $this->getMockBuilder('Symfony\Component\Config\Resource\FileResource') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php deleted file mode 100644 index ca9e317e25718..0000000000000 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/RecursiveIteratorStateCheckerTest.php +++ /dev/null @@ -1,192 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Tests\Component\ResourceWatcher\StateChecker; - -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Config\Resource\DirectoryResource; -use Symfony\Component\ResourceWatcher\StateChecker\RecursiveIteratorStateChecker; - -class RecursiveIteratorStateCheckerTest extends \PHPUnit_Framework_TestCase -{ - private $tempPath; - - protected function setUp() - { - $this->tempPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . - 'symfony_resource_watcher_recucrisve_statechecker'; - - if (!file_exists($this->tempPath)) { - mkdir($this->tempPath, 0777, true); - } - } - - protected function tearDown() - { - if (file_exists($this->tempPath)) { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($this->tempPath), \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($iterator as $path) { - if ($path->isDir()) { - rmdir((string) $path); - } else { - unlink((string) $path); - } - } - - rmdir($this->tempPath); - } - } - - public function testFileStatusChecks() - { - touch($path = $this->generateRandomPath(), time() - 10); - $resource = new FileResource($path); - $checker = new RecursiveIteratorStateChecker($resource); - - $this->assertTrue($checker->isResourceExists()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($path, time() - 2); - filemtime($path); - - $this->assertEquals(array('modified' => array($resource)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertEquals(0, count($checker->checkChanges())); - - file_put_contents($path, 'test2'); - filemtime($path); - - $this->assertEquals(array('modified' => array($resource)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - unlink($path); - - $this->assertTrue($checker->isResourceExists()); - $this->assertEquals(array('deleted' => array($resource)), $checker->checkChanges()); - $this->assertFalse($checker->isResourceExists()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertEquals(0, count($checker->checkChanges())); - } - - public function testDirectoryStatusChecks() - { - mkdir($path = $this->generateRandomPath(), 0777, true); - - $resource = new DirectoryResource($path); - $checker = new RecursiveIteratorStateChecker($resource); - - $this->assertTrue($checker->isResourceExists()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($filePath = $this->generateRandomPath($path), time() - 10); - $file1 = new FileResource($filePath); - filemtime($file1->getResource()); - - $this->assertEquals(array('created' => array($file1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($file1->getResource(), time() - 5); - filemtime($file1->getResource()); - - $this->assertEquals(array('modified' => array($file1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($filePath = $this->generateRandomPath($path), time() - 10); - $file2 = new FileResource($filePath); - filemtime($file2->getResource()); - - $this->assertEquals(array('created' => array($file2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - unlink($file1->getResource()); - - $this->assertEquals(array('deleted' => array($file1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertTrue($checker->isResourceExists()); - - touch($file2->getResource()); - filemtime($file2->getResource()); - - $this->assertEquals(array('modified' => array($file2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - unlink($file2->getResource()); - - $this->assertEquals(array('deleted' => array($file2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertTrue($checker->isResourceExists()); - - rmdir($resource->getResource()); - $this->assertEquals(array('deleted' => array($resource)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertFalse($checker->isResourceExists()); - } - - public function testDirectoryInsideDirectoryChecks() - { - mkdir($path = $this->generateRandomPath(), 0777, true); - $resource = new DirectoryResource($path); - $checker = new RecursiveIteratorStateChecker($resource); - - $this->assertTrue($checker->isResourceExists()); - $this->assertEquals(0, count($checker->checkChanges())); - - mkdir($dir1Path = $this->generateRandomPath($path), 0777, true); - $dir1 = new DirectoryResource($dir1Path); - - $this->assertEquals(array('created' => array($dir1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - mkdir($dir2Path = $this->generateRandomPath($dir1Path), 0777, true); - $dir2 = new DirectoryResource($dir2Path); - - $this->assertEquals(array('created' => array($dir2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($filePath = $this->generateRandomPath($dir1Path), time() - 10); - $file1 = new FileResource($filePath); - filemtime($file1->getResource()); - - $this->assertEquals(array('created' => array($file1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - touch($filePath = $this->generateRandomPath($dir2Path), time() - 10); - $file2 = new FileResource($filePath); - filemtime($file1->getResource()); - - $this->assertEquals(array('created' => array($file2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - - unlink($file1->getResource()); - - $this->assertEquals(array('deleted' => array($file1)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - $this->assertTrue($checker->isResourceExists()); - - touch($file2, time() - 5); - filemtime($file2->getResource()); - - $this->assertEquals(array('modified' => array($file2)), $checker->checkChanges()); - $this->assertEquals(0, count($checker->checkChanges())); - } - - private function generateRandomPath($prefix = null) - { - if (null === $prefix) { - $prefix = $this->tempPath; - } - - return $prefix . DIRECTORY_SEPARATOR . md5(microtime(true) . rand(1, 9999)); - } -} From 7a5587def595c4f90fb2c8899faa348c0649a87f Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 23:31:59 +0100 Subject: [PATCH 17/36] [Config] getFilteredChildResources() method added to DirectoryResource --- .../Config/Resource/DirectoryResource.php | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 1818f6da9fbb1..10424c3c6b4cb 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -36,29 +36,15 @@ public function __construct($resource, $pattern = null) /** * Returns the list of filtered file and directory childs of directory resource. * - * @param Boolean $recursive search for files recursive - * * @return array An array of files */ - public function getFilteredChilds($recursive = true) + public function getFilteredChilds() { - $iterator = $recursive - ? new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) - : new \DirectoryIterator($this->resource); - $childs = array(); - foreach ($iterator as $file) { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { // if regex filtering is enabled only return matching files - if (isset($this->filterRegexList) && $file->isFile()) { - $regexMatched = false; - foreach ($this->filterRegexList as $regex) { - if (preg_match($regex, (string) $file)) { - $regexMatched = true; - } - } - if (!$regexMatched) { - continue; - } + if (!$this->isFileMatchesFilters($file)) { + continue; } // always monitor directories for changes, except the .. entries @@ -73,6 +59,36 @@ public function getFilteredChilds($recursive = true) return $childs; } + /** + * Returns child resources that matches directory filters. + * + * @return array + */ + public function getFilteredChildResources() + { + $iterator = new \DirectoryIterator($this->resource); + + $resources = array(); + foreach ($iterator as $file) { + // if regex filtering is enabled only return matching files + if (!$this->isFileMatchesFilters($file)) { + continue; + } + + // always monitor directories for changes, except the .. entries + // (otherwise deleted files wouldn't get detected) + if ($file->isDir() && '/..' === substr($file, -3)) { + continue; + } + + $resources[] = $file->isDir() + ? new DirectoryResource((string) $file) + : new FileResource((string) $file); + } + + return $resources; + } + /** * Returns a string representation of the Resource. * @@ -151,4 +167,28 @@ public function unserialize($serialized) { list($this->resource, $this->pattern) = unserialize($serialized); } + + /** + * Checks that passed file matches specified in resource filters. + * + * @param \SplFileInfo $file + * + * @return Boolean + */ + private function isFileMatchesFilters(\SplFileInfo $file) + { + if (isset($this->filterRegexList) && $file->isFile()) { + $regexMatched = false; + foreach ($this->filterRegexList as $regex) { + if (preg_match($regex, (string) $file)) { + $regexMatched = true; + } + } + if (!$regexMatched) { + return false; + } + } + + return true; + } } From c6d85a60937c856cc2d6688d859a508287c7c5ee Mon Sep 17 00:00:00 2001 From: everzet Date: Sat, 19 Nov 2011 23:32:37 +0100 Subject: [PATCH 18/36] [ResourceWatcher] refactored recursive iterator tracker to support new state checkers --- .../Tracker/RecursiveIteratorTracker.php | 35 +++++++------------ .../Tracker/RecursiveIteratorTrackerTest.php | 10 +++--- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php index 6213746c82ee2..2d7b40f04dfa1 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php @@ -13,8 +13,11 @@ use Symfony\Component\ResourceWatcher\Event\Event; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\ResourceWatcher\StateChecker\DirectoryStateChecker; +use Symfony\Component\ResourceWatcher\StateChecker\FileStateChecker; use Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface; -use Symfony\Component\ResourceWatcher\StateChecker\RecursiveIteratorStateChecker; /** * Recursive iterator resources tracker. @@ -32,15 +35,19 @@ class RecursiveIteratorTracker implements TrackerInterface */ public function track(ResourceInterface $resource) { - $this->addStateChecker(new RecursiveIteratorStateChecker($resource)); + $checker = $resource instanceof DirectoryResource + ? new DirectoryStateChecker($resource) + : new FileStateChecker($resource); + + $this->addResourceStateChecker($checker); } /** - * Adds state checker to tracker. + * Adds resource state checker. * * @param StateCheckerInterface $checker */ - public function addStateChecker(StateCheckerInterface $checker) + public function addResourceStateChecker(StateCheckerInterface $checker) { $this->checkers[$this->getResourceTrackingId($checker->getResource())] = $checker; } @@ -78,24 +85,8 @@ public function checkChanges() { $events = array(); foreach ($this->checkers as $trackingId => $checker) { - $changeset = $checker->checkChanges(); - - foreach ($changeset as $type => $resources) { - switch ($type) { - case 'created': - $eventType = Event::CREATED; - break; - case 'modified': - $eventType = Event::MODIFIED; - break; - case 'deleted': - $eventType = Event::DELETED; - break; - } - - foreach ($resources as $resource) { - $events[] = new Event($trackingId, $resource, $eventType); - } + foreach ($checker->checkChanges() as $change) { + $events[] = new Event($trackingId, $change['resource'], $change['event']); } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php index ade6d4ab486dc..486052c7ae07c 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php @@ -34,7 +34,7 @@ public function testIsResourceTracked() ->method('getResource') ->will($this->returnValue($resource)); - $tracker->addStateChecker($checker); + $tracker->addResourceStateChecker($checker); $this->assertTrue($tracker->isResourceTracked($resource)); } @@ -68,15 +68,15 @@ public function testCheckChanges() ->method('getResource') ->will($this->returnValue($resource)); - $tracker->addStateChecker($checker); + $tracker->addResourceStateChecker($checker); $checker ->expects($this->once()) ->method('checkChanges') ->will($this->returnValue(array( - 'created' => array($resource), - 'modified' => array($resource), - 'deleted' => array($resource), + array('event' => Event::CREATED, 'resource' => $resource), + array('event' => Event::MODIFIED, 'resource' => $resource), + array('event' => Event::DELETED, 'resource' => $resource), ))); $this->assertEquals(array( From 742964675ee02fa12a4d3d717162c48727150601 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Nov 2011 12:11:24 +0100 Subject: [PATCH 19/36] [Config] updated DirectoryResource tests --- .../Config/Resource/DirectoryResource.php | 27 +++++------ .../Config/Resource/DirectoryResourceTest.php | 45 +++++++++++++------ 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 10424c3c6b4cb..b36e0372db964 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -43,7 +43,7 @@ public function getFilteredChilds() $childs = array(); foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { // if regex filtering is enabled only return matching files - if (!$this->isFileMatchesFilters($file)) { + if ($file->isFile() && !$this->isFileMatchesPattern($file)) { continue; } @@ -71,7 +71,7 @@ public function getFilteredChildResources() $resources = array(); foreach ($iterator as $file) { // if regex filtering is enabled only return matching files - if (!$this->isFileMatchesFilters($file)) { + if ($file->isFile() && !$this->isFileMatchesPattern($file)) { continue; } @@ -109,6 +109,11 @@ public function getResource() return $this->resource; } + /** + * Returns check pattern. + * + * @return mixed + */ public function getPattern() { return $this->pattern; @@ -145,7 +150,7 @@ public function isFresh($timestamp) return false; } - return $this->getModificationTime() <= $timestamp; + return $this->getModificationTime() < $timestamp; } /** @@ -155,7 +160,7 @@ public function isFresh($timestamp) */ public function exists() { - return file_exists($this->resource); + return is_dir($this->resource); } public function serialize() @@ -175,18 +180,10 @@ public function unserialize($serialized) * * @return Boolean */ - private function isFileMatchesFilters(\SplFileInfo $file) + private function isFileMatchesPattern(\SplFileInfo $file) { - if (isset($this->filterRegexList) && $file->isFile()) { - $regexMatched = false; - foreach ($this->filterRegexList as $regex) { - if (preg_match($regex, (string) $file)) { - $regexMatched = true; - } - } - if (!$regexMatched) { - return false; - } + if ($this->pattern) { + return preg_match($this->pattern, $file->getBasename()); } return true; diff --git a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php index f27369c208825..07be0434534fb 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php @@ -117,20 +117,8 @@ public function testIsFreshDeleteDirectory() $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the whole resource is removed'); } - /** - * @covers Symfony\Component\Config\Resource\DirectoryResource::exists - */ - public function testExists() - { - $this->assertTrue($this->resource->exists(), '->exists() returns true if the directory still exist'); - - $this->removeDirectory($this->directory); - $this->assertFalse($this->resource->exists(), '->exists() returns false if the directory does not exist'); - } - /** * @covers Symfony\Component\Config\Resource\DirectoryResource::isFresh - * @covers Symfony\Component\Config\Resource\DirectoryResource::getModificationTime */ public function testIsFreshCreateFileInSubdirectory() { @@ -160,7 +148,6 @@ public function testIsFreshModifySubdirectory() /** * @covers Symfony\Component\Config\Resource\DirectoryResource::isFresh - * @covers Symfony\Component\Config\Resource\DirectoryResource::getFilteredChilds */ public function testFilterRegexListNoMatch() { @@ -180,4 +167,36 @@ public function testFilterRegexListMatch() touch($this->directory.'/new.xml', time() + 20); $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an new file matching the filter regex is created '); } + + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::exists + */ + public function testDirectoryExists() + { + $resource = new DirectoryResource($this->directory); + + $this->assertTrue($resource->exists(), '->exists() returns true if directory exists '); + + unlink($this->directory.'/tmp.xml'); + rmdir($this->directory); + + $this->assertFalse($resource->exists(), '->exists() returns false if directory does not exists'); + } + + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::getModificationTime + */ + public function testGetModificationTime() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + touch($this->directory.'/new.xml', $time = time() + 20); + $this->assertSame($time, $resource->getModificationTime(), '->getModificationTime() returns time of the last modificated resource'); + + touch($this->directory.'/some', time() + 60); + $this->assertSame($time, $resource->getModificationTime(), '->getModificationTime() returns time of last modificated resource, that only matches pattern'); + + touch($this->directory, $time2 = time() + 90); + $this->assertSame($time2, $resource->getModificationTime(), '->getModificationTime() returns modification time of the directory itself'); + } } From ebd6fd86aefcb6ae5581d8522eed9505754d3b42 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Nov 2011 14:48:15 +0100 Subject: [PATCH 20/36] [Config] update FileResourceTest --- .../Config/Resource/FileResourceTest.php | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php index fd3c09036f668..c45e932f3d65c 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php @@ -27,7 +27,9 @@ protected function setUp() protected function tearDown() { - unlink($this->file); + if ($this->file) { + unlink($this->file); + } } /** @@ -51,21 +53,24 @@ public function testIsFresh() } /** - * @covers Symfony\Component\Config\Resource\FileResource::exists + * @covers Symfony\Component\Config\Resource\FileResource::getModificationTime */ - public function testExists() + public function testGetModificationTime() { - $this->assertTrue($this->resource->exists(), '->exists() returns true if the resource does exist'); - - $resource = new FileResource('/____foo/foobar'.rand(1, 999999)); - $this->assertFalse($resource->exists(), '->exists() returns false if the resource does not exist'); + touch($this->file, $time = time() + 100); + $this->assertSame($time, $this->resource->getModificationTime()); } /** - * @covers Symfony\Component\Config\Resource\FileResource::getModificationTime + * @covers Symfony\Component\Config\Resource\FileResource::exists */ - public function testGetModificationTime() + public function testExists() { - $this->assertSame(filemtime($this->resource->getResource()), $this->resource->getModificationTime()); + $this->assertTrue($this->resource->exists(), '->exists() returns true if the resource exists'); + + unlink($this->file); + $this->file = null; + + $this->assertFalse($this->resource->exists(), '->exists() returns false if the resource does not exists'); } } From 0ab201d8dc1151b1fbd98dc9eba8d019cf86b505 Mon Sep 17 00:00:00 2001 From: everzet Date: Fri, 25 Nov 2011 14:54:21 +0100 Subject: [PATCH 21/36] [ResourceWatcher] removed InotifyTracker as i have no ability to test it yet --- .../ResourceWatcher/ResourceWatcher.php | 6 +- .../Tracker/InotifyTracker.php | 124 ------------------ 2 files changed, 1 insertion(+), 129 deletions(-) delete mode 100644 src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index 0643d6dd135eb..ec37373d2830a 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -40,11 +40,7 @@ public function __construct(TrackerInterface $tracker = null) if (null !== $tracker) { $this->tracker = $tracker; } else { - if (function_exists('inotify_init')) { - $this->tracker = new InotifyTracker(); - } else { - $this->tracker = new RecursiveIteratorTracker(); - } + $this->tracker = new RecursiveIteratorTracker(); } } diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php deleted file mode 100644 index 6a9e3265ac10d..0000000000000 --- a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ResourceWatcher\Tracker; - -use Symfony\Component\ResourceWatcher\Event\Event; -use Symfony\Component\Config\Resource\ResourceInterface; -use Symfony\Component\Config\Resource\DirectoryResource; -use Symfony\Component\Config\Resource\FileResource; - -/** - * Inotify events resources tracker. - * - * @author Konstantin Kudryashov - */ -class InotifyTracker implements TrackerInterface -{ - private $stream; - private $trackingIds = array(); - private $tracks = array(); - - /** - * Initializes tracker. - */ - public function __construct() - { - $this->stream = inotify_init(); - } - - /** - * Destructs tracker. - */ - public function __destruct() - { - fclose($this->stream); - } - - /** - * Starts to track provided resource for changes. - * - * @param ResourceInterface $resource - */ - public function track(ResourceInterface $resource) - { - $trackingId = inotify_add_watch( - $this->stream, $resource->getResource(), IN_CREATE | IN_MODIFY | IN_DELETE - ); - $this->trackingIds[$resource->getResource()] = $trackingId; - - $this->tracks[$trackingId] = $resource; - } - - /** - * Checks whether provided resource is tracked by this tracker. - * - * @param ResourceInterface $resource - * - * @return Boolean - */ - public function isResourceTracked(ResourceInterface $resource) - { - return null !== $this->getResourceTrackingId($resource); - } - - /** - * Returns resource tracking ID. - * - * @param ResourceInterface $resource - * - * @return mixed - */ - public function getResourceTrackingId(ResourceInterface $resource) - { - return isset($this->trackingIds[$resource->getResource()]) - ? $this->trackingIds[$resource->getResource()] - : null; - } - - /** - * Checks tracked resources for changes. - * - * @return array change events array - */ - public function checkChanges() - { - $events = array(); - - if ($iEvents = inotify_read($this->stream)) { - foreach ($iEvents as $iEvent) { - $trackingId = $iEvent['wd']; - - if (isset($this->tracks[$trackingId])) { - $resource = $this->tracks[$trackingId]; - } else { - if (is_dir($iEvent['name'])) { - $resource = new DirectoryResource($iEvent['name']); - } else { - $resource = new FileResource($iEvent['name']); - } - } - - if ($iEvent['mask'] & IN_CREATE) { - $event = Event::CREATED; - } elseif ($iEvent['mask'] & IN_MODIFY) { - $event = Event::MODIFIED; - } elseif ($iEvent['mask'] & IN_DELETE) { - $event = Event::DELETED; - } - - $events[] = new Event($trackingId, $resource, $event); - } - } - - return $events; - } -} From 816373d3c5e96ad99b7045e86032410105bb0b42 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 22:51:48 +0100 Subject: [PATCH 22/36] [Config] added new methods and their tests to File and Directory resources --- .../Config/Resource/DirectoryResource.php | 69 +++++++++++----- .../Config/Resource/FileResource.php | 10 +++ .../Config/Resource/ResourceInterface.php | 7 ++ .../Config/Resource/DirectoryResourceTest.php | 80 +++++++++++++++++++ .../Config/Resource/FileResourceTest.php | 12 +++ 5 files changed, 156 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index b36e0372db964..c2c1e589597f4 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -43,7 +43,7 @@ public function getFilteredChilds() $childs = array(); foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { // if regex filtering is enabled only return matching files - if ($file->isFile() && !$this->isFileMatchesPattern($file)) { + if ($file->isFile() && !$this->hasFile($file)) { continue; } @@ -64,14 +64,14 @@ public function getFilteredChilds() * * @return array */ - public function getFilteredChildResources() + public function getFilteredResources() { $iterator = new \DirectoryIterator($this->resource); $resources = array(); foreach ($iterator as $file) { // if regex filtering is enabled only return matching files - if ($file->isFile() && !$this->isFileMatchesPattern($file)) { + if ($file->isFile() && !$this->hasFile($file)) { continue; } @@ -81,9 +81,16 @@ public function getFilteredChildResources() continue; } - $resources[] = $file->isDir() - ? new DirectoryResource((string) $file) - : new FileResource((string) $file); + // if file is dot - continue + if ($file->isDot()) { + continue; + } + + if ($file->isFile()) { + $resources[] = new FileResource($file->getRealPath()); + } elseif ($file->isDir()) { + $resources[] = new DirectoryResource($file->getRealPath()); + } } return $resources; @@ -119,6 +126,30 @@ public function getPattern() return $this->pattern; } + /** + * Checks that passed file exists in resource and matches resource filters. + * + * @param SplFileInfo|string $file + * + * @return Boolean + */ + public function hasFile($file) + { + if (!$file instanceof \SplFileInfo) { + $file = new \SplFileInfo($file); + } + + if (0 !== strpos($file->getRealPath(), realpath($this->resource))) { + return false; + } + + if ($this->pattern) { + return (bool) preg_match($this->pattern, $file->getBasename()); + } + + return true; + } + /** * Returns resource mtime. * @@ -163,6 +194,16 @@ public function exists() return is_dir($this->resource); } + /** + * Returns unique resource ID. + * + * @return string + */ + public function getId() + { + return md5($this->resource.$this->pattern); + } + public function serialize() { return serialize(array($this->resource, $this->pattern)); @@ -172,20 +213,4 @@ public function unserialize($serialized) { list($this->resource, $this->pattern) = unserialize($serialized); } - - /** - * Checks that passed file matches specified in resource filters. - * - * @param \SplFileInfo $file - * - * @return Boolean - */ - private function isFileMatchesPattern(\SplFileInfo $file) - { - if ($this->pattern) { - return preg_match($this->pattern, $file->getBasename()); - } - - return true; - } } diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 2499de68e5f8c..9d699dae004ec 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -90,6 +90,16 @@ public function exists() return file_exists($this->resource); } + /** + * Returns unique resource ID. + * + * @return string + */ + public function getId() + { + return md5($this->resource); + } + public function serialize() { return serialize($this->resource); diff --git a/src/Symfony/Component/Config/Resource/ResourceInterface.php b/src/Symfony/Component/Config/Resource/ResourceInterface.php index e684fd6b09253..b4529e8de661d 100644 --- a/src/Symfony/Component/Config/Resource/ResourceInterface.php +++ b/src/Symfony/Component/Config/Resource/ResourceInterface.php @@ -54,4 +54,11 @@ function exists(); * @return mixed The resource */ function getResource(); + + /** + * Returns unique resource ID. + * + * @return string + */ + function getId(); } diff --git a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php index 07be0434534fb..e0f47fb33b1d8 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/DirectoryResourceTest.php @@ -49,6 +49,20 @@ protected function removeDirectory($directory) { rmdir($directory); } + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::getId + */ + public function testGetId() + { + $resource1 = new DirectoryResource($this->directory); + $resource2 = new DirectoryResource($this->directory); + $resource3 = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + $this->assertNotNull($resource1->getId()); + $this->assertEquals($resource1->getId(), $resource2->getId()); + $this->assertNotEquals($resource1->getId(), $resource3->getId()); + } + /** * @covers Symfony\Component\Config\Resource\DirectoryResource::getResource */ @@ -168,6 +182,72 @@ public function testFilterRegexListMatch() $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an new file matching the filter regex is created '); } + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::hasFile + */ + public function testHasFile() + { + $resource = new DirectoryResource($this->directory, '/\.foo$/'); + + touch($this->directory.'/new.foo', time() + 20); + + $this->assertFalse($resource->hasFile($this->directory.'/tmp.xml')); + $this->assertTrue($resource->hasFile($this->directory.'/new.foo')); + } + + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::getFilteredChilds + */ + public function testGetFilteredChilds() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + touch($file1 = $this->directory.'/new.xml', time() + 20); + touch($file2 = $this->directory.'/old.foo', time() + 20); + touch($this->directory.'/old', time() + 20); + mkdir($dir = $this->directory.'/sub'); + touch($file3 = $this->directory.'/sub/file.foo', time() + 20); + + $childs = $resource->getFilteredChilds(); + $this->assertSame(5, count($childs)); + + $childs = array_map(function($item) { + return (string) $item; + }, $childs); + + $this->assertContains($file1, $childs); + $this->assertContains($file2, $childs); + $this->assertContains($dir, $childs); + $this->assertContains($this->directory.'/tmp.xml', $childs); + $this->assertContains($file3, $childs); + } + + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::getFilteredResources + */ + public function testGetFilteredResources() + { + $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/'); + + touch($file1 = $this->directory.'/new.xml', time() + 20); + touch($file2 = $this->directory.'/old.foo', time() + 20); + touch($this->directory.'/old', time() + 20); + mkdir($dir = $this->directory.'/sub'); + touch($file3 = $this->directory.'/sub/file.foo', time() + 20); + + $resources = $resource->getFilteredResources(); + $this->assertSame(4, count($resources)); + + $childs = array_map(function($item) { + return realpath($item->getResource()); + }, $resources); + + $this->assertContains(realpath($file1), $childs); + $this->assertContains(realpath($file2), $childs); + $this->assertContains(realpath($dir), $childs); + $this->assertContains(realpath($this->directory.'/tmp.xml'), $childs); + } + /** * @covers Symfony\Component\Config\Resource\DirectoryResource::exists */ diff --git a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php index c45e932f3d65c..1a6ba15380928 100644 --- a/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php +++ b/tests/Symfony/Tests/Component/Config/Resource/FileResourceTest.php @@ -32,6 +32,18 @@ protected function tearDown() } } + /** + * @covers Symfony\Component\Config\Resource\DirectoryResource::getId + */ + public function testGetId() + { + $resource1 = new FileResource($this->file); + $resource2 = new FileResource($this->file); + + $this->assertNotNull($resource1->getId()); + $this->assertEquals($resource1->getId(), $resource2->getId()); + } + /** * @covers Symfony\Component\Config\Resource\FileResource::getResource */ From bc8e4370b5fff71c68f06a771704a623ec3461a2 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 22:59:49 +0100 Subject: [PATCH 23/36] [ResourceWatcher] updated ResourceWatcher and TrackerInterface --- .../ResourceWatcher/ResourceWatcher.php | 50 ++++++++++++------- .../Tracker/TrackerInterface.php | 13 +---- .../ResourceWatcher/ResourceWatcherTest.php | 28 +++++++---- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index ec37373d2830a..b638a6dd1b1f0 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -12,6 +12,8 @@ namespace Symfony\Component\ResourceWatcher; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\ResourceWatcher\Event\Event; use Symfony\Component\ResourceWatcher\Event\EventListener; use Symfony\Component\ResourceWatcher\Event\EventListenerInterface; @@ -39,20 +41,44 @@ public function __construct(TrackerInterface $tracker = null) { if (null !== $tracker) { $this->tracker = $tracker; + } elseif (function_exists('inotify_init')) { + $this->tracker = new InotifyTracker(); } else { $this->tracker = new RecursiveIteratorTracker(); } } + /** + * Returns current tracker instance. + * + * @return TrackerInterface + */ + public function getTracker() + { + return $this->tracker; + } + /** * Track resource with watcher. * - * @param ResourceInterface $resource resource to track - * @param callable $callback event callback - * @param integer $eventsMask event types bitmask + * @param ResourceInterface|string $resource resource to track + * @param callable $callback event callback + * @param integer $eventsMask event types bitmask */ - public function track(ResourceInterface $resource, $callback, $eventsMask = Event::ALL) + public function track($resource, $callback, $eventsMask = Event::ALL) { + if (!$resource instanceof ResourceInterface) { + if (is_file($resource)) { + $resource = new FileResource($resource); + } elseif (is_dir($resource)) { + $resource = new DirectoryResource($resource); + } else { + throw new \InvalidArgumentException(sprintf( + 'First argument to track() should be either file or directory resource, but got "%s"', $resource + )); + } + } + if (!is_callable($callback)) { throw new \InvalidArgumentException('Second argument to track() should be callable.'); } @@ -71,7 +97,7 @@ public function addListener(EventListenerInterface $listener) $this->getTracker()->track($listener->getResource()); } - $trackingId = $this->getTracker()->getResourceTrackingId($listener->getResource()); + $trackingId = $listener->getResource()->getId(); if (!isset($this->listeners[$trackingId])) { $this->listeners[$trackingId] = array(); @@ -109,12 +135,10 @@ public function start($checkInterval = 1000000, $timeLimit = null) break; } - if (count($events = $this->getTracker()->checkChanges())) { + if (count($events = $this->getTracker()->getEvents())) { $this->notifyListeners($events); } } - - $this->watching = false; } /** @@ -125,16 +149,6 @@ public function stop() $this->watching = false; } - /** - * Returns current tracker instance. - * - * @return TrackerInterface - */ - protected function getTracker() - { - return $this->tracker; - } - /** * Notifies all registered resource event listeners about their events. * diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php b/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php index e93d9ebe05f02..1497ce029d4e0 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/TrackerInterface.php @@ -37,18 +37,9 @@ function track(ResourceInterface $resource); function isResourceTracked(ResourceInterface $resource); /** - * Returns resource tracking ID. - * - * @param ResourceInterface $resource - * - * @return mixed - */ - function getResourceTrackingId(ResourceInterface $resource); - - /** - * Checks tracked resources for changes. + * Checks tracked resources for change events. * * @return array change events array */ - function checkChanges(); + function getEvents(); } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php index 1e95e4811ba5c..1290c68860124 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/ResourceWatcherTest.php @@ -36,10 +36,10 @@ public function testUntrackedResourceTrack() ->method('track') ->with($resource) ->will($this->returnValue(null)); - $tracker + + $resource ->expects($this->once()) - ->method('getResourceTrackingId') - ->with($resource); + ->method('getId'); $watcher = new ResourceWatcher($tracker); $watcher->track($resource, function(){}); @@ -63,10 +63,10 @@ public function testTrackedResourceTrack() $tracker ->expects($this->never()) ->method('track'); - $tracker + + $resource ->expects($this->once()) - ->method('getResourceTrackingId') - ->with($resource); + ->method('getId'); $watcher = new ResourceWatcher($tracker); $watcher->track($resource, function(){}); @@ -111,10 +111,16 @@ public function testWatching() $tracker ->expects($this->exactly(2)) ->method('track'); - $tracker - ->expects($this->exactly(3)) - ->method('getResourceTrackingId') - ->will($this->onConsecutiveCalls(1, 2, 2)); + + $resource1 + ->expects($this->once()) + ->method('getId') + ->will($this->returnValue(1)); + + $resource2 + ->expects($this->exactly(2)) + ->method('getId') + ->will($this->onConsecutiveCalls(2, 2)); $watcher = new ResourceWatcher($tracker); $watcher->addListener($listener1); @@ -148,7 +154,7 @@ public function testWatching() $tracker ->expects($this->once()) - ->method('checkChanges') + ->method('getEvents') ->will($this->returnValue(array( new Event(1, $resource1, 1), new Event(2, $resource2, 1), From 40e7f4ed950a46d8e50679b0f5635b41262df633 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:02:02 +0100 Subject: [PATCH 24/36] [ResourceWatcher] added time information to events --- .../Component/ResourceWatcher/Event/Event.php | 12 ++++++++++++ .../Component/ResourceWatcher/Event/EventTest.php | 1 + 2 files changed, 13 insertions(+) diff --git a/src/Symfony/Component/ResourceWatcher/Event/Event.php b/src/Symfony/Component/ResourceWatcher/Event/Event.php index e562454cb3b06..0c1a1ebf9616c 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/Event.php +++ b/src/Symfony/Component/ResourceWatcher/Event/Event.php @@ -28,6 +28,7 @@ class Event private $trackingId; private $resource; private $type; + private $time; /** * Initializes resource event. @@ -41,6 +42,7 @@ public function __construct($trackingId, ResourceInterface $resource, $type) $this->trackingId = $trackingId; $this->resource = $resource; $this->type = $type; + $this->time = time(); } /** @@ -72,4 +74,14 @@ public function getType() { return $this->type; } + + /** + * Returns time on which event occured. + * + * @return integer + */ + public function getTime() + { + return $this->time; + } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php index c97126b32ad67..799048d5734be 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Event/EventTest.php @@ -23,5 +23,6 @@ public function testConstructAndGetters() $this->assertEquals($id, $event->getTrackingId()); $this->assertSame($res, $event->getResource()); $this->assertSame($type, $event->getType()); + $this->assertNotNull($event->getTime()); } } From 6752b30699a3b4ace63657ea1281c0531cf26f70 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:16:32 +0100 Subject: [PATCH 25/36] [ResourceWatcher] updated FileStateChecker to match FileResource API --- .../StateChecker/ResourceStateChecker.php | 6 +++--- .../StateChecker/FileStateCheckerTest.php | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php index 5280cf4a24d5a..b2be5371d5ec3 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php @@ -33,7 +33,7 @@ abstract class ResourceStateChecker implements StateCheckerInterface public function __construct(ResourceInterface $resource) { $this->resource = $resource; - $this->timestamp = $resource->getModificationTime(); + $this->timestamp = $resource->getModificationTime() + 1; } /** @@ -51,7 +51,7 @@ public function getResource() * * @return array */ - public function checkChanges() + public function getChangeset() { $changeset = array(); @@ -64,7 +64,7 @@ public function checkChanges() $this->deleted = true; } elseif (!$this->resource->isFresh($this->timestamp)) { $changeset[] = array('event' => Event::MODIFIED, 'resource' => $this->resource); - $this->timestamp = $this->resource->getModificationTime(); + $this->timestamp = $this->resource->getModificationTime() + 1; } return $changeset; diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php index a235f5dfa1627..1de97ca152aa5 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php @@ -43,10 +43,10 @@ public function testNoChanges() $this->resource ->expects($this->once()) ->method('isFresh') - ->with(11) + ->with(12) ->will($this->returnValue(true)); - $this->assertEquals(array(), $this->checker->checkChanges()); + $this->assertEquals(array(), $this->checker->getChangeset()); } public function testDeleted() @@ -58,7 +58,7 @@ public function testDeleted() $this->assertEquals( array(array('event' => Event::DELETED, 'resource' => $this->resource)), - $this->checker->checkChanges() + $this->checker->getChangeset() ); } @@ -71,12 +71,12 @@ public function testModified() $this->resource ->expects($this->once()) ->method('isFresh') - ->with(11) + ->with(12) ->will($this->returnValue(false)); $this->assertEquals( array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), - $this->checker->checkChanges() + $this->checker->getChangeset() ); } @@ -89,18 +89,18 @@ public function testConsecutiveChecks() $this->resource ->expects($this->once()) ->method('isFresh') - ->with(11) + ->with(12) ->will($this->returnValue(false)); $this->assertEquals( array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), - $this->checker->checkChanges() + $this->checker->getChangeset() ); $this->assertEquals( array(array('event' => Event::DELETED, 'resource' => $this->resource)), - $this->checker->checkChanges() + $this->checker->getChangeset() ); - $this->assertEquals(array(), $this->checker->checkChanges()); + $this->assertEquals(array(), $this->checker->getChangeset()); } protected function createFileResourceMock() From e73385b89e016c50d41bf43b498196555200465b Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:27:45 +0100 Subject: [PATCH 26/36] [ResourceWatcher] refactored DirectoryStateChecker --- .../StateChecker/DirectoryStateChecker.php | 33 +++++++++++-------- .../DirectoryStateCheckerTest.php | 33 ++++++++++++------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php index 0dafb4ba09016..4570dd13d9ef9 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/DirectoryStateChecker.php @@ -34,7 +34,7 @@ public function __construct(DirectoryResource $resource) parent::__construct($resource); foreach ($this->createDirectoryChildCheckers($resource) as $checker) { - $this->childs[(string) $checker->getResource()] = $checker; + $this->childs[$checker->getResource()->getId()] = $checker; } } @@ -43,25 +43,30 @@ public function __construct(DirectoryResource $resource) * * @return array */ - public function checkChanges() + public function getChangeset() { - $changeset = parent::checkChanges(); - if ((isset($changeset[0]) && $changeset[0]['event'] === Event::DELETED) || $this->isDeleted()) { - return $changeset; + $changeset = parent::getChangeset(); + if (count($changeset) && Event::MODIFIED === $changeset[0]['event']) { + $changeset = array(); } - foreach ($this->childs as $path => $checker) { - foreach ($checker->checkChanges() as $change) { + foreach ($this->childs as $id => $checker) { + foreach ($checker->getChangeset() as $change) { + if (Event::DELETED === $change['event'] && $id === $change['resource']->getId()) { + unset($this->childs[$id]); + } $changeset[] = $change; } } - foreach ($this->createDirectoryChildCheckers($this->getResource()) as $checker) { - if (!isset($this->childs[(string) $checker->getResource()])) { - $this->childs[(string) $checker->getResource()] = $checker; - $changeset[] = array( - 'event' => Event::CREATED, 'resource' => $checker->getResource() - ); + if ($this->getResource()->exists()) { + foreach ($this->createDirectoryChildCheckers($this->getResource()) as $checker) { + if (!isset($this->childs[$checker->getResource()->getId()])) { + $this->childs[$checker->getResource()->getId()] = $checker; + $changeset[] = array( + 'event' => Event::CREATED, 'resource' => $checker->getResource() + ); + } } } @@ -78,7 +83,7 @@ public function checkChanges() private function createDirectoryChildCheckers(DirectoryResource $resource) { $checkers = array(); - foreach ($resource->getFilteredChildResources() as $resource) { + foreach ($resource->getFilteredResources() as $resource) { if ($resource instanceof DirectoryResource) { $checkers[] = new DirectoryStateChecker($resource); } else { diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php index 3aa6ffb85a46f..538f6578bb631 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php @@ -17,14 +17,14 @@ use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; -class DirectoryStateCheckerTest extends FileStateCheckerTest +class DirectoryStateCheckerTest extends \PHPUnit_Framework_TestCase { protected function setUp() { $this->resource = $this->createDirectoryResourceMock(); $this->resource ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array())); $this->resource ->expects($this->any()) @@ -39,7 +39,7 @@ public function testDeepFileChanged() $resource = $this->createDirectoryResourceMock(); $resource ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foo = $this->createDirectoryResourceMock() ))); @@ -47,9 +47,10 @@ public function testDeepFileChanged() ->expects($this->any()) ->method('getModificationTime') ->will($this->returnValue(11)); + $foo ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foobar = $this->createFileResourceMock() ))); @@ -57,6 +58,7 @@ public function testDeepFileChanged() ->expects($this->any()) ->method('getModificationTime') ->will($this->returnValue(22)); + $foobar ->expects($this->any()) ->method('getModificationTime') @@ -70,7 +72,7 @@ public function testDeepFileChanged() $this->assertEquals(array( array('event' => Event::MODIFIED, 'resource' => $foobar) - ), $checker->checkChanges()); + ), $checker->getChangeset()); } public function testDeepFileDeleted() @@ -78,7 +80,7 @@ public function testDeepFileDeleted() $resource = $this->createDirectoryResourceMock(); $resource ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foo = $this->createDirectoryResourceMock() ))); @@ -88,7 +90,7 @@ public function testDeepFileDeleted() ->will($this->returnValue(11)); $foo ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foobar = $this->createFileResourceMock() ))); @@ -109,7 +111,7 @@ public function testDeepFileDeleted() $this->assertEquals(array( array('event' => Event::DELETED, 'resource' => $foobar) - ), $checker->checkChanges()); + ), $checker->getChangeset()); } public function testDeepFileCreated() @@ -117,7 +119,7 @@ public function testDeepFileCreated() $resource = $this->createDirectoryResourceMock(); $resource ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foo = $this->createDirectoryResourceMock() ))); @@ -127,7 +129,7 @@ public function testDeepFileCreated() ->will($this->returnValue(11)); $foo ->expects($this->any()) - ->method('getFilteredChildResources') + ->method('getFilteredResources') ->will($this->returnValue(array( $foobar = $this->createFileResourceMock() ))); @@ -148,13 +150,13 @@ public function testDeepFileCreated() $this->assertEquals(array( array('event' => Event::DELETED, 'resource' => $foobar) - ), $checker->checkChanges()); + ), $checker->getChangeset()); } protected function touchResource(ResourceInterface $resource, $exists = true, $fresh = true) { $resource - ->expects($this->once()) + ->expects($this->any()) ->method('exists') ->will($this->returnValue($exists)); @@ -172,4 +174,11 @@ protected function createDirectoryResourceMock() ->disableOriginalConstructor() ->getMock(); } + + protected function createFileResourceMock() + { + return $this->getMockBuilder('Symfony\Component\Config\Resource\FileResource') + ->disableOriginalConstructor() + ->getMock(); + } } From 554a48d65f580b80cf6a4125726d4c246a3a96d9 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:28:48 +0100 Subject: [PATCH 27/36] [ResourceWatcher] refactored RecursiveIteratorTracker --- .../Tracker/RecursiveIteratorTracker.php | 26 ++---- .../Tracker/RecursiveIteratorTrackerTest.php | 88 ------------------- 2 files changed, 7 insertions(+), 107 deletions(-) delete mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php index 2d7b40f04dfa1..ed5877ae2844c 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php @@ -49,7 +49,7 @@ public function track(ResourceInterface $resource) */ public function addResourceStateChecker(StateCheckerInterface $checker) { - $this->checkers[$this->getResourceTrackingId($checker->getResource())] = $checker; + $this->checkers[$checker->getResource()->getId()] = $checker; } /** @@ -61,32 +61,20 @@ public function addResourceStateChecker(StateCheckerInterface $checker) */ public function isResourceTracked(ResourceInterface $resource) { - return isset($this->checkers[$this->getResourceTrackingId($resource)]); + return isset($this->checkers[$resource->getId()]); } /** - * Returns resource tracking ID. - * - * @param ResourceInterface $resource - * - * @return mixed - */ - public function getResourceTrackingId(ResourceInterface $resource) - { - return md5((string) $resource); - } - - /** - * Checks tracked resources for changes. + * Checks tracked resources for change events. * * @return array change events array */ - public function checkChanges() + public function getEvents() { $events = array(); - foreach ($this->checkers as $trackingId => $checker) { - foreach ($checker->checkChanges() as $change) { - $events[] = new Event($trackingId, $change['resource'], $change['event']); + foreach ($this->checkers as $id => $checker) { + foreach ($checker->getChangeset() as $change) { + $events[] = new Event($id, $change['resource'], $change['event']); } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php deleted file mode 100644 index 486052c7ae07c..0000000000000 --- a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Tests\Component\ResourceWatcher\Tracker; - -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Config\Resource\DirectoryResource; -use Symfony\Component\ResourceWatcher\Tracker\RecursiveIteratorTracker; -use Symfony\Component\ResourceWatcher\Event\Event; - -class RecursiveIteratorTrackerTestTest extends \PHPUnit_Framework_TestCase -{ - public function testIsResourceTracked() - { - $tracker = new RecursiveIteratorTracker(); - $resource = new FileResource(__FILE__); - - $this->assertFalse($tracker->isResourceTracked($resource)); - - $checker = $this - ->getMockBuilder('Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface') - ->disableOriginalConstructor() - ->getMock(); - $checker - ->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($resource)); - - $tracker->addResourceStateChecker($checker); - - $this->assertTrue($tracker->isResourceTracked($resource)); - } - - public function testGetResourceTrackingId() - { - $tracker = new RecursiveIteratorTracker(); - $file = new FileResource(__FILE__); - $dir = new DirectoryResource(__DIR__); - - $this->assertNotNull($tracker->getResourceTrackingId($file)); - $this->assertNotNull($tracker->getResourceTrackingId($dir)); - - $this->assertNotEquals( - $tracker->getResourceTrackingId($file), $tracker->getResourceTrackingId($dir) - ); - } - - public function testCheckChanges() - { - $tracker = new RecursiveIteratorTracker(); - $resource = new FileResource(__FILE__); - $trackingId = $tracker->getResourceTrackingId($resource); - - $checker = $this - ->getMockBuilder('Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface') - ->disableOriginalConstructor() - ->getMock(); - $checker - ->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($resource)); - - $tracker->addResourceStateChecker($checker); - - $checker - ->expects($this->once()) - ->method('checkChanges') - ->will($this->returnValue(array( - array('event' => Event::CREATED, 'resource' => $resource), - array('event' => Event::MODIFIED, 'resource' => $resource), - array('event' => Event::DELETED, 'resource' => $resource), - ))); - - $this->assertEquals(array( - new Event($trackingId, $resource, Event::CREATED), - new Event($trackingId, $resource, Event::MODIFIED), - new Event($trackingId, $resource, Event::DELETED), - ), $tracker->checkChanges()); - } -} From cd9a2a7f17ce861f395195c70bec030e6d2a4332 Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:29:15 +0100 Subject: [PATCH 28/36] [ResourceWatcher] updated StateCheckerInterface --- .../ResourceWatcher/StateChecker/StateCheckerInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php index b8155423e3b06..54494491c6a2a 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/StateCheckerInterface.php @@ -30,5 +30,5 @@ function getResource(); * * @return array */ - function checkChanges(); + function getChangeset(); } From 51172e2b291435bc16b88d9be44839e2cbb58aff Mon Sep 17 00:00:00 2001 From: everzet Date: Tue, 29 Nov 2011 23:29:46 +0100 Subject: [PATCH 29/36] [ResourceWatcher] return of the InotifyTracker --- .../Tracker/InotifyTracker.php | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php new file mode 100644 index 0000000000000..616f4ccbf5393 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +/** + * Inotify events resources tracker. + * + * @author Konstantin Kudryashov + */ +class InotifyTracker implements TrackerInterface +{ + private $stream; + private $trackedResources = array(); + private $resourcePaths = array(); + + /** + * Initializes tracker. + */ + public function __construct() + { + $this->stream = inotify_init(); + stream_set_blocking($this->stream, 0); + } + + /** + * Destructs tracker. + */ + public function __destruct() + { + fclose($this->stream); + } + + /** + * Starts to track provided resource for changes. + * + * @param ResourceInterface $resource + */ + public function track(ResourceInterface $resource) + { + $this->watchResource($resource, $resource, realpath($resource->getResource())); + } + + /** + * Checks whether provided resource is tracked by this tracker. + * + * @param ResourceInterface $resource + * + * @return Boolean + */ + public function isResourceTracked(ResourceInterface $resource) + { + return null !== $this->getTrackingId($resource); + } + + /** + * Checks tracked resources for change events. + * + * @return array change events array + */ + public function getEvents() + { + $events = array(); + if ($iEvents = inotify_read($this->stream)) { + foreach ($iEvents as $iEvent) { + $trackingId = $iEvent['wd']; + $resourcePath = $this->resourcePaths[$trackingId].DIRECTORY_SEPARATOR.$iEvent['name']; + $tracked = $this->trackedResources[$trackingId]; + + if ('' == $iEvent['name']) { + continue; + } elseif (is_dir($resourcePath)) { + $resource = new DirectoryResource($resourcePath); + } elseif (is_file($resourcePath)) { + $resource = new FileResource($resourcePath); + } else { + continue; + } + + if ($resource instanceof FileResource) { + $file = new \SplFileInfo($resourcePath); + if ($tracked instanceof DirectoryResource && !$tracked->hasFile($file)) { + continue; + } + } + + if (IN_CREATE === ($iEvent['mask'] & IN_CREATE)) { + if ($resource instanceof DirectoryResource) { + $this->watchResource($resource, $tracked, $resource->getResource()); + } + $event = Event::CREATED; + } elseif (IN_MODIFY === ($iEvent['mask'] & IN_MODIFY)) { + $event = Event::MODIFIED; + } elseif (IN_DELETE === ($iEvent['mask'] & IN_DELETE)) { + $this->unwatchResource($resource); + $event = Event::DELETED; + } + + $events[] = new Event($tracked->getId(), $resource, $event); + } + } + + return $events; + } + + private function watchResource(ResourceInterface $resource, ResourceInterface $parent, $path) + { + $trackingId = inotify_add_watch( + $this->stream, $resource->getResource(), IN_CREATE | IN_MODIFY | IN_DELETE + ); + + $this->trackedResources[$trackingId] = $parent; + + if (!is_dir($path)) { + $path = dirname($path); + } + $this->resourcePaths[$trackingId] = rtrim($path, DIRECTORY_SEPARATOR); + + if ($resource instanceof DirectoryResource) { + foreach ($resource->getFilteredResources() as $child) { + if ($child instanceof DirectoryResource) { + $this->watchResource($child, $parent, realpath($child->getResource())); + } + } + } + } + + private function unwatchResource(ResourceInterface $resource) + { + if ($id = $this->getTrackingId($resource)) { + inotify_rm_watch($this->stream, $id); + unset($this->resourcePaths[$id]); + unset($this->trackedResources[$id]); + } + } + + private function getTrackingId(ResourceInterface $resource) + { + foreach ($this->trackedResources as $trackingId => $trackedResource) { + if ($trackedResource->getId() === $resource->getId()) { + return $trackingId; + } + } + } +} From f0f9fd9f2c94a4a2efa22af1faa13172e1a639cb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 5 Dec 2011 13:41:56 +0100 Subject: [PATCH 30/36] [ResourceWatcher] removed unneeded code --- src/Symfony/Component/ResourceWatcher/ResourceWatcher.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index b638a6dd1b1f0..77ad813a7abdd 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -99,10 +99,6 @@ public function addListener(EventListenerInterface $listener) $trackingId = $listener->getResource()->getId(); - if (!isset($this->listeners[$trackingId])) { - $this->listeners[$trackingId] = array(); - } - $this->listeners[$trackingId][] = $listener; } From cb8a3d1c580baf5e7fa242617e384a6b336605d6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 5 Dec 2011 13:42:15 +0100 Subject: [PATCH 31/36] [ResourceWatcher] added an exception when inotify is not available --- .../Component/ResourceWatcher/Tracker/InotifyTracker.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php index 616f4ccbf5393..84587d4e3032f 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php @@ -32,6 +32,10 @@ class InotifyTracker implements TrackerInterface */ public function __construct() { + if (!function_exists('inotify_init')) { + throw new \RuntimeException('You must install inotify to be able to use this tracker.'); + } + $this->stream = inotify_init(); stream_set_blocking($this->stream, 0); } From 879ddffc68c37c283cb721f19caf5fa0565c26dc Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 5 Dec 2011 13:58:28 +0100 Subject: [PATCH 32/36] [Config] made ResourceInterface extends Serializable --- src/Symfony/Component/Config/Resource/DirectoryResource.php | 2 +- src/Symfony/Component/Config/Resource/FileResource.php | 2 +- src/Symfony/Component/Config/Resource/ResourceInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index c2c1e589597f4..0a4c39d1512f2 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier */ -class DirectoryResource implements ResourceInterface, \Serializable +class DirectoryResource implements ResourceInterface { private $resource; private $pattern; diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 9d699dae004ec..2ebc39f2daa59 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier */ -class FileResource implements ResourceInterface, \Serializable +class FileResource implements ResourceInterface { private $resource; diff --git a/src/Symfony/Component/Config/Resource/ResourceInterface.php b/src/Symfony/Component/Config/Resource/ResourceInterface.php index b4529e8de661d..7febbd81394ab 100644 --- a/src/Symfony/Component/Config/Resource/ResourceInterface.php +++ b/src/Symfony/Component/Config/Resource/ResourceInterface.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier */ -interface ResourceInterface +interface ResourceInterface extends \Serializable { /** * Returns a string representation of the Resource. From a62c41305e94e22fd85dbd7e32831857f530c68c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 5 Dec 2011 15:35:34 +0100 Subject: [PATCH 33/36] [ResourceWatcher] removed dead code --- .../StateChecker/DirectoryStateCheckerTest.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php index 538f6578bb631..ac4f955bd3b3f 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php @@ -19,21 +19,6 @@ class DirectoryStateCheckerTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - $this->resource = $this->createDirectoryResourceMock(); - $this->resource - ->expects($this->any()) - ->method('getFilteredResources') - ->will($this->returnValue(array())); - $this->resource - ->expects($this->any()) - ->method('getModificationTime') - ->will($this->returnValue(11)); - - $this->checker = new DirectoryStateChecker($this->resource); - } - public function testDeepFileChanged() { $resource = $this->createDirectoryResourceMock(); From fbb5849132cf15dd4dbe27a8d338d2118c96ad42 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 5 Dec 2011 15:48:41 +0100 Subject: [PATCH 34/36] [ResourceWatcher] added the possibility to watch files that do not exist yet * fixed DELETED event when starting to watch a file that does not exist yet * fixed files that are deleted and then re-created --- .../Config/Resource/DirectoryResource.php | 8 ++ .../Config/Resource/FileResource.php | 6 +- .../StateChecker/ResourceStateChecker.php | 22 ++-- .../DirectoryStateCheckerTest.php | 43 +++++--- .../StateChecker/FileStateCheckerTest.php | 102 ++++++++++-------- 5 files changed, 117 insertions(+), 64 deletions(-) diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 0a4c39d1512f2..8530400f7facf 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -66,6 +66,10 @@ public function getFilteredChilds() */ public function getFilteredResources() { + if (!$this->exists()) { + return array(); + } + $iterator = new \DirectoryIterator($this->resource); $resources = array(); @@ -157,6 +161,10 @@ public function hasFile($file) */ public function getModificationTime() { + if (!$this->exists()) { + return -1; + } + clearstatcache(true, $this->resource); $newestMTime = filemtime($this->resource); diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 2ebc39f2daa59..0931112f4debc 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -29,7 +29,7 @@ class FileResource implements ResourceInterface */ public function __construct($resource) { - $this->resource = realpath($resource); + $this->resource = file_exists($resource) ? realpath($resource) : $resource; } /** @@ -59,6 +59,10 @@ public function getResource() */ public function getModificationTime() { + if (!$this->exists()) { + return -1; + } + clearstatcache(true, $this->resource); return filemtime($this->resource); diff --git a/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php index b2be5371d5ec3..c51f708577ee8 100644 --- a/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php +++ b/src/Symfony/Component/ResourceWatcher/StateChecker/ResourceStateChecker.php @@ -34,6 +34,7 @@ public function __construct(ResourceInterface $resource) { $this->resource = $resource; $this->timestamp = $resource->getModificationTime() + 1; + $this->deleted = !$resource->exists(); } /** @@ -53,21 +54,26 @@ public function getResource() */ public function getChangeset() { - $changeset = array(); + if ($this->deleted) { + if (!$this->resource->exists()) { + return array(); + } - if ($this->isDeleted()) { - return $changeset; - } + $this->timestamp = $this->resource->getModificationTime() + 1; + $this->deleted = false; - if (!$this->resource->exists()) { - $changeset[] = array('event' => Event::DELETED, 'resource' => $this->resource); + return array(array('event' => Event::CREATED, 'resource' => $this->resource)); + } elseif (!$this->resource->exists()) { $this->deleted = true; + + return array(array('event' => Event::DELETED, 'resource' => $this->resource)); } elseif (!$this->resource->isFresh($this->timestamp)) { - $changeset[] = array('event' => Event::MODIFIED, 'resource' => $this->resource); $this->timestamp = $this->resource->getModificationTime() + 1; + + return array(array('event' => Event::MODIFIED, 'resource' => $this->resource)); } - return $changeset; + return array(); } /** diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php index ac4f955bd3b3f..301d41efe2ff4 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/DirectoryStateCheckerTest.php @@ -28,6 +28,7 @@ public function testDeepFileChanged() ->will($this->returnValue(array( $foo = $this->createDirectoryResourceMock() ))); + $resource ->expects($this->any()) ->method('getModificationTime') @@ -77,7 +78,7 @@ public function testDeepFileDeleted() ->expects($this->any()) ->method('getFilteredResources') ->will($this->returnValue(array( - $foobar = $this->createFileResourceMock() + $foobar = $this->createFileResourceMock(array(true, false)) ))); $foo ->expects($this->any()) @@ -116,7 +117,7 @@ public function testDeepFileCreated() ->expects($this->any()) ->method('getFilteredResources') ->will($this->returnValue(array( - $foobar = $this->createFileResourceMock() + $foobar = $this->createFileResourceMock(array(false, true)) ))); $foo ->expects($this->any()) @@ -140,30 +141,48 @@ public function testDeepFileCreated() protected function touchResource(ResourceInterface $resource, $exists = true, $fresh = true) { - $resource - ->expects($this->any()) - ->method('exists') - ->will($this->returnValue($exists)); - if ($exists) { $resource - ->expects($this->once()) + ->expects($this->any()) ->method('isFresh') ->will($this->returnValue($fresh)); } } - protected function createDirectoryResourceMock() + protected function createDirectoryResourceMock($exists = true) { - return $this->getMockBuilder('Symfony\Component\Config\Resource\DirectoryResource') + $resource = $this->getMockBuilder('Symfony\Component\Config\Resource\DirectoryResource') ->disableOriginalConstructor() ->getMock(); + + $this->setResourceExists($resource, $exists); + + return $resource; } - protected function createFileResourceMock() + protected function createFileResourceMock($exists = true) { - return $this->getMockBuilder('Symfony\Component\Config\Resource\FileResource') + $resource = $this->getMockBuilder('Symfony\Component\Config\Resource\FileResource') ->disableOriginalConstructor() ->getMock(); + + $this->setResourceExists($resource, $exists); + + return $resource; + } + + protected function setResourceExists($resource, $exists) + { + if (is_array($exists)) { + $resource + ->expects($this->any()) + ->method('exists') + ->will($this->onConsecutiveCalls($exists)); + } else { + $resource + ->expects($this->any()) + ->method('exists') + ->will($this->returnValue($exists)); + } } } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php index 1de97ca152aa5..91d0b011ee8d9 100644 --- a/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php +++ b/tests/Symfony/Tests/Component/ResourceWatcher/StateChecker/FileStateCheckerTest.php @@ -16,97 +16,113 @@ class FileStateCheckerTest extends \PHPUnit_Framework_TestCase { - protected $resource; - - protected function setUp() - { - $this->resource = $this->createFileResourceMock(); - $this->resource - ->expects($this->any()) - ->method('getModificationTime') - ->will($this->returnValue(11)); - - $this->checker = new FileStateChecker($this->resource); - } - public function testGetResource() { - $this->assertSame($this->resource, $this->checker->getResource()); + $resource = $this->createResource(); + $checker = $this->createChecker($resource); + + $this->assertSame($resource, $checker->getResource()); } public function testNoChanges() { - $this->resource - ->expects($this->once()) - ->method('exists') - ->will($this->returnValue(true)); - $this->resource + $resource = $this->createResource(true); + $checker = $this->createChecker($resource); + + $resource ->expects($this->once()) ->method('isFresh') ->with(12) ->will($this->returnValue(true)); - $this->assertEquals(array(), $this->checker->getChangeset()); + $this->assertEquals(array(), $checker->getChangeset()); } public function testDeleted() { - $this->resource - ->expects($this->once()) + $resource = $this->createResource(null); + $resource + ->expects($this->any()) ->method('exists') - ->will($this->returnValue(false)); + ->will($this->onConsecutiveCalls(true, false)); + + $checker = $this->createChecker($resource); $this->assertEquals( - array(array('event' => Event::DELETED, 'resource' => $this->resource)), - $this->checker->getChangeset() + array(array('event' => Event::DELETED, 'resource' => $resource)), + $checker->getChangeset() ); } public function testModified() { - $this->resource - ->expects($this->once()) - ->method('exists') - ->will($this->returnValue(true)); - $this->resource + $resource = $this->createResource(true); + $checker = $this->createChecker($resource); + + $resource ->expects($this->once()) ->method('isFresh') ->with(12) ->will($this->returnValue(false)); $this->assertEquals( - array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), - $this->checker->getChangeset() + array(array('event' => Event::MODIFIED, 'resource' => $resource)), + $checker->getChangeset() ); } public function testConsecutiveChecks() { - $this->resource - ->expects($this->exactly(2)) + $resource = $this->createResource(null); + $resource + ->expects($this->any()) ->method('exists') - ->will($this->onConsecutiveCalls(true, false)); - $this->resource + ->will($this->onConsecutiveCalls(true, true, false)); + $checker = $this->createChecker($resource); + + $resource ->expects($this->once()) ->method('isFresh') ->with(12) ->will($this->returnValue(false)); $this->assertEquals( - array(array('event' => Event::MODIFIED, 'resource' => $this->resource)), - $this->checker->getChangeset() + array(array('event' => Event::MODIFIED, 'resource' => $resource)), + $checker->getChangeset() ); + $this->assertEquals( - array(array('event' => Event::DELETED, 'resource' => $this->resource)), - $this->checker->getChangeset() + array(array('event' => Event::DELETED, 'resource' => $resource)), + $checker->getChangeset() ); - $this->assertEquals(array(), $this->checker->getChangeset()); + + $this->assertEquals(array(), $checker->getChangeset()); } - protected function createFileResourceMock() + protected function createResource($exists = true) { - return $this->getMockBuilder('Symfony\Component\Config\Resource\FileResource') + $resource = $this + ->getMockBuilder('Symfony\Component\Config\Resource\FileResource') ->disableOriginalConstructor() ->getMock(); + + $resource + ->expects($this->any()) + ->method('getModificationTime') + ->will($this->returnValue(11)); + + if (null !== $exists) { + $resource + ->expects($this->any()) + ->method('exists') + ->will($this->returnValue($exists)); + } + + return $resource; + } + + protected function createChecker($resource) + { + return new FileStateChecker($resource); } } From 51b8613ebebcd1a182fe5a63fbd5efe94bb6abb7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 29 Dec 2011 15:42:54 +0100 Subject: [PATCH 35/36] [ResourceWatcher] added exception classes for the component --- .../ResourceWatcher/Event/EventListener.php | 3 ++- .../Exception/ExceptionInterface.php | 21 +++++++++++++++++ .../Exception/InvalidArgumentException.php | 23 +++++++++++++++++++ .../Exception/RuntimeException.php | 23 +++++++++++++++++++ .../ResourceWatcher/ResourceWatcher.php | 5 ++-- .../Tracker/InotifyTracker.php | 3 ++- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/ResourceWatcher/Exception/ExceptionInterface.php create mode 100644 src/Symfony/Component/ResourceWatcher/Exception/InvalidArgumentException.php create mode 100644 src/Symfony/Component/ResourceWatcher/Exception/RuntimeException.php diff --git a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php index a8e8b38f3ba19..8589bf85c523a 100644 --- a/src/Symfony/Component/ResourceWatcher/Event/EventListener.php +++ b/src/Symfony/Component/ResourceWatcher/Event/EventListener.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ResourceWatcher\Event; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException; /** * Resource change listener. @@ -34,7 +35,7 @@ class EventListener implements EventListenerInterface public function __construct(ResourceInterface $resource, $callback, $eventsMask) { if (!is_callable($callback)) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( 'EventListener\'s second argument should be callable.' ); } diff --git a/src/Symfony/Component/ResourceWatcher/Exception/ExceptionInterface.php b/src/Symfony/Component/ResourceWatcher/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..620cc434feaa4 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface +{ +} diff --git a/src/Symfony/Component/ResourceWatcher/Exception/InvalidArgumentException.php b/src/Symfony/Component/ResourceWatcher/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000..8e4575e8c02c1 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Exception/InvalidArgumentException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Exception; + +use \InvalidArgumentException as BaseInvalidArgumentException; + +/** + * InvalidArgumentException + * + * @author Fabien Potencier + */ +class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/ResourceWatcher/Exception/RuntimeException.php b/src/Symfony/Component/ResourceWatcher/Exception/RuntimeException.php new file mode 100644 index 0000000000000..f464350fc2bc6 --- /dev/null +++ b/src/Symfony/Component/ResourceWatcher/Exception/RuntimeException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ResourceWatcher\Exception; + +use \RuntimeException as BaseRuntimeException; + +/** + * RuntimeException + * + * @author Fabien Potencier + */ +class RuntimeException extends BaseRuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php index 77ad813a7abdd..32492ecb78daf 100644 --- a/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php +++ b/src/Symfony/Component/ResourceWatcher/ResourceWatcher.php @@ -20,6 +20,7 @@ use Symfony\Component\ResourceWatcher\Tracker\TrackerInterface; use Symfony\Component\ResourceWatcher\Tracker\InotifyTracker; use Symfony\Component\ResourceWatcher\Tracker\RecursiveIteratorTracker; +use Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException; /** * Resources changes watcher. @@ -73,14 +74,14 @@ public function track($resource, $callback, $eventsMask = Event::ALL) } elseif (is_dir($resource)) { $resource = new DirectoryResource($resource); } else { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'First argument to track() should be either file or directory resource, but got "%s"', $resource )); } } if (!is_callable($callback)) { - throw new \InvalidArgumentException('Second argument to track() should be callable.'); + throw new InvalidArgumentException('Second argument to track() should be callable.'); } $this->addListener(new EventListener($resource, $callback, $eventsMask)); diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php index 84587d4e3032f..8e8245b2f6f68 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php @@ -15,6 +15,7 @@ use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\ResourceWatcher\Exception\RuntimeException; /** * Inotify events resources tracker. @@ -33,7 +34,7 @@ class InotifyTracker implements TrackerInterface public function __construct() { if (!function_exists('inotify_init')) { - throw new \RuntimeException('You must install inotify to be able to use this tracker.'); + throw new RuntimeException('You must install inotify to be able to use this tracker.'); } $this->stream = inotify_init(); From 11611e9126cc1dde38384f855310cc43696aa41b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 29 Dec 2011 16:20:34 +0100 Subject: [PATCH 36/36] - --- .../Tracker/InotifyTracker.php | 5 + .../Tracker/RecursiveIteratorTracker.php | 9 +- .../Tracker/InotifyTrackerTest.php | 39 +++++++ .../Tracker/RecursiveIteratorTrackerTest.php | 30 +++++ .../ResourceWatcher/Tracker/TrackerTest.php | 107 ++++++++++++++++++ 5 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Tracker/InotifyTrackerTest.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php create mode 100644 tests/Symfony/Tests/Component/ResourceWatcher/Tracker/TrackerTest.php diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php index 8e8245b2f6f68..4fb1c9e652846 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/InotifyTracker.php @@ -16,6 +16,7 @@ use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\ResourceWatcher\Exception\RuntimeException; +use Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException; /** * Inotify events resources tracker. @@ -56,6 +57,10 @@ public function __destruct() */ public function track(ResourceInterface $resource) { + if (!$resource->exists()) { + throw new InvalidArgumentException(sprintf('Unable to track a non-existent resource (%s)', $resource)); + } + $this->watchResource($resource, $resource, realpath($resource->getResource())); } diff --git a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php index ed5877ae2844c..08014eaae9007 100644 --- a/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php +++ b/src/Symfony/Component/ResourceWatcher/Tracker/RecursiveIteratorTracker.php @@ -18,6 +18,7 @@ use Symfony\Component\ResourceWatcher\StateChecker\DirectoryStateChecker; use Symfony\Component\ResourceWatcher\StateChecker\FileStateChecker; use Symfony\Component\ResourceWatcher\StateChecker\StateCheckerInterface; +use Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException; /** * Recursive iterator resources tracker. @@ -35,9 +36,11 @@ class RecursiveIteratorTracker implements TrackerInterface */ public function track(ResourceInterface $resource) { - $checker = $resource instanceof DirectoryResource - ? new DirectoryStateChecker($resource) - : new FileStateChecker($resource); + if (!$resource->exists()) { + throw new InvalidArgumentException(sprintf('Unable to track a non-existent resource (%s)', $resource)); + } + + $checker = $resource instanceof DirectoryResource ? new DirectoryStateChecker($resource) : new FileStateChecker($resource); $this->addResourceStateChecker($checker); } diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/InotifyTrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/InotifyTrackerTest.php new file mode 100644 index 0000000000000..5b7e80f6d6a32 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/InotifyTrackerTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Tracker\InotifyTracker; + +class InotifyTrackerTest extends TrackerTest +{ + public function setUp() + { + if (!function_exists('inotify_init')) { + $this->markTestSkipped('Inotify is required for this test'); + } + + parent::setUp(); + } + + /** + * @return TrackerInterface + */ + protected function getTracker() + { + return new InotifyTracker(); + } + + protected function getMiminumInterval() + { + return 100; + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php new file mode 100644 index 0000000000000..878a1f4ddc036 --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/RecursiveIteratorTrackerTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Tracker\RecursiveIteratorTracker; + +class RecursiveIteratorTrackerTest extends TrackerTest +{ + /** + * @return TrackerInterface + */ + protected function getTracker() + { + return new RecursiveIteratorTracker(); + } + + protected function getMiminumInterval() + { + return 2000000; + } +} diff --git a/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/TrackerTest.php b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/TrackerTest.php new file mode 100644 index 0000000000000..d8e7086c32cca --- /dev/null +++ b/tests/Symfony/Tests/Component/ResourceWatcher/Tracker/TrackerTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\ResourceWatcher\Tracker; + +use Symfony\Component\ResourceWatcher\Event\Event; +use Symfony\Component\ResourceWatcher\Tracker\TrackerInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; + +abstract class TrackerTest extends \PHPUnit_Framework_TestCase +{ + protected $tmpDir; + + public function setUp() + { + $this->tmpDir = sys_get_temp_dir().'/sf2_resource_watcher_tests'; + if (is_dir($this->tmpDir)) { + $this->cleanDir($this->tmpDir); + } + + mkdir($this->tmpDir); + } + + public function tearDown() + { + $this->cleanDir($this->tmpDir); + } + + /** + * @expectedException Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException + */ + public function testDoesNotTrackMissingFiles() + { + $tracker = $this->getTracker(); + + $tracker->track(new FileResource(__DIR__.'/missingfile')); + } + + /** + * @expectedException Symfony\Component\ResourceWatcher\Exception\InvalidArgumentException + */ + public function testDoesNotTrackMissingDirectories() + { + $tracker = $this->getTracker(); + + $tracker->track(new DirectoryResource(__DIR__.'/missingdir')); + } + + public function testTrackFileChanges() + { + $tracker = $this->getTracker(); + + touch($file = $this->tmpDir.'/foo'); + + $tracker->track($resource = new FileResource($file)); + + usleep($this->getMiminumInterval()); + touch($file); + + $events = $tracker->getEvents(); + $this->assertCount(1, $events); + $this->assertEquals(Event::MODIFIED, $events[0]->getType()); + + usleep($this->getMiminumInterval()); + unlink($file); + + $events = $tracker->getEvents(); + $this->assertCount(1, $events); + $this->assertEquals(Event::DELETED, $events[0]->getType()); + } + + abstract protected function getMiminumInterval(); + + /** + * @return TrackerInterface + */ + abstract protected function getTracker(); + + protected function cleanDir($dir) + { + if (!is_dir($dir)) { + return; + } + + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($dir, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) + { + if (is_file($file)) { + unlink($file); + } + } + + rmdir($dir); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy