diff options
Diffstat (limited to 'apps/files/lib/BackgroundJob')
-rw-r--r-- | apps/files/lib/BackgroundJob/CleanupDirectEditingTokens.php | 33 | ||||
-rw-r--r-- | apps/files/lib/BackgroundJob/CleanupFileLocks.php | 46 | ||||
-rw-r--r-- | apps/files/lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php | 39 | ||||
-rw-r--r-- | apps/files/lib/BackgroundJob/DeleteOrphanedItems.php | 145 | ||||
-rw-r--r-- | apps/files/lib/BackgroundJob/ScanFiles.php | 174 | ||||
-rw-r--r-- | apps/files/lib/BackgroundJob/TransferOwnership.php | 145 |
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); + } +} |