diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index d3f05b25510bb..ff66274909d61 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -78,6 +78,7 @@ $config, Server::get(\OCA\DAV\CalDAV\Sharing\Backend::class), Server::get(FederatedCalendarMapper::class), + Server::get(\OCP\ICacheFactory::class), true ); diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 105442296690d..9b547f5dce65d 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -43,6 +43,8 @@ use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; +use OCP\ICache; +use OCP\ICacheFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; @@ -202,6 +204,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription private string $dbObjectInvitationsTable = 'calendar_invitations'; private array $cachedObjects = []; + private readonly ICache $publishStatusCache; + public function __construct( private IDBConnection $db, private Principal $principalBackend, @@ -212,8 +216,10 @@ public function __construct( private IConfig $config, private Sharing\Backend $calendarSharingBackend, private FederatedCalendarMapper $federatedCalendarMapper, + ICacheFactory $cacheFactory, private bool $legacyEndpoint = false, ) { + $this->publishStatusCache = $cacheFactory->createInMemory(); } /** @@ -923,6 +929,8 @@ public function updateCalendar($calendarId, PropPatch $propPatch) { * @return void */ public function deleteCalendar($calendarId, bool $forceDeletePermanently = false) { + $this->publishStatusCache->remove((string)$calendarId); + $this->atomic(function () use ($calendarId, $forceDeletePermanently): void { // The calendar is deleted right away if this is either enforced by the caller // or the special contacts birthday calendar or when the preference of an empty @@ -3221,7 +3229,7 @@ public function preloadShares(array $resourceIds): void { * @return string|null */ public function setPublishStatus($value, $calendar) { - return $this->atomic(function () use ($value, $calendar) { + $publishStatus = $this->atomic(function () use ($value, $calendar) { $calendarId = $calendar->getResourceId(); $calendarData = $this->getCalendarById($calendarId); @@ -3249,13 +3257,21 @@ public function setPublishStatus($value, $calendar) { $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData)); return null; }, $this->db); + + $this->publishStatusCache->set((string)$calendar->getResourceId(), $publishStatus ?? false); + return $publishStatus; } /** * @param Calendar $calendar - * @return mixed + * @return string|false */ public function getPublishStatus($calendar) { + $cached = $this->publishStatusCache->get((string)$calendar->getResourceId()); + if ($cached !== null) { + return $cached; + } + $query = $this->db->getQueryBuilder(); $result = $query->select('publicuri') ->from('dav_shares') @@ -3263,9 +3279,46 @@ public function getPublishStatus($calendar) { ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->executeQuery(); - $row = $result->fetch(); + $publishStatus = $result->fetchOne(); + $result->closeCursor(); + + $this->publishStatusCache->set((string)$calendar->getResourceId(), $publishStatus); + return $publishStatus; + } + + /** + * @param int[] $resourceIds + */ + public function preloadPublishStatuses(array $resourceIds): void { + $query = $this->db->getQueryBuilder(); + $result = $query->select('resourceid', 'publicuri') + ->from('dav_shares') + ->where($query->expr()->in( + 'resourceid', + $query->createNamedParameter($resourceIds, IQueryBuilder::PARAM_INT_ARRAY), + IQueryBuilder::PARAM_INT_ARRAY, + )) + ->andWhere($query->expr()->eq( + 'access', + $query->createNamedParameter(self::ACCESS_PUBLIC, IQueryBuilder::PARAM_INT), + IQueryBuilder::PARAM_INT, + )) + ->executeQuery(); + + $hasPublishStatuses = []; + while ($row = $result->fetch()) { + $this->publishStatusCache->set((string)$row['resourceid'], $row['publicuri']); + $hasPublishStatuses[(int)$row['resourceid']] = true; + } + + // Also remember resources with no publish status + foreach ($resourceIds as $resourceId) { + if (!isset($hasPublishStatuses[$resourceId])) { + $this->publishStatusCache->set((string)$resourceId, false); + } + } + $result->closeCursor(); - return $row ? reset($row) : false; } /** diff --git a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php index 76378e7a1c570..167a5a20df087 100644 --- a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php +++ b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php @@ -6,7 +6,9 @@ */ namespace OCA\DAV\CalDAV\Publishing; +use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarHome; use OCA\DAV\CalDAV\Publishing\Xml\Publisher; use OCP\AppFramework\Http; use OCP\IConfig; @@ -91,6 +93,20 @@ public function initialize(Server $server) { } public function propFind(PropFind $propFind, INode $node) { + if ($node instanceof CalendarHome && $propFind->getDepth() === 1) { + $backend = $node->getCalDAVBackend(); + if ($backend instanceof CalDavBackend) { + $calendars = array_filter( + $node->getChildren(), + static fn ($child) => $child instanceof Calendar, + ); + $resourceIds = array_map( + static fn (Calendar $calendar) => $calendar->getResourceId(), + $calendars, + ); + $backend->preloadPublishStatuses($resourceIds); + } + } if ($node instanceof Calendar) { $propFind->handle('{' . self::NS_CALENDARSERVER . '}publish-url', function () use ($node) { if ($node->getPublishStatus()) { diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index afcae136822dd..0226a9461b7bf 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -16,6 +16,7 @@ use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\EventDispatcher\IEventDispatcher; +use OCP\ICacheFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; @@ -82,6 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $config, Server::get(Backend::class), Server::get(FederatedCalendarMapper::class), + Server::get(ICacheFactory::class), ); $caldav->createCalendar("principals/users/$user", $name, []); return self::SUCCESS; diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 82f048136bfa8..81f98da4c45b4 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -36,6 +36,7 @@ use OCP\Comments\ICommentsManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\IRootFolder; +use OCP\ICacheFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; @@ -108,6 +109,7 @@ public function __construct() { $config, $calendarSharingBackend, Server::get(FederatedCalendarMapper::class), + Server::get(ICacheFactory::class), false, ); $userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users', $logger, $l10n, $config, $federatedCalendarFactory); diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index fb1eb389c61d8..70f9228838811 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -59,6 +59,8 @@ abstract class AbstractCalDavBackend extends TestCase { protected RemoteUserPrincipalBackend&MockObject $remoteUserPrincipalBackend; protected FederationSharingService&MockObject $federationSharingService; protected FederatedCalendarMapper&MockObject $federatedCalendarMapper; + protected ICacheFactory $cacheFactory; + public const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; public const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; public const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; @@ -110,6 +112,7 @@ protected function setUp(): void { new Service(new SharingMapper($this->db)), $this->federationSharingService, $this->logger); + $this->cacheFactory = $this->createMock(ICacheFactory::class); $this->backend = new CalDavBackend( $this->db, $this->principal, @@ -120,6 +123,7 @@ protected function setUp(): void { $this->config, $this->sharingBackend, $this->federatedCalendarMapper, + $this->cacheFactory, false, ); diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php index 7e8db22c8f0cb..39b8427ba6dfb 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -14,6 +14,7 @@ use OCA\DAV\CalDAV\PublicCalendarRoot; use OCA\DAV\Connector\Sabre\Principal; use OCP\EventDispatcher\IEventDispatcher; +use OCP\ICacheFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; @@ -43,6 +44,7 @@ class PublicCalendarRootTest extends TestCase { protected IConfig&MockObject $config; private ISecureRandom $random; private LoggerInterface&MockObject $logger; + protected ICacheFactory&MockObject $cacheFactory; protected FederatedCalendarMapper&MockObject $federatedCalendarMapper; @@ -56,6 +58,7 @@ protected function setUp(): void { $this->random = Server::get(ISecureRandom::class); $this->logger = $this->createMock(LoggerInterface::class); $this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class); + $this->cacheFactory = $this->createMock(ICacheFactory::class); $dispatcher = $this->createMock(IEventDispatcher::class); $config = $this->createMock(IConfig::class); $sharingBackend = $this->createMock(\OCA\DAV\CalDAV\Sharing\Backend::class); @@ -78,6 +81,7 @@ protected function setUp(): void { $config, $sharingBackend, $this->federatedCalendarMapper, + $this->cacheFactory, false, ); $this->l10n = $this->createMock(IL10N::class);