Skip to content

Commit

Permalink
Merge pull request #37961 from nextcloud/poc/noid/systemtags-perf
Browse files Browse the repository at this point in the history
SystemTags endpoint to return tags used by a user with meta data
  • Loading branch information
blizzz authored May 11, 2023
2 parents e176848 + df662f5 commit b6c034a
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 54 deletions.
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@
'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php',
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php',
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => $baseDir . '/../lib/SystemTag/SystemTagsByIdCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => $baseDir . '/../lib/SystemTag/SystemTagsInUseCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php',
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsByIdCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsInUseCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
Expand Down
4 changes: 4 additions & 0 deletions apps/dav/lib/RootCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
use Sabre\DAV\SimpleCollection;
Expand All @@ -65,6 +66,7 @@ public function __construct() {
$dispatcher = \OC::$server->get(IEventDispatcher::class);
$config = \OC::$server->get(IConfig::class);
$proxyMapper = \OC::$server->query(ProxyMapper::class);
$rootFolder = \OCP\Server::get(IRootFolder::class);

$userPrincipalBackend = new Principal(
$userManager,
Expand Down Expand Up @@ -131,6 +133,7 @@ public function __construct() {
$groupManager,
\OC::$server->getEventDispatcher()
);
$systemTagInUseCollection = \OCP\Server::get(SystemTag\SystemTagsInUseCollection::class);
$commentsCollection = new Comments\RootCollection(
\OC::$server->getCommentsManager(),
$userManager,
Expand Down Expand Up @@ -179,6 +182,7 @@ public function __construct() {
$systemAddressBookRoot]),
$systemTagCollection,
$systemTagRelationsCollection,
$systemTagInUseCollection,
$commentsCollection,
$uploadCollection,
$avatarCollection,
Expand Down
19 changes: 19 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class SystemTagNode implements \Sabre\DAV\INode {
*/
protected $isAdmin;

protected int $numberOfFiles = -1;
protected int $referenceFileId = -1;

/**
* Sets up the node, expects a full path name
*
Expand Down Expand Up @@ -179,4 +182,20 @@ public function delete() {
throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
}
}

public function getNumberOfFiles(): int {
return $this->numberOfFiles;
}

public function setNumberOfFiles(int $numberOfFiles): void {
$this->numberOfFiles = $numberOfFiles;
}

public function getReferenceFileId(): int {
return $this->referenceFileId;
}

public function setReferenceFileId(int $referenceFileId): void {
$this->referenceFileId = $referenceFileId;
}
}
24 changes: 24 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned';
public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';

/**
* @var \Sabre\DAV\Server $server
Expand Down Expand Up @@ -243,6 +245,11 @@ public function handleGetProperties(
return;
}

// child nodes from systemtags-assigned should point to normal tag endpoint
if (preg_match('/^systemtags-assigned\/[0-9]+/', $propFind->getPath())) {
$propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
}

$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
return $node->getSystemTag()->getId();
});
Expand Down Expand Up @@ -277,6 +284,16 @@ public function handleGetProperties(
}
return implode('|', $groups);
});

if ($node instanceof SystemTagNode) {
$propFind->handle(self::NUM_FILES_PROPERTYNAME, function () use ($node): int {
return $node->getNumberOfFiles();
});

$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int {
return $node->getReferenceFileId();
});
}
}

private function propfindForFile(PropFind $propFind, Node $node): void {
Expand Down Expand Up @@ -374,6 +391,8 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
self::USERVISIBLE_PROPERTYNAME,
self::USERASSIGNABLE_PROPERTYNAME,
self::GROUPS_PROPERTYNAME,
self::NUM_FILES_PROPERTYNAME,
self::FILEID_PROPERTYNAME,
], function ($props) use ($node) {
$tag = $node->getSystemTag();
$name = $tag->getName();
Expand Down Expand Up @@ -410,6 +429,11 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
$this->tagManager->setTagGroups($tag, $groupIds);
}

if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) {
// read-only properties
throw new Forbidden();
}

if ($updateTag) {
$node->update($name, $userVisible, $userAssignable);
}
Expand Down
108 changes: 108 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagsInUseCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @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 <https://www.gnu.org/licenses/>.
*
*/

namespace OCA\DAV\SystemTag;

use OC\SystemTag\SystemTag;
use OC\SystemTag\SystemTagsInFilesDetector;
use OC\User\NoUserException;
use OCP\Files\IRootFolder;
use OCP\Files\NotPermittedException;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTagManager;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\SimpleCollection;

class SystemTagsInUseCollection extends SimpleCollection {
protected IUserSession $userSession;
protected IRootFolder $rootFolder;
protected string $mediaType;
protected ISystemTagManager $systemTagManager;
protected SystemTagsInFilesDetector $systemTagsInFilesDetector;

/** @noinspection PhpMissingParentConstructorInspection */
public function __construct(
IUserSession $userSession,
IRootFolder $rootFolder,
ISystemTagManager $systemTagManager,
SystemTagsInFilesDetector $systemTagsInFilesDetector,
string $mediaType = ''
) {
$this->userSession = $userSession;
$this->rootFolder = $rootFolder;
$this->systemTagManager = $systemTagManager;
$this->mediaType = $mediaType;
$this->systemTagsInFilesDetector = $systemTagsInFilesDetector;
$this->name = 'systemtags-assigned';
if ($this->mediaType != '') {
$this->name .= '/' . $this->mediaType;
}
}

public function setName($name): void {
throw new Forbidden('Permission denied to rename this collection');
}

public function getChild($name): self {
if ($this->mediaType !== '') {
throw new NotFound('Invalid media type');
}
return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name);
}

/**
* @return SystemTagNode[]
* @throws NotPermittedException
* @throws Forbidden
*/
public function getChildren(): array {
$user = $this->userSession->getUser();
$userFolder = null;
try {
if ($user) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
}
} catch (NoUserException) {
// will throw a Sabre exception in the next step.
}
if ($user === null || $userFolder === null) {
throw new Forbidden('Permission denied to read this collection');
}

$result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType);
$children = [];
foreach ($result as $tagData) {
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
// read only, so we can submit the isAdmin parameter as false generally
$node = new SystemTagNode($tag, $user, false, $this->systemTagManager);
$node->setNumberOfFiles($tagData['number_files']);
$node->setReferenceFileId($tagData['ref_file_id']);
$children[] = $node;
}
return $children;
}
}
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,7 @@
'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php',
'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php',
'OC\\SystemTag\\SystemTagObjectMapper' => $baseDir . '/lib/private/SystemTag/SystemTagObjectMapper.php',
'OC\\SystemTag\\SystemTagsInFilesDetector' => $baseDir . '/lib/private/SystemTag/SystemTagsInFilesDetector.php',
'OC\\TagManager' => $baseDir . '/lib/private/TagManager.php',
'OC\\Tagging\\Tag' => $baseDir . '/lib/private/Tagging/Tag.php',
'OC\\Tagging\\TagMapper' => $baseDir . '/lib/private/Tagging/TagMapper.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php',
'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php',
'OC\\SystemTag\\SystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagObjectMapper.php',
'OC\\SystemTag\\SystemTagsInFilesDetector' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagsInFilesDetector.php',
'OC\\TagManager' => __DIR__ . '/../../..' . '/lib/private/TagManager.php',
'OC\\Tagging\\Tag' => __DIR__ . '/../../..' . '/lib/private/Tagging/Tag.php',
'OC\\Tagging\\TagMapper' => __DIR__ . '/../../..' . '/lib/private/Tagging/TagMapper.php',
Expand Down
21 changes: 20 additions & 1 deletion lib/private/Files/Cache/CacheQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,27 @@ public function __construct(IDBConnection $connection, SystemConfig $systemConfi
parent::__construct($connection, $systemConfig, $logger);
}

public function selectTagUsage(): self {
$this
->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable')
->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files')
->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id')
->from('filecache', 'filecache')
->leftJoin('filecache', 'systemtag_object_mapping', 'systemtagmap', $this->expr()->andX(
$this->expr()->eq('filecache.fileid', $this->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
$this->expr()->eq('systemtagmap.objecttype', $this->createNamedParameter('files'))
))
->leftJoin('systemtagmap', 'systemtag', 'systemtag', $this->expr()->andX(
$this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
$this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true))
))
->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable');

return $this;
}

public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ? $alias : 'filecache';
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
->from('filecache', $name);
Expand Down
Loading

0 comments on commit b6c034a

Please sign in to comment.