Skip to content

Commit

Permalink
feat(systemtags): add etag support and handle proppatch
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Oct 23, 2024
1 parent 90adba4 commit 995abff
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 73 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 @@ -365,6 +365,7 @@
'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\\SystemTagsObjectList' => $baseDir . '/../lib/SystemTag/SystemTagsObjectList.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 @@ -380,6 +380,7 @@ class ComposerStaticInitDAV
'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\\SystemTagsObjectList' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectList.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
9 changes: 7 additions & 2 deletions apps/dav/lib/SystemTag/SystemTagNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,12 @@ public function childExists($name) {
}

public function getChildren() {
// We currently don't have a method to list allowed tag mappings types
return [new SystemTagObjectType($this->tag, 'files', $this->tagManager, $this->tagMapper)];
$objectTypes = $this->tagMapper->getAvailableObjectTypes();
return array_map(
function ($objectType) {
return new SystemTagObjectType($this->tag, $objectType, $this->tagManager, $this->tagMapper);
},
$objectTypes
);
}
}
36 changes: 30 additions & 6 deletions apps/dav/lib/SystemTag/SystemTagObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* SystemTagObjectType property
* This property represent a type of object which tags are assigned to.
*/
class SystemTagObjectType implements \Sabre\DAV\INode {
class SystemTagObjectType implements \Sabre\DAV\IFile {
public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';

/** @var string[] */
Expand All @@ -39,19 +39,43 @@ public function getObjectsIds(): array {
return $this->objectsIds;
}

public function delete() {
throw new MethodNotAllowed();
/**
* Returns the system tag represented by this node
*
* @return ISystemTag system tag
*/
public function getSystemTag() {
return $this->tag;
}

public function getName() {
return $this->type;
}

public function getLastModified() {
return null;
}

public function getETag() {
return '"' . $this->tag->getETag() . '"';
}

public function setName($name) {
throw new MethodNotAllowed();
}

public function getLastModified() {
return null;
public function put($data) {
throw new MethodNotAllowed();
}
public function get() {
throw new MethodNotAllowed();
}
public function delete() {
throw new MethodNotAllowed();
}
public function getContentType() {
throw new MethodNotAllowed();
}
public function getSize() {
throw new MethodNotAllowed();
}
}
43 changes: 42 additions & 1 deletion apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\DAV\SystemTag;

use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCA\DAV\Connector\Sabre\Node;
use OCP\IGroupManager;
use OCP\IUser;
Expand All @@ -20,6 +21,7 @@
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Conflict;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\PreconditionFailed;
use Sabre\DAV\Exception\UnsupportedMediaType;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
Expand Down Expand Up @@ -101,6 +103,9 @@ public function __construct(
*/
public function initialize(\Sabre\DAV\Server $server) {
$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';

$server->xml->elementMap[self::OBJECTIDS_PROPERTYNAME] = SystemTagsObjectList::class;

$server->protectedProperties[] = self::ID_PROPERTYNAME;

Expand Down Expand Up @@ -234,6 +239,10 @@ public function handleGetProperties(
$propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
}

$propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node): string|null {
return $node->getSystemTag()->getETag();
});

$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
return $node->getSystemTag()->getId();
});
Expand Down Expand Up @@ -380,9 +389,37 @@ private function getTagsForFile(int $fileId, ?IUser $user): array {
*/
public function handleUpdateProperties($path, PropPatch $propPatch) {
$node = $this->server->tree->getNodeForPath($path);
if (!($node instanceof SystemTagNode)) {
if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagObjectType)) {
return;
}

$propPatch->handle([self::OBJECTIDS_PROPERTYNAME], function ($props) use ($node) {
if (!($node instanceof SystemTagObjectType)) {
return false;
}

if (isset($props[self::OBJECTIDS_PROPERTYNAME])) {
$propValue = $props[self::OBJECTIDS_PROPERTYNAME];
if (!($propValue instanceof SystemTagsObjectList) || count($propValue?->getObjects() ?: []) === 0) {
throw new BadRequest('Invalid object-ids property');
}

$objects = $propValue->getObjects();
$objectTypes = array_unique(array_values($objects));

if (count($objectTypes) !== 1 || $objectTypes[0] !== $node->getName()) {
throw new BadRequest('Invalid object-ids property. All object types must be of the same type: ' . $node->getName());
}

$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), array_keys($objects));
}

if ($props[self::OBJECTIDS_PROPERTYNAME] === null) {
$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), []);
}

return true;
});

$propPatch->handle([
self::DISPLAYNAME_PROPERTYNAME,
Expand All @@ -392,6 +429,10 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
self::NUM_FILES_PROPERTYNAME,
self::REFERENCE_FILEID_PROPERTYNAME,
], function ($props) use ($node) {
if (!($node instanceof SystemTagNode)) {
return false;
}

$tag = $node->getSystemTag();
$name = $tag->getName();
$userVisible = $tag->isUserVisible();
Expand Down
2 changes: 1 addition & 1 deletion apps/dav/lib/SystemTag/SystemTagsInUseCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function getChildren(): array {
$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']);
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable'], $tagData['etag']);
// read only, so we can submit the isAdmin parameter as false generally
$node = new SystemTagNode($tag, $user, false, $this->systemTagManager, $this->tagMapper);
$node->setNumberOfFiles((int)$tagData['number_files']);
Expand Down
50 changes: 45 additions & 5 deletions apps/dav/lib/SystemTag/SystemTagsObjectList.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

/**
Expand All @@ -7,21 +8,60 @@
*/
namespace OCA\DAV\SystemTag;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlDeserializable;
use Sabre\Xml\XmlSerializable;

/**
* This property contains multiple "object-id" elements.
*/
class SystemTagsObjectList implements XmlSerializable {
class SystemTagsObjectList implements XmlSerializable, XmlDeserializable {

public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
public const OBJECTID_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}object-id';
public const OBJECTID_PROPERTYNAME = '{http://nextcloud.org/ns}id';
public const OBJECTTYPE_PROPERTYNAME = '{http://nextcloud.org/ns}type';

/**
* @param array<string, string> $objects An array of object ids and their types
*/
public function __construct(
private array $objects,
) { }
) {
}

public function getObjects(): array {
return $this->objects;
}

public static function xmlDeserialize(Reader $reader) {
$tree = $reader->parseInnerTree();
if ($tree === null) {
return null;
}

$objects = [];
foreach ($tree as $elem) {
if ($elem['name'] === self::OBJECTID_ROOT_PROPERTYNAME) {
$value = $elem['value'];
$id = '';
$type = '';
foreach ($value as $subElem) {
if ($subElem['name'] === self::OBJECTID_PROPERTYNAME) {
$id = $subElem['value'];
} elseif ($subElem['name'] === self::OBJECTTYPE_PROPERTYNAME) {
$type = $subElem['value'];
}
}
if ($id !== '' && $type !== '') {
$objects[$id] = $type;
}
}
}

return new self($objects);
}

/**
* The xmlSerialize method is called during xml writing.
Expand All @@ -31,9 +71,9 @@ public function __construct(
*/
public function xmlSerialize(Writer $writer) {
foreach ($this->objects as $objectsId => $type) {
$writer->startElement('{' . self::NS_NEXTCLOUD . '}object-id');
$writer->writeElement('{' . self::NS_NEXTCLOUD . '}id', $objectsId);
$writer->writeElement('{' . self::NS_NEXTCLOUD . '}type', $type);
$writer->startElement(SystemTagPlugin::OBJECTIDS_PROPERTYNAME);
$writer->writeElement(self::OBJECTID_PROPERTYNAME, $objectsId);
$writer->writeElement(self::OBJECTTYPE_PROPERTYNAME, $type);
$writer->endElement();
}
}
Expand Down
Loading

0 comments on commit 995abff

Please sign in to comment.