diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index dd657564ea9ef..110134a4ae6cf 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -55,6 +55,7 @@
OCA\DAV\Command\RetentionCleanupCommand
OCA\DAV\Command\SendEventReminders
OCA\DAV\Command\SyncBirthdayCalendar
+ OCA\DAV\Command\SyncResourcesRooms
OCA\DAV\Command\SyncSystemAddressBook
OCA\DAV\Command\RemoveInvalidShares
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index ce98cece3a192..bafe7172d185b 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -50,6 +50,7 @@
'OCA\\DAV\\CalDAV\\CalendarManager' => $baseDir . '/../lib/CalDAV/CalendarManager.php',
'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarProvider' => $baseDir . '/../lib/CalDAV/CalendarProvider.php',
+ 'OCA\\DAV\\CalDAV\\CalendarResourcesRoomsSyncService' => $baseDir . '/../lib/CalDAV/CalendarResourcesRoomsSyncService.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
@@ -132,6 +133,7 @@
'OCA\\DAV\\Command\\RetentionCleanupCommand' => $baseDir . '/../lib/Command/RetentionCleanupCommand.php',
'OCA\\DAV\\Command\\SendEventReminders' => $baseDir . '/../lib/Command/SendEventReminders.php',
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => $baseDir . '/../lib/Command/SyncBirthdayCalendar.php',
+ 'OCA\\DAV\\Command\\SyncResourcesRooms' => $baseDir . '/../lib/Command/SyncResourcesRooms.php',
'OCA\\DAV\\Command\\SyncSystemAddressBook' => $baseDir . '/../lib/Command/SyncSystemAddressBook.php',
'OCA\\DAV\\Comments\\CommentNode' => $baseDir . '/../lib/Comments/CommentNode.php',
'OCA\\DAV\\Comments\\CommentsPlugin' => $baseDir . '/../lib/Comments/CommentsPlugin.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index a5a7d34d128d4..26c4aa268e81e 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -65,6 +65,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\CalendarManager' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarManager.php',
'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarProvider.php',
+ 'OCA\\DAV\\CalDAV\\CalendarResourcesRoomsSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarResourcesRoomsSyncService.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
@@ -147,6 +148,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Command\\RetentionCleanupCommand' => __DIR__ . '/..' . '/../lib/Command/RetentionCleanupCommand.php',
'OCA\\DAV\\Command\\SendEventReminders' => __DIR__ . '/..' . '/../lib/Command/SendEventReminders.php',
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => __DIR__ . '/..' . '/../lib/Command/SyncBirthdayCalendar.php',
+ 'OCA\\DAV\\Command\\SyncResourcesRooms' => __DIR__ . '/..' . '/../lib/Command/SyncResourcesRooms.php',
'OCA\\DAV\\Command\\SyncSystemAddressBook' => __DIR__ . '/..' . '/../lib/Command/SyncSystemAddressBook.php',
'OCA\\DAV\\Comments\\CommentNode' => __DIR__ . '/..' . '/../lib/Comments/CommentNode.php',
'OCA\\DAV\\Comments\\CommentsPlugin' => __DIR__ . '/..' . '/../lib/Comments/CommentsPlugin.php',
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index 8674986262623..fbafd090ebe65 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -32,8 +32,7 @@
*/
namespace OCA\DAV\AppInfo;
-use Exception;
-use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob;
+use OCA\DAV\BackgroundJob\CalendarResourcesRoomsSyncService;
use OCA\DAV\CalDAV\Activity\Backend;
use OCA\DAV\CalDAV\CalendarManager;
use OCA\DAV\CalDAV\CalendarProvider;
@@ -44,7 +43,6 @@
use OCA\DAV\CalDAV\Reminder\Notifier;
use OCA\DAV\Capabilities;
-use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\ContactsManager;
use OCA\DAV\CardDAV\PhotoCache;
use OCA\DAV\CardDAV\SyncService;
@@ -243,20 +241,6 @@ public function registerHooks(HookManager $hm,
// Here we should recalculate if reminders should be sent to new or old sharees
});
-
- $eventHandler = function () use ($container, $serverContainer): void {
- try {
- /** @var UpdateCalendarResourcesRoomsBackgroundJob $job */
- $job = $container->query(UpdateCalendarResourcesRoomsBackgroundJob::class);
- $job->run([]);
- $serverContainer->getJobList()->setLastRun($job);
- } catch (Exception $ex) {
- $serverContainer->get(LoggerInterface::class)->error($ex->getMessage(), ['exception' => $ex]);
- }
- };
-
- $dispatcher->addListener('\OCP\Calendar\Resource\ForceRefreshEvent', $eventHandler);
- $dispatcher->addListener('\OCP\Calendar\Room\ForceRefreshEvent', $eventHandler);
}
public function registerContactsManager(IContactsManager $cm, IAppContainer $container): void {
diff --git a/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
index f7addd58248c7..b71bd5794f270 100644
--- a/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
+++ b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
@@ -28,427 +28,29 @@
namespace OCA\DAV\BackgroundJob;
-use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\CalendarResourcesRoomsSyncService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
-use OCP\Calendar\BackendTemporarilyUnavailableException;
-use OCP\Calendar\IMetadataProvider;
-use OCP\Calendar\Resource\IBackend as IResourceBackend;
-use OCP\Calendar\Resource\IManager as IResourceManager;
-use OCP\Calendar\Resource\IResource;
-use OCP\Calendar\Room\IManager as IRoomManager;
-use OCP\Calendar\Room\IRoom;
-use OCP\IDBConnection;
class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
- /** @var IResourceManager */
- private $resourceManager;
+ private CalendarResourcesRoomsSyncService $syncService;
- /** @var IRoomManager */
- private $roomManager;
-
- /** @var IDBConnection */
- private $dbConnection;
-
- /** @var CalDavBackend */
- private $calDavBackend;
-
- public function __construct(ITimeFactory $time,
- IResourceManager $resourceManager,
- IRoomManager $roomManager,
- IDBConnection $dbConnection,
- CalDavBackend $calDavBackend) {
+ public function __construct(CalendarResourcesRoomsSyncService $syncService,
+ ITimeFactory $time) {
parent::__construct($time);
- $this->resourceManager = $resourceManager;
- $this->roomManager = $roomManager;
- $this->dbConnection = $dbConnection;
- $this->calDavBackend = $calDavBackend;
// Run once an hour
$this->setInterval(60 * 60);
$this->setTimeSensitivity(self::TIME_SENSITIVE);
- }
-
- /**
- * @param $argument
- */
- public function run($argument): void {
- $this->runForBackend(
- $this->resourceManager,
- 'calendar_resources',
- 'calendar_resources_md',
- 'resource_id',
- 'principals/calendar-resources'
- );
- $this->runForBackend(
- $this->roomManager,
- 'calendar_rooms',
- 'calendar_rooms_md',
- 'room_id',
- 'principals/calendar-rooms'
- );
- }
-
- /**
- * Run background-job for one specific backendManager
- * either ResourceManager or RoomManager
- *
- * @param IResourceManager|IRoomManager $backendManager
- * @param string $dbTable
- * @param string $dbTableMetadata
- * @param string $foreignKey
- * @param string $principalPrefix
- */
- private function runForBackend($backendManager,
- string $dbTable,
- string $dbTableMetadata,
- string $foreignKey,
- string $principalPrefix): void {
- $backends = $backendManager->getBackends();
-
- foreach ($backends as $backend) {
- $backendId = $backend->getBackendIdentifier();
-
- try {
- if ($backend instanceof IResourceBackend) {
- $list = $backend->listAllResources();
- } else {
- $list = $backend->listAllRooms();
- }
- } catch (BackendTemporarilyUnavailableException $ex) {
- continue;
- }
-
- $cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
- $newIds = array_diff($list, $cachedList);
- $deletedIds = array_diff($cachedList, $list);
- $editedIds = array_intersect($list, $cachedList);
-
- foreach ($newIds as $newId) {
- try {
- if ($backend instanceof IResourceBackend) {
- $resource = $backend->getResource($newId);
- } else {
- $resource = $backend->getRoom($newId);
- }
-
- $metadata = [];
- if ($resource instanceof IMetadataProvider) {
- $metadata = $this->getAllMetadataOfBackend($resource);
- }
- } catch (BackendTemporarilyUnavailableException $ex) {
- continue;
- }
-
- $id = $this->addToCache($dbTable, $backendId, $resource);
- $this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
- // we don't create the calendar here, it is created lazily
- // when an event is actually scheduled with this resource / room
- }
-
- foreach ($deletedIds as $deletedId) {
- $id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
- $this->deleteFromCache($dbTable, $id);
- $this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
-
- $principalName = implode('-', [$backendId, $deletedId]);
- $this->deleteCalendarDataForResource($principalPrefix, $principalName);
- }
- foreach ($editedIds as $editedId) {
- $id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
-
- try {
- if ($backend instanceof IResourceBackend) {
- $resource = $backend->getResource($editedId);
- } else {
- $resource = $backend->getRoom($editedId);
- }
-
- $metadata = [];
- if ($resource instanceof IMetadataProvider) {
- $metadata = $this->getAllMetadataOfBackend($resource);
- }
- } catch (BackendTemporarilyUnavailableException $ex) {
- continue;
- }
-
- $this->updateCache($dbTable, $id, $resource);
-
- if ($resource instanceof IMetadataProvider) {
- $cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
- $this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
- }
- }
- }
- }
-
- /**
- * add entry to cache that exists remotely but not yet in cache
- *
- * @param string $table
- * @param string $backendId
- * @param IResource|IRoom $remote
- *
- * @return int Insert id
- */
- private function addToCache(string $table,
- string $backendId,
- $remote): int {
- $query = $this->dbConnection->getQueryBuilder();
- $query->insert($table)
- ->values([
- 'backend_id' => $query->createNamedParameter($backendId),
- 'resource_id' => $query->createNamedParameter($remote->getId()),
- 'email' => $query->createNamedParameter($remote->getEMail()),
- 'displayname' => $query->createNamedParameter($remote->getDisplayName()),
- 'group_restrictions' => $query->createNamedParameter(
- $this->serializeGroupRestrictions(
- $remote->getGroupRestrictions()
- ))
- ])
- ->executeStatement();
- return $query->getLastInsertId();
- }
-
- /**
- * @param string $table
- * @param string $foreignKey
- * @param int $foreignId
- * @param array $metadata
- */
- private function addMetadataToCache(string $table,
- string $foreignKey,
- int $foreignId,
- array $metadata): void {
- foreach ($metadata as $key => $value) {
- $query = $this->dbConnection->getQueryBuilder();
- $query->insert($table)
- ->values([
- $foreignKey => $query->createNamedParameter($foreignId),
- 'key' => $query->createNamedParameter($key),
- 'value' => $query->createNamedParameter($value),
- ])
- ->executeStatement();
- }
- }
-
- /**
- * delete entry from cache that does not exist anymore remotely
- *
- * @param string $table
- * @param int $id
- */
- private function deleteFromCache(string $table,
- int $id): void {
- $query = $this->dbConnection->getQueryBuilder();
- $query->delete($table)
- ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
- ->executeStatement();
- }
-
- /**
- * @param string $table
- * @param string $foreignKey
- * @param int $id
- */
- private function deleteMetadataFromCache(string $table,
- string $foreignKey,
- int $id): void {
- $query = $this->dbConnection->getQueryBuilder();
- $query->delete($table)
- ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
- ->executeStatement();
+ $this->syncService = $syncService;
}
/**
- * update an existing entry in cache
- *
- * @param string $table
- * @param int $id
- * @param IResource|IRoom $remote
+ * @param mixed $argument
*/
- private function updateCache(string $table,
- int $id,
- $remote): void {
- $query = $this->dbConnection->getQueryBuilder();
- $query->update($table)
- ->set('email', $query->createNamedParameter($remote->getEMail()))
- ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
- ->set('group_restrictions', $query->createNamedParameter(
- $this->serializeGroupRestrictions(
- $remote->getGroupRestrictions()
- )))
- ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
- ->executeStatement();
- }
-
- /**
- * @param string $dbTable
- * @param string $foreignKey
- * @param int $id
- * @param array $metadata
- * @param array $cachedMetadata
- */
- private function updateMetadataCache(string $dbTable,
- string $foreignKey,
- int $id,
- array $metadata,
- array $cachedMetadata): void {
- $newMetadata = array_diff_key($metadata, $cachedMetadata);
- $deletedMetadata = array_diff_key($cachedMetadata, $metadata);
-
- foreach ($newMetadata as $key => $value) {
- $query = $this->dbConnection->getQueryBuilder();
- $query->insert($dbTable)
- ->values([
- $foreignKey => $query->createNamedParameter($id),
- 'key' => $query->createNamedParameter($key),
- 'value' => $query->createNamedParameter($value),
- ])
- ->executeStatement();
- }
-
- foreach ($deletedMetadata as $key => $value) {
- $query = $this->dbConnection->getQueryBuilder();
- $query->delete($dbTable)
- ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
- ->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
- ->executeStatement();
- }
-
- $existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
- foreach ($existingKeys as $existingKey) {
- if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
- $query = $this->dbConnection->getQueryBuilder();
- $query->update($dbTable)
- ->set('value', $query->createNamedParameter($metadata[$existingKey]))
- ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
- ->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
- ->executeStatement();
- }
- }
- }
-
- /**
- * serialize array of group restrictions to store them in database
- *
- * @param array $groups
- *
- * @return string
- */
- private function serializeGroupRestrictions(array $groups): string {
- return \json_encode($groups);
- }
-
- /**
- * Gets all metadata of a backend
- *
- * @param IResource|IRoom $resource
- *
- * @return array
- */
- private function getAllMetadataOfBackend($resource): array {
- if (!($resource instanceof IMetadataProvider)) {
- return [];
- }
-
- $keys = $resource->getAllAvailableMetadataKeys();
- $metadata = [];
- foreach ($keys as $key) {
- $metadata[$key] = $resource->getMetadataForKey($key);
- }
-
- return $metadata;
- }
-
- /**
- * @param string $table
- * @param string $foreignKey
- * @param int $id
- *
- * @return array
- */
- private function getAllMetadataOfCache(string $table,
- string $foreignKey,
- int $id): array {
- $query = $this->dbConnection->getQueryBuilder();
- $query->select(['key', 'value'])
- ->from($table)
- ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
- $result = $query->executeQuery();
- $rows = $result->fetchAll();
- $result->closeCursor();
-
- $metadata = [];
- foreach ($rows as $row) {
- $metadata[$row['key']] = $row['value'];
- }
-
- return $metadata;
- }
-
- /**
- * Gets all cached rooms / resources by backend
- *
- * @param $tableName
- * @param $backendId
- *
- * @return array
- */
- private function getAllCachedByBackend(string $tableName,
- string $backendId): array {
- $query = $this->dbConnection->getQueryBuilder();
- $query->select('resource_id')
- ->from($tableName)
- ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
- $result = $query->executeQuery();
- $rows = $result->fetchAll();
- $result->closeCursor();
-
- return array_map(function ($row): string {
- return $row['resource_id'];
- }, $rows);
- }
-
- /**
- * @param $principalPrefix
- * @param $principalUri
- */
- private function deleteCalendarDataForResource(string $principalPrefix,
- string $principalUri): void {
- $calendar = $this->calDavBackend->getCalendarByUri(
- implode('/', [$principalPrefix, $principalUri]),
- CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
-
- if ($calendar !== null) {
- $this->calDavBackend->deleteCalendar(
- $calendar['id'],
- true // Because this wasn't deleted by a user
- );
- }
- }
-
- /**
- * @param $table
- * @param $backendId
- * @param $resourceId
- *
- * @return int
- */
- private function getIdForBackendAndResource(string $table,
- string $backendId,
- string $resourceId): int {
- $query = $this->dbConnection->getQueryBuilder();
- $query->select('id')
- ->from($table)
- ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
- ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
- $result = $query->executeQuery();
-
- $id = (int) $result->fetchOne();
- $result->closeCursor();
- return $id;
+ public function run($argument): void {
+ $this->syncService->sync();
}
}
diff --git a/apps/dav/lib/CalDAV/CalendarResourcesRoomsSyncService.php b/apps/dav/lib/CalDAV/CalendarResourcesRoomsSyncService.php
new file mode 100644
index 0000000000000..e522123692300
--- /dev/null
+++ b/apps/dav/lib/CalDAV/CalendarResourcesRoomsSyncService.php
@@ -0,0 +1,442 @@
+
+ *
+ * @author Christoph Wurst
+ * @author Georg Ehrke
+ * @author Roeland Jago Douma
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\CalDAV;
+
+use OCP\Calendar\BackendTemporarilyUnavailableException;
+use OCP\Calendar\IMetadataProvider;
+use OCP\Calendar\Resource\IBackend as IResourceBackend;
+use OCP\Calendar\Resource\IManager as IResourceManager;
+use OCP\Calendar\Resource\IResource;
+use OCP\Calendar\Room\IManager as IRoomManager;
+use OCP\Calendar\Room\IRoom;
+use OCP\IDBConnection;
+
+class CalendarResourcesRoomsSyncService {
+
+ /** @var IResourceManager */
+ private $resourceManager;
+
+ /** @var IRoomManager */
+ private $roomManager;
+
+ /** @var IDBConnection */
+ private $dbConnection;
+
+ /** @var CalDavBackend */
+ private $calDavBackend;
+
+ public function __construct(IResourceManager $resourceManager,
+ IRoomManager $roomManager,
+ IDBConnection $dbConnection,
+ CalDavBackend $calDavBackend) {
+ $this->resourceManager = $resourceManager;
+ $this->roomManager = $roomManager;
+ $this->dbConnection = $dbConnection;
+ $this->calDavBackend = $calDavBackend;
+ }
+
+ public function sync(): void {
+ $this->runForBackend(
+ $this->resourceManager,
+ 'calendar_resources',
+ 'calendar_resources_md',
+ 'resource_id',
+ 'principals/calendar-resources'
+ );
+ $this->runForBackend(
+ $this->roomManager,
+ 'calendar_rooms',
+ 'calendar_rooms_md',
+ 'room_id',
+ 'principals/calendar-rooms'
+ );
+ }
+
+ /**
+ * Run background-job for one specific backendManager
+ * either ResourceManager or RoomManager
+ *
+ * @param IResourceManager|IRoomManager $backendManager
+ * @param string $dbTable
+ * @param string $dbTableMetadata
+ * @param string $foreignKey
+ * @param string $principalPrefix
+ */
+ private function runForBackend($backendManager,
+ string $dbTable,
+ string $dbTableMetadata,
+ string $foreignKey,
+ string $principalPrefix): void {
+ $backends = $backendManager->getBackends();
+
+ foreach ($backends as $backend) {
+ $backendId = $backend->getBackendIdentifier();
+
+ try {
+ if ($backend instanceof IResourceBackend) {
+ $list = $backend->listAllResources();
+ } else {
+ $list = $backend->listAllRooms();
+ }
+ } catch (BackendTemporarilyUnavailableException $ex) {
+ continue;
+ }
+
+ $cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
+ $newIds = array_diff($list, $cachedList);
+ $deletedIds = array_diff($cachedList, $list);
+ $editedIds = array_intersect($list, $cachedList);
+
+ foreach ($newIds as $newId) {
+ try {
+ if ($backend instanceof IResourceBackend) {
+ $resource = $backend->getResource($newId);
+ } else {
+ $resource = $backend->getRoom($newId);
+ }
+
+ $metadata = [];
+ if ($resource instanceof IMetadataProvider) {
+ $metadata = $this->getAllMetadataOfBackend($resource);
+ }
+ } catch (BackendTemporarilyUnavailableException $ex) {
+ continue;
+ }
+
+ $id = $this->addToCache($dbTable, $backendId, $resource);
+ $this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
+ // we don't create the calendar here, it is created lazily
+ // when an event is actually scheduled with this resource / room
+ }
+
+ foreach ($deletedIds as $deletedId) {
+ $id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
+ $this->deleteFromCache($dbTable, $id);
+ $this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
+
+ $principalName = implode('-', [$backendId, $deletedId]);
+ $this->deleteCalendarDataForResource($principalPrefix, $principalName);
+ }
+
+ foreach ($editedIds as $editedId) {
+ $id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
+
+ try {
+ if ($backend instanceof IResourceBackend) {
+ $resource = $backend->getResource($editedId);
+ } else {
+ $resource = $backend->getRoom($editedId);
+ }
+
+ $metadata = [];
+ if ($resource instanceof IMetadataProvider) {
+ $metadata = $this->getAllMetadataOfBackend($resource);
+ }
+ } catch (BackendTemporarilyUnavailableException $ex) {
+ continue;
+ }
+
+ $this->updateCache($dbTable, $id, $resource);
+
+ if ($resource instanceof IMetadataProvider) {
+ $cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
+ $this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
+ }
+ }
+ }
+ }
+
+ /**
+ * add entry to cache that exists remotely but not yet in cache
+ *
+ * @param string $table
+ * @param string $backendId
+ * @param IResource|IRoom $remote
+ *
+ * @return int Insert id
+ */
+ private function addToCache(string $table,
+ string $backendId,
+ $remote): int {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->insert($table)
+ ->values([
+ 'backend_id' => $query->createNamedParameter($backendId),
+ 'resource_id' => $query->createNamedParameter($remote->getId()),
+ 'email' => $query->createNamedParameter($remote->getEMail()),
+ 'displayname' => $query->createNamedParameter($remote->getDisplayName()),
+ 'group_restrictions' => $query->createNamedParameter(
+ $this->serializeGroupRestrictions(
+ $remote->getGroupRestrictions()
+ ))
+ ])
+ ->executeStatement();
+ return $query->getLastInsertId();
+ }
+
+ /**
+ * @param string $table
+ * @param string $foreignKey
+ * @param int $foreignId
+ * @param array $metadata
+ */
+ private function addMetadataToCache(string $table,
+ string $foreignKey,
+ int $foreignId,
+ array $metadata): void {
+ foreach ($metadata as $key => $value) {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->insert($table)
+ ->values([
+ $foreignKey => $query->createNamedParameter($foreignId),
+ 'key' => $query->createNamedParameter($key),
+ 'value' => $query->createNamedParameter($value),
+ ])
+ ->executeStatement();
+ }
+ }
+
+ /**
+ * delete entry from cache that does not exist anymore remotely
+ *
+ * @param string $table
+ * @param int $id
+ */
+ private function deleteFromCache(string $table,
+ int $id): void {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->delete($table)
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
+ ->executeStatement();
+ }
+
+ /**
+ * @param string $table
+ * @param string $foreignKey
+ * @param int $id
+ */
+ private function deleteMetadataFromCache(string $table,
+ string $foreignKey,
+ int $id): void {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->delete($table)
+ ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
+ ->executeStatement();
+ }
+
+ /**
+ * update an existing entry in cache
+ *
+ * @param string $table
+ * @param int $id
+ * @param IResource|IRoom $remote
+ */
+ private function updateCache(string $table,
+ int $id,
+ $remote): void {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->update($table)
+ ->set('email', $query->createNamedParameter($remote->getEMail()))
+ ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
+ ->set('group_restrictions', $query->createNamedParameter(
+ $this->serializeGroupRestrictions(
+ $remote->getGroupRestrictions()
+ )))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
+ ->executeStatement();
+ }
+
+ /**
+ * @param string $dbTable
+ * @param string $foreignKey
+ * @param int $id
+ * @param array $metadata
+ * @param array $cachedMetadata
+ */
+ private function updateMetadataCache(string $dbTable,
+ string $foreignKey,
+ int $id,
+ array $metadata,
+ array $cachedMetadata): void {
+ $newMetadata = array_diff_key($metadata, $cachedMetadata);
+ $deletedMetadata = array_diff_key($cachedMetadata, $metadata);
+
+ foreach ($newMetadata as $key => $value) {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->insert($dbTable)
+ ->values([
+ $foreignKey => $query->createNamedParameter($id),
+ 'key' => $query->createNamedParameter($key),
+ 'value' => $query->createNamedParameter($value),
+ ])
+ ->executeStatement();
+ }
+
+ foreach ($deletedMetadata as $key => $value) {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->delete($dbTable)
+ ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
+ ->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
+ ->executeStatement();
+ }
+
+ $existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
+ foreach ($existingKeys as $existingKey) {
+ if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->update($dbTable)
+ ->set('value', $query->createNamedParameter($metadata[$existingKey]))
+ ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
+ ->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
+ ->executeStatement();
+ }
+ }
+ }
+
+ /**
+ * serialize array of group restrictions to store them in database
+ *
+ * @param array $groups
+ *
+ * @return string
+ */
+ private function serializeGroupRestrictions(array $groups): string {
+ return \json_encode($groups);
+ }
+
+ /**
+ * Gets all metadata of a backend
+ *
+ * @param IResource|IRoom $resource
+ *
+ * @return array
+ */
+ private function getAllMetadataOfBackend($resource): array {
+ if (!($resource instanceof IMetadataProvider)) {
+ return [];
+ }
+
+ $keys = $resource->getAllAvailableMetadataKeys();
+ $metadata = [];
+ foreach ($keys as $key) {
+ $metadata[$key] = $resource->getMetadataForKey($key);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param string $table
+ * @param string $foreignKey
+ * @param int $id
+ *
+ * @return array
+ */
+ private function getAllMetadataOfCache(string $table,
+ string $foreignKey,
+ int $id): array {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->select(['key', 'value'])
+ ->from($table)
+ ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
+ $result = $query->executeQuery();
+ $rows = $result->fetchAll();
+ $result->closeCursor();
+
+ $metadata = [];
+ foreach ($rows as $row) {
+ $metadata[$row['key']] = $row['value'];
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * Gets all cached rooms / resources by backend
+ *
+ * @param string $tableName
+ * @param string $backendId
+ *
+ * @return array
+ */
+ private function getAllCachedByBackend(string $tableName,
+ string $backendId): array {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->select('resource_id')
+ ->from($tableName)
+ ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
+ $result = $query->executeQuery();
+ $rows = $result->fetchAll();
+ $result->closeCursor();
+
+ return array_map(function ($row): string {
+ return $row['resource_id'];
+ }, $rows);
+ }
+
+ /**
+ * @param string $principalPrefix
+ * @param string $principalUri
+ */
+ private function deleteCalendarDataForResource(string $principalPrefix,
+ string $principalUri): void {
+ $calendar = $this->calDavBackend->getCalendarByUri(
+ implode('/', [$principalPrefix, $principalUri]),
+ CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
+
+ if ($calendar !== null) {
+ $this->calDavBackend->deleteCalendar(
+ $calendar['id'],
+ true // Because this wasn't deleted by a user
+ );
+ }
+ }
+
+ /**
+ * @param string $table
+ * @param string $backendId
+ * @param string $resourceId
+ *
+ * @return int
+ */
+ private function getIdForBackendAndResource(string $table,
+ string $backendId,
+ string $resourceId): int {
+ $query = $this->dbConnection->getQueryBuilder();
+ $query->select('id')
+ ->from($table)
+ ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
+ ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
+ $result = $query->executeQuery();
+
+ $id = (int) $result->fetchOne();
+ $result->closeCursor();
+ return $id;
+ }
+}
diff --git a/apps/dav/lib/Command/SyncResourcesRooms.php b/apps/dav/lib/Command/SyncResourcesRooms.php
new file mode 100644
index 0000000000000..b0394200f764a
--- /dev/null
+++ b/apps/dav/lib/Command/SyncResourcesRooms.php
@@ -0,0 +1,53 @@
+
+ *
+ * @author Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+namespace OCA\DAV\Command;
+
+use OCA\DAV\CalDAV\CalendarResourcesRoomsSyncService;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SyncResourcesRooms extends Command {
+
+ private CalendarResourcesRoomsSyncService $syncService;
+
+ public function __construct(CalendarResourcesRoomsSyncService $syncService) {
+ parent::__construct();
+ $this->syncService = $syncService;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('dav:sync-resources-rooms')
+ ->setDescription('Sync resources and rooms');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $this->syncService->sync();
+
+ return 0;
+ }
+}
diff --git a/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php b/apps/dav/tests/unit/CalDAV/UpdateCalendarResourcesRoomsBackgroundJobTest.php
similarity index 92%
rename from apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php
rename to apps/dav/tests/unit/CalDAV/UpdateCalendarResourcesRoomsBackgroundJobTest.php
index 59b68452862ed..6c5fea32a3b6a 100644
--- a/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php
+++ b/apps/dav/tests/unit/CalDAV/UpdateCalendarResourcesRoomsBackgroundJobTest.php
@@ -1,4 +1,24 @@
+ *
+ * @author 2022 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
declare(strict_types=1);
@@ -29,10 +49,8 @@
*/
namespace OCA\DAV\Tests\unit\BackgroundJob;
-use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob;
-
use OCA\DAV\CalDAV\CalDavBackend;
-use OCP\AppFramework\Utility\ITimeFactory;
+use OCA\DAV\CalDAV\CalendarResourcesRoomsSyncService;
use OCP\Calendar\BackendTemporarilyUnavailableException;
use OCP\Calendar\IMetadataProvider;
use OCP\Calendar\Resource\IBackend;
@@ -47,9 +65,6 @@ interface tmpI extends IResource, IMetadataProvider {
class UpdateCalendarResourcesRoomsBackgroundJobTest extends TestCase {
- /** @var ITimeFactory|MockObject */
- private $time;
-
/** @var IResourceManager|MockObject */
private $resourceManager;
@@ -59,19 +74,17 @@ class UpdateCalendarResourcesRoomsBackgroundJobTest extends TestCase {
/** @var CalDavBackend|MockObject */
private $calDavBackend;
- /** @var UpdateCalendarResourcesRoomsBackgroundJob */
- private $backgroundJob;
+ /** @var CalendarResourcesRoomsSyncService */
+ private $syncService;
protected function setUp(): void {
parent::setUp();
- $this->time = $this->createMock(ITimeFactory::class);
$this->resourceManager = $this->createMock(IResourceManager::class);
$this->roomManager = $this->createMock(IRoomManager::class);
$this->calDavBackend = $this->createMock(CalDavBackend::class);
- $this->backgroundJob = new UpdateCalendarResourcesRoomsBackgroundJob(
- $this->time,
+ $this->syncService = new CalendarResourcesRoomsSyncService(
$this->resourceManager,
$this->roomManager,
self::$realDatabase,
@@ -116,7 +129,7 @@ protected function tearDown(): void {
* [backend4, res9, Beamer2, {}] - []
*/
- public function testRun() {
+ public function testRun(): void {
$this->createTestResourcesInCache();
$backend2 = $this->createMock(IBackend::class);
@@ -226,7 +239,7 @@ public function testRun() {
['backend4', $backend4],
]);
- $this->backgroundJob->run([]);
+ $this->syncService->sync();
$query = self::$realDatabase->getQueryBuilder();
$query->select('*')->from('calendar_resources');
@@ -353,7 +366,7 @@ public function testRun() {
], $rows2);
}
- protected function createTestResourcesInCache() {
+ protected function createTestResourcesInCache(): void {
$query = self::$realDatabase->getQueryBuilder();
$query->insert('calendar_resources')
->values([