diff options
-rw-r--r-- | lib/private/Files/Node/Folder.php | 113 | ||||
-rw-r--r-- | lib/private/Files/Node/LazyRoot.php | 7 | ||||
-rw-r--r-- | lib/public/Files/FileInfo.php | 5 | ||||
-rw-r--r-- | lib/public/Files/Folder.php | 7 | ||||
-rw-r--r-- | tests/lib/Files/Node/FolderTest.php | 171 |
5 files changed, 298 insertions, 5 deletions
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 8813b6c0775..7746757c2a5 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -26,7 +26,10 @@ namespace OC\Files\Node; +use OC\DB\QueryBuilder\Literal; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\FileInfo; +use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; @@ -358,4 +361,114 @@ class Folder extends Node implements \OCP\Files\Folder { $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); return trim($this->getRelativePath($uniqueName), '/'); } + + /** + * @param int $since + * @return \OCP\Files\Node[] + */ + public function getRecent($since) { + $mimetypeLoader = \OC::$server->getMimeTypeLoader(); + $mounts = $this->root->getMountsIn($this->path); + $mounts[] = $this->getMountPoint(); + + $mounts = array_filter($mounts, function (IMountPoint $mount) { + return $mount->getStorage(); + }); + $storageIds = array_map(function (IMountPoint $mount) { + return $mount->getStorage()->getCache()->getNumericStorageId(); + }, $mounts); + /** @var IMountPoint[] $mountMap */ + $mountMap = array_combine($storageIds, $mounts); + $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER); + + //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) + + $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query = $builder + ->select('f.*') + ->from('filecache', 'f') + ->where($builder->expr()->gt('f.storage_mtime', $builder->createNamedParameter($since, IQueryBuilder::PARAM_INT))) + ->andWhere($builder->expr()->in('f.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($builder->expr()->orX( + // handle non empty folders separate + $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)), + $builder->expr()->eq('f.size', new Literal(0)) + )) + ->orderBy('f.mtime', 'DESC'); + + $result = $query->execute()->fetchAll(); + + // select folders with their mtime being the mtime of the oldest file in the folder + // this way we still show new folders but dont bumb the folder every time a file in it is changed + $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $query = $builder + ->select('p.fileid', 'p.storage', 'p.mimetype', 'p.mimepart', 'p.size', 'p.path', 'p.etag', 'f1.storage_mtime', 'f1.mtime', 'p.permissions') + ->from('filecache', 'f1') + ->leftJoin('f1', 'filecache', 'f2', $builder->expr()->andX( // find the f1 with lowest mtime in the folder + $builder->expr()->eq('f1.parent', 'f2.parent'), + $builder->expr()->gt('f1.storage_mtime', 'f2.storage_mtime') + )) + ->innerJoin('f1', 'filecache', 'p', $builder->expr()->eq('f1.parent', 'p.fileid')) + ->where($builder->expr()->isNull('f2.fileid')) + ->andWhere($builder->expr()->gt('f1.storage_mtime', $builder->createNamedParameter($since, IQueryBuilder::PARAM_INT))) + ->andWhere($builder->expr()->in('f1.storage', $builder->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($builder->expr()->neq('f1.size', new Literal(0))) + ->orderBy('f1.storage_mtime', 'DESC'); + + $folderResults = $query->execute()->fetchAll(); + + $found = []; // we sometimes get duplicate folders + $folderResults = array_filter($folderResults, function ($item) use (&$found) { + $isFound = isset($found[$item['fileid']]); + $found[$item['fileid']] = true; + return !$isFound; + }); + + $result = array_merge($folderResults, $result); + + usort($result, function ($a, $b) use ($folderMimetype) { + $diff = $b['mtime'] - $a['mtime']; + if ($diff === 0) { + return $a['mimetype'] === $folderMimetype ? -1 : 1; + } else { + return $diff; + } + }); + + $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) { + $mount = $mountMap[$entry['storage']]; + $entry['internalPath'] = $entry['path']; + $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']); + $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']); + $path = $this->getAbsolutePath($mount, $entry['path']); + if (is_null($path)) { + return null; + } + $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount); + return $this->root->createNode($fileInfo->getPath(), $fileInfo); + }, $result)); + + return array_values(array_filter($files, function (Node $node) { + $relative = $this->getRelativePath($node->getPath()); + return $relative !== null && $relative !== '/'; + })); + } + + private function getAbsolutePath(IMountPoint $mount, $path) { + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) { + /** @var \OC\Files\Storage\Wrapper\Jail $storage */ + $jailRoot = $storage->getSourcePath(''); + $rootLength = strlen($jailRoot) + 1; + if ($path === $jailRoot) { + return $mount->getMountPoint(); + } else if (substr($path, 0, $rootLength) === $jailRoot . '/') { + return $mount->getMountPoint() . substr($path, $rootLength); + } else { + return null; + } + } else { + return $mount->getMountPoint() . $path; + } + } } diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index 1203fc4d162..adc41153313 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -471,5 +471,10 @@ class LazyRoot implements IRootFolder { return $this->__call(__FUNCTION__, func_get_args()); } - + /** + * @inheritDoc + */ + public function getRecent($type) { + return $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/public/Files/FileInfo.php b/lib/public/Files/FileInfo.php index d61726c4217..04790d41556 100644 --- a/lib/public/Files/FileInfo.php +++ b/lib/public/Files/FileInfo.php @@ -59,6 +59,11 @@ interface FileInfo { const SPACE_UNLIMITED = -3; /** + * @since 9.1.0 + */ + const MIMETYPE_FOLDER = 'httpd/unix-directory'; + + /** * Get the Etag of the file or folder * * @return string diff --git a/lib/public/Files/Folder.php b/lib/public/Files/Folder.php index 9a2a338b559..b6686732f42 100644 --- a/lib/public/Files/Folder.php +++ b/lib/public/Files/Folder.php @@ -175,4 +175,11 @@ interface Folder extends Node { * @since 8.1.0 */ public function getNonExistingName($name); + + /** + * @param int $since + * @return \OCP\Files\Node[] + * @since 9.1.0 + */ + public function getRecent($since); } diff --git a/tests/lib/Files/Node/FolderTest.php b/tests/lib/Files/Node/FolderTest.php index 7ce9fff1419..cae6b4a80c0 100644 --- a/tests/lib/Files/Node/FolderTest.php +++ b/tests/lib/Files/Node/FolderTest.php @@ -12,6 +12,8 @@ use OC\Files\Cache\Cache; use OC\Files\FileInfo; use OC\Files\Mount\MountPoint; use OC\Files\Node\Node; +use OC\Files\Storage\Temporary; +use OC\Files\Storage\Wrapper\Jail; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OC\Files\View; @@ -760,9 +762,9 @@ class FolderTest extends \Test\TestCase { public function uniqueNameProvider() { return [ // input, existing, expected - ['foo', [] , 'foo'], - ['foo', ['foo'] , 'foo (2)'], - ['foo', ['foo', 'foo (2)'] , 'foo (3)'] + ['foo', [], 'foo'], + ['foo', ['foo'], 'foo (2)'], + ['foo', ['foo', 'foo (2)'], 'foo (3)'] ]; } @@ -782,7 +784,7 @@ class FolderTest extends \Test\TestCase { ->method('file_exists') ->will($this->returnCallback(function ($path) use ($existingFiles, $folderPath) { foreach ($existingFiles as $existing) { - if ($folderPath . '/' . $existing === $path){ + if ($folderPath . '/' . $existing === $path) { return true; } } @@ -792,4 +794,165 @@ class FolderTest extends \Test\TestCase { $node = new \OC\Files\Node\Folder($root, $view, $folderPath); $this->assertEquals($expected, $node->getNonExistingName($name)); } + + public function testRecent() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + $folderPath = '/bar/foo'; + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */ + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */ + $folderInfo = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor()->getMock(); + + $baseTime = 1000; + $storage = new Temporary(); + $mount = new MountPoint($storage, ''); + + $folderInfo->expects($this->any()) + ->method('getMountPoint') + ->will($this->returnValue($mount)); + + $cache = $storage->getCache(); + + $id1 = $cache->put('bar/foo/inside.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + $id2 = $cache->put('bar/foo/old.txt', [ + 'storage_mtime' => $baseTime - 100, + 'mtime' => $baseTime - 100, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + $cache->put('bar/asd/outside.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + $cache->put('bar/foo/toold.txt', [ + 'storage_mtime' => $baseTime - 600, + 'mtime' => $baseTime - 600, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + + $node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo); + + + $nodes = $node->getRecent($baseTime - 500); + $ids = array_map(function (Node $node) { + return (int)$node->getId(); + }, $nodes); + $this->assertEquals([$id1, $id2], $ids); + } + + public function testRecentFolder() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + $folderPath = '/bar/foo'; + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */ + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */ + $folderInfo = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor()->getMock(); + + $baseTime = 1000; + $storage = new Temporary(); + $mount = new MountPoint($storage, ''); + + $folderInfo->expects($this->any()) + ->method('getMountPoint') + ->will($this->returnValue($mount)); + + $cache = $storage->getCache(); + + $id1 = $cache->put('bar/foo/folder', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime, + 'mimetype' => \OCP\Files\FileInfo::MIMETYPE_FOLDER, + 'size' => 3 + ]); + $id2 = $cache->put('bar/foo/folder/bar.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime, + 'mimetype' => 'text/plain', + 'size' => 3, + 'parent' => $id1 + ]); + $id3 = $cache->put('bar/foo/folder/asd.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime - 100, + 'mimetype' => 'text/plain', + 'size' => 3, + 'parent' => $id1 + ]); + + $node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo); + + + $nodes = $node->getRecent($baseTime - 500); + $ids = array_map(function (Node $node) { + return (int)$node->getId(); + }, $nodes); + $this->assertEquals([$id2, $id1, $id3], $ids);// sort folders before files with the same mtime, folders get the lowest child mtime + } + + public function testRecentJail() { + $manager = $this->getMock('\OC\Files\Mount\Manager'); + $folderPath = '/bar/foo'; + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject $view + */ + $view = $this->getMock('\OC\Files\View'); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\Node\Root $root */ + $root = $this->getMock('\OC\Files\Node\Root', array('getUser', 'getMountsIn', 'getMount'), array($manager, $view, $this->user)); + /** @var \PHPUnit_Framework_MockObject_MockObject|\OC\Files\FileInfo $folderInfo */ + $folderInfo = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor()->getMock(); + + $baseTime = 1000; + $storage = new Temporary(); + $jail = new Jail([ + 'storage' => $storage, + 'root' => 'folder' + ]); + $mount = new MountPoint($jail, '/bar/foo'); + + $folderInfo->expects($this->any()) + ->method('getMountPoint') + ->will($this->returnValue($mount)); + + $cache = $storage->getCache(); + + $id1 = $cache->put('folder/inside.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + $cache->put('outside.txt', [ + 'storage_mtime' => $baseTime - 100, + 'mtime' => $baseTime - 100, + 'mimetype' => 'text/plain', + 'size' => 3 + ]); + + $node = new \OC\Files\Node\Folder($root, $view, $folderPath, $folderInfo); + + $nodes = $node->getRecent($baseTime - 500); + $ids = array_map(function (Node $node) { + return (int)$node->getId(); + }, $nodes); + $this->assertEquals([$id1], $ids); + } } |