From 664daf7fe132b6452620fad4aee5a14e44ea5e24 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:25:42 -0400 Subject: [PATCH 01/10] refactor: register note file event listeners Signed-off-by: Josh --- lib/AppInfo/Application.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 44337ee62..681587aeb 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,12 +9,18 @@ namespace OCA\Notes\AppInfo; +use OCA\Notes\Listener\NoteFileEventsListener; use OCA\Notes\Reference\NoteReferenceProvider; +use OCA\Notes\Service\MetaService; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\Files\Events\Node\BeforeNodeDeletedEvent; +use OCP\Files\Events\Node\BeforeNodeRenamedEvent; +use OCP\Files\Events\Node\BeforeNodeTouchedEvent; +use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Share\Events\BeforeShareCreatedEvent; /** @phan-suppress-next-line PhanUnreferencedUseNormal */ @@ -31,20 +37,18 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerSearchProvider(SearchProvider::class); $context->registerDashboardWidget(DashboardWidget::class); - $context->registerEventListener( - BeforeTemplateRenderedEvent::class, - BeforeTemplateRenderedListener::class - ); - $context->registerEventListener( - BeforeShareCreatedEvent::class, - BeforeShareCreatedListener::class - ); + + $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); + $context->registerEventListener(BeforeShareCreatedEvent::class, BeforeShareCreatedListener::class); + + $context->registerEventListener(BeforeNodeWrittenEvent::class, NoteFileEventsListener::class); + $context->registerEventListener(BeforeNodeTouchedEvent::class, NoteFileEventsListener::class); + $context->registerEventListener(BeforeNodeDeletedEvent::class, NoteFileEventsListener::class); + $context->registerEventListener(BeforeNodeRenamedEvent::class, NoteFileEventsListener::class); + $context->registerReferenceProvider(NoteReferenceProvider::class); } public function boot(IBootContext $context): void { - $context->injectFn(function (NotesHooks $notesHooks) { - $notesHooks->register(); - }); } } From 7a3a0ba9e37037e8e6a630185db8aab0dec95e41 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:26:53 -0400 Subject: [PATCH 02/10] refactor: remove legacy note hooks Signed-off-by: Josh --- lib/AppInfo/NotesHooks.php | 85 -------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 lib/AppInfo/NotesHooks.php diff --git a/lib/AppInfo/NotesHooks.php b/lib/AppInfo/NotesHooks.php deleted file mode 100644 index 385a4da1a..000000000 --- a/lib/AppInfo/NotesHooks.php +++ /dev/null @@ -1,85 +0,0 @@ -logger = $logger; - $this->rootFolder = $rootFolder; - $this->metaService = $metaService; - } - - public function register() : void { - $this->listenTo( - $this->rootFolder, - '\OC\Files', - 'preWrite', - function (Node $node) { - // $this->logger->debug('preWrite: ' . $node->getPath()); - $this->onFileModified($node); - } - ); - $this->listenTo( - $this->rootFolder, - '\OC\Files', - 'preTouch', - function (Node $node) { - // $this->logger->debug('preTouch: ' . $node->getPath()); - $this->onFileModified($node); - } - ); - $this->listenTo( - $this->rootFolder, - '\OC\Files', - 'preDelete', - function (Node $node) { - // $this->logger->debug('preDelete: ' . $node->getPath()); - $this->onFileModified($node); - } - ); - $this->listenTo( - $this->rootFolder, - '\OC\Files', - 'preRename', - function (Node $source, Node $target) { - // $this->logger->debug('preRename: ' . $source->getPath()); - $this->onFileModified($source); - } - ); - } - - private function listenTo($service, string $scope, string $method, callable $callback) : void { - /* @phan-suppress-next-line PhanUndeclaredMethod */ - $service->listen($scope, $method, $callback); - } - - private function onFileModified(Node $node) : void { - // $this->logger->debug('NotesHook for ' . $node->getPath()); - try { - $this->metaService->deleteByNote($node->getId()); - } catch (\Throwable $e) { - } - } -} From 954a1fcfd3fbe7c99d6f89899635f20cb944daed Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:30:59 -0400 Subject: [PATCH 03/10] refactor: add note file event listener Signed-off-by: Josh --- lib/Listener/NoteFileEventsListener.php | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lib/Listener/NoteFileEventsListener.php diff --git a/lib/Listener/NoteFileEventsListener.php b/lib/Listener/NoteFileEventsListener.php new file mode 100644 index 000000000..6cd9c581c --- /dev/null +++ b/lib/Listener/NoteFileEventsListener.php @@ -0,0 +1,55 @@ + */ +class NoteFileEventsListener implements IEventListener { + public function __construct( + private MetaService $metaService, + ) { + } + + public function handle(Event $event): void { + if ($event instanceof BeforeNodeWrittenEvent) { + $this->onFileModified($event->getNode()); + return; + } + + if ($event instanceof BeforeNodeTouchedEvent) { + $this->onFileModified($event->getNode()); + return; + } + + if ($event instanceof BeforeNodeDeletedEvent) { + $this->onFileModified($event->getNode()); + return; + } + + if ($event instanceof BeforeNodeRenamedEvent) { + $this->onFileModified($event->getSource()); + } + } + + private function onFileModified(Node $node): void { + try { + $this->metaService->deleteByNote($node->getId()); + } catch (\Throwable $e) { + } + } +} From 3d83fe11245e9c28a2441aba982ed8e52a3e42d1 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:33:57 -0400 Subject: [PATCH 04/10] chore: drop NotesHooks from psalm-baseline.xml Signed-off-by: Josh --- tests/psalm-baseline.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index ba9b0c09c..f9077f1cf 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -6,15 +6,6 @@ noteUtil->getRoot()]]> - - - - - - - - - From ca32f7448a5b9d6b4920052f691c150d064f4f9c Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:38:00 -0400 Subject: [PATCH 05/10] docs: update meta invalidation comment for file events Signed-off-by: Josh --- lib/Service/MetaService.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Service/MetaService.php b/lib/Service/MetaService.php index df01bf738..e0fc94026 100644 --- a/lib/Service/MetaService.php +++ b/lib/Service/MetaService.php @@ -28,25 +28,25 @@ * Therefore, the Notes app maintains this information on its own. It is saved * in the database table `notes_meta`. To be honest, we do not store the exact * changed time, but a time `t` that is at some point between the real changed - * time and the next synchronization time. However, this is totally sufficient + * time and next synchronization time. However, this is totally sufficient * for this purpose. * - * Therefore, on synchronization, the method `MetaService.getAll` is called. - * It generates an ETag for each note and compares it with the ETag from + * Therefore, on synchronization, the method `MetaService::getAll` is called. + * It generates an ETag for each note and compares it with the ETag from the * `notes_meta` database table in order to detect changes (or creates an entry * if not existent). If there are changes, the ETag is updated and `LastUpdate` * is set to the current time. The ETag is a hash over all note attributes * (except content, see below). * * But in order to further speed up synchronization, the content is not - * compared every time (this would be very expensive!). Instead, a file hook - * (see `OCA\Notes\NotesHook`) deletes the meta entry on every file change. As - * a consequence, a new entry in `note_meta` is created on next + * compared every time (this would be very expensive!). Instead, a file event + * listener invalidates the meta entry on every relevant file change. As a + * consequence, a new entry in `notes_meta` is created on the next * synchronization. * * Hence, instead of using the real content for generating the note's ETag, it - * uses a "content ETag" which is a hash over the content. Additionaly to the - * file hooks, this "content ETag" is updated if Nextcloud's "file ETag" has + * uses a "content ETag" which is a hash over the content. Additionally to the + * file events, this "content ETag" is updated if Nextcloud's "file ETag" has * changed (but again, the "file ETag" is just an indicator, since it is not a * hash over the content). * From cc57ff279856285de5d0e37725e28282358d0198 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 20:48:26 -0400 Subject: [PATCH 06/10] chore: drop unneeded MetaService import in Application Signed-off-by: Josh --- lib/AppInfo/Application.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 681587aeb..65899a688 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -11,7 +11,6 @@ use OCA\Notes\Listener\NoteFileEventsListener; use OCA\Notes\Reference\NoteReferenceProvider; -use OCA\Notes\Service\MetaService; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; From 1ee42ac567525dde2c0c7f1c8a57b7c6ded5bd71 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 22:31:18 -0400 Subject: [PATCH 07/10] chore: tidy up Application Signed-off-by: Josh --- lib/AppInfo/Application.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 65899a688..cafb4a21a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -35,19 +35,17 @@ public function __construct(array $urlParams = []) { public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerSearchProvider(SearchProvider::class); + $context->registerReferenceProvider(NoteReferenceProvider::class); $context->registerDashboardWidget(DashboardWidget::class); - $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); $context->registerEventListener(BeforeShareCreatedEvent::class, BeforeShareCreatedListener::class); - $context->registerEventListener(BeforeNodeWrittenEvent::class, NoteFileEventsListener::class); $context->registerEventListener(BeforeNodeTouchedEvent::class, NoteFileEventsListener::class); $context->registerEventListener(BeforeNodeDeletedEvent::class, NoteFileEventsListener::class); $context->registerEventListener(BeforeNodeRenamedEvent::class, NoteFileEventsListener::class); - - $context->registerReferenceProvider(NoteReferenceProvider::class); } public function boot(IBootContext $context): void { + // Intentionally empty } } From a3421f21efad4bbaf95bdcd599ab2fd9a54e728e Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 22:38:16 -0400 Subject: [PATCH 08/10] chore: tidy up NoteFileEventsListener for merge Signed-off-by: Josh --- lib/Listener/NoteFileEventsListener.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Listener/NoteFileEventsListener.php b/lib/Listener/NoteFileEventsListener.php index 6cd9c581c..5c5b369fb 100644 --- a/lib/Listener/NoteFileEventsListener.php +++ b/lib/Listener/NoteFileEventsListener.php @@ -18,7 +18,7 @@ use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Node; -/** @template-implements IEventListener */ +/** @template-implements IEventListener */ class NoteFileEventsListener implements IEventListener { public function __construct( private MetaService $metaService, @@ -50,6 +50,9 @@ private function onFileModified(Node $node): void { try { $this->metaService->deleteByNote($node->getId()); } catch (\Throwable $e) { + // Intentionally non-fatal: MetaService::getAll() will reconcile on next sync. + // Consider: Log at debug level so persistent failures remain diagnosable. + // (logger would need to be injected if added) } } } From 55ad15cdce8b0784f5eaeb6cf80c683d044ec3df Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 22:56:16 -0400 Subject: [PATCH 09/10] chore: streamline NoteFileEventsListener and make psalm happier Signed-off-by: Josh --- lib/Listener/NoteFileEventsListener.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/Listener/NoteFileEventsListener.php b/lib/Listener/NoteFileEventsListener.php index 5c5b369fb..a7432c9b2 100644 --- a/lib/Listener/NoteFileEventsListener.php +++ b/lib/Listener/NoteFileEventsListener.php @@ -28,20 +28,11 @@ public function __construct( public function handle(Event $event): void { if ($event instanceof BeforeNodeWrittenEvent) { $this->onFileModified($event->getNode()); - return; - } - - if ($event instanceof BeforeNodeTouchedEvent) { + } elseif ($event instanceof BeforeNodeTouchedEvent) { $this->onFileModified($event->getNode()); - return; - } - - if ($event instanceof BeforeNodeDeletedEvent) { + } elseif ($event instanceof BeforeNodeDeletedEvent) { $this->onFileModified($event->getNode()); - return; - } - - if ($event instanceof BeforeNodeRenamedEvent) { + } elseif ($event instanceof BeforeNodeRenamedEvent) { $this->onFileModified($event->getSource()); } } From 642254fb3ab348a44ef1358eb98356eb5548f435 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 26 Apr 2026 23:02:10 -0400 Subject: [PATCH 10/10] chore: fixup NoteFileEventsListener for psalm Six of one half dozen of another... Signed-off-by: Josh --- lib/Listener/NoteFileEventsListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Listener/NoteFileEventsListener.php b/lib/Listener/NoteFileEventsListener.php index a7432c9b2..64c3af425 100644 --- a/lib/Listener/NoteFileEventsListener.php +++ b/lib/Listener/NoteFileEventsListener.php @@ -18,7 +18,7 @@ use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Node; -/** @template-implements IEventListener */ +/** @template-implements IEventListener */ class NoteFileEventsListener implements IEventListener { public function __construct( private MetaService $metaService,