aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/lib/BackgroundJob
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/lib/BackgroundJob')
-rw-r--r--apps/files/lib/BackgroundJob/CleanupDirectEditingTokens.php33
-rw-r--r--apps/files/lib/BackgroundJob/CleanupFileLocks.php46
-rw-r--r--apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php39
-rw-r--r--apps/files/lib/BackgroundJob/DeleteOrphanedItems.php145
-rw-r--r--apps/files/lib/BackgroundJob/ScanFiles.php174
-rw-r--r--apps/files/lib/BackgroundJob/TransferOwnership.php145
6 files changed, 416 insertions, 166 deletions
diff --git a/apps/files/lib/BackgroundJob/CleanupDirectEditingTokens.php b/apps/files/lib/BackgroundJob/CleanupDirectEditingTokens.php
new file mode 100644
index 00000000000..a1032b2787d
--- /dev/null
+++ b/apps/files/lib/BackgroundJob/CleanupDirectEditingTokens.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Files\BackgroundJob;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DirectEditing\IManager;
+
+class CleanupDirectEditingTokens extends TimedJob {
+ public function __construct(
+ ITimeFactory $time,
+ private IManager $manager,
+ ) {
+ parent::__construct($time);
+ $this->setInterval(15 * 60);
+ }
+
+ /**
+ * Makes the background job do its work
+ *
+ * @param array $argument unused argument
+ * @throws \Exception
+ */
+ public function run($argument) {
+ $this->manager->cleanup();
+ }
+}
diff --git a/apps/files/lib/BackgroundJob/CleanupFileLocks.php b/apps/files/lib/BackgroundJob/CleanupFileLocks.php
index f4898b24873..91bb145884b 100644
--- a/apps/files/lib/BackgroundJob/CleanupFileLocks.php
+++ b/apps/files/lib/BackgroundJob/CleanupFileLocks.php
@@ -1,57 +1,39 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Morris Jobke <hey@morrisjobke.de>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-
namespace OCA\Files\BackgroundJob;
-use OC\BackgroundJob\TimedJob;
use OC\Lock\DBLockingProvider;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\Lock\ILockingProvider;
+use OCP\Server;
/**
* Clean up all file locks that are expired for the DB file locking provider
*/
class CleanupFileLocks extends TimedJob {
-
- /**
- * Default interval in minutes
- *
- * @var int $defaultIntervalMin
- **/
- protected $defaultIntervalMin = 5;
-
/**
* sets the correct interval for this timed job
*/
- public function __construct() {
- $this->interval = $this->defaultIntervalMin * 60;
+ public function __construct(ITimeFactory $time) {
+ parent::__construct($time);
+ $this->setInterval(5 * 60);
}
/**
* Makes the background job do its work
*
* @param array $argument unused argument
+ * @throws \Exception
*/
public function run($argument) {
- $lockingProvider = \OC::$server->getLockingProvider();
- if($lockingProvider instanceof DBLockingProvider) {
+ $lockingProvider = Server::get(ILockingProvider::class);
+ if ($lockingProvider instanceof DBLockingProvider) {
$lockingProvider->cleanExpiredLocks();
}
}
diff --git a/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php b/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php
new file mode 100644
index 00000000000..8a20b6dfb0c
--- /dev/null
+++ b/apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Files\BackgroundJob;
+
+use OCA\Files\Db\OpenLocalEditorMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+
+/**
+ * Delete all expired "Open local editor" token
+ */
+class DeleteExpiredOpenLocalEditor extends TimedJob {
+ public function __construct(
+ ITimeFactory $time,
+ protected OpenLocalEditorMapper $mapper,
+ ) {
+ parent::__construct($time);
+
+ // Run every 12h
+ $this->interval = 12 * 3600;
+ $this->setTimeSensitivity(self::TIME_INSENSITIVE);
+ }
+
+ /**
+ * Makes the background job do its work
+ *
+ * @param array $argument unused argument
+ */
+ public function run($argument): void {
+ $this->mapper->deleteExpiredTokens($this->time->getTime());
+ }
+}
diff --git a/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php
index 5c4ba6ac06c..b925974f24a 100644
--- a/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php
+++ b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php
@@ -1,59 +1,34 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Vincent Petry <pvince81@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-
namespace OCA\Files\BackgroundJob;
-use OC\BackgroundJob\TimedJob;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
/**
* Delete all share entries that have no matching entries in the file cache table.
*/
class DeleteOrphanedItems extends TimedJob {
-
- const CHUNK_SIZE = 200;
-
- /** @var \OCP\IDBConnection */
- protected $connection;
-
- /** @var \OCP\ILogger */
- protected $logger;
-
- /**
- * Default interval in minutes
- *
- * @var int $defaultIntervalMin
- **/
- protected $defaultIntervalMin = 60;
+ public const CHUNK_SIZE = 200;
/**
* sets the correct interval for this timed job
*/
- public function __construct() {
- $this->interval = $this->defaultIntervalMin * 60;
- $this->connection = \OC::$server->getDatabaseConnection();
- $this->logger = \OC::$server->getLogger();
+ public function __construct(
+ ITimeFactory $time,
+ protected IDBConnection $connection,
+ protected LoggerInterface $logger,
+ ) {
+ parent::__construct($time);
+ $this->setInterval(60 * 60);
}
/**
@@ -76,38 +51,87 @@ class DeleteOrphanedItems extends TimedJob {
* @param string $typeCol
* @return int Number of deleted entries
*/
- protected function cleanUp($table, $idCol, $typeCol) {
+ protected function cleanUp(string $table, string $idCol, string $typeCol): int {
$deletedEntries = 0;
- $query = $this->connection->getQueryBuilder();
- $query->select('t1.' . $idCol)
- ->from($table, 't1')
- ->where($query->expr()->eq($typeCol, $query->expr()->literal('files')))
- ->andWhere($query->expr()->isNull('t2.fileid'))
- ->leftJoin('t1', 'filecache', 't2', $query->expr()->eq($query->expr()->castColumn('t1.' . $idCol, IQueryBuilder::PARAM_INT), 't2.fileid'))
- ->groupBy('t1.' . $idCol)
- ->setMaxResults(self::CHUNK_SIZE);
-
$deleteQuery = $this->connection->getQueryBuilder();
$deleteQuery->delete($table)
->where($deleteQuery->expr()->eq($idCol, $deleteQuery->createParameter('objectid')));
- $deletedInLastChunk = self::CHUNK_SIZE;
- while ($deletedInLastChunk === self::CHUNK_SIZE) {
- $result = $query->execute();
- $deletedInLastChunk = 0;
- while ($row = $result->fetch()) {
- $deletedInLastChunk++;
- $deletedEntries += $deleteQuery->setParameter('objectid', (int) $row[$idCol])
- ->execute();
+ if ($this->connection->getShardDefinition('filecache')) {
+ $sourceIdChunks = $this->getItemIds($table, $idCol, $typeCol, 1000);
+ foreach ($sourceIdChunks as $sourceIdChunk) {
+ $deletedSources = $this->findMissingSources($sourceIdChunk);
+ $deleteQuery->setParameter('objectid', $deletedSources, IQueryBuilder::PARAM_INT_ARRAY);
+ $deletedEntries += $deleteQuery->executeStatement();
+ }
+ } else {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('t1.' . $idCol)
+ ->from($table, 't1')
+ ->where($query->expr()->eq($typeCol, $query->expr()->literal('files')))
+ ->leftJoin('t1', 'filecache', 't2', $query->expr()->eq($query->expr()->castColumn('t1.' . $idCol, IQueryBuilder::PARAM_INT), 't2.fileid'))
+ ->andWhere($query->expr()->isNull('t2.fileid'))
+ ->groupBy('t1.' . $idCol)
+ ->setMaxResults(self::CHUNK_SIZE);
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete($table)
+ ->where($deleteQuery->expr()->in($idCol, $deleteQuery->createParameter('objectid')));
+
+ $deletedInLastChunk = self::CHUNK_SIZE;
+ while ($deletedInLastChunk === self::CHUNK_SIZE) {
+ $chunk = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
+ $deletedInLastChunk = count($chunk);
+
+ $deleteQuery->setParameter('objectid', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
+ $deletedEntries += $deleteQuery->executeStatement();
}
- $result->closeCursor();
}
return $deletedEntries;
}
/**
+ * @param string $table
+ * @param string $idCol
+ * @param string $typeCol
+ * @param int $chunkSize
+ * @return \Iterator<int[]>
+ * @throws \OCP\DB\Exception
+ */
+ private function getItemIds(string $table, string $idCol, string $typeCol, int $chunkSize): \Iterator {
+ $query = $this->connection->getQueryBuilder();
+ $query->select($idCol)
+ ->from($table)
+ ->where($query->expr()->eq($typeCol, $query->expr()->literal('files')))
+ ->groupBy($idCol)
+ ->andWhere($query->expr()->gt($idCol, $query->createParameter('min_id')))
+ ->setMaxResults($chunkSize);
+
+ $minId = 0;
+ while (true) {
+ $query->setParameter('min_id', $minId);
+ $rows = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
+ if (count($rows) > 0) {
+ $minId = $rows[count($rows) - 1];
+ yield $rows;
+ } else {
+ break;
+ }
+ }
+ }
+
+ private function findMissingSources(array $ids): array {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->select('fileid')
+ ->from('filecache')
+ ->where($qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
+ $found = $qb->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
+ return array_diff($ids, $found);
+ }
+
+ /**
* Deleting orphaned system tag mappings
*
* @return int Number of deleted entries
@@ -150,5 +174,4 @@ class DeleteOrphanedItems extends TimedJob {
$this->logger->debug("$deletedEntries orphaned comment read marks deleted", ['app' => 'DeleteOrphanedItems']);
return $deletedEntries;
}
-
}
diff --git a/apps/files/lib/BackgroundJob/ScanFiles.php b/apps/files/lib/BackgroundJob/ScanFiles.php
index cb2b9d4b1c9..f3f9093d648 100644
--- a/apps/files/lib/BackgroundJob/ScanFiles.php
+++ b/apps/files/lib/BackgroundJob/ScanFiles.php
@@ -1,33 +1,21 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Files\BackgroundJob;
use OC\Files\Utils\Scanner;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
-use OCP\ILogger;
-use OCP\IUser;
-use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
/**
* Class ScanFiles is a background job used to run the file scanner over the user
@@ -35,81 +23,121 @@ use OCP\IUserManager;
*
* @package OCA\Files\BackgroundJob
*/
-class ScanFiles extends \OC\BackgroundJob\TimedJob {
- /** @var IConfig */
- private $config;
- /** @var IUserManager */
- private $userManager;
- /** @var IDBConnection */
- private $dbConnection;
- /** @var ILogger */
- private $logger;
+class ScanFiles extends TimedJob {
/** Amount of users that should get scanned per execution */
- const USERS_PER_SESSION = 500;
+ public const USERS_PER_SESSION = 500;
- /**
- * @param IConfig|null $config
- * @param IUserManager|null $userManager
- * @param IDBConnection|null $dbConnection
- * @param ILogger|null $logger
- */
- public function __construct(IConfig $config = null,
- IUserManager $userManager = null,
- IDBConnection $dbConnection = null,
- ILogger $logger = null) {
+ public function __construct(
+ private IConfig $config,
+ private IEventDispatcher $dispatcher,
+ private LoggerInterface $logger,
+ private IDBConnection $connection,
+ ITimeFactory $time,
+ ) {
+ parent::__construct($time);
// Run once per 10 minutes
$this->setInterval(60 * 10);
-
- if (is_null($userManager) || is_null($config)) {
- $this->fixDIForJobs();
- } else {
- $this->config = $config;
- $this->userManager = $userManager;
- $this->logger = $logger;
- }
}
- protected function fixDIForJobs() {
- $this->config = \OC::$server->getConfig();
- $this->userManager = \OC::$server->getUserManager();
- $this->logger = \OC::$server->getLogger();
- }
-
- /**
- * @param IUser $user
- */
- protected function runScanner(IUser $user) {
+ protected function runScanner(string $user): void {
try {
$scanner = new Scanner(
- $user->getUID(),
- $this->dbConnection,
- $this->logger
+ $user,
+ null,
+ $this->dispatcher,
+ $this->logger
);
$scanner->backgroundScan('');
} catch (\Exception $e) {
- $this->logger->logException($e, ['app' => 'files']);
+ $this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'files']);
}
\OC_Util::tearDownFS();
}
/**
+ * Find a storage which have unindexed files and return a user with access to the storage
+ *
+ * @return string|false
+ */
+ private function getUserToScan() {
+ if ($this->connection->getShardDefinition('filecache')) {
+ // for sharded filecache, the "LIMIT" from the normal query doesn't work
+
+ // first we try it with a "LEFT JOIN" on mounts, this is fast, but might return a storage that isn't mounted.
+ // we also ask for up to 10 results from different storages to increase the odds of finding a result that is mounted
+ $query = $this->connection->getQueryBuilder();
+ $query->select('m.user_id')
+ ->from('filecache', 'f')
+ ->leftJoin('f', 'mounts', 'm', $query->expr()->eq('m.storage_id', 'f.storage'))
+ ->where($query->expr()->eq('f.size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->gt('f.parent', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->setMaxResults(10)
+ ->groupBy('f.storage')
+ ->runAcrossAllShards();
+
+ $result = $query->executeQuery();
+ while ($res = $result->fetch()) {
+ if ($res['user_id']) {
+ return $res['user_id'];
+ }
+ }
+
+ // as a fallback, we try a slower approach where we find all mounted storages first
+ // this is essentially doing the inner join manually
+ $storages = $this->getAllMountedStorages();
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('m.user_id')
+ ->from('filecache', 'f')
+ ->leftJoin('f', 'mounts', 'm', $query->expr()->eq('m.storage_id', 'f.storage'))
+ ->where($query->expr()->eq('f.size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->gt('f.parent', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->in('f.storage', $query->createNamedParameter($storages, IQueryBuilder::PARAM_INT_ARRAY)))
+ ->setMaxResults(1)
+ ->runAcrossAllShards();
+ return $query->executeQuery()->fetchOne();
+ } else {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('m.user_id')
+ ->from('filecache', 'f')
+ ->innerJoin('f', 'mounts', 'm', $query->expr()->eq('m.storage_id', 'f.storage'))
+ ->where($query->expr()->eq('f.size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->gt('f.parent', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
+ ->setMaxResults(1)
+ ->runAcrossAllShards();
+
+ return $query->executeQuery()->fetchOne();
+ }
+ }
+
+ private function getAllMountedStorages(): array {
+ $query = $this->connection->getQueryBuilder();
+ $query->selectDistinct('storage_id')
+ ->from('mounts');
+ return $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
+ }
+
+ /**
* @param $argument
* @throws \Exception
*/
protected function run($argument) {
- $offset = $this->config->getAppValue('files', 'cronjob_scan_files', 0);
- $users = $this->userManager->search('', self::USERS_PER_SESSION, $offset);
- if (!count($users)) {
- // No users found, reset offset and retry
- $offset = 0;
- $users = $this->userManager->search('', self::USERS_PER_SESSION);
+ if ($this->config->getSystemValueBool('files_no_background_scan', false)) {
+ return;
}
- $offset += self::USERS_PER_SESSION;
- $this->config->setAppValue('files', 'cronjob_scan_files', $offset);
-
- foreach ($users as $user) {
+ $usersScanned = 0;
+ $lastUser = '';
+ $user = $this->getUserToScan();
+ while ($user && $usersScanned < self::USERS_PER_SESSION && $lastUser !== $user) {
$this->runScanner($user);
+ $lastUser = $user;
+ $user = $this->getUserToScan();
+ $usersScanned += 1;
+ }
+
+ if ($lastUser === $user) {
+ $this->logger->warning("User $user still has unscanned files after running background scan, background scan might be stopped prematurely");
}
}
}
diff --git a/apps/files/lib/BackgroundJob/TransferOwnership.php b/apps/files/lib/BackgroundJob/TransferOwnership.php
new file mode 100644
index 00000000000..de8d1989733
--- /dev/null
+++ b/apps/files/lib/BackgroundJob/TransferOwnership.php
@@ -0,0 +1,145 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Files\BackgroundJob;
+
+use OCA\Files\AppInfo\Application;
+use OCA\Files\Db\TransferOwnership as Transfer;
+use OCA\Files\Db\TransferOwnershipMapper;
+use OCA\Files\Exception\TransferOwnershipException;
+use OCA\Files\Service\OwnershipTransferService;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\Files\IRootFolder;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Notification\IManager as NotificationManager;
+use Psr\Log\LoggerInterface;
+use function ltrim;
+
+class TransferOwnership extends QueuedJob {
+ public function __construct(
+ ITimeFactory $timeFactory,
+ private IUserManager $userManager,
+ private OwnershipTransferService $transferService,
+ private LoggerInterface $logger,
+ private NotificationManager $notificationManager,
+ private TransferOwnershipMapper $mapper,
+ private IRootFolder $rootFolder,
+ ) {
+ parent::__construct($timeFactory);
+ }
+
+ protected function run($argument) {
+ $id = $argument['id'];
+
+ $transfer = $this->mapper->getById($id);
+ $sourceUser = $transfer->getSourceUser();
+ $destinationUser = $transfer->getTargetUser();
+ $fileId = $transfer->getFileId();
+
+ $userFolder = $this->rootFolder->getUserFolder($sourceUser);
+ $node = $userFolder->getFirstNodeById($fileId);
+
+ if (!$node) {
+ $this->logger->alert('Could not transfer ownership: Node not found');
+ $this->failedNotication($transfer);
+ return;
+ }
+ $path = $userFolder->getRelativePath($node->getPath());
+
+ $sourceUserObject = $this->userManager->get($sourceUser);
+ $destinationUserObject = $this->userManager->get($destinationUser);
+
+ if (!$sourceUserObject instanceof IUser) {
+ $this->logger->alert('Could not transfer ownership: Unknown source user ' . $sourceUser);
+ $this->failedNotication($transfer);
+ return;
+ }
+
+ if (!$destinationUserObject instanceof IUser) {
+ $this->logger->alert("Unknown destination user $destinationUser");
+ $this->failedNotication($transfer);
+ return;
+ }
+
+ try {
+ $this->transferService->transfer(
+ $sourceUserObject,
+ $destinationUserObject,
+ ltrim($path, '/')
+ );
+ $this->successNotification($transfer);
+ } catch (TransferOwnershipException $e) {
+ $this->logger->error(
+ $e->getMessage(),
+ [
+ 'exception' => $e,
+ ],
+ );
+ $this->failedNotication($transfer);
+ }
+
+ $this->mapper->delete($transfer);
+ }
+
+ private function failedNotication(Transfer $transfer): void {
+ // Send notification to source user
+ $notification = $this->notificationManager->createNotification();
+ $notification->setUser($transfer->getSourceUser())
+ ->setApp(Application::APP_ID)
+ ->setDateTime($this->time->getDateTime())
+ ->setSubject('transferOwnershipFailedSource', [
+ 'sourceUser' => $transfer->getSourceUser(),
+ 'targetUser' => $transfer->getTargetUser(),
+ 'nodeName' => $transfer->getNodeName(),
+ ])
+ ->setObject('transfer', (string)$transfer->getId());
+ $this->notificationManager->notify($notification);
+ // Send notification to source user
+ $notification = $this->notificationManager->createNotification();
+ $notification->setUser($transfer->getTargetUser())
+ ->setApp(Application::APP_ID)
+ ->setDateTime($this->time->getDateTime())
+ ->setSubject('transferOwnershipFailedTarget', [
+ 'sourceUser' => $transfer->getSourceUser(),
+ 'targetUser' => $transfer->getTargetUser(),
+ 'nodeName' => $transfer->getNodeName(),
+ ])
+ ->setObject('transfer', (string)$transfer->getId());
+ $this->notificationManager->notify($notification);
+ }
+
+ private function successNotification(Transfer $transfer): void {
+ // Send notification to source user
+ $notification = $this->notificationManager->createNotification();
+ $notification->setUser($transfer->getSourceUser())
+ ->setApp(Application::APP_ID)
+ ->setDateTime($this->time->getDateTime())
+ ->setSubject('transferOwnershipDoneSource', [
+ 'sourceUser' => $transfer->getSourceUser(),
+ 'targetUser' => $transfer->getTargetUser(),
+ 'nodeName' => $transfer->getNodeName(),
+ ])
+ ->setObject('transfer', (string)$transfer->getId());
+ $this->notificationManager->notify($notification);
+
+ // Send notification to source user
+ $notification = $this->notificationManager->createNotification();
+ $notification->setUser($transfer->getTargetUser())
+ ->setApp(Application::APP_ID)
+ ->setDateTime($this->time->getDateTime())
+ ->setSubject('transferOwnershipDoneTarget', [
+ 'sourceUser' => $transfer->getSourceUser(),
+ 'targetUser' => $transfer->getTargetUser(),
+ 'nodeName' => $transfer->getNodeName(),
+ ])
+ ->setObject('transfer', (string)$transfer->getId());
+ $this->notificationManager->notify($notification);
+ }
+}