diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 44337ee62..cafb4a21a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,12 +9,17 @@ namespace OCA\Notes\AppInfo; +use OCA\Notes\Listener\NoteFileEventsListener; use OCA\Notes\Reference\NoteReferenceProvider; 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 */ @@ -30,21 +35,17 @@ public function __construct(array $urlParams = []) { 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->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); } public function boot(IBootContext $context): void { - $context->injectFn(function (NotesHooks $notesHooks) { - $notesHooks->register(); - }); + // Intentionally empty } } 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) { - } - } -} diff --git a/lib/Listener/NoteFileEventsListener.php b/lib/Listener/NoteFileEventsListener.php new file mode 100644 index 000000000..64c3af425 --- /dev/null +++ b/lib/Listener/NoteFileEventsListener.php @@ -0,0 +1,49 @@ + */ +class NoteFileEventsListener implements IEventListener { + public function __construct( + private MetaService $metaService, + ) { + } + + public function handle(Event $event): void { + if ($event instanceof BeforeNodeWrittenEvent) { + $this->onFileModified($event->getNode()); + } elseif ($event instanceof BeforeNodeTouchedEvent) { + $this->onFileModified($event->getNode()); + } elseif ($event instanceof BeforeNodeDeletedEvent) { + $this->onFileModified($event->getNode()); + } elseif ($event instanceof BeforeNodeRenamedEvent) { + $this->onFileModified($event->getSource()); + } + } + + 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) + } + } +} 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). * 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()]]> - - - - - - - - -