diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php index 43c82035c7172..9a32f5cb93467 100644 --- a/lib/private/Files/Cache/SearchBuilder.php +++ b/lib/private/Files/Cache/SearchBuilder.php @@ -366,7 +366,7 @@ public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders, ?IMe } if ($field === 'last_activity') { - $field = $query->func()->greatest('file.mtime', $query->createFunction('COALESCE(fe.upload_time, 0)')); + $field = $query->func()->greatest('file.mtime', $query->func()->coalesce('fe.upload_time', $query->createFunction('0'))); } } $query->addOrderBy($field, $order->getDirection()); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 7a58c41f60d1f..764eafae6e327 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -501,10 +501,15 @@ public function getRecent($limit, $offset = 0) { ] ); - $filterNonRecentFiles = new SearchComparison( - ISearchComparison::COMPARE_GREATER_THAN, - 'mtime', - strtotime('-2 week') + // Uploads keep the mtime of the file they come from, so look at when + // they were uploaded too, like the Files app's recent view does + $since = strtotime('-2 week'); + $filterNonRecentFiles = new SearchBinaryOperator( + ISearchBinaryOperator::OPERATOR_OR, + [ + new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'mtime', $since), + new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'upload_time', $since), + ] ); if ($offset === 0 && $limit <= 100) { $query = new SearchQuery( @@ -520,7 +525,7 @@ public function getRecent($limit, $offset = 0) { [ new SearchOrder( ISearchOrder::DIRECTION_DESCENDING, - 'mtime' + 'last_activity' ), ] ); @@ -532,7 +537,7 @@ public function getRecent($limit, $offset = 0) { [ new SearchOrder( ISearchOrder::DIRECTION_DESCENDING, - 'mtime' + 'last_activity' ), ] ); diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php index 57b8229b6d671..28535654b5749 100644 --- a/tests/lib/Files/Cache/CacheTest.php +++ b/tests/lib/Files/Cache/CacheTest.php @@ -14,6 +14,7 @@ use OC\Files\Cache\QuerySearchHelper; use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; use OC\Files\Storage\Temporary; use OC\SystemConfig; @@ -25,6 +26,7 @@ use OCP\Files\Cache\ICacheEntry; use OCP\Files\IMimeTypeLoader; use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOrder; use OCP\Files\Storage\IStorage; use OCP\FilesMetadata\IFilesMetadataManager; use OCP\IDBConnection; @@ -633,6 +635,24 @@ public function testSearchByQuery(): void { new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', 100), 10, 0, [], $user))); } + public function testSearchOrderedByLastActivity(): void { + $this->cache->put('folder', ['size' => 0, 'mtime' => 10, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + $this->cache->put('folder/edited', ['size' => 1, 'mtime' => 300, 'mimetype' => 'foo/file']); + // Uploaded last, keeping an older mtime from the uploader's device + $this->cache->put('folder/uploaded', ['size' => 1, 'mtime' => 100, 'upload_time' => 400, 'mimetype' => 'foo/file']); + $this->cache->put('folder/old', ['size' => 1, 'mtime' => 200, 'mimetype' => 'foo/file']); + + $results = $this->cache->searchQuery(new SearchQuery( + new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', 'foo/file'), + 10, + 0, + [new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'last_activity')], + $this->createMock(IUser::class), + )); + + $this->assertSame(['uploaded', 'edited', 'old'], array_map(fn (ICacheEntry $entry): string => $entry->getName(), $results)); + } + public static function movePathProvider(): array { return [ ['folder/foo', 'folder/foobar', ['1', '2']], diff --git a/tests/lib/Files/Node/FolderTest.php b/tests/lib/Files/Node/FolderTest.php index 55632711b1883..5a6881b31430b 100644 --- a/tests/lib/Files/Node/FolderTest.php +++ b/tests/lib/Files/Node/FolderTest.php @@ -820,6 +820,59 @@ public function testRecent(): void { $this->assertEquals([$id1, $id2, $id3], $ids); } + public function testRecentIncludesUploads(): void { + $manager = $this->createMock(Manager::class); + $view = $this->getRootViewMock(); + /** @var \PHPUnit\Framework\MockObject\MockObject|Root $root */ + $root = $this->getMockBuilder(Root::class) + ->onlyMethods(['getUser', 'getMountsIn', 'getMount']) + ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory, $this->appConfig]) + ->getMock(); + /** @var \PHPUnit\Framework\MockObject\MockObject|FileInfo $folderInfo */ + $folderInfo = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor()->getMock(); + + $baseTime = time(); + $storage = new Temporary(); + $mount = new MountPoint($storage, ''); + + $folderInfo->method('getMountPoint')->willReturn($mount); + $root->method('getMount')->willReturn($mount); + $root->method('getMountsIn')->willReturn([]); + + $cache = $storage->getCache(); + $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + $cache->insert('bar', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]); + $edited = $cache->put('bar/edited.txt', [ + 'storage_mtime' => $baseTime - 100, + 'mtime' => $baseTime - 100, + 'mimetype' => 'text/plain', + 'size' => 3, + 'permissions' => Constants::PERMISSION_ALL, + ]); + // Uploaded just now, keeping the mtime it had on the uploader's device + $uploaded = $cache->put('bar/uploaded.txt', [ + 'storage_mtime' => $baseTime, + 'mtime' => $baseTime - 30 * 24 * 60 * 60, + 'upload_time' => $baseTime, + 'mimetype' => 'text/plain', + 'size' => 3, + 'permissions' => Constants::PERMISSION_ALL, + ]); + $cache->put('bar/old.txt', [ + 'storage_mtime' => $baseTime - 30 * 24 * 60 * 60, + 'mtime' => $baseTime - 30 * 24 * 60 * 60, + 'mimetype' => 'text/plain', + 'size' => 3, + 'permissions' => Constants::PERMISSION_ALL, + ]); + + $node = new Folder($root, $view, '/bar', $folderInfo); + + $ids = array_map(fn (Node $node): int => (int)$node->getId(), $node->getRecent(5)); + $this->assertEquals([$uploaded, $edited], $ids); + } + public function testRecentFolder(): void { $manager = $this->createMock(Manager::class); $folderPath = '/bar/foo';