diff options
Diffstat (limited to 'apps/files_reminders/lib')
10 files changed, 196 insertions, 59 deletions
diff --git a/apps/files_reminders/lib/AppInfo/Application.php b/apps/files_reminders/lib/AppInfo/Application.php index f0a3cc0f6f5..2776e9db0b1 100644 --- a/apps/files_reminders/lib/AppInfo/Application.php +++ b/apps/files_reminders/lib/AppInfo/Application.php @@ -16,6 +16,7 @@ use OCA\FilesReminders\Listener\NodeDeletedListener; use OCA\FilesReminders\Listener\SabrePluginAddListener; use OCA\FilesReminders\Listener\UserDeletedListener; use OCA\FilesReminders\Notification\Notifier; +use OCA\FilesReminders\SetupChecks\NeedNotificationsApp; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -42,5 +43,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class); + + $context->registerSetupCheck(NeedNotificationsApp::class); } } diff --git a/apps/files_reminders/lib/BackgroundJob/ScheduledNotifications.php b/apps/files_reminders/lib/BackgroundJob/ScheduledNotifications.php index d8467d1740c..ab8c762d674 100644 --- a/apps/files_reminders/lib/BackgroundJob/ScheduledNotifications.php +++ b/apps/files_reminders/lib/BackgroundJob/ScheduledNotifications.php @@ -13,10 +13,10 @@ use OCA\FilesReminders\Db\ReminderMapper; use OCA\FilesReminders\Service\ReminderService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\BackgroundJob\Job; +use OCP\BackgroundJob\TimedJob; use Psr\Log\LoggerInterface; -class ScheduledNotifications extends Job { +class ScheduledNotifications extends TimedJob { public function __construct( ITimeFactory $time, protected ReminderMapper $reminderMapper, @@ -24,6 +24,8 @@ class ScheduledNotifications extends Job { protected LoggerInterface $logger, ) { parent::__construct($time); + + $this->setInterval(60); } /** diff --git a/apps/files_reminders/lib/Controller/ApiController.php b/apps/files_reminders/lib/Controller/ApiController.php index dbc340610b2..c95a74a04f4 100644 --- a/apps/files_reminders/lib/Controller/ApiController.php +++ b/apps/files_reminders/lib/Controller/ApiController.php @@ -14,8 +14,8 @@ use DateTimeInterface; use DateTimeZone; use Exception; use OCA\FilesReminders\Exception\NodeNotFoundException; +use OCA\FilesReminders\Exception\ReminderNotFoundException; use OCA\FilesReminders\Service\ReminderService; -use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; @@ -53,15 +53,14 @@ class ApiController extends OCSController { try { $reminder = $this->reminderService->getDueForUser($user, $fileId); - $reminderData = [ + if ($reminder === null) { + return new DataResponse(['dueDate' => null], Http::STATUS_OK); + } + return new DataResponse([ 'dueDate' => $reminder->getDueDate()->format(DateTimeInterface::ATOM), // ISO 8601 - ]; - return new DataResponse($reminderData, Http::STATUS_OK); - } catch (DoesNotExistException $e) { - $reminderData = [ - 'dueDate' => null, - ]; - return new DataResponse($reminderData, Http::STATUS_OK); + ], Http::STATUS_OK); + } catch (NodeNotFoundException $e) { + return new DataResponse(['dueDate' => null], Http::STATUS_OK); } } @@ -125,7 +124,7 @@ class ApiController extends OCSController { try { $this->reminderService->remove($user, $fileId); return new DataResponse([], Http::STATUS_OK); - } catch (DoesNotExistException $e) { + } catch (NodeNotFoundException|ReminderNotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); } } diff --git a/apps/files_reminders/lib/Dav/PropFindPlugin.php b/apps/files_reminders/lib/Dav/PropFindPlugin.php index 8bb88170013..014e636eb2d 100644 --- a/apps/files_reminders/lib/Dav/PropFindPlugin.php +++ b/apps/files_reminders/lib/Dav/PropFindPlugin.php @@ -10,9 +10,10 @@ declare(strict_types=1); namespace OCA\FilesReminders\Dav; use DateTimeInterface; +use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\Node; use OCA\FilesReminders\Service\ReminderService; -use OCP\AppFramework\Db\DoesNotExistException; +use OCP\Files\Folder; use OCP\IUser; use OCP\IUserSession; use Sabre\DAV\INode; @@ -43,6 +44,15 @@ class PropFindPlugin extends ServerPlugin { return; } + if ( + $node instanceof Directory + && $propFind->getDepth() > 0 + && $propFind->getStatus(static::REMINDER_DUE_DATE_PROPERTY) !== null + ) { + $folder = $node->getNode(); + $this->cacheFolder($folder); + } + $propFind->handle( static::REMINDER_DUE_DATE_PROPERTY, function () use ($node) { @@ -52,9 +62,8 @@ class PropFindPlugin extends ServerPlugin { } $fileId = $node->getId(); - try { - $reminder = $this->reminderService->getDueForUser($user, $fileId); - } catch (DoesNotExistException $e) { + $reminder = $this->reminderService->getDueForUser($user, $fileId, false); + if ($reminder === null) { return ''; } @@ -62,4 +71,12 @@ class PropFindPlugin extends ServerPlugin { }, ); } + + private function cacheFolder(Folder $folder): void { + $user = $this->userSession->getUser(); + if (!($user instanceof IUser)) { + return; + } + $this->reminderService->cacheFolder($user, $folder); + } } diff --git a/apps/files_reminders/lib/Db/ReminderMapper.php b/apps/files_reminders/lib/Db/ReminderMapper.php index e747c8af397..63cba437d07 100644 --- a/apps/files_reminders/lib/Db/ReminderMapper.php +++ b/apps/files_reminders/lib/Db/ReminderMapper.php @@ -13,6 +13,7 @@ use DateTime; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IDBConnection; @@ -39,16 +40,6 @@ class ReminderMapper extends QBMapper { return $this->update($reminderUpdate); } - public function find(int $id): Reminder { - $qb = $this->db->getQueryBuilder(); - - $qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified') - ->from($this->getTableName()) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))); - - return $this->findEntity($qb); - } - /** * @throws DoesNotExistException */ @@ -141,4 +132,20 @@ class ReminderMapper extends QBMapper { return $this->findEntities($qb); } + + /** + * @return Reminder[] + */ + public function findAllInFolder(IUser $user, Folder $folder) { + $qb = $this->db->getQueryBuilder(); + + $qb->select('r.id', 'r.user_id', 'r.file_id', 'r.due_date', 'r.updated_at', 'r.created_at', 'r.notified') + ->from($this->getTableName(), 'r') + ->innerJoin('r', 'filecache', 'f', $qb->expr()->eq('r.file_id', 'f.fileid')) + ->where($qb->expr()->eq('r.user_id', $qb->createNamedParameter($user->getUID(), IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT))) + ->orderBy('r.due_date', 'ASC'); + + return $this->findEntities($qb); + } } diff --git a/apps/files_reminders/lib/Exception/ReminderNotFoundException.php b/apps/files_reminders/lib/Exception/ReminderNotFoundException.php new file mode 100644 index 00000000000..fd7031a834f --- /dev/null +++ b/apps/files_reminders/lib/Exception/ReminderNotFoundException.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\FilesReminders\Exception; + +use Exception; + +class ReminderNotFoundException extends Exception { +} diff --git a/apps/files_reminders/lib/Listener/LoadAdditionalScriptsListener.php b/apps/files_reminders/lib/Listener/LoadAdditionalScriptsListener.php index a0173aabc64..765bf1e3ce2 100644 --- a/apps/files_reminders/lib/Listener/LoadAdditionalScriptsListener.php +++ b/apps/files_reminders/lib/Listener/LoadAdditionalScriptsListener.php @@ -30,8 +30,9 @@ class LoadAdditionalScriptsListener implements IEventListener { return; } - if (!$this->appManager->isEnabledForUser('notifications')) { - $this->logger->error('Failed to register the `files_reminders` app. This could happen due to the `notifications` app being disabled.', ['app' => 'files_reminders']); + if (!$this->appManager->isEnabledForUser(Application::APP_ID) + || !$this->appManager->isEnabledForUser('notifications') + ) { return; } diff --git a/apps/files_reminders/lib/Notification/Notifier.php b/apps/files_reminders/lib/Notification/Notifier.php index 000b04119cd..337ef04c814 100644 --- a/apps/files_reminders/lib/Notification/Notifier.php +++ b/apps/files_reminders/lib/Notification/Notifier.php @@ -14,6 +14,7 @@ use OCP\Files\FileInfo; use OCP\Files\IRootFolder; use OCP\IURLGenerator; use OCP\L10N\IFactory; +use OCP\Notification\AlreadyProcessedException; use OCP\Notification\IAction; use OCP\Notification\INotification; use OCP\Notification\INotifier; @@ -51,8 +52,8 @@ class Notifier implements INotifier { $fileId = $params['fileId']; $node = $this->root->getUserFolder($notification->getUser())->getFirstNodeById($fileId); - if (!$node) { - throw new UnknownNotificationException(); + if ($node === null) { + throw new AlreadyProcessedException(); } $path = rtrim($node->getPath(), '/'); @@ -75,7 +76,7 @@ class Notifier implements INotifier { [ 'name' => [ 'type' => 'highlight', - 'id' => $node->getId(), + 'id' => (string)$node->getId(), 'name' => $node->getName(), ], ], diff --git a/apps/files_reminders/lib/Service/ReminderService.php b/apps/files_reminders/lib/Service/ReminderService.php index 8bd6887e754..6ee39562076 100644 --- a/apps/files_reminders/lib/Service/ReminderService.php +++ b/apps/files_reminders/lib/Service/ReminderService.php @@ -15,11 +15,15 @@ use OCA\FilesReminders\AppInfo\Application; use OCA\FilesReminders\Db\Reminder; use OCA\FilesReminders\Db\ReminderMapper; use OCA\FilesReminders\Exception\NodeNotFoundException; +use OCA\FilesReminders\Exception\ReminderNotFoundException; use OCA\FilesReminders\Exception\UserNotFoundException; use OCA\FilesReminders\Model\RichReminder; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; +use OCP\ICache; +use OCP\ICacheFactory; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; @@ -28,6 +32,9 @@ use Psr\Log\LoggerInterface; use Throwable; class ReminderService { + + private ICache $cache; + public function __construct( protected IUserManager $userManager, protected IURLGenerator $urlGenerator, @@ -35,23 +42,49 @@ class ReminderService { protected ReminderMapper $reminderMapper, protected IRootFolder $root, protected LoggerInterface $logger, + protected ICacheFactory $cacheFactory, ) { + $this->cache = $this->cacheFactory->createInMemory(); } - /** - * @throws DoesNotExistException - */ - public function get(int $id): RichReminder { - $reminder = $this->reminderMapper->find($id); - return new RichReminder($reminder, $this->root); + public function cacheFolder(IUser $user, Folder $folder): void { + $reminders = $this->reminderMapper->findAllInFolder($user, $folder); + $reminderMap = []; + foreach ($reminders as $reminder) { + $reminderMap[$reminder->getFileId()] = $reminder; + } + + $nodes = $folder->getDirectoryListing(); + foreach ($nodes as $node) { + $reminder = $reminderMap[$node->getId()] ?? false; + $this->cache->set("{$user->getUID()}-{$node->getId()}", $reminder); + } } /** - * @throws DoesNotExistException + * @throws NodeNotFoundException */ - public function getDueForUser(IUser $user, int $fileId): RichReminder { - $reminder = $this->reminderMapper->findDueForUser($user, $fileId); - return new RichReminder($reminder, $this->root); + public function getDueForUser(IUser $user, int $fileId, bool $checkNode = true): ?RichReminder { + if ($checkNode) { + $this->checkNode($user, $fileId); + } + /** @var null|false|Reminder $cachedReminder */ + $cachedReminder = $this->cache->get("{$user->getUID()}-$fileId"); + if ($cachedReminder === false) { + return null; + } + if ($cachedReminder instanceof Reminder) { + return new RichReminder($cachedReminder, $this->root); + } + + try { + $reminder = $this->reminderMapper->findDueForUser($user, $fileId); + $this->cache->set("{$user->getUID()}-$fileId", $reminder); + return new RichReminder($reminder, $this->root); + } catch (DoesNotExistException $e) { + $this->cache->set("{$user->getUID()}-$fileId", false); + return null; + } } /** @@ -74,18 +107,9 @@ class ReminderService { */ public function createOrUpdate(IUser $user, int $fileId, DateTime $dueDate): bool { $now = new DateTime('now', new DateTimeZone('UTC')); - try { - $reminder = $this->reminderMapper->findDueForUser($user, $fileId); - $reminder->setDueDate($dueDate); - $reminder->setUpdatedAt($now); - $this->reminderMapper->update($reminder); - return false; - } catch (DoesNotExistException $e) { - $node = $this->root->getUserFolder($user->getUID())->getFirstNodeById($fileId); - if (!$node) { - throw new NodeNotFoundException(); - } - // Create new reminder if no reminder is found + $this->checkNode($user, $fileId); + $reminder = $this->getDueForUser($user, $fileId); + if ($reminder === null) { $reminder = new Reminder(); $reminder->setUserId($user->getUID()); $reminder->setFileId($fileId); @@ -93,29 +117,40 @@ class ReminderService { $reminder->setUpdatedAt($now); $reminder->setCreatedAt($now); $this->reminderMapper->insert($reminder); + $this->cache->set("{$user->getUID()}-$fileId", $reminder); return true; } + $reminder->setDueDate($dueDate); + $reminder->setUpdatedAt($now); + $this->reminderMapper->update($reminder); + $this->cache->set("{$user->getUID()}-$fileId", $reminder); + return false; } /** - * @throws DoesNotExistException + * @throws NodeNotFoundException + * @throws ReminderNotFoundException */ public function remove(IUser $user, int $fileId): void { - $reminder = $this->reminderMapper->findDueForUser($user, $fileId); - $this->reminderMapper->delete($reminder); + $this->checkNode($user, $fileId); + $reminder = $this->getDueForUser($user, $fileId); + if ($reminder === null) { + throw new ReminderNotFoundException(); + } + $this->deleteReminder($reminder); } public function removeAllForNode(Node $node): void { $reminders = $this->reminderMapper->findAllForNode($node); foreach ($reminders as $reminder) { - $this->reminderMapper->delete($reminder); + $this->deleteReminder($reminder); } } public function removeAllForUser(IUser $user): void { $reminders = $this->reminderMapper->findAllForUser($user); foreach ($reminders as $reminder) { - $this->reminderMapper->delete($reminder); + $this->deleteReminder($reminder); } } @@ -147,6 +182,7 @@ class ReminderService { try { $this->notificationManager->notify($notification); $this->reminderMapper->markNotified($reminder); + $this->cache->set("{$user->getUID()}-{$reminder->getFileId()}", $reminder); } catch (Throwable $th) { $this->logger->error($th->getMessage(), $th->getTrace()); } @@ -158,7 +194,24 @@ class ReminderService { ->modify('-1 day'); $reminders = $this->reminderMapper->findNotified($buffer, $limit); foreach ($reminders as $reminder) { - $this->reminderMapper->delete($reminder); + $this->deleteReminder($reminder); + } + } + + private function deleteReminder(Reminder $reminder): void { + $this->reminderMapper->delete($reminder); + $this->cache->set("{$reminder->getUserId()}-{$reminder->getFileId()}", false); + } + + + /** + * @throws NodeNotFoundException + */ + private function checkNode(IUser $user, int $fileId): void { + $userFolder = $this->root->getUserFolder($user->getUID()); + $node = $userFolder->getFirstNodeById($fileId); + if ($node === null) { + throw new NodeNotFoundException(); } } } diff --git a/apps/files_reminders/lib/SetupChecks/NeedNotificationsApp.php b/apps/files_reminders/lib/SetupChecks/NeedNotificationsApp.php new file mode 100644 index 00000000000..e5890567181 --- /dev/null +++ b/apps/files_reminders/lib/SetupChecks/NeedNotificationsApp.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\FilesReminders\SetupChecks; + +use OCP\App\IAppManager; +use OCP\IL10N; +use OCP\SetupCheck\ISetupCheck; +use OCP\SetupCheck\SetupResult; + +class NeedNotificationsApp implements ISetupCheck { + public function __construct( + private IAppManager $appManager, + private IL10N $l10n, + ) { + } + + public function getName(): string { + return $this->l10n->t('Files reminder'); + } + + public function getCategory(): string { + return 'system'; + } + + public function run(): SetupResult { + if ($this->appManager->isEnabledForAnyone('notifications')) { + return SetupResult::success($this->l10n->t('The "files_reminders" app can work properly.')); + } else { + return SetupResult::warning($this->l10n->t('The "files_reminders" app needs the notification app to work properly. You should either enable notifications or disable files_reminder.')); + } + } +} |