aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/private/Files/Node/Folder.php113
-rw-r--r--lib/private/Files/Node/LazyRoot.php7
-rw-r--r--lib/public/Files/FileInfo.php5
-rw-r--r--lib/public/Files/Folder.php7
-rw-r--r--tests/lib/Files/Node/FolderTest.php171
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);
+ }
}