mirror of
https://github.com/nextcloud/server.git
synced 2026-06-20 14:09:38 -04:00
Merge pull request #59212 from nextcloud/backport/58761/stable29
Some checks failed
Integration sqlite / changes (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, --tags ~@large files_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, capabilities_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, collaboration_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, comments_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, dav_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, federation_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, filesdrop_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, ldap_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, openldap_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, openldap_numerical_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, remoteapi_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, setup_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, sharees_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, sharing_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, videoverification_features) (push) Has been cancelled
Integration sqlite / integration-sqlite-summary (push) Has been cancelled
Some checks failed
Integration sqlite / changes (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, --tags ~@large files_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, capabilities_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, collaboration_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, comments_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, dav_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, federation_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, filesdrop_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, ldap_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, openldap_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, openldap_numerical_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, remoteapi_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, setup_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, sharees_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, sharing_features) (push) Has been cancelled
Integration sqlite / integration-sqlite (8.2, stable29, videoverification_features) (push) Has been cancelled
Integration sqlite / integration-sqlite-summary (push) Has been cancelled
[stable29] feat(recent-files): add recent_files_limit config on files settings
This commit is contained in:
commit
cc73bd2db1
19 changed files with 77 additions and 8 deletions
2
.github/workflows/files-external-s3.yml
vendored
2
.github/workflows/files-external-s3.yml
vendored
|
|
@ -127,7 +127,7 @@ jobs:
|
|||
env:
|
||||
SERVICES: s3
|
||||
DEBUG: 1
|
||||
image: localstack/localstack
|
||||
image: localstack/localstack@sha256:9d4253786e0effe974d77fe3c390358391a56090a4fff83b4600d8a64404d95d # v4.5.0
|
||||
ports:
|
||||
- "4566:4566"
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Capabilities implements ICapability {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array{dav: array{chunking: string, search_supports_creation_time: bool, search_supports_upload_time: bool, bulkupload?: string, absence-supported?: bool}}
|
||||
* @return array{dav: array{chunking: string, search_supports_creation_time: bool, search_supports_upload_time: bool, search_supports_last_activity: bool, bulkupload?: string, absence-supported?: bool}}
|
||||
*/
|
||||
public function getCapabilities() {
|
||||
$capabilities = [
|
||||
|
|
@ -46,6 +46,7 @@ class Capabilities implements ICapability {
|
|||
'chunking' => '1.0',
|
||||
'search_supports_creation_time' => true,
|
||||
'search_supports_upload_time' => true,
|
||||
'search_supports_last_activity' => true,
|
||||
]
|
||||
];
|
||||
if ($this->config->getSystemValueBool('bulkupload.enabled', true)) {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ class FilesPlugin extends ServerPlugin {
|
|||
public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
|
||||
public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
|
||||
public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
|
||||
public const LAST_ACTIVITY_PROPERTYNAME = '{http://nextcloud.org/ns}last_activity';
|
||||
public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
|
||||
public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
|
||||
public const SUBFILE_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-file-count';
|
||||
|
|
@ -401,6 +402,10 @@ class FilesPlugin extends ServerPlugin {
|
|||
return $node->getFileInfo()->getCreationTime();
|
||||
});
|
||||
|
||||
$propFind->handle(self::LAST_ACTIVITY_PROPERTYNAME, function () use ($node) {
|
||||
return $node->getFileInfo()->getLastActivity();
|
||||
});
|
||||
|
||||
foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
|
||||
$propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ class FileSearchBackend implements ISearchBackend {
|
|||
new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{DAV:}creationdate', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{http://nextcloud.org/ns}upload_time', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{http://nextcloud.org/ns}last_activity', true, false, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
|
||||
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
|
|
@ -323,6 +324,8 @@ class FileSearchBackend implements ISearchBackend {
|
|||
return $node->getNode()->getCreationTime();
|
||||
case '{http://nextcloud.org/ns}upload_time':
|
||||
return $node->getNode()->getUploadTime();
|
||||
case '{http://nextcloud.org/ns}last_activity':
|
||||
return $node->getNode()->getLastActivity();
|
||||
case FilesPlugin::SIZE_PROPERTYNAME:
|
||||
return $node->getSize();
|
||||
case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
|
||||
|
|
@ -351,6 +354,8 @@ class FileSearchBackend implements ISearchBackend {
|
|||
$direction = $order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING;
|
||||
if (str_starts_with($order->property->name, FilesPlugin::FILE_METADATA_PREFIX)) {
|
||||
return new SearchOrder($direction, substr($order->property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX)), IMetadataQuery::EXTRA);
|
||||
} elseif ($order->property->name === FilesPlugin::LAST_ACTIVITY_PROPERTYNAME) {
|
||||
return new SearchOrder($direction, 'last_activity');
|
||||
} else {
|
||||
return new SearchOrder($direction, $this->mapPropertyNameToColumn($order->property));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@
|
|||
"required": [
|
||||
"chunking",
|
||||
"search_supports_creation_time",
|
||||
"search_supports_upload_time"
|
||||
"search_supports_upload_time",
|
||||
"search_supports_last_activity"
|
||||
],
|
||||
"properties": {
|
||||
"chunking": {
|
||||
|
|
@ -43,6 +44,9 @@
|
|||
"search_supports_upload_time": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search_supports_last_activity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bulkupload": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class CapabilitiesTest extends TestCase {
|
|||
'chunking' => '1.0',
|
||||
'search_supports_creation_time' => true,
|
||||
'search_supports_upload_time' => true,
|
||||
'search_supports_last_activity' => true,
|
||||
],
|
||||
];
|
||||
$this->assertSame($expected, $capabilities->getCapabilities());
|
||||
|
|
@ -69,6 +70,7 @@ class CapabilitiesTest extends TestCase {
|
|||
'chunking' => '1.0',
|
||||
'search_supports_creation_time' => true,
|
||||
'search_supports_upload_time' => true,
|
||||
'search_supports_last_activity' => true,
|
||||
'bulkupload' => '1.0',
|
||||
],
|
||||
];
|
||||
|
|
@ -91,6 +93,7 @@ class CapabilitiesTest extends TestCase {
|
|||
'chunking' => '1.0',
|
||||
'search_supports_creation_time' => true,
|
||||
'search_supports_upload_time' => true,
|
||||
'search_supports_last_activity' => true,
|
||||
'absence-supported' => true,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ use OCP\AppFramework\Http\ContentSecurityPolicy;
|
|||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IAppConfig;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
|
||||
use OCP\Constants;
|
||||
|
|
@ -77,6 +78,7 @@ class ViewController extends Controller {
|
|||
private ITemplateManager $templateManager;
|
||||
private UserConfig $userConfig;
|
||||
private ViewConfig $viewConfig;
|
||||
private IAppConfig $appConfig;
|
||||
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
|
|
@ -90,6 +92,7 @@ class ViewController extends Controller {
|
|||
ITemplateManager $templateManager,
|
||||
UserConfig $userConfig,
|
||||
ViewConfig $viewConfig,
|
||||
IAppConfig $appConfig,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
|
|
@ -102,6 +105,7 @@ class ViewController extends Controller {
|
|||
$this->templateManager = $templateManager;
|
||||
$this->userConfig = $userConfig;
|
||||
$this->viewConfig = $viewConfig;
|
||||
$this->appConfig = $appConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,6 +220,7 @@ class ViewController extends Controller {
|
|||
$this->initialState->provideInitialState('storageStats', $storageInfo);
|
||||
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
|
||||
$this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs());
|
||||
$this->initialState->provideInitialState('recent_limit', $this->appConfig->getAppValueInt('recent_limit', 100));
|
||||
|
||||
// File sorting user config
|
||||
$filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import type { FileStat, ResponseDataDetailed, SearchResult } from 'webdav'
|
|||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Folder, Permission, davGetRecentSearch, davGetClient, davResultToNode, davRootPath, davRemoteURL } from '@nextcloud/files'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { getBaseUrl } from '@nextcloud/router'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { useUserConfigStore } from '../store/userconfig.ts'
|
||||
|
|
@ -33,6 +34,7 @@ import { pinia } from '../store/index.ts'
|
|||
const client = davGetClient()
|
||||
|
||||
const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 14))
|
||||
const recentLimit = loadState<number>('files', 'recent_limit', 100)
|
||||
|
||||
/**
|
||||
* Helper to map a WebDAV result to a Nextcloud node
|
||||
|
|
@ -70,7 +72,7 @@ export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
|||
try {
|
||||
contentsResponse = await client.search('/', {
|
||||
details: true,
|
||||
data: davGetRecentSearch(lastTwoWeeksTimestamp),
|
||||
data: davGetRecentSearch(lastTwoWeeksTimestamp, recentLimit),
|
||||
signal: abort.signal,
|
||||
}) as ResponseDataDetailed<SearchResult>
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ use OCP\App\IAppManager;
|
|||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IAppConfig;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
|
|
@ -82,6 +83,8 @@ class ViewControllerTest extends TestCase {
|
|||
private $userSession;
|
||||
/** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $appManager;
|
||||
/** @var IAppConfig|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $appConfig;
|
||||
/** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $rootFolder;
|
||||
/** @var IInitialState|\PHPUnit\Framework\MockObject\MockObject */
|
||||
|
|
@ -108,6 +111,7 @@ class ViewControllerTest extends TestCase {
|
|||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->appManager = $this->createMock(IAppManager::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->initialState = $this->createMock(IInitialState::class);
|
||||
|
|
@ -166,6 +170,7 @@ class ViewControllerTest extends TestCase {
|
|||
$this->templateManager,
|
||||
$this->userConfig,
|
||||
$this->viewConfig,
|
||||
$this->appConfig,
|
||||
])
|
||||
->setMethods([
|
||||
'getStorageInfo',
|
||||
|
|
|
|||
|
|
@ -188,6 +188,10 @@ class TrashItem implements ITrashItem {
|
|||
return $this->fileInfo->getUploadTime();
|
||||
}
|
||||
|
||||
public function getLastActivity(): int {
|
||||
return $this->fileInfo->getLastActivity();
|
||||
}
|
||||
|
||||
public function getParentId(): int {
|
||||
return $this->fileInfo->getParentId();
|
||||
}
|
||||
|
|
|
|||
4
dist/files-init.js
vendored
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -175,7 +175,11 @@ class QuerySearchHelper {
|
|||
|
||||
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
|
||||
|
||||
$joinExtendedCache = in_array('creation_time', $requestedFields) || in_array('upload_time', $requestedFields);
|
||||
$orderFields = array_map(fn ($order) => $order->getField(), $searchQuery->getOrder());
|
||||
|
||||
$joinExtendedCache = in_array('creation_time', $requestedFields)
|
||||
|| in_array('upload_time', $requestedFields)
|
||||
|| in_array('last_activity', $orderFields);
|
||||
|
||||
$query = $builder->selectFileCache('file', $joinExtendedCache);
|
||||
|
||||
|
|
|
|||
|
|
@ -372,6 +372,10 @@ class SearchBuilder {
|
|||
if ($field === 'mtime') {
|
||||
$field = $query->func()->add($field, $query->createNamedParameter(0));
|
||||
}
|
||||
|
||||
if ($field === 'last_activity') {
|
||||
$field = $query->func()->greatest('file.mtime', $query->createFunction('COALESCE(fe.upload_time, 0)'));
|
||||
}
|
||||
}
|
||||
$query->addOrderBy($field, $order->getDirection());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -412,6 +412,10 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
|
|||
return (int) $this->data['upload_time'];
|
||||
}
|
||||
|
||||
public function getLastActivity(): int {
|
||||
return max($this->getUploadTime(), $this->getMTime());
|
||||
}
|
||||
|
||||
public function getParentId(): int {
|
||||
return $this->data['parent'] ?? -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -569,6 +569,13 @@ class LazyFolder implements Folder {
|
|||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLastActivity(): int {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
public function getRelativePath($path) {
|
||||
return PathHelper::getRelativePath($this->getPath(), $path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,6 +495,10 @@ class Node implements INode {
|
|||
return $this->getFileInfo()->getUploadTime();
|
||||
}
|
||||
|
||||
public function getLastActivity(): int {
|
||||
return $this->getFileInfo()->getLastActivity();
|
||||
}
|
||||
|
||||
public function getParentId(): int {
|
||||
return $this->fileInfo->getParentId();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ class SearchOrder implements ISearchOrder {
|
|||
return $a->getId() <=> $b->getId();
|
||||
case 'permissions':
|
||||
return $a->getPermissions() <=> $b->getPermissions();
|
||||
case 'last_activity':
|
||||
return $a->getLastActivity() <=> $b->getLastActivity();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,6 +301,16 @@ interface FileInfo {
|
|||
*/
|
||||
public function getUploadTime(): int;
|
||||
|
||||
/**
|
||||
* Get the last activity date as unix timestamp
|
||||
*
|
||||
* Last activity is the more recent of the upload time and the modification time
|
||||
*
|
||||
* @return int
|
||||
* @since 29.0.16
|
||||
*/
|
||||
public function getLastActivity(): int;
|
||||
|
||||
/**
|
||||
* Get the fileid or the parent folder
|
||||
* or -1 if this item has no parent folder (because it is the root)
|
||||
|
|
|
|||
Loading…
Reference in a new issue