aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Activity/Event.php1
-rw-r--r--lib/private/Activity/Manager.php39
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php13
-rw-r--r--lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php121
-rw-r--r--lib/private/Files/View.php44
-rw-r--r--lib/private/Search/SearchComposer.php21
-rw-r--r--lib/private/Server.php3
-rw-r--r--lib/private/Share20/Manager.php2
-rw-r--r--lib/private/TaskProcessing/Db/Task.php10
-rw-r--r--lib/private/TaskProcessing/Db/TaskMapper.php25
-rw-r--r--lib/private/TaskProcessing/Manager.php94
-rw-r--r--lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php44
-rw-r--r--lib/private/Template/JSConfigHelper.php6
-rw-r--r--lib/private/TemplateLayout.php3
15 files changed, 327 insertions, 101 deletions
diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php
index 39cdc12b3fb..0ccad1d0a4e 100644
--- a/lib/private/Activity/Event.php
+++ b/lib/private/Activity/Event.php
@@ -450,7 +450,6 @@ class Event implements IEvent {
return
$this->getApp() !== ''
&& $this->getType() !== ''
- && $this->getAffectedUser() !== ''
&& $this->getTimestamp() !== 0
/**
* Disabled for BC with old activities
diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php
index 4e10f8a0c1a..7471b7d708a 100644
--- a/lib/private/Activity/Manager.php
+++ b/lib/private/Activity/Manager.php
@@ -11,12 +11,14 @@ use OCP\Activity\ActivitySettings;
use OCP\Activity\Exceptions\FilterNotFoundException;
use OCP\Activity\Exceptions\IncompleteActivityException;
use OCP\Activity\Exceptions\SettingNotFoundException;
+use OCP\Activity\IBulkConsumer;
use OCP\Activity\IConsumer;
use OCP\Activity\IEvent;
use OCP\Activity\IFilter;
use OCP\Activity\IManager;
use OCP\Activity\IProvider;
use OCP\Activity\ISetting;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
@@ -46,6 +48,7 @@ class Manager implements IManager {
protected IValidator $validator,
protected IRichTextFormatter $richTextFormatter,
protected IL10N $l10n,
+ protected ITimeFactory $timeFactory,
) {
}
@@ -96,6 +99,31 @@ class Manager implements IManager {
* {@inheritDoc}
*/
public function publish(IEvent $event): void {
+ if ($event->getAuthor() === '' && $this->session->getUser() instanceof IUser) {
+ $event->setAuthor($this->session->getUser()->getUID());
+ }
+
+ if (!$event->getTimestamp()) {
+ $event->setTimestamp($this->timeFactory->getTime());
+ }
+
+ if ($event->getAffectedUser() === '' || !$event->isValid()) {
+ throw new IncompleteActivityException('The given event is invalid');
+ }
+
+ foreach ($this->getConsumers() as $c) {
+ $c->receive($event);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void {
+ if (empty($affectedUserIds)) {
+ throw new IncompleteActivityException('The given event is invalid');
+ }
+
if ($event->getAuthor() === '') {
if ($this->session->getUser() instanceof IUser) {
$event->setAuthor($this->session->getUser()->getUID());
@@ -103,7 +131,7 @@ class Manager implements IManager {
}
if (!$event->getTimestamp()) {
- $event->setTimestamp(time());
+ $event->setTimestamp($this->timeFactory->getTime());
}
if (!$event->isValid()) {
@@ -111,10 +139,17 @@ class Manager implements IManager {
}
foreach ($this->getConsumers() as $c) {
- $c->receive($event);
+ if ($c instanceof IBulkConsumer) {
+ $c->bulkReceive($event, $affectedUserIds, $setting);
+ }
+ foreach ($affectedUserIds as $affectedUserId) {
+ $event->setAffectedUser($affectedUserId);
+ $c->receive($event);
+ }
}
}
+
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* activity consumers are actually requested
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index d82ec08f362..76afca9dee8 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -415,7 +415,7 @@ class Root extends Folder implements IRootFolder {
*/
public function getByIdInPath(int $id, string $path): array {
$mountCache = $this->getUserMountCache();
- if (strpos($path, '/', 1) > 0) {
+ if ($path !== '' && strpos($path, '/', 1) > 0) {
[, $user] = explode('/', $path);
} else {
$user = null;
diff --git a/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
new file mode 100644
index 00000000000..369182b069d
--- /dev/null
+++ b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\ObjectStore;
+
+class InvalidObjectStoreConfigurationException extends \Exception {
+
+}
diff --git a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
index fdfe989addc..ffc33687340 100644
--- a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
+++ b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
@@ -34,9 +34,13 @@ class PrimaryObjectStoreConfig {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForRoot(): ?array {
- $config = $this->getObjectStoreConfig();
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
+
+ $config = $this->getObjectStoreConfiguration('root');
- if ($config && $config['arguments']['multibucket']) {
+ if ($config['arguments']['multibucket']) {
if (!isset($config['arguments']['bucket'])) {
$config['arguments']['bucket'] = '';
}
@@ -51,38 +55,102 @@ class PrimaryObjectStoreConfig {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForUser(IUser $user): ?array {
- $config = $this->getObjectStoreConfig();
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
- if ($config && $config['arguments']['multibucket']) {
+ $store = $this->getObjectStoreForUser($user);
+ $config = $this->getObjectStoreConfiguration($store);
+
+ if ($config['arguments']['multibucket']) {
$config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
}
return $config;
}
/**
- * @return ?ObjectStoreConfig
+ * @param string $name
+ * @return ObjectStoreConfig
*/
- private function getObjectStoreConfig(): ?array {
+ public function getObjectStoreConfiguration(string $name): array {
+ $configs = $this->getObjectStoreConfigs();
+ $name = $this->resolveAlias($name);
+ if (!isset($configs[$name])) {
+ throw new \Exception("Object store configuration for '$name' not found");
+ }
+ if (is_string($configs[$name])) {
+ throw new \Exception("Object store configuration for '{$configs[$name]}' not found");
+ }
+ return $configs[$name];
+ }
+
+ public function resolveAlias(string $name): string {
+ $configs = $this->getObjectStoreConfigs();
+
+ while (isset($configs[$name]) && is_string($configs[$name])) {
+ $name = $configs[$name];
+ }
+ return $name;
+ }
+
+ public function hasObjectStore(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', null);
+ $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
+ return $objectStore || $objectStoreMultiBucket;
+ }
+
+ public function hasMultipleObjectStorages(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', []);
+ return isset($objectStore['default']);
+ }
+
+ /**
+ * @return ?array<string, ObjectStoreConfig|string>
+ * @throws InvalidObjectStoreConfigurationException
+ */
+ public function getObjectStoreConfigs(): ?array {
$objectStore = $this->config->getSystemValue('objectstore', null);
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
// new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
if ($objectStoreMultiBucket) {
$objectStoreMultiBucket['arguments']['multibucket'] = true;
- return $this->validateObjectStoreConfig($objectStoreMultiBucket);
+ return [
+ 'default' => 'server1',
+ 'server1' => $this->validateObjectStoreConfig($objectStoreMultiBucket),
+ 'root' => 'server1',
+ ];
} elseif ($objectStore) {
- return $this->validateObjectStoreConfig($objectStore);
+ if (!isset($objectStore['default'])) {
+ $objectStore = [
+ 'default' => 'server1',
+ 'root' => 'server1',
+ 'server1' => $objectStore,
+ ];
+ }
+ if (!isset($objectStore['root'])) {
+ $objectStore['root'] = 'default';
+ }
+
+ if (!is_string($objectStore['default'])) {
+ throw new InvalidObjectStoreConfigurationException('The \'default\' object storage configuration is required to be a reference to another configuration.');
+ }
+ return array_map($this->validateObjectStoreConfig(...), $objectStore);
} else {
return null;
}
}
/**
- * @return ObjectStoreConfig
+ * @param array|string $config
+ * @return string|ObjectStoreConfig
*/
- private function validateObjectStoreConfig(array $config) {
+ private function validateObjectStoreConfig(array|string $config): array|string {
+ if (is_string($config)) {
+ return $config;
+ }
if (!isset($config['class'])) {
- throw new \Exception('No class configured for object store');
+ throw new InvalidObjectStoreConfigurationException('No class configured for object store');
}
if (!isset($config['arguments'])) {
$config['arguments'] = [];
@@ -90,17 +158,17 @@ class PrimaryObjectStoreConfig {
$class = $config['class'];
$arguments = $config['arguments'];
if (!is_array($arguments)) {
- throw new \Exception('Configured object store arguments are not an array');
+ throw new InvalidObjectStoreConfigurationException('Configured object store arguments are not an array');
}
if (!isset($arguments['multibucket'])) {
$arguments['multibucket'] = false;
}
if (!is_bool($arguments['multibucket'])) {
- throw new \Exception('arguments.multibucket must be a boolean in object store configuration');
+ throw new InvalidObjectStoreConfigurationException('arguments.multibucket must be a boolean in object store configuration');
}
if (!is_string($class)) {
- throw new \Exception('Configured class for object store is not a string');
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not a string');
}
if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
@@ -109,7 +177,7 @@ class PrimaryObjectStoreConfig {
}
if (!is_a($class, IObjectStore::class, true)) {
- throw new \Exception('Configured class for object store is not an object store');
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not an object store');
}
return [
'class' => $class,
@@ -117,8 +185,8 @@ class PrimaryObjectStoreConfig {
];
}
- private function getBucketForUser(IUser $user, array $config): string {
- $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
+ public function getBucketForUser(IUser $user, array $config): string {
+ $bucket = $this->getSetBucketForUser($user);
if ($bucket === null) {
/*
@@ -129,7 +197,7 @@ class PrimaryObjectStoreConfig {
$config['arguments']['bucket'] = '';
}
$mapper = new Mapper($user, $this->config);
- $numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
+ $numBuckets = $config['arguments']['num_buckets'] ?? 64;
$bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
@@ -137,4 +205,21 @@ class PrimaryObjectStoreConfig {
return $bucket;
}
+
+ public function getSetBucketForUser(IUser $user): ?string {
+ return $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
+ }
+
+ public function getObjectStoreForUser(IUser $user): string {
+ if ($this->hasMultipleObjectStorages()) {
+ $value = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'objectstore', null);
+ if ($value === null) {
+ $value = $this->resolveAlias('default');
+ $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'objectstore', $value);
+ }
+ return $value;
+ } else {
+ return 'default';
+ }
+ }
}
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 63eecf5e1d6..a852f453963 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -1828,43 +1828,25 @@ class View {
* @return string
* @throws NotFoundException
*/
- public function getPath($id, ?int $storageId = null) {
+ public function getPath($id, ?int $storageId = null): string {
$id = (int)$id;
- $manager = Filesystem::getMountManager();
- $mounts = $manager->findIn($this->fakeRoot);
- $mounts[] = $manager->find($this->fakeRoot);
- $mounts = array_filter($mounts);
- // reverse the array, so we start with the storage this view is in
- // which is the most likely to contain the file we're looking for
- $mounts = array_reverse($mounts);
-
- // put non-shared mounts in front of the shared mount
- // this prevents unneeded recursion into shares
- usort($mounts, function (IMountPoint $a, IMountPoint $b) {
- return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
- });
+ $rootFolder = Server::get(Files\IRootFolder::class);
- if (!is_null($storageId)) {
- $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
- return $mount->getNumericStorageId() === $storageId;
- });
+ $node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
+ if ($node) {
+ if ($storageId === null || $storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
+ }
+ } else {
+ throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
- foreach ($mounts as $mount) {
- /**
- * @var \OC\Files\Mount\MountPoint $mount
- */
- if ($mount->getStorage()) {
- $cache = $mount->getStorage()->getCache();
- $internalPath = $cache->getPathById($id);
- if (is_string($internalPath)) {
- $fullPath = $mount->getMountPoint() . $internalPath;
- if (!is_null($path = $this->getRelativePath($fullPath))) {
- return $path;
- }
- }
+ foreach ($rootFolder->getByIdInPath($id, $this->getRoot()) as $node) {
+ if ($storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
}
}
+
throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php
index 77d7ca62311..be366e8ba6c 100644
--- a/lib/private/Search/SearchComposer.php
+++ b/lib/private/Search/SearchComposer.php
@@ -15,6 +15,7 @@ use OCP\IAppConfig;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\FilterDefinition;
+use OCP\Search\IExternalProvider;
use OCP\Search\IFilter;
use OCP\Search\IFilteringProvider;
use OCP\Search\IInAppSearch;
@@ -118,7 +119,7 @@ class SearchComposer {
}
}
- $this->providers = $this->filterProviders($this->providers);
+ $this->filterProviders();
$this->loadFilters();
}
@@ -178,6 +179,7 @@ class SearchComposer {
if ($order === null) {
return;
}
+ $isExternalProvider = $provider instanceof IExternalProvider ? $provider->isExternalProvider() : false;
$triggers = [$provider->getId()];
if ($provider instanceof IFilteringProvider) {
$triggers += $provider->getAlternateIds();
@@ -192,6 +194,7 @@ class SearchComposer {
'name' => $provider->getName(),
'icon' => $this->fetchIcon($appId, $provider->getId()),
'order' => $order,
+ 'isExternalProvider' => $isExternalProvider,
'triggers' => array_values($triggers),
'filters' => $this->getFiltersType($filters, $provider->getId()),
'inAppSearch' => $provider instanceof IInAppSearch,
@@ -211,19 +214,21 @@ class SearchComposer {
/**
* Filter providers based on 'unified_search.providers_allowed' core app config array
- * @param array $providers
- * @return array
+ * Will remove providers that are not in the allowed list
*/
- private function filterProviders(array $providers): array {
+ private function filterProviders(): void {
$allowedProviders = $this->appConfig->getValueArray('core', 'unified_search.providers_allowed');
if (empty($allowedProviders)) {
- return $providers;
+ return;
}
- return array_values(array_filter($providers, function ($p) use ($allowedProviders) {
- return in_array($p['id'], $allowedProviders);
- }));
+ foreach (array_keys($this->providers) as $providerId) {
+ if (!in_array($providerId, $allowedProviders, true)) {
+ unset($this->providers[$providerId]);
+ unset($this->handlers[$providerId]);
+ }
+ }
}
private function fetchIcon(string $appId, string $providerId): string {
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 171fee2afa1..22cd13438b8 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -657,7 +657,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(\OCP\IConfig::class),
$c->get(IValidator::class),
$c->get(IRichTextFormatter::class),
- $l10n
+ $l10n,
+ $c->get(ITimeFactory::class),
);
});
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 855bb173d56..28f29d6b20f 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -1778,7 +1778,7 @@ class Manager implements IManager {
}
}
}
- return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
+ return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
}
/**
diff --git a/lib/private/TaskProcessing/Db/Task.php b/lib/private/TaskProcessing/Db/Task.php
index 4d919deaf94..05c0ae9ac74 100644
--- a/lib/private/TaskProcessing/Db/Task.php
+++ b/lib/private/TaskProcessing/Db/Task.php
@@ -45,6 +45,8 @@ use OCP\TaskProcessing\Task as OCPTask;
* @method int getStartedAt()
* @method setEndedAt(int $endedAt)
* @method int getEndedAt()
+ * @method setAllowCleanup(int $allowCleanup)
+ * @method int getAllowCleanup()
*/
class Task extends Entity {
protected $lastUpdated;
@@ -63,16 +65,17 @@ class Task extends Entity {
protected $scheduledAt;
protected $startedAt;
protected $endedAt;
+ protected $allowCleanup;
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at'];
+ public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at', 'allow_cleanup'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt'];
+ public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt', 'allowCleanup'];
public function __construct() {
@@ -94,6 +97,7 @@ class Task extends Entity {
$this->addType('scheduledAt', 'integer');
$this->addType('startedAt', 'integer');
$this->addType('endedAt', 'integer');
+ $this->addType('allowCleanup', 'integer');
}
public function toRow(): array {
@@ -122,6 +126,7 @@ class Task extends Entity {
'scheduledAt' => $task->getScheduledAt(),
'startedAt' => $task->getStartedAt(),
'endedAt' => $task->getEndedAt(),
+ 'allowCleanup' => $task->getAllowCleanup() ? 1 : 0,
]);
return $taskEntity;
}
@@ -144,6 +149,7 @@ class Task extends Entity {
$task->setScheduledAt($this->getScheduledAt());
$task->setStartedAt($this->getStartedAt());
$task->setEndedAt($this->getEndedAt());
+ $task->setAllowCleanup($this->getAllowCleanup() !== 0);
return $task;
}
}
diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php
index 91fd68820ae..fee96534633 100644
--- a/lib/private/TaskProcessing/Db/TaskMapper.php
+++ b/lib/private/TaskProcessing/Db/TaskMapper.php
@@ -183,16 +183,39 @@ class TaskMapper extends QBMapper {
/**
* @param int $timeout
+ * @param bool $force If true, ignore the allow_cleanup flag
* @return int the number of deleted tasks
* @throws Exception
*/
- public function deleteOlderThan(int $timeout): int {
+ public function deleteOlderThan(int $timeout, bool $force = false): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
+ if (!$force) {
+ $qb->andWhere($qb->expr()->eq('allow_cleanup', $qb->createPositionalParameter(1, IQueryBuilder::PARAM_INT)));
+ }
return $qb->executeStatement();
}
+ /**
+ * @param int $timeout
+ * @param bool $force If true, ignore the allow_cleanup flag
+ * @return \Generator<Task>
+ * @throws Exception
+ */
+ public function getTasksToCleanup(int $timeout, bool $force = false): \Generator {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
+ if (!$force) {
+ $qb->andWhere($qb->expr()->eq('allow_cleanup', $qb->createPositionalParameter(1, IQueryBuilder::PARAM_INT)));
+ }
+ foreach ($this->yieldEntities($qb) as $entity) {
+ yield $entity;
+ };
+ }
+
public function update(Entity $entity): Entity {
$entity->setLastUpdated($this->timeFactory->now()->getTimestamp());
return parent::update($entity);
diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php
index 11fb2bed559..e288f2981a8 100644
--- a/lib/private/TaskProcessing/Manager.php
+++ b/lib/private/TaskProcessing/Manager.php
@@ -30,6 +30,7 @@ use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
use OCP\ICache;
@@ -78,6 +79,8 @@ class Manager implements IManager {
'ai.taskprocessing_provider_preferences',
];
+ public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 30 * 4; // 4 months
+
/** @var list<IProvider>|null */
private ?array $providers = null;
@@ -1449,6 +1452,97 @@ class Manager implements IManager {
}
/**
+ * @param Task $task
+ * @return list<int>
+ * @throws NotFoundException
+ */
+ public function extractFileIdsFromTask(Task $task): array {
+ $ids = [];
+ $taskTypes = $this->getAvailableTaskTypes();
+ if (!isset($taskTypes[$task->getTaskTypeId()])) {
+ throw new NotFoundException('Could not find task type');
+ }
+ $taskType = $taskTypes[$task->getTaskTypeId()];
+ foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $inputSlot */
+ $inputSlot = $task->getInput()[$key];
+ if (is_array($inputSlot)) {
+ $ids = array_merge($inputSlot, $ids);
+ } else {
+ $ids[] = $inputSlot;
+ }
+ }
+ }
+ if ($task->getOutput() !== null) {
+ foreach ($taskType['outputShape'] + $taskType['optionalOutputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $outputSlot */
+ $outputSlot = $task->getOutput()[$key];
+ if (is_array($outputSlot)) {
+ $ids = array_merge($outputSlot, $ids);
+ } else {
+ $ids[] = $outputSlot;
+ }
+ }
+ }
+ }
+ return $ids;
+ }
+
+ /**
+ * @param ISimpleFolder $folder
+ * @param int $ageInSeconds
+ * @return \Generator
+ */
+ public function clearFilesOlderThan(ISimpleFolder $folder, int $ageInSeconds = self::MAX_TASK_AGE_SECONDS): \Generator {
+ foreach ($folder->getDirectoryListing() as $file) {
+ if ($file->getMTime() < time() - $ageInSeconds) {
+ try {
+ $fileName = $file->getName();
+ $file->delete();
+ yield $fileName;
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param int $ageInSeconds
+ * @return \Generator
+ * @throws Exception
+ * @throws InvalidPathException
+ * @throws NotFoundException
+ * @throws \JsonException
+ * @throws \OCP\Files\NotFoundException
+ */
+ public function cleanupTaskProcessingTaskFiles(int $ageInSeconds = self::MAX_TASK_AGE_SECONDS): \Generator {
+ $taskIdsToCleanup = [];
+ foreach ($this->taskMapper->getTasksToCleanup($ageInSeconds) as $task) {
+ $taskIdsToCleanup[] = $task->getId();
+ $ocpTask = $task->toPublicTask();
+ $fileIds = $this->extractFileIdsFromTask($ocpTask);
+ foreach ($fileIds as $fileId) {
+ // only look for output files stored in appData/TaskProcessing/
+ $file = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/core/TaskProcessing/');
+ if ($file instanceof File) {
+ try {
+ $fileId = $file->getId();
+ $fileName = $file->getName();
+ $file->delete();
+ yield ['task_id' => $task->getId(), 'file_id' => $fileId, 'file_name' => $fileName];
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
+ }
+ }
+ }
+ }
+ return $taskIdsToCleanup;
+ }
+
+ /**
* Make a request to the task's webhookUri if necessary
*
* @param Task $task
diff --git a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
index 42d073a024d..52fc204b752 100644
--- a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
+++ b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
@@ -10,17 +10,14 @@ use OC\TaskProcessing\Db\TaskMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\AppData\IAppDataFactory;
-use OCP\Files\NotFoundException;
-use OCP\Files\NotPermittedException;
-use OCP\Files\SimpleFS\ISimpleFolder;
use Psr\Log\LoggerInterface;
class RemoveOldTasksBackgroundJob extends TimedJob {
- public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 30 * 4; // 4 months
private \OCP\Files\IAppData $appData;
public function __construct(
ITimeFactory $timeFactory,
+ private Manager $taskProcessingManager,
private TaskMapper $taskMapper,
private LoggerInterface $logger,
IAppDataFactory $appDataFactory,
@@ -32,48 +29,29 @@ class RemoveOldTasksBackgroundJob extends TimedJob {
$this->appData = $appDataFactory->get('core');
}
-
/**
* @inheritDoc
*/
protected function run($argument): void {
try {
- $this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS);
- } catch (\OCP\DB\Exception $e) {
- $this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]);
+ iterator_to_array($this->taskProcessingManager->cleanupTaskProcessingTaskFiles());
+ } catch (\Exception $e) {
+ $this->logger->warning('Failed to delete stale task processing tasks files', ['exception' => $e]);
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('text2image'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
- // noop
+ $this->taskMapper->deleteOlderThan(Manager::MAX_TASK_AGE_SECONDS);
+ } catch (\OCP\DB\Exception $e) {
+ $this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]);
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('audio2text'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
+ iterator_to_array($this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('text2image')));
+ } catch (\OCP\Files\NotFoundException $e) {
// noop
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('TaskProcessing'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
+ iterator_to_array($this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('audio2text')));
+ } catch (\OCP\Files\NotFoundException $e) {
// noop
}
}
-
- /**
- * @param ISimpleFolder $folder
- * @param int $ageInSeconds
- * @return void
- */
- private function clearFilesOlderThan(ISimpleFolder $folder, int $ageInSeconds): void {
- foreach ($folder->getDirectoryListing() as $file) {
- if ($file->getMTime() < time() - $ageInSeconds) {
- try {
- $file->delete();
- } catch (NotPermittedException $e) {
- $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
- }
- }
- }
- }
-
}
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index 07e557d0706..044fa8147a0 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -10,6 +10,7 @@ namespace OC\Template;
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Authentication\Token\IProvider;
use OC\CapabilitiesManager;
+use OC\Core\AppInfo\ConfigLexicon;
use OC\Files\FilenameValidator;
use OC\Share\Share;
use OCA\Provisioning_API\Controller\AUserDataOCSController;
@@ -22,6 +23,7 @@ use OCP\Authentication\Token\IToken;
use OCP\Constants;
use OCP\Defaults;
use OCP\Files\FileInfo;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IInitialStateService;
@@ -50,6 +52,7 @@ class JSConfigHelper {
protected ISession $session,
protected ?IUser $currentUser,
protected IConfig $config,
+ protected readonly IAppConfig $appConfig,
protected IGroupManager $groupManager,
protected IniGetWrapper $iniWrapper,
protected IURLGenerator $urlGenerator,
@@ -94,8 +97,7 @@ class JSConfigHelper {
}
}
- $enableLinkPasswordByDefault = $this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no');
- $enableLinkPasswordByDefault = $enableLinkPasswordByDefault === 'yes';
+ $enableLinkPasswordByDefault = $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_DEFAULT);
$defaultExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
$defaultExpireDate = $enforceDefaultExpireDate = null;
if ($defaultExpireDateEnabled) {
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index cfc387d2164..42861eddc0d 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -20,6 +20,7 @@ use OC\Template\JSResourceLocator;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\INavigationManager;
@@ -44,6 +45,7 @@ class TemplateLayout {
public function __construct(
private IConfig $config,
+ private readonly IAppConfig $appConfig,
private IAppManager $appManager,
private InitialStateService $initialState,
private INavigationManager $navigationManager,
@@ -223,6 +225,7 @@ class TemplateLayout {
\OC::$server->getSession(),
\OC::$server->getUserSession()->getUser(),
$this->config,
+ $this->appConfig,
\OC::$server->getGroupManager(),
\OC::$server->get(IniGetWrapper::class),
\OC::$server->getURLGenerator(),