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

Added searchByTags to view, storage and cache #12617

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions apps/files_sharing/lib/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,46 @@ public function searchByMime($mimetype) {
return $result;
}

/**
* search for files by tag
*
* @param string|int $tag tag to search for
* @param string $userId owner of the tags
* @return array file data
*/
public function searchByTag($tag, $userId = null) {
// TODO: inject this
$tagger = \OC::$server->getTagManager()->load('files', null, null, $userId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$userId is expected to be a \OCP\IUser. - Also I don't get why we pass here an ID and below the user object? Maybe it makes more sense to always pass the IUser?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder... @icewind1991 asked me to pass IUser to the tag manager.

One problem I could see for searchByTag is that you might not actually have an instance of another user but only the one from the session. We'd need to add yet another provider in OC::$server to be able to get/create the user object of another non-logged in user ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally prefer passing a string everywhere where only the string is needed...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that manually creating the user requires you to do new \OC\User\User($userId, null) at the moment, null being ugly and having to use the private namespace is ugly too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just asked @icewind1991 again he said it depends on the use case.
For searchByTags() it is less cumbersome for the API consumer to just pass a string.

If the inconsistency still makes you unhappy I can change TagManager to use strings instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change TagManager to take strings instead, especially that we only need the user id anyway.

$result = array();
$exploreDirs = array('');
// FIXME: this is so wrong and unefficient, need to replace with actual DB queries
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@icewind1991 I didn't hear your 😱 ? 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared storage refactoring will take care of it 😄

while (count($exploreDirs) > 0) {
$dir = array_pop($exploreDirs);
$files = $this->getFolderContents($dir);
// no results?
if (!$files) {
// maybe it's a single shared file
$file = $this->get('');
$tags = $tagger->getTagsForObjects(array((int)$file['fileid']));
if (!empty($tags) && in_array($tag, current($tags))) {
$result[] = $file;
}
continue;
}
foreach ($files as $file) {
if ($file['mimetype'] === 'httpd/unix-directory') {
$exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
} else {
$tags = $tagger->getTagsForObjects(array((int)$file['fileid']));
if (!empty($tags) && in_array($tag, current($tags))) {
$result[] = $file;
}
}
}
}
return $result;
}

/**
* get the size of a folder and set it in the cache
*
Expand Down
33 changes: 33 additions & 0 deletions apps/files_sharing/tests/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,39 @@ function testSearchByMime() {
$this->verifyFiles($check, $results);
}

/**
* Test searching by tag
*/
function testSearchByTag() {
$id1 = $this->sharedCache->get('bar.txt')['fileid'];
$id2 = $this->sharedCache->get('subdir/another too.txt')['fileid'];
$id3 = $this->sharedCache->get('subdir/not a text file.xml')['fileid'];
$id4 = $this->sharedCache->get('subdir/another.txt')['fileid'];
$tagManager = \OC::$server->getTagManager()->load('files');
$tagManager->tagAs($id1, 'tag1');
$tagManager->tagAs($id1, 'tag2');
$tagManager->tagAs($id2, 'tag1');
$tagManager->tagAs($id3, 'tag1');
$tagManager->tagAs($id4, 'tag2');
$results = $this->sharedStorage->getCache()->searchByTag('tag1');
$check = array(
array(
'name' => 'bar.txt',
'path' => 'bar.txt'
),
array(
'name' => 'another too.txt',
'path' => 'subdir/another too.txt'
),
array(
'name' => 'not a text file.xml',
'path' => 'subdir/not a text file.xml'
),
);
$this->verifyFiles($check, $results);
$tagManager->delete(array('tag1', 'tag2'));
}

function testGetFolderContentsInRoot() {
$results = $this->user2View->getDirectoryContent('/');

Expand Down
48 changes: 48 additions & 0 deletions lib/private/files/cache/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,54 @@ public function searchByMime($mimetype) {
return $files;
}

/**
* Search for files by tag of a given users.
*
* Note that every user can tag files differently.
*
* @param string|int $tag name or tag id
* @param string $userId owner of the tags
* @return array file data
*/
public function searchByTag($tag, $userId = null) {
if (is_null($userId)) {
$userId = \OC::$server->getUserSession()->getUser()->getUID();
}
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
'`mimetype`, `mimepart`, `size`, `mtime`, ' .
'`encrypted`, `unencrypted_size`, `etag`, `permissions` ' .
'FROM `*PREFIX*filecache` `file`, ' .
'`*PREFIX*vcategory_to_object` `tagmap`, ' .
'`*PREFIX*vcategory` `tag` ' .
// JOIN filecache to vcategory_to_object
'WHERE `file`.`fileid` = `tagmap`.`objid` '.
// JOIN vcategory_to_object to vcategory
'AND `tagmap`.`type` = `tag`.`type` ' .
'AND `tagmap`.`categoryid` = `tag`.`id` ' .
// conditions
'AND `file`.`storage` = ? '.
'AND `tag`.`type` = "files" ' .
'AND `tag`.`uid` = ? ';
if (is_int($tag)) {
$sql .= 'AND `tag`.`id` = ? ';
} else {
$sql .= 'AND `tag`.`category` = ? ';
}
$result = \OC_DB::executeAudited(
$sql,
array(
$this->getNumericStorageId(),
$userId,
$tag
)
);
$files = array();
while ($row = $result->fetchRow()) {
$files[] = $row;
}
return $files;
}

/**
* update the folder size and the size of all parent folders
*
Expand Down
12 changes: 12 additions & 0 deletions lib/private/files/cache/wrapper/cachewrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ public function searchByMime($mimetype) {
return array_map(array($this, 'formatCacheEntry'), $results);
}

/**
* search for files by tag
*
* @param string|int $tag name or tag id
* @param string $userId owner of the tags
* @return array file data
*/
public function searchByTag($tag, $userId = null) {
$results = $this->cache->searchByTag($tag, $userId);
return array_map(array($this, 'formatCacheEntry'), $results);
}

/**
* update the folder size and the size of all parent folders
*
Expand Down
7 changes: 7 additions & 0 deletions lib/private/files/filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,13 @@ static public function searchByMime($query) {
return self::$defaultInstance->searchByMime($query);
}

/**
* @param string $tag
*/
static public function searchByTag($tag) {
return self::$defaultInstance->searchByTag($tag);
}

/**
* check if a file or folder has been updated since $time
*
Expand Down
10 changes: 10 additions & 0 deletions lib/private/files/node/folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,16 @@ public function searchByMime($mimetype) {
return $this->searchCommon($mimetype, 'searchByMime');
}

/**
* search for files by tag
*
* @param string $tag
* @return Node[]
*/
public function searchByTag($tag) {
return $this->searchCommon($tag, 'searchByTag');
}

/**
* @param string $query
* @param string $method
Expand Down
4 changes: 4 additions & 0 deletions lib/private/files/node/nonexistingfolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ public function searchByMime($mime) {
throw new NotFoundException();
}

public function searchByTag($mime) {
throw new NotFoundException();
}

public function getById($id) {
throw new NotFoundException();
}
Expand Down
10 changes: 10 additions & 0 deletions lib/private/files/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,16 @@ public function searchByMime($mimetype) {
return $this->searchCommon($mimetype, 'searchByMime');
}

/**
* search for files by tag
*
* @param string $tag
* @return FileInfo[]
*/
public function searchByTag($tag) {
return $this->searchCommon($tag, 'searchByTag');
}

/**
* @param string $query
* @param string $method
Expand Down
3 changes: 1 addition & 2 deletions lib/private/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ function __construct($webRoot) {
});
$this->registerService('TagManager', function (Server $c) {
$tagMapper = $c->query('TagMapper');
$user = \OC_User::getUser();
return new TagManager($tagMapper, $user);
return new TagManager($tagMapper, $c->getUserSession());
});
$this->registerService('RootFolder', function (Server $c) {
// TODO: get user and user manager from container as well
Expand Down
22 changes: 13 additions & 9 deletions lib/private/tagmanager.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
class TagManager implements \OCP\ITagManager {

/**
* User
* User session
*
* @var string
* @var \OCP\IUserSession
*/
private $user;
private $userSession;

/**
* TagMapper
Expand All @@ -55,12 +55,11 @@ class TagManager implements \OCP\ITagManager {
* Constructor.
*
* @param TagMapper $mapper Instance of the TagMapper abstraction layer.
* @param string $user The user whose data the object will operate on.
* @param \OCP\IUserSession $userSession the user session
*/
public function __construct(TagMapper $mapper, $user) {

public function __construct(TagMapper $mapper, \OCP\IUserSession $userSession) {
$this->mapper = $mapper;
$this->user = $user;
$this->userSession = $userSession;

}

Expand All @@ -71,10 +70,15 @@ public function __construct(TagMapper $mapper, $user) {
* @param string $type The type identifier e.g. 'contact' or 'event'.
* @param array $defaultTags An array of default tags to be used if none are stored.
* @param boolean $includeShared Whether to include tags for items shared with this user by others.
* @param string $userId user for which to retrieve the tags, defaults to the currently
* logged in user
* @return \OCP\ITags
*/
public function load($type, $defaultTags=array(), $includeShared=false) {
return new Tags($this->mapper, $this->user, $type, $defaultTags, $includeShared);
public function load($type, $defaultTags = array(), $includeShared = false, $userId = null) {
if (is_null($userId)) {
$userId = $this->userSession->getUser()->getUId();
}
return new Tags($this->mapper, $userId, $type, $defaultTags, $includeShared);
}

}
8 changes: 8 additions & 0 deletions lib/public/files/folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ public function search($query);
*/
public function searchByMime($mimetype);

/**
* search for files by tag
*
* @param string|int $tag tag name or tag id
* @return \OCP\Files\Node[]
*/
public function searchByTag($tag);

/**
* get a file or folder inside the folder by it's internal id
*
Expand Down
7 changes: 4 additions & 3 deletions lib/public/itagmanager.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@
interface ITagManager {

/**
* Create a new \OCP\ITags instance and load tags from db.
* Create a new \OCP\ITags instance and load tags from db for the current user.
*
* @see \OCP\ITags
* @param string $type The type identifier e.g. 'contact' or 'event'.
* @param array $defaultTags An array of default tags to be used if none are stored.
* @param boolean $includeShared Whether to include tags for items shared with this user by others.
* @param string $userId user for which to retrieve the tags, defaults to the currently
* logged in user
* @return \OCP\ITags
*/
public function load($type, $defaultTags=array(), $includeShared=false);

public function load($type, $defaultTags = array(), $includeShared = false, $userId = null);
}
57 changes: 57 additions & 0 deletions tests/lib/files/cache/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,63 @@ function testSearch() {
$this->assertEquals(2, count($this->cache->searchByMime('foo/file')));
}

function testSearchByTag() {
$userId = $this->getUniqueId('user');
\OC_User::createUser($userId, $userId);
$this->loginAsUser($userId);
$user = new \OC\User\User($userId, null);

$file1 = 'folder';
$file2 = 'folder/foobar';
$file3 = 'folder/foo';
$file4 = 'folder/foo2';
$file5 = 'folder/foo3';
$data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder');
$fileData = array();
$fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file');
$fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file');
$fileData['foo2'] = array('size' => 25, 'mtime' => 28, 'mimetype' => 'foo/file');
$fileData['foo3'] = array('size' => 88, 'mtime' => 34, 'mimetype' => 'foo/file');

$id1 = $this->cache->put($file1, $data1);
$id2 = $this->cache->put($file2, $fileData['foobar']);
$id3 = $this->cache->put($file3, $fileData['foo']);
$id4 = $this->cache->put($file4, $fileData['foo2']);
$id5 = $this->cache->put($file5, $fileData['foo3']);

$tagManager = \OC::$server->getTagManager()->load('files', null, null, $userId);
$this->assertTrue($tagManager->tagAs($id1, 'tag1'));
$this->assertTrue($tagManager->tagAs($id1, 'tag2'));
$this->assertTrue($tagManager->tagAs($id2, 'tag2'));
$this->assertTrue($tagManager->tagAs($id3, 'tag1'));
$this->assertTrue($tagManager->tagAs($id4, 'tag2'));

// use tag name
$results = $this->cache->searchByTag('tag1', $userId);

$this->assertEquals(2, count($results));

$this->assertEquals('folder', $results[0]['name']);
$this->assertEquals('foo', $results[1]['name']);

// use tag id
$tags = $tagManager->getTagsForUser($userId);
$this->assertNotEmpty($tags);
$tags = array_filter($tags, function($tag) { return $tag->getName() === 'tag2'; });
$results = $this->cache->searchByTag(current($tags)->getId(), $userId);
$this->assertEquals(3, count($results));

$this->assertEquals('folder', $results[0]['name']);
$this->assertEquals('foobar', $results[1]['name']);
$this->assertEquals('foo2', $results[2]['name']);

$tagManager->delete('tag1');
$tagManager->delete('tag2');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPDoc indicates string[]|integer[], though code seems to change it to an array automatically. - Adjust PHPDoc?


$this->logout();
\OC_User::deleteUser($userId);
}

function testMove() {
$file1 = 'folder';
$file2 = 'folder/bar';
Expand Down
Loading