Skip to content

Commit e321ecd

Browse files
committed
add recent files to node api
1 parent d499f68 commit e321ecd

File tree

5 files changed

+298
-5
lines changed

5 files changed

+298
-5
lines changed

lib/private/Files/Node/Folder.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
namespace OC\Files\Node;
2828

29+
use OC\DB\QueryBuilder\Literal;
30+
use OCP\DB\QueryBuilder\IQueryBuilder;
2931
use OCP\Files\FileInfo;
32+
use OCP\Files\Mount\IMountPoint;
3033
use OCP\Files\NotFoundException;
3134
use OCP\Files\NotPermittedException;
3235

@@ -358,4 +361,114 @@ public function getNonExistingName($name) {
358361
$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
359362
return trim($this->getRelativePath($uniqueName), '/');
360363
}
364+
365+
/**
366+
* @param int $since
367+
* @return \OCP\Files\Node[]
368+
*/
369+
public function getRecent($since) {
370+
$mimetypeLoader = \OC::$server->getMimeTypeLoader();
371+
$mounts = $this->root->getMountsIn($this->path);
372+
$mounts[] = $this->getMountPoint();
373+
374+
$mounts = array_filter($mounts, function (IMountPoint $mount) {
375+
return $mount->getStorage();
376+
});
377+
$storageIds = array_map(function (IMountPoint $mount) {
378+
return $mount->getStorage()->getCache()->getNumericStorageId();
379+
}, $mounts);
380+
/** @var IMountPoint[] $mountMap */
381+
$mountMap = array_combine($storageIds, $mounts);
382+
$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
383+
384+
//todo look into options of filtering path based on storage id (only search in files/ for home storage, filter by share root for shared, etc)
385+
386+
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
387+
$query = $builder
388+
->select('f.*')
389+
->from('filecache', 'f')
390+
->where($builder->expr()->gt('f.storage_mtime', $builder->createNamedParameter($since, IQueryBuilder::PARAM_INT)))
391+
->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
392+
->andWhere($builder->expr()->orX(
393+
// handle non empty folders separate
394+
$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
395+
$builder->expr()->eq('f.size', new Literal(0))
396+
))
397+
->orderBy('f.mtime', 'DESC');
398+
399+
$result = $query->execute()->fetchAll();
400+
401+
// select folders with their mtime being the mtime of the oldest file in the folder
402+
// this way we still show new folders but dont bumb the folder every time a file in it is changed
403+
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
404+
$query = $builder
405+
->select('p.fileid', 'p.storage', 'p.mimetype', 'p.mimepart', 'p.size', 'p.path', 'p.etag', 'f1.storage_mtime', 'f1.mtime', 'p.permissions')
406+
->from('filecache', 'f1')
407+
->leftJoin('f1', 'filecache', 'f2', $builder->expr()->andX( // find the f1 with lowest mtime in the folder
408+
$builder->expr()->eq('f1.parent', 'f2.parent'),
409+
$builder->expr()->gt('f1.storage_mtime', 'f2.storage_mtime')
410+
))
411+
->innerJoin('f1', 'filecache', 'p', $builder->expr()->eq('f1.parent', 'p.fileid'))
412+
->where($builder->expr()->isNull('f2.fileid'))
413+
->andWhere($builder->expr()->gt('f1.storage_mtime', $builder->createNamedParameter($since, IQueryBuilder::PARAM_INT)))
414+
->andWhere($builder->expr()->in('f1.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)))
415+
->andWhere($builder->expr()->neq('f1.size', new Literal(0)))
416+
->orderBy('f1.storage_mtime', 'DESC');
417+
418+
$folderResults = $query->execute()->fetchAll();
419+
420+
$found = []; // we sometimes get duplicate folders
421+
$folderResults = array_filter($folderResults, function ($item) use (&$found) {
422+
$isFound = isset($found[$item['fileid']]);
423+
$found[$item['fileid']] = true;
424+
return !$isFound;
425+
});
426+
427+
$result = array_merge($folderResults, $result);
428+
429+
usort($result, function ($a, $b) use ($folderMimetype) {
430+
$diff = $b['mtime'] - $a['mtime'];
431+
if ($diff === 0) {
432+
return $a['mimetype'] === $folderMimetype ? -1 : 1;
433+
} else {
434+
return $diff;
435+
}
436+
});
437+
438+
$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
439+
$mount = $mountMap[$entry['storage']];
440+
$entry['internalPath'] = $entry['path'];
441+
$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
442+
$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
443+
$path = $this->getAbsolutePath($mount, $entry['path']);
444+
if (is_null($path)) {
445+
return null;
446+
}
447+
$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
448+
return $this->root->createNode($fileInfo->getPath(), $fileInfo);
449+
}, $result));
450+
451+
return array_values(array_filter($files, function (Node $node) {
452+
$relative = $this->getRelativePath($node->getPath());
453+
return $relative !== null && $relative !== '/';
454+
}));
455+
}
456+
457+
private function getAbsolutePath(IMountPoint $mount, $path) {
458+
$storage = $mount->getStorage();
459+
if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
460+
/** @var \OC\Files\Storage\Wrapper\Jail $storage */
461+
$jailRoot = $storage->getSourcePath('');
462+
$rootLength = strlen($jailRoot) + 1;
463+
if ($path === $jailRoot) {
464+
return $mount->getMountPoint();
465+
} else if (substr($path, 0, $rootLength) === $jailRoot . '/') {
466+
return $mount->getMountPoint() . substr($path, $rootLength);
467+
} else {
468+
return null;
469+
}
470+
} else {
471+
return $mount->getMountPoint() . $path;
472+
}
473+
}
361474
}

lib/private/Files/Node/LazyRoot.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,5 +471,10 @@ public function unlock($type) {
471471
return $this->__call(__FUNCTION__, func_get_args());
472472
}
473473

474-
474+
/**
475+
* @inheritDoc
476+
*/
477+
public function getRecent($type) {
478+
return $this->__call(__FUNCTION__, func_get_args());
479+
}
475480
}

lib/public/Files/FileInfo.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ interface FileInfo {
5858
*/
5959
const SPACE_UNLIMITED = -3;
6060

61+
/**
62+
* @since 9.1.0
63+
*/
64+
const MIMETYPE_FOLDER = 'httpd/unix-directory';
65+
6166
/**
6267
* Get the Etag of the file or folder
6368
*

lib/public/Files/Folder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,11 @@ public function isCreatable();
175175
* @since 8.1.0
176176
*/
177177
public function getNonExistingName($name);
178+
179+
/**
180+
* @param int $since
181+
* @return \OCP\Files\Node[]
182+
* @since 9.1.0
183+
*/
184+
public function getRecent($since);
178185
}

tests/lib/Files/Node/FolderTest.php

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use OC\Files\FileInfo;
1313
use OC\Files\Mount\MountPoint;
1414
use OC\Files\Node\Node;
15+
use OC\Files\Storage\Temporary;
16+
use OC\Files\Storage\Wrapper\Jail;
1517
use OCP\Files\NotFoundException;
1618
use OCP\Files\NotPermittedException;
1719
use OC\Files\View;
@@ -760,9 +762,9 @@ public function testGetByIdMultipleStorages() {
760762
public function uniqueNameProvider() {
761763
return [
762764
// input, existing, expected
763-
['foo', [] , 'foo'],
764-
['foo', ['foo'] , 'foo (2)'],
765-
['foo', ['foo', 'foo (2)'] , 'foo (3)']
765+
['foo', [], 'foo'],
766+
['foo', ['foo'], 'foo (2)'],
767+
['foo', ['foo', 'foo (2)'], 'foo (3)']
766768
];
767769
}
768770

@@ -782,7 +784,7 @@ public function testGetUniqueName($name, $existingFiles, $expected) {
782784
->method('file_exists')
783785
->will($this->returnCallback(function ($path) use ($existingFiles, $folderPath) {
784786
foreach ($existingFiles as $existing) {
785-
if ($folderPath . '/' . $existing === $path){
787+
if ($folderPath . '/' . $existing === $path) {
786788
return true;
787789
}
788790
}
@@ -792,4 +794,165 @@ public function testGetUniqueName($name, $existingFiles, $expected) {
792794
$node = new \OC\Files\Node\Folder($root, $view, $folderPath);
793795
$this->assertEquals($expected, $node->getNonExistingName($name));
794796
}
797+
798+
public function testRecent() {
799+
$manager = $this->getMock('\OC\Files\Mount\Manager');
800+
$folderPath = '/bar/foo';
801+
/**
802+
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
803+
*/
804+
$view = $this->getMock('\OC\Files\View');
805+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
806+
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
807+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
808+
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
809+
->disableOriginalConstructor()->getMock();
810+
811+
$baseTime = 1000;
812+
$storage = new Temporary();
813+
$mount = new MountPoint($storage, '');
814+
815+
$folderInfo->expects($this->any())
816+
->method('getMountPoint')
817+
->will($this->returnValue($mount));
818+
819+
$cache = $storage->getCache();
820+
821+
$id1 = $cache->put('bar/foo/inside.txt', [
822+
'storage_mtime' => $baseTime,
823+
'mtime' => $baseTime,
824+
'mimetype' => 'text/plain',
825+
'size' => 3
826+
]);
827+
$id2 = $cache->put('bar/foo/old.txt', [
828+
'storage_mtime' => $baseTime - 100,
829+
'mtime' => $baseTime - 100,
830+
'mimetype' => 'text/plain',
831+
'size' => 3
832+
]);
833+
$cache->put('bar/asd/outside.txt', [
834+
'storage_mtime' => $baseTime,
835+
'mtime' => $baseTime,
836+
'mimetype' => 'text/plain',
837+
'size' => 3
838+
]);
839+
$cache->put('bar/foo/toold.txt', [
840+
'storage_mtime' => $baseTime - 600,
841+
'mtime' => $baseTime - 600,
842+
'mimetype' => 'text/plain',
843+
'size' => 3
844+
]);
845+
846+
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
847+
848+
849+
$nodes = $node->getRecent($baseTime - 500);
850+
$ids = array_map(function (Node $node) {
851+
return (int)$node->getId();
852+
}, $nodes);
853+
$this->assertEquals([$id1, $id2], $ids);
854+
}
855+
856+
public function testRecentFolder() {
857+
$manager = $this->getMock('\OC\Files\Mount\Manager');
858+
$folderPath = '/bar/foo';
859+
/**
860+
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
861+
*/
862+
$view = $this->getMock('\OC\Files\View');
863+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
864+
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
865+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
866+
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
867+
->disableOriginalConstructor()->getMock();
868+
869+
$baseTime = 1000;
870+
$storage = new Temporary();
871+
$mount = new MountPoint($storage, '');
872+
873+
$folderInfo->expects($this->any())
874+
->method('getMountPoint')
875+
->will($this->returnValue($mount));
876+
877+
$cache = $storage->getCache();
878+
879+
$id1 = $cache->put('bar/foo/folder', [
880+
'storage_mtime' => $baseTime,
881+
'mtime' => $baseTime,
882+
'mimetype' => \OCP\Files\FileInfo::MIMETYPE_FOLDER,
883+
'size' => 3
884+
]);
885+
$id2 = $cache->put('bar/foo/folder/bar.txt', [
886+
'storage_mtime' => $baseTime,
887+
'mtime' => $baseTime,
888+
'mimetype' => 'text/plain',
889+
'size' => 3,
890+
'parent' => $id1
891+
]);
892+
$id3 = $cache->put('bar/foo/folder/asd.txt', [
893+
'storage_mtime' => $baseTime,
894+
'mtime' => $baseTime - 100,
895+
'mimetype' => 'text/plain',
896+
'size' => 3,
897+
'parent' => $id1
898+
]);
899+
900+
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
901+
902+
903+
$nodes = $node->getRecent($baseTime - 500);
904+
$ids = array_map(function (Node $node) {
905+
return (int)$node->getId();
906+
}, $nodes);
907+
$this->assertEquals([$id2, $id1, $id3], $ids);// sort folders before files with the same mtime, folders get the lowest child mtime
908+
}
909+
910+
public function testRecentJail() {
911+
$manager = $this->getMock('\OC\Files\Mount\Manager');
912+
$folderPath = '/bar/foo';
913+
/**
914+
* @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view
915+
*/
916+
$view = $this->getMock('\OC\Files\View');
917+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */
918+
$root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user));
919+
/** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */
920+
$folderInfo = $this->getMockBuilder('\OC\Files\FileInfo')
921+
->disableOriginalConstructor()->getMock();
922+
923+
$baseTime = 1000;
924+
$storage = new Temporary();
925+
$jail = new Jail([
926+
'storage' => $storage,
927+
'root' => 'folder'
928+
]);
929+
$mount = new MountPoint($jail, '/bar/foo');
930+
931+
$folderInfo->expects($this->any())
932+
->method('getMountPoint')
933+
->will($this->returnValue($mount));
934+
935+
$cache = $storage->getCache();
936+
937+
$id1 = $cache->put('folder/inside.txt', [
938+
'storage_mtime' => $baseTime,
939+
'mtime' => $baseTime,
940+
'mimetype' => 'text/plain',
941+
'size' => 3
942+
]);
943+
$cache->put('outside.txt', [
944+
'storage_mtime' => $baseTime - 100,
945+
'mtime' => $baseTime - 100,
946+
'mimetype' => 'text/plain',
947+
'size' => 3
948+
]);
949+
950+
$node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo);
951+
952+
$nodes = $node->getRecent($baseTime - 500);
953+
$ids = array_map(function (Node $node) {
954+
return (int)$node->getId();
955+
}, $nodes);
956+
$this->assertEquals([$id1], $ids);
957+
}
795958
}

0 commit comments

Comments
 (0)