From efe257cc5939abae06adca541cbdf773d65b746b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 25 Apr 2025 20:23:28 +0200 Subject: [PATCH 1/3] feat: add an option to filter what paths get checked for updates Signed-off-by: Robin Appelman --- lib/private/Files/Cache/Watcher.php | 12 ++++++++++++ lib/private/Files/Storage/Common.php | 1 + lib/public/Files/Cache/IWatcher.php | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php index f1de5d3cfb87c..b3c6504952572 100644 --- a/lib/private/Files/Cache/Watcher.php +++ b/lib/private/Files/Cache/Watcher.php @@ -36,6 +36,8 @@ class Watcher implements IWatcher { /** @var callable[] */ protected $onUpdate = []; + protected ?string $checkFilter = null; + /** * @param \OC\Files\Storage\Storage $storage */ @@ -52,6 +54,10 @@ public function setPolicy($policy) { $this->watchPolicy = $policy; } + public function setCheckFilter(?string $filter): void { + $this->checkFilter = $filter; + } + /** * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS */ @@ -116,6 +122,12 @@ public function update($path, $cachedData) { * @return bool */ public function needsUpdate($path, $cachedData) { + if ($this->checkFilter !== null) { + if (!preg_match($this->checkFilter, $path)) { + return false; + } + } + if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) { $this->checkedPaths[] = $path; return $cachedData['storage_mtime'] === null || $this->storage->hasUpdated($path, $cachedData['storage_mtime']); diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 9532582a42adc..54cd4d5ba2b3a 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -330,6 +330,7 @@ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatch $this->watcher = new Watcher($storage); $globalPolicy = Server::get(IConfig::class)->getSystemValueInt('filesystem_check_changes', Watcher::CHECK_NEVER); $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy)); + $this->watcher->setCheckFilter($this->getMountOption('filesystem_check_filter')); } return $this->watcher; } diff --git a/lib/public/Files/Cache/IWatcher.php b/lib/public/Files/Cache/IWatcher.php index 62b90f672c638..55063eec817ed 100644 --- a/lib/public/Files/Cache/IWatcher.php +++ b/lib/public/Files/Cache/IWatcher.php @@ -34,6 +34,17 @@ interface IWatcher { */ public function setPolicy($policy); + /** + * Set a filter regex, only paths matching the regex will be checked for updates. + * + * When set to `null`, every path will be checked for updates + * + * @param ?string $filter + * @return void + * @since 33.0.0 + */ + public function setCheckFilter(?string $filter): void; + /** * @return int either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS * @since 9.0.0 From 56cc62d215a51b9dbc6637f6b920edead8929825 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 20 Nov 2025 18:55:01 +0100 Subject: [PATCH 2/3] fix: use interfaces instead of classes in Cache\Watcher type hints Signed-off-by: Robin Appelman --- lib/private/Files/Cache/Watcher.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php index b3c6504952572..f47822cbce9e0 100644 --- a/lib/private/Files/Cache/Watcher.php +++ b/lib/private/Files/Cache/Watcher.php @@ -7,8 +7,11 @@ */ namespace OC\Files\Cache; +use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\IScanner; use OCP\Files\Cache\IWatcher; +use OCP\Files\Storage\IStorage; /** * check the storage backends for updates and change the cache accordingly @@ -19,17 +22,17 @@ class Watcher implements IWatcher { protected $checkedPaths = []; /** - * @var \OC\Files\Storage\Storage $storage + * @var IStorage $storage */ protected $storage; /** - * @var Cache $cache + * @var ICache $cache */ protected $cache; /** - * @var Scanner $scanner ; + * @var IScanner $scanner ; */ protected $scanner; @@ -38,10 +41,7 @@ class Watcher implements IWatcher { protected ?string $checkFilter = null; - /** - * @param \OC\Files\Storage\Storage $storage - */ - public function __construct(\OC\Files\Storage\Storage $storage) { + public function __construct(IStorage $storage) { $this->storage = $storage; $this->cache = $storage->getCache(); $this->scanner = $storage->getScanner(); From eedeae492db1c2985875861f56e87cf2e397c481 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 20 Nov 2025 18:55:14 +0100 Subject: [PATCH 3/3] test: add tests for watcher check filter Signed-off-by: Robin Appelman --- tests/lib/Files/Cache/WatcherTest.php | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/lib/Files/Cache/WatcherTest.php b/tests/lib/Files/Cache/WatcherTest.php index 6d0a8e0886ba3..33a81e9f56f13 100644 --- a/tests/lib/Files/Cache/WatcherTest.php +++ b/tests/lib/Files/Cache/WatcherTest.php @@ -8,9 +8,13 @@ namespace Test\Files\Cache; +use OC\Files\Cache\CacheEntry; use OC\Files\Cache\Watcher; use OC\Files\Storage\Storage; use OC\Files\Storage\Temporary; +use OCP\Files\Cache\IWatcher; +use OCP\Files\Storage\IStorage; +use PHPUnit\Framework\Attributes\DataProvider; /** * Class WatcherTest @@ -196,4 +200,43 @@ private function getTestStorage($scan = true) { $this->storages[] = $storage; return $storage; } + + public static function checkFilterProvider(): array { + return [ + [null, [ + '' => true, + 'foo' => true, + 'foo.txt' => true, + ]], + ['/^.+$/', [ + '' => false, + 'foo' => true, + 'foo.txt' => true, + ]], + ['/^.+\..+$/', [ + '' => false, + 'foo' => false, + 'foo.txt' => true, + ]] + ]; + } + + #[DataProvider('checkFilterProvider')] + public function testCheckFilter($filter, $paths) { + $storage = $this->createMock(IStorage::class); + $storage->method('hasUpdated') + ->willReturn(true); + $watcher = new Watcher($storage); + $watcher->setPolicy(IWatcher::CHECK_ALWAYS); + + $watcher->setCheckFilter($filter); + + $entry = new CacheEntry([ + 'storage_mtime' => 0, + ]); + + foreach ($paths as $patch => $shouldUpdate) { + $this->assertEquals($shouldUpdate, $watcher->needsUpdate($patch, $entry)); + } + } }