Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable27] Calendar optimizations #39858

Merged
merged 2 commits into from Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 34 additions & 5 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Expand Up @@ -217,6 +217,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
private IConfig $config;
private bool $legacyEndpoint;
private string $dbObjectPropertiesTable = 'calendarobjects_props';
private array $cachedObjects = [];

public function __construct(IDBConnection $db,
Principal $principalBackend,
Expand Down Expand Up @@ -1112,6 +1113,10 @@ public function getDeletedCalendarObjectsByPrincipal(string $principalUri): arra
* @return array|null
*/
public function getCalendarObject($calendarId, $objectUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$key = $calendarId . '::' . $objectUri . '::' . $calendarType;
if (isset($this->cachedObjects[$key])) {
return $this->cachedObjects[$key];
}
$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
->from('calendarobjects')
Expand All @@ -1126,6 +1131,12 @@ public function getCalendarObject($calendarId, $objectUri, int $calendarType = s
return null;
}

$object = $this->rowToCalendarObject($row);
$this->cachedObjects[$key] = $object;
return $object;
}

private function rowToCalendarObject(array $row): array {
return [
'id' => $row['id'],
'uri' => $row['uri'],
Expand Down Expand Up @@ -1212,6 +1223,7 @@ public function getMultipleCalendarObjects($calendarId, array $uris, $calendarTy
* @return string
*/
public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$this->cachedObjects = [];
$extraData = $this->getDenormalizedData($calendarData);

return $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $extraData, $calendarType) {
Expand Down Expand Up @@ -1306,6 +1318,7 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData, $ca
* @return string
*/
public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$this->cachedObjects = [];
$extraData = $this->getDenormalizedData($calendarData);

return $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $extraData, $calendarType) {
Expand Down Expand Up @@ -1359,6 +1372,7 @@ public function updateCalendarObject($calendarId, $objectUri, $calendarData, $ca
* @throws Exception
*/
public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, int $objectId, string $oldPrincipalUri, string $newPrincipalUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
$this->cachedObjects = [];
return $this->atomic(function () use ($sourceCalendarId, $targetCalendarId, $objectId, $oldPrincipalUri, $newPrincipalUri, $calendarType) {
$object = $this->getCalendarObjectById($oldPrincipalUri, $objectId);
if (empty($object)) {
Expand Down Expand Up @@ -1406,6 +1420,7 @@ public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId,
* @param int $classification
*/
public function setClassification($calendarObjectId, $classification) {
$this->cachedObjects = [];
if (!in_array($classification, [
self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
])) {
Expand All @@ -1430,6 +1445,7 @@ public function setClassification($calendarObjectId, $classification) {
* @return void
*/
public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR, bool $forceDeletePermanently = false) {
$this->cachedObjects = [];
$this->atomic(function () use ($calendarId, $objectUri, $calendarType, $forceDeletePermanently) {
$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);

Expand Down Expand Up @@ -1511,6 +1527,7 @@ public function deleteCalendarObject($calendarId, $objectUri, $calendarType = se
* @throws Forbidden
*/
public function restoreCalendarObject(array $objectData): void {
$this->cachedObjects = [];
$this->atomic(function () use ($objectData) {
$id = (int) $objectData['id'];
$restoreUri = str_replace("-deleted.ics", ".ics", $objectData['uri']);
Expand Down Expand Up @@ -1638,12 +1655,8 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self:
}
}
}
$columns = ['uri'];
if ($requirePostFilter) {
$columns = ['uri', 'calendardata'];
}
$query = $this->db->getQueryBuilder();
$query->select($columns)
$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
Expand All @@ -1664,6 +1677,11 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self:

$result = [];
while ($row = $stmt->fetch()) {
// if we leave it as a blob we can't read it both from the post filter and the rowToCalendarObject
if (isset($row['calendardata'])) {
$row['calendardata'] = $this->readBlob($row['calendardata']);
}

if ($requirePostFilter) {
// validateFilterForObject will parse the calendar data
// catch parsing errors
Expand All @@ -1688,6 +1706,8 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self:
}
}
$result[] = $row['uri'];
$key = $calendarId . '::' . $row['uri'] . '::' . $calendarType;
$this->cachedObjects[$key] = $this->rowToCalendarObject($row);
}

return $result;
Expand Down Expand Up @@ -2648,6 +2668,7 @@ public function getSchedulingObjects($principalUri) {
* @return void
*/
public function deleteSchedulingObject($principalUri, $objectUri) {
$this->cachedObjects = [];
$query = $this->db->getQueryBuilder();
$query->delete('schedulingobjects')
->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
Expand All @@ -2664,6 +2685,7 @@ public function deleteSchedulingObject($principalUri, $objectUri) {
* @return void
*/
public function createSchedulingObject($principalUri, $objectUri, $objectData) {
$this->cachedObjects = [];
$query = $this->db->getQueryBuilder();
$query->insert('schedulingobjects')
->values([
Expand All @@ -2687,6 +2709,7 @@ public function createSchedulingObject($principalUri, $objectUri, $objectData) {
* @return void
*/
protected function addChange(int $calendarId, string $objectUri, int $operation, int $calendarType = self::CALENDAR_TYPE_CALENDAR): void {
$this->cachedObjects = [];
$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';

$this->atomic(function () use ($calendarId, $objectUri, $operation, $calendarType, $table) {
Expand Down Expand Up @@ -2858,6 +2881,10 @@ public function getShares(int $resourceId): array {
return $this->calendarSharingBackend->getShares($resourceId);
}

public function preloadShares(array $resourceIds): void {
$this->calendarSharingBackend->preloadShares($resourceIds);
}

/**
* @param boolean $value
* @param \OCA\DAV\CalDAV\Calendar $calendar
Expand Down Expand Up @@ -2929,6 +2956,7 @@ public function applyShareAcl(int $resourceId, array $acl): array {
* @param int $calendarType
*/
public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$this->cachedObjects = [];
$this->atomic(function () use ($calendarId, $objectUri, $calendarData, $calendarType) {
$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);

Expand Down Expand Up @@ -3093,6 +3121,7 @@ protected function readCalendarData($objectData) {
* @param int $objectId
*/
protected function purgeProperties($calendarId, $objectId) {
$this->cachedObjects = [];
$query = $this->db->getQueryBuilder();
$query->delete($this->dbObjectPropertiesTable)
->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
Expand Down
5 changes: 5 additions & 0 deletions apps/dav/lib/CalDAV/CalendarHome.php
Expand Up @@ -58,6 +58,7 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {

/** @var LoggerInterface */
private $logger;
private ?array $cachedChildren = null;

public function __construct(BackendInterface $caldavBackend, $principalInfo, LoggerInterface $logger) {
parent::__construct($caldavBackend, $principalInfo);
Expand Down Expand Up @@ -97,6 +98,9 @@ public function createExtendedCollection($name, MkCol $mkCol): void {
* @inheritdoc
*/
public function getChildren() {
if ($this->cachedChildren) {
return $this->cachedChildren;
}
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objects = [];
foreach ($calendars as $calendar) {
Expand Down Expand Up @@ -136,6 +140,7 @@ public function getChildren() {
}
}

$this->cachedChildren = $objects;
return $objects;
}

Expand Down
48 changes: 48 additions & 0 deletions apps/dav/lib/DAV/Sharing/Backend.php
Expand Up @@ -30,6 +30,7 @@

use OCA\DAV\Connector\Sabre\Principal;
use OCP\AppFramework\Db\TTransactional;
use OCP\Cache\CappedMemoryCache;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
Expand All @@ -48,19 +49,23 @@ class Backend {
public const ACCESS_READ_WRITE = 2;
public const ACCESS_READ = 3;

private CappedMemoryCache $shareCache;

public function __construct(IDBConnection $db, IUserManager $userManager, IGroupManager $groupManager, Principal $principalBackend, string $resourceType) {
$this->db = $db;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->principalBackend = $principalBackend;
$this->resourceType = $resourceType;
$this->shareCache = new CappedMemoryCache();
}

/**
* @param list<array{href: string, commonName: string, readOnly: bool}> $add
* @param list<string> $remove
*/
public function updateShares(IShareable $shareable, array $add, array $remove): void {
$this->shareCache->clear();
$this->atomic(function () use ($shareable, $add, $remove) {
foreach ($add as $element) {
$principal = $this->principalBackend->findByUri($element['href'], '');
Expand All @@ -81,6 +86,7 @@ public function updateShares(IShareable $shareable, array $add, array $remove):
* @param array{href: string, commonName: string, readOnly: bool} $element
*/
private function shareWith(IShareable $shareable, array $element): void {
$this->shareCache->clear();
$user = $element['href'];
$parts = explode(':', $user, 2);
if ($parts[0] !== 'principal') {
Expand Down Expand Up @@ -124,6 +130,7 @@ private function shareWith(IShareable $shareable, array $element): void {
}

public function deleteAllShares(int $resourceId): void {
$this->shareCache->clear();
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId)))
Expand All @@ -132,6 +139,7 @@ public function deleteAllShares(int $resourceId): void {
}

public function deleteAllSharesByUser(string $principaluri): void {
$this->shareCache->clear();
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('principaluri', $query->createNamedParameter($principaluri)))
Expand All @@ -140,6 +148,7 @@ public function deleteAllSharesByUser(string $principaluri): void {
}

private function unshare(IShareable $shareable, string $element): void {
$this->shareCache->clear();
$parts = explode(':', $element, 2);
if ($parts[0] !== 'principal') {
return;
Expand Down Expand Up @@ -172,6 +181,10 @@ private function unshare(IShareable $shareable, string $element): void {
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
public function getShares(int $resourceId): array {
$cached = $this->shareCache->get($resourceId);
if ($cached) {
return $cached;
}
$query = $this->db->getQueryBuilder();
$result = $query->select(['principaluri', 'access'])
->from('dav_shares')
Expand All @@ -193,9 +206,44 @@ public function getShares(int $resourceId): array {
];
}

$this->shareCache->set((string)$resourceId, $shares);
return $shares;
}

public function preloadShares(array $resourceIds): void {
$resourceIds = array_filter($resourceIds, function(int $resourceId) {
return !isset($this->shareCache[$resourceId]);
});
if (count($resourceIds) === 0) {
return;
}
$query = $this->db->getQueryBuilder();
$result = $query->select(['resourceid', 'principaluri', 'access'])
->from('dav_shares')
->where($query->expr()->in('resourceid', $query->createNamedParameter($resourceIds, IQueryBuilder::PARAM_INT_ARRAY)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
->groupBy(['principaluri', 'access', 'resourceid'])
->executeQuery();

$sharesByResource = array_fill_keys($resourceIds, []);
while ($row = $result->fetch()) {
$resourceId = (int)$row['resourceid'];
$p = $this->principalBackend->getPrincipalByPath($row['principaluri']);
$sharesByResource[$resourceId][] = [
'href' => "principal:{$row['principaluri']}",
'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '',
'status' => 1,
'readOnly' => (int) $row['access'] === self::ACCESS_READ,
'{http://owncloud.org/ns}principal' => (string)$row['principaluri'],
'{http://owncloud.org/ns}group-share' => isset($p['uri']) ? str_starts_with($p['uri'], 'principals/groups') : false
];
}

foreach ($resourceIds as $resourceId) {
$this->shareCache->set($resourceId, $sharesByResource[$resourceId]);
}
}

/**
* For shared resources the sharee is set in the ACL of the resource
*
Expand Down
16 changes: 16 additions & 0 deletions apps/dav/lib/DAV/Sharing/Plugin.php
Expand Up @@ -24,6 +24,8 @@
*/
namespace OCA\DAV\DAV\Sharing;

use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\Connector\Sabre\Auth;
use OCA\DAV\DAV\Sharing\Xml\Invite;
use OCA\DAV\DAV\Sharing\Xml\ShareRequest;
Expand Down Expand Up @@ -201,6 +203,20 @@ public function httpPost(RequestInterface $request, ResponseInterface $response)
* @return void
*/
public function propFind(PropFind $propFind, INode $node) {
if ($node instanceof CalendarHome && $propFind->getDepth() === 1) {
$backend = $node->getCalDAVBackend();
if ($backend instanceof CalDavBackend) {
$calendars = $node->getChildren();
$calendars = array_filter($calendars, function (INode $node) {
return $node instanceof IShareable;
});
/** @var int[] $resourceIds */
$resourceIds = array_map(function (IShareable $node) {
return $node->getResourceId();
}, $calendars);
$backend->preloadShares($resourceIds);
}
}
if ($node instanceof IShareable) {
$propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function () use ($node) {
return new Invite(
Expand Down