From 0b3f0716fc8e278a5727885548bfa406004263dc Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 15 Dec 2014 17:49:24 +0100 Subject: [PATCH 1/3] Returns and update tags through WebDAV PROPFIND and PROPPATCH Added oc:tags and oc:favorites in PROPFIND response. It is possible to update them with PROPPATCH. These properties are optional which means they need to be requested explicitly --- apps/files/appinfo/remote.php | 1 + lib/private/connector/sabre/directory.php | 43 ++- lib/private/connector/sabre/node.php | 11 + lib/private/connector/sabre/server.php | 7 + lib/private/connector/sabre/taglist.php | 102 +++++++ lib/private/connector/sabre/tagsplugin.php | 289 +++++++++++++++++++ tests/lib/connector/sabre/directory.php | 54 ++++ tests/lib/connector/sabre/tagsplugin.php | 314 +++++++++++++++++++++ 8 files changed, 812 insertions(+), 9 deletions(-) create mode 100644 lib/private/connector/sabre/taglist.php create mode 100644 lib/private/connector/sabre/tagsplugin.php create mode 100644 tests/lib/connector/sabre/tagsplugin.php diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index 26bef966f7..c622f08395 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -53,6 +53,7 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree) { $rootDir = new OC_Connector_Sabre_Directory($view, $rootInfo); $objectTree->init($rootDir, $view, $mountManager); + $server->addPlugin(new \OC\Connector\Sabre\TagsPlugin($objectTree, \OC::$server->getTagManager())); $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view)); }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php index ec5f82f9da..4bb6383902 100644 --- a/lib/private/connector/sabre/directory.php +++ b/lib/private/connector/sabre/directory.php @@ -21,9 +21,18 @@ * */ +use OC\Connector\Sabre\TagList; + class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota { + /** + * Cached directory content + * + * @var \OCP\FileInfo[] + */ + private $dirContent; + /** * Creates a new file in the directory * @@ -132,19 +141,34 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node return $node; } + /** + * Return the directory content as fileinfo objects + * + * @return \OCP\FileInfo[] + */ + public function getDirectoryContent() { + } + /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] */ public function getChildren() { - - $folder_content = $this->fileView->getDirectoryContent($this->path); - $paths = array(); - foreach($folder_content as $info) { - $paths[] = $this->path.'/'.$info['name']; - $properties[$this->path.'/'.$info['name']][self::GETETAG_PROPERTYNAME] = '"' . $info['etag'] . '"'; + if (!is_null($this->dirContent)) { + return $this->dirContent; } + $folderContent = $this->fileView->getDirectoryContent($this->path); + + $properties = array(); + $paths = array(); + foreach($folderContent as $info) { + $name = $info->getName(); + $paths[] = $this->path . '/' . $name; + $properties[$this->path.'/' . $name][self::GETETAG_PROPERTYNAME] = '"' . $info->getEtag() . '"'; + } + // TODO: move this to a beforeGetPropertiesForPath event to pre-cache properties + // TODO: only fetch the requested properties if(count($paths)>0) { // // the number of arguments within IN conditions are limited in most databases @@ -169,12 +193,13 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node } $nodes = array(); - foreach($folder_content as $info) { + foreach($folderContent as $info) { $node = $this->getChild($info->getName(), $info); - $node->setPropertyCache($properties[$this->path.'/'.$info['name']]); + $node->setPropertyCache($properties[$this->path . '/' . $info->getName()]); $nodes[] = $node; } - return $nodes; + $this->dirContent = $nodes; + return $this->dirContent; } /** diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php index a22dc9c5fb..3173ab8a30 100644 --- a/lib/private/connector/sabre/node.php +++ b/lib/private/connector/sabre/node.php @@ -1,5 +1,7 @@ info->getId(); + } + /** * @return string|null */ diff --git a/lib/private/connector/sabre/server.php b/lib/private/connector/sabre/server.php index 137082eea6..a836af2a0b 100644 --- a/lib/private/connector/sabre/server.php +++ b/lib/private/connector/sabre/server.php @@ -144,6 +144,13 @@ class OC_Connector_Sabre_Server extends Sabre\DAV\Server { $path = rtrim($path,'/'); + // This event allows people to intercept these requests early on in the + // process. + // + // We're not doing anything with the result, but this can be helpful to + // pre-fetch certain expensive live properties. + $this->broadCastEvent('beforeGetPropertiesForPath', array($path, $propertyNames, $depth)); + $returnPropertyList = array(); $parentNode = $this->tree->getNodeForPath($path); diff --git a/lib/private/connector/sabre/taglist.php b/lib/private/connector/sabre/taglist.php new file mode 100644 index 0000000000..2b61be3e49 --- /dev/null +++ b/lib/private/connector/sabre/taglist.php @@ -0,0 +1,102 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see . + * + */ + +namespace OC\Connector\Sabre; + +use Sabre\DAV; + +/** + * TagList property + * + * This property contains multiple "tag" elements, each containing a tag name. + */ +class TagList extends DAV\Property { + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** + * tags + * + * @var array + */ + private $tags; + + /** + * @param array $tags + */ + public function __construct(array $tags) { + $this->tags = $tags; + } + + /** + * Returns the tags + * + * @return array + */ + public function getTags() { + + return $this->tags; + + } + + /** + * Serializes this property. + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $dom) { + + $prefix = $server->xmlNamespaces[self::NS_OWNCLOUD]; + + foreach($this->tags as $tag) { + + $elem = $dom->ownerDocument->createElement($prefix . ':tag'); + $elem->appendChild($dom->ownerDocument->createTextNode($tag)); + + $dom->appendChild($elem); + } + + } + + /** + * Unserializes this property from a DOM Element + * + * This method returns an instance of this class. + * It will only decode tag values. + * + * @param \DOMElement $dom + * @return DAV\Property\TagList + */ + static function unserialize(\DOMElement $dom) { + + $tags = array(); + foreach($dom->childNodes as $child) { + if (DAV\XMLUtil::toClarkNotation($child)==='{' . self::NS_OWNCLOUD . '}tag') { + $tags[] = $child->textContent; + } + } + return new self($tags); + + } + +} diff --git a/lib/private/connector/sabre/tagsplugin.php b/lib/private/connector/sabre/tagsplugin.php new file mode 100644 index 0000000000..86a66f5a8b --- /dev/null +++ b/lib/private/connector/sabre/tagsplugin.php @@ -0,0 +1,289 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see . + * + */ + +class TagsPlugin extends \Sabre\DAV\ServerPlugin +{ + + // namespace + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const TAGS_PROPERTYNAME = '{' . self::NS_OWNCLOUD . '}tags'; + const FAVORITE_PROPERTYNAME = '{' . self::NS_OWNCLOUD . '}favorite'; + const TAG_FAVORITE = '_$!!$_'; + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCP\ITagManager + */ + private $tagManager; + + /** + * @var \OCP\ITags + */ + private $tagger; + + /** + * Array of file id to tags array + * The null value means the cache wasn't initialized. + * + * @var array + */ + private $cachedTags; + + /** + * @param \OCP\ITagManager $tagManager tag manager + */ + public function __construct(\Sabre\DAV\ObjectTree $objectTree, \OCP\ITagManager $tagManager) { + $this->objectTree = $objectTree; + $this->tagManager = $tagManager; + $this->tagger = null; + $this->cachedTags = null; + } + + /** + * This initializes the plugin. + * + * This function is called by \Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param \Sabre\DAV\Server $server + * @return void + */ + public function initialize(\Sabre\DAV\Server $server) { + + $server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc'; + $server->propertyMap[self::TAGS_PROPERTYNAME] = 'OC\\Connector\\Sabre\\TagList'; + + $this->server = $server; + $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); + $this->server->subscribeEvent('beforeGetPropertiesForPath', array($this, 'beforeGetPropertiesForPath')); + $this->server->subscribeEvent('updateProperties', array($this, 'updateProperties')); + } + + /** + * Searches and removes a value from the given array + * + * @param array $requestedProps + * @param string $propName to remove + * @return boolean true if the property was present, false otherwise + */ + private function findAndRemoveProperty(&$requestedProps, $propName) { + $index = array_search($propName, $requestedProps); + if ($index !== false) { + unset($requestedProps[$index]); + return true; + } + return false; + } + + /** + * Returns the tagger + * + * @return \OCP\ITags tagger + */ + private function getTagger() { + if (!$this->tagger) { + $this->tagger = $this->tagManager->load('files'); + } + return $this->tagger; + } + + /** + * Returns tags and favorites. + * + * @param integer $fileId file id + * @return array list($tags, $favorite) with $tags as tag array + * and $favorite is a boolean whether the file was favorited + */ + private function getTagsAndFav($fileId) { + $isFav = false; + $tags = $this->getTags($fileId); + if ($tags) { + $favPos = array_search(self::TAG_FAVORITE, $tags); + if ($favPos !== false) { + $isFav = true; + unset($tags[$favPos]); + } + } + return array($tags, $isFav); + } + + /** + * Returns tags for the given file id + * + * @param integer $fileId file id + * @return array list of tags for that file + */ + private function getTags($fileId) { + if (isset($this->cachedTags[$fileId])) { + return $this->cachedTags[$fileId]; + } else { + $tags = $this->getTagger()->getTagsForObjects(array($fileId)); + if ($tags) { + return current($tags); + } + } + return null; + } + + /** + * Updates the tags of the given file id + * + * @param int $fileId + * @param array $tags array of tag strings + */ + private function updateTags($fileId, $tags) { + $tagger = $this->getTagger(); + $currentTags = $this->getTags($fileId); + + $newTags = array_diff($tags, $currentTags); + foreach ($newTags as $tag) { + if ($tag === self::TAG_FAVORITE) { + continue; + } + $tagger->tagAs($fileId, $tag); + } + $deletedTags = array_diff($currentTags, $tags); + foreach ($deletedTags as $tag) { + if ($tag === self::TAG_FAVORITE) { + continue; + } + $tagger->unTag($fileId, $tag); + } + } + + /** + * Pre-fetch tags info + * + * @param string $path + * @param array $requestedProperties + * @param integer $depth + * @return void + */ + public function beforeGetPropertiesForPath( + $path, + array $requestedProperties, + $depth + ) { + $node = $this->objectTree->getNodeForPath($path); + if (!($node instanceof \OC_Connector_Sabre_Directory)) { + return; + } + + if ($this->findAndRemoveProperty($requestedProperties, self::TAGS_PROPERTYNAME) + || $this->findAndRemoveProperty($requestedProperties, self::FAVORITE_PROPERTYNAME) + ) { + $fileIds = array(); + // note: pre-fetching only supported for depth <= 1 + $folderContent = $node->getChildren(); + // TODO: refactor somehow with the similar array that is created + // in getChildren() + foreach ($folderContent as $info) { + $fileIds[] = $info->getId(); + } + $tags = $this->getTagger()->getTagsForObjects($fileIds); + if ($tags) { + $this->cachedTags = $tags; + } + } + } + + /** + * Adds tags and favorites properties to the response, + * if requested. + * + * @param string $path + * @param \Sabre\DAV\INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return void + */ + public function beforeGetProperties( + $path, + \Sabre\DAV\INode $node, + array &$requestedProperties, + array &$returnedProperties + ) { + if (!($node instanceof \OC_Connector_Sabre_Node)) { + return; + } + + $tags = null; + $isFav = null; + if ($this->findAndRemoveProperty($requestedProperties, self::TAGS_PROPERTYNAME)) { + list($tags, $isFav) = $this->getTagsAndFav($node->getId()); + $returnedProperties[200][self::TAGS_PROPERTYNAME] = new TagList($tags); + } + if ($this->findAndRemoveProperty($requestedProperties, self::FAVORITE_PROPERTYNAME)) { + if (is_null($tags)) { + list($tags, $isFav) = $this->getTagsAndFav($node->getId()); + } + $returnedProperties[200][self::FAVORITE_PROPERTYNAME] = $isFav; + } + } + + /** + * Updates tags and favorites properties, if applicable. + * + * @param string $path + * @param \Sabre\DAV\INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return bool success status + */ + public function updateProperties(array &$properties, array &$result, \Sabre\DAV\INode $node) { + if (!($node instanceof \OC_Connector_Sabre_Node)) { + return; + } + + $fileId = $node->getId(); + if (isset($properties[self::TAGS_PROPERTYNAME])) { + $tagsProp = $properties[self::TAGS_PROPERTYNAME]; + unset($properties[self::TAGS_PROPERTYNAME]); + $this->updateTags($fileId, $tagsProp->getTags()); + $result[200][self::TAGS_PROPERTYNAME] = new TagList($tagsProp->getTags()); + } + if (isset($properties[self::FAVORITE_PROPERTYNAME])) { + $favState = $properties[self::FAVORITE_PROPERTYNAME]; + unset($properties[self::FAVORITE_PROPERTYNAME]); + if ((int)$favState === 1 || $favState === 'true') { + $favState = true; + $this->getTagger()->tagAs($fileId, self::TAG_FAVORITE); + } else { + $favState = false; + $this->getTagger()->unTag($fileId, self::TAG_FAVORITE); + } + $result[200][self::FAVORITE_PROPERTYNAME] = $favState; + } + return true; + } +} diff --git a/tests/lib/connector/sabre/directory.php b/tests/lib/connector/sabre/directory.php index d8dca35cd7..e9bfea81b7 100644 --- a/tests/lib/connector/sabre/directory.php +++ b/tests/lib/connector/sabre/directory.php @@ -101,4 +101,58 @@ class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { $dir = $this->getRootDir(); $dir->delete(); } + + public function testGetChildren() { + $info1 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info2 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info1->expects($this->any()) + ->method('getName') + ->will($this->returnValue('first')); + $info1->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('abc')); + $info2->expects($this->any()) + ->method('getName') + ->will($this->returnValue('second')); + $info2->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('def')); + + $this->view->expects($this->once()) + ->method('getDirectoryContent') + ->with('') + ->will($this->returnValue(array($info1, $info2))); + + $this->view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnValue('')); + + $dir = new OC_Connector_Sabre_Directory($this->view, $this->info); + $nodes = $dir->getChildren(); + + $this->assertEquals(2, count($nodes)); + + // calling a second time just returns the cached values, + // does not call getDirectoryContents again + $nodes = $dir->getChildren(); + + $properties = array('testprop', OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME); + $this->assertEquals(2, count($nodes)); + $this->assertEquals( + array( + OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME => '"abc"' + ), + $nodes[0]->getProperties($properties) + ); + $this->assertEquals( + array( + OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME => '"def"' + ), + $nodes[1]->getProperties($properties) + ); + } } diff --git a/tests/lib/connector/sabre/tagsplugin.php b/tests/lib/connector/sabre/tagsplugin.php new file mode 100644 index 0000000000..2afea061ec --- /dev/null +++ b/tests/lib/connector/sabre/tagsplugin.php @@ -0,0 +1,314 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class TagsPlugin extends \Test\TestCase { + + const TAGS_PROPERTYNAME = \OC\Connector\Sabre\TagsPlugin::TAGS_PROPERTYNAME; + const FAVORITE_PROPERTYNAME = \OC\Connector\Sabre\TagsPlugin::FAVORITE_PROPERTYNAME; + const TAG_FAVORITE = \OC\Connector\Sabre\TagsPlugin::TAG_FAVORITE; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OCP\ITagManager + */ + private $tagManager; + + /** + * @var \OCP\ITags + */ + private $tagger; + + /** + * @var \OC\Connector\Sabre\TagsPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\ObjectTree') + ->disableOriginalConstructor() + ->getMock(); + $this->tagger = $this->getMock('\OCP\ITags'); + $this->tagManager = $this->getMock('\OCP\ITagManager'); + $this->tagManager->expects($this->any()) + ->method('load') + ->with('files') + ->will($this->returnValue($this->tagger)); + $this->plugin = new \OC\Connector\Sabre\TagsPlugin($this->tree, $this->tagManager); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testGetProperties($tags, $requestedProperties, $expectedProperties) { + $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + $expectedCallCount = 1; + } + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => $tags))); + + $returnedProperties = array(); + + $this->plugin->beforeGetProperties( + '', + $node, + $requestedProperties, + $returnedProperties + ); + + $this->assertEquals($expectedProperties, $returnedProperties); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testPreloadThenGetProperties($tags, $requestedProperties, $expectedProperties) { + $node1 = $this->getMockBuilder('\OC_Connector_Sabre_File') + ->disableOriginalConstructor() + ->getMock(); + $node1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $node2 = $this->getMockBuilder('\OC_Connector_Sabre_File') + ->disableOriginalConstructor() + ->getMock(); + $node2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + // this guarantees that getTagsForObjects + // is only called once and then the tags + // are cached + $expectedCallCount = 1; + } + + $node = $this->getMockBuilder('\OC_Connector_Sabre_Directory') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $node->expects($this->exactly($expectedCallCount)) + ->method('getChildren') + ->will($this->returnValue(array($node1, $node2))); + + $this->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('/subdir') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(111, 222))) + ->will($this->returnValue( + array( + 111 => $tags, + 123 => $tags + ) + )); + + $returnedProperties = array(); + + $this->plugin->beforeGetPropertiesForPath( + '/subdir', + $requestedProperties, + 1 + ); + + $this->plugin->beforeGetProperties( + '/subdir/test.txt', + $node1, + $requestedProperties, + $returnedProperties + ); + + $this->assertEquals($expectedProperties, $returnedProperties); + } + + function tagsGetPropertiesDataProvider() { + return array( + // request both, receive both + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array('tag1', 'tag2')), + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request tags alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array('tag1', 'tag2')), + ) + ) + ), + // request fav alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request none + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(), + array(), + ), + // request both with none set, receive both + array( + array(), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array()), + self::FAVORITE_PROPERTYNAME => false, + ) + ) + ), + ); + } + + public function testUpdateTags() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => array('tagkeep', 'tagremove', self::TAG_FAVORITE)))); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // it will untag tag3 + $this->tagger->expects($this->at(3)) + ->method('unTag') + ->with(123, 'tagremove'); + + // properties to set + $properties = array( + self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + ); + $result = array(); + + $this->plugin->updateProperties( + $properties, + $result, + $node + ); + + // all requested properties removed, as they were processed already + $this->assertEmpty($properties); + + $this->assertEquals( + new \OC\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')), + $result[200][self::TAGS_PROPERTYNAME] + ); + $this->assertFalse(isset($result[200][self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateFav() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + // set favorite tag + $this->tagger->expects($this->once()) + ->method('tagAs') + ->with(123, self::TAG_FAVORITE); + + // properties to set + $properties = array( + self::FAVORITE_PROPERTYNAME => true + ); + $result = array(); + $this->plugin->updateProperties( + $properties, + $result, + $node + ); + + // all requested properties removed, as they were processed already + $this->assertEmpty($properties); + + $this->assertTrue($result[200][self::FAVORITE_PROPERTYNAME]); + $this->assertFalse(isset($result[200][self::TAGS_PROPERTYNAME])); + + // unfavorite now + // set favorite tag + $this->tagger->expects($this->once()) + ->method('unTag') + ->with(123, self::TAG_FAVORITE); + + $properties = array( + self::FAVORITE_PROPERTYNAME => false + ); + $result = array(); + $this->plugin->updateProperties( + $properties, + $result, + $node + ); + + $this->assertFalse($result[200][self::FAVORITE_PROPERTYNAME]); + $this->assertFalse(isset($result[200][self::TAGS_PROPERTYNAME])); + } + +} From 3c679004217e0a1a600bd2d83075a01fb4bf4438 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 18 Dec 2014 16:04:36 +0100 Subject: [PATCH 2/3] Remove obsolete method --- lib/private/connector/sabre/directory.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php index 4bb6383902..7eeca9199f 100644 --- a/lib/private/connector/sabre/directory.php +++ b/lib/private/connector/sabre/directory.php @@ -141,14 +141,6 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node return $node; } - /** - * Return the directory content as fileinfo objects - * - * @return \OCP\FileInfo[] - */ - public function getDirectoryContent() { - } - /** * Returns an array with all the child nodes * From 6224e29f25931c175fca23148fa491e182de8348 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 18 Dec 2014 16:43:00 +0100 Subject: [PATCH 3/3] Fix code style issues for tags plugin --- lib/private/connector/sabre/directory.php | 4 +--- lib/private/connector/sabre/taglist.php | 2 +- lib/private/connector/sabre/tagsplugin.php | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php index 7eeca9199f..0447202072 100644 --- a/lib/private/connector/sabre/directory.php +++ b/lib/private/connector/sabre/directory.php @@ -21,15 +21,13 @@ * */ -use OC\Connector\Sabre\TagList; - class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota { /** * Cached directory content * - * @var \OCP\FileInfo[] + * @var \OCP\Files\FileInfo[] */ private $dirContent; diff --git a/lib/private/connector/sabre/taglist.php b/lib/private/connector/sabre/taglist.php index 2b61be3e49..56cab393fe 100644 --- a/lib/private/connector/sabre/taglist.php +++ b/lib/private/connector/sabre/taglist.php @@ -85,7 +85,7 @@ class TagList extends DAV\Property { * It will only decode tag values. * * @param \DOMElement $dom - * @return DAV\Property\TagList + * @return \OC\Connector\Sabre\TagList */ static function unserialize(\DOMElement $dom) { diff --git a/lib/private/connector/sabre/tagsplugin.php b/lib/private/connector/sabre/tagsplugin.php index 86a66f5a8b..dd0b5172bd 100644 --- a/lib/private/connector/sabre/tagsplugin.php +++ b/lib/private/connector/sabre/tagsplugin.php @@ -28,8 +28,8 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; - const TAGS_PROPERTYNAME = '{' . self::NS_OWNCLOUD . '}tags'; - const FAVORITE_PROPERTYNAME = '{' . self::NS_OWNCLOUD . '}favorite'; + const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags'; + const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite'; const TAG_FAVORITE = '_$!!$_'; /**