Skip to content

Commit

Permalink
IFilesMetadata
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
  • Loading branch information
ArtificialOwl committed Nov 6, 2023
1 parent 6d42d14 commit 1065e8a
Show file tree
Hide file tree
Showing 39 changed files with 626 additions and 378 deletions.
9 changes: 8 additions & 1 deletion apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Michael Jobst <mjobst+github@tecratech.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
Expand Down Expand Up @@ -49,8 +50,8 @@
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Tree;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
Expand Down Expand Up @@ -84,6 +85,7 @@ class FilesPlugin extends ServerPlugin {
public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
public const SUBFILE_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-file-count';
public const FILE_METADATA_PREFIX = '{http://nextcloud.org/ns}metadata-';
public const FILE_METADATA_SIZE = '{http://nextcloud.org/ns}file-metadata-size';
public const FILE_METADATA_GPS = '{http://nextcloud.org/ns}file-metadata-gps';

Expand Down Expand Up @@ -389,6 +391,11 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node)
$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
return $node->getFileInfo()->getCreationTime();
});

foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
$propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
}

/**
* Return file/folder name as displayname. The primary reason to
* implement it this way is to avoid costly fallback to
Expand Down
102 changes: 60 additions & 42 deletions apps/dav/lib/Files/FileSearchBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*
* @author Christian <16852529+cviereck@users.noreply.github.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
Expand Down Expand Up @@ -42,6 +43,9 @@
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
use OCP\Files\Search\ISearchQuery;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FilesMetadata\Model\IMetadataQuery;
use OCP\FilesMetadata\Model\IMetadataValueWrapper;
use OCP\IUser;
use OCP\Share\IManager;
use Sabre\DAV\Exception\NotFound;
Expand All @@ -57,37 +61,14 @@
class FileSearchBackend implements ISearchBackend {
public const OPERATOR_LIMIT = 100;

/** @var CachingTree */
private $tree;

/** @var IUser */
private $user;

/** @var IRootFolder */
private $rootFolder;

/** @var IManager */
private $shareManager;

/** @var View */
private $view;

/**
* FileSearchBackend constructor.
*
* @param CachingTree $tree
* @param IUser $user
* @param IRootFolder $rootFolder
* @param IManager $shareManager
* @param View $view
* @internal param IRootFolder $rootFolder
*/
public function __construct(CachingTree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) {
$this->tree = $tree;
$this->user = $user;
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
$this->view = $view;
public function __construct(
private CachingTree $tree,
private IUser $user,
private IRootFolder $rootFolder,
private IManager $shareManager,
private View $view,
private IFilesMetadataManager $filesMetadataManager,
) {
}

/**
Expand Down Expand Up @@ -115,7 +96,7 @@ public function getPropertyDefinitionsForScope(string $href, ?string $path): arr
// all valid scopes support the same schema

//todo dynamically load all propfind properties that are supported
return [
$props = [
// queryable properties
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
Expand All @@ -137,6 +118,33 @@ public function getPropertyDefinitionsForScope(string $href, ?string $path): arr
new SearchPropertyDefinition(FilesPlugin::FILE_METADATA_SIZE, true, false, false, SearchPropertyDefinition::DATATYPE_STRING),
new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, true, false, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
];

return array_merge($props, $this->getPropertyDefinitionsForMetadata());
}


private function getPropertyDefinitionsForMetadata(): array {
$metadataProps = [];
$metadata = $this->filesMetadataManager->getKnownMetadata();
$indexes = $metadata->getIndexes();
foreach ($metadata->getKeys() as $key) {
$isIndex = in_array($key, $indexes);
$type = match ($metadata->getType($key)) {
IMetadataValueWrapper::TYPE_INT => SearchPropertyDefinition::DATATYPE_INTEGER,
IMetadataValueWrapper::TYPE_FLOAT => SearchPropertyDefinition::DATATYPE_DECIMAL,
IMetadataValueWrapper::TYPE_BOOL => SearchPropertyDefinition::DATATYPE_BOOLEAN,
default => SearchPropertyDefinition::DATATYPE_STRING
};
$metadataProps[] = new SearchPropertyDefinition(
FilesPlugin::FILE_METADATA_PREFIX . $key,
true,
$isIndex,
$isIndex,
$type
);
}

return $metadataProps;
}

/**
Expand Down Expand Up @@ -300,11 +308,20 @@ private function getHrefForNode(Node $node) {

/**
* @param Query $query
*
* @return ISearchQuery
*/
private function transformQuery(Query $query): ISearchQuery {
$orders = array_map(function (Order $order): ISearchOrder {
$direction = $order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING;
if (str_starts_with($order->property->name, FilesPlugin::FILE_METADATA_PREFIX)) {
return new SearchOrder($direction, substr($order->property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX)), IMetadataQuery::EXTRA);
} else {
return new SearchOrder($direction, $this->mapPropertyNameToColumn($order->property));
}
}, $query->orderBy);

$limit = $query->limit;
$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
$offset = $limit->firstResult;

$limitHome = false;
Expand Down Expand Up @@ -352,14 +369,6 @@ private function countSearchOperators(Operator $operator): int {
}
}

/**
* @param Order $order
* @return ISearchOrder
*/
private function mapSearchOrder(Order $order) {
return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
}

/**
* @param Operator $operator
* @return ISearchOperator
Expand Down Expand Up @@ -387,7 +396,16 @@ private function transformSearchOperation(Operator $operator) {
if (!($operator->arguments[1] instanceof Literal)) {
throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
}
return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));

$property = $operator->arguments[0];
$value = $this->castValue($property, $operator->arguments[1]->value);
if (str_starts_with($property->name, FilesPlugin::FILE_METADATA_PREFIX)) {
return new SearchComparison($trimmedType, substr($property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX)), $value, IMetadataQuery::EXTRA);
} else {
return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($property), $value);
}

// no break
case Operator::OPERATION_IS_COLLECTION:
return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
default:
Expand Down
5 changes: 4 additions & 1 deletion apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
Expand Down Expand Up @@ -76,6 +77,7 @@
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\ICacheFactory;
use OCP\IRequest;
use OCP\Profiler\IProfiler;
Expand Down Expand Up @@ -316,7 +318,8 @@ public function __construct(IRequest $request, string $baseUri) {
$user,
\OC::$server->getRootFolder(),
\OC::$server->getShareManager(),
$view
$view,
\OCP\Server::get(IFilesMetadataManager::class)
));
$this->server->addPlugin(
new BulkUploadPlugin(
Expand Down
1 change: 1 addition & 0 deletions apps/files/lib/Command/Scan.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @author Joel S <joel.devbox@protonmail.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author martin.mattel@diemattels.at <martin.mattel@diemattels.at>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
Expand Down
6 changes: 6 additions & 0 deletions apps/files_trashbin/lib/Trash/TrashItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
Expand All @@ -23,6 +24,7 @@
namespace OCA\Files_Trashbin\Trash;

use OCP\Files\FileInfo;
use OCP\FilesMetadata\Model\IFilesMetadata;
use OCP\IUser;

class TrashItem implements ITrashItem {
Expand Down Expand Up @@ -191,6 +193,10 @@ public function getParentId(): int {
return $this->fileInfo->getParentId();
}

/**
* @inheritDoc
* @return array<string, int|string|bool|float|string[]|int[]>
*/
public function getMetadata(): array {
return $this->fileInfo->getMetadata();
}
Expand Down
66 changes: 40 additions & 26 deletions core/Command/FilesMetadata/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@

namespace OC\Core\Command\FilesMetadata;

use OC\User\NoUserException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\IFilesMetadataManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -43,52 +46,63 @@ public function __construct(
parent::__construct();
}

protected function configure() {
protected function configure(): void {
$this->setName('metadata:get')
->setDescription('get stored metadata about a file, by its id')
->addArgument(
'fileId',
InputArgument::REQUIRED,
'id of the file document'
'fileId',
InputArgument::REQUIRED,
'id of the file document'
)
->addArgument(
'userId',
InputArgument::OPTIONAL,
'file owner'
'userId',
InputArgument::OPTIONAL,
'file owner'
)
->addOption(
'as-array',
'',
InputOption::VALUE_NONE,
'display metadata as a simple key=>value array'
'as-array',
'',
InputOption::VALUE_NONE,
'display metadata as a simple key=>value array'
)
->addOption(
'refresh',
'',
InputOption::VALUE_NONE,
'refresh metadata'
)
'refresh',
'',
InputOption::VALUE_NONE,
'refresh metadata'
)
->addOption(
'reset',
'',
InputOption::VALUE_NONE,
'refresh metadata from scratch'
'reset',
'',
InputOption::VALUE_NONE,
'refresh metadata from scratch'
);
}

/**
* @throws NotPermittedException
* @throws FilesMetadataNotFoundException
* @throws NoUserException
* @throws NotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$fileId = (int)$input->getArgument('fileId');

if ($input->getOption('reset')) {
$this->filesMetadataManager->deleteMetadata($fileId);
if (!$input->getOption('refresh')) {
return 0;
}
}

if ($input->getOption('refresh')) {
$node = $this->rootFolder->getUserFolder($input->getArgument('userId'))->getById($fileId);
$file = $node[0];
if (null === $file) {
if (count($node) === 0) {
throw new NotFoundException();
}

$metadata = $this->filesMetadataManager->refreshMetadata(
$file,
IFilesMetadataManager::PROCESS_LIVE | IFilesMetadataManager::PROCESS_BACKGROUND,
$input->getOption('reset')
$node[0],
IFilesMetadataManager::PROCESS_LIVE | IFilesMetadataManager::PROCESS_BACKGROUND
);
} else {
$metadata = $this->filesMetadataManager->getMetadata($fileId);
Expand Down
Loading

0 comments on commit 1065e8a

Please sign in to comment.