aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/lib/Service/OwnershipTransferService.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/lib/Service/OwnershipTransferService.php')
-rw-r--r--apps/files/lib/Service/OwnershipTransferService.php537
1 files changed, 402 insertions, 135 deletions
diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php
index 870aa9f9d77..afef5d2093d 100644
--- a/apps/files/lib/Service/OwnershipTransferService.php
+++ b/apps/files/lib/Service/OwnershipTransferService.php
@@ -3,46 +3,33 @@
declare(strict_types=1);
/**
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Sascha Wiswedel <sascha.wiswedel@nextcloud.com>
- * @author Tobia De Koninck <LEDfan@users.noreply.github.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Service;
use Closure;
+use Exception;
use OC\Files\Filesystem;
use OC\Files\View;
+use OC\User\NoUserException;
+use OCA\Encryption\Util;
use OCA\Files\Exception\TransferOwnershipException;
+use OCA\Files_External\Config\ConfigAdapter;
use OCP\Encryption\IManager as IEncryptionManager;
+use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IUserMountCache;
+use OCP\Files\File;
use OCP\Files\FileInfo;
-use OCP\Files\IHomeStorage;
use OCP\Files\InvalidPathException;
+use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
+use OCP\Files\NotFoundException;
use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Server;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
use Symfony\Component\Console\Helper\ProgressBar;
@@ -57,26 +44,15 @@ use function rtrim;
class OwnershipTransferService {
- /** @var IEncryptionManager */
- private $encryptionManager;
-
- /** @var IShareManager */
- private $shareManager;
-
- /** @var IMountManager */
- private $mountManager;
-
- /** @var IUserMountCache */
- private $userMountCache;
-
- public function __construct(IEncryptionManager $manager,
- IShareManager $shareManager,
- IMountManager $mountManager,
- IUserMountCache $userMountCache) {
- $this->encryptionManager = $manager;
- $this->shareManager = $shareManager;
- $this->mountManager = $mountManager;
- $this->userMountCache = $userMountCache;
+ public function __construct(
+ private IEncryptionManager $encryptionManager,
+ private IShareManager $shareManager,
+ private IMountManager $mountManager,
+ private IUserMountCache $userMountCache,
+ private IUserManager $userManager,
+ private IFactory $l10nFactory,
+ private IRootFolder $rootFolder,
+ ) {
}
/**
@@ -87,25 +63,36 @@ class OwnershipTransferService {
* @param OutputInterface|null $output
* @param bool $move
* @throws TransferOwnershipException
- * @throws \OC\User\NoUserException
+ * @throws NoUserException
*/
- public function transfer(IUser $sourceUser,
- IUser $destinationUser,
- string $path,
- ?OutputInterface $output = null,
- bool $move = false,
- bool $firstLogin = false): void {
+ public function transfer(
+ IUser $sourceUser,
+ IUser $destinationUser,
+ string $path,
+ ?OutputInterface $output = null,
+ bool $move = false,
+ bool $firstLogin = false,
+ bool $includeExternalStorage = false,
+ ): void {
$output = $output ?? new NullOutput();
$sourceUid = $sourceUser->getUID();
$destinationUid = $destinationUser->getUID();
$sourcePath = rtrim($sourceUid . '/files/' . $path, '/');
- // target user has to be ready
- if ($destinationUser->getLastLogin() === 0 || !$this->encryptionManager->isReadyForUser($destinationUid)) {
- throw new TransferOwnershipException("The target user is not ready to accept files. The user has at least to have logged in once.", 2);
+ // If encryption is on we have to ensure the user has logged in before and that all encryption modules are ready
+ if (($this->encryptionManager->isEnabled() && $destinationUser->getLastLogin() === 0)
+ || !$this->encryptionManager->isReadyForUser($destinationUid)) {
+ throw new TransferOwnershipException('The target user is not ready to accept files. The user has at least to have logged in once.', 2);
}
// setup filesystem
+ // Requesting the user folder will set it up if the user hasn't logged in before
+ // We need a setupFS for the full filesystem setup before as otherwise we will just return
+ // a lazy root folder which does not create the destination users folder
+ \OC_Util::setupFS($sourceUser->getUID());
+ \OC_Util::setupFS($destinationUser->getUID());
+ $this->rootFolder->getUserFolder($sourceUser->getUID());
+ $this->rootFolder->getUserFolder($destinationUser->getUID());
Filesystem::initMountPoints($sourceUid);
Filesystem::initMountPoints($destinationUid);
@@ -114,19 +101,15 @@ class OwnershipTransferService {
if ($move) {
$finalTarget = "$destinationUid/files/";
} else {
+ $l = $this->l10nFactory->get('files', $this->l10nFactory->getUserLanguage($destinationUser));
$date = date('Y-m-d H-i-s');
- // Remove some characters which are prone to cause errors
- $cleanUserName = str_replace(['\\', '/', ':', '.', '?', '#', '\'', '"'], '-', $sourceUser->getDisplayName());
- // Replace multiple dashes with one dash
- $cleanUserName = preg_replace('/-{2,}/s', '-', $cleanUserName);
- $cleanUserName = $cleanUserName ?: $sourceUid;
-
- $finalTarget = "$destinationUid/files/transferred from $cleanUserName on $date";
+ $cleanUserName = $this->sanitizeFolderName($sourceUser->getDisplayName()) ?: $sourceUid;
+ $finalTarget = "$destinationUid/files/" . $this->sanitizeFolderName($l->t('Transferred from %1$s on %2$s', [$cleanUserName, $date]));
try {
$view->verifyPath(dirname($finalTarget), basename($finalTarget));
} catch (InvalidPathException $e) {
- $finalTarget = "$destinationUid/files/transferred from $sourceUid on $date";
+ $finalTarget = "$destinationUid/files/" . $this->sanitizeFolderName($l->t('Transferred from %1$s on %2$s', [$sourceUid, $date]));
}
}
@@ -134,14 +117,13 @@ class OwnershipTransferService {
throw new TransferOwnershipException("Unknown path provided: $path", 1);
}
- if ($move && (
- !$view->is_dir($finalTarget) || (
- !$firstLogin &&
- count($view->getDirectoryContent($finalTarget)) > 0
- )
- )
- ) {
- throw new TransferOwnershipException("Destination path does not exists or is not empty", 1);
+ if ($move && !$view->is_dir($finalTarget)) {
+ // Initialize storage
+ \OC_Util::setupFS($destinationUser->getUID());
+ }
+
+ if ($move && !$firstLogin && count($view->getDirectoryContent($finalTarget)) > 0) {
+ throw new TransferOwnershipException('Destination path does not exists or is not empty', 1);
}
@@ -162,22 +144,60 @@ class OwnershipTransferService {
$sourcePath
);
+ $sourceSize = $view->getFileInfo($sourcePath)->getSize();
+
// transfer the files
$this->transferFiles(
$sourceUid,
$sourcePath,
$finalTarget,
$view,
- $output
+ $output,
+ $includeExternalStorage,
);
+ $sizeDifference = $sourceSize - $view->getFileInfo($finalTarget)->getSize();
+ // transfer the incoming shares
+ $sourceShares = $this->collectIncomingShares(
+ $sourceUid,
+ $output,
+ $sourcePath,
+ );
+ $destinationShares = $this->collectIncomingShares(
+ $destinationUid,
+ $output,
+ null,
+ );
+ $this->transferIncomingShares(
+ $sourceUid,
+ $destinationUid,
+ $sourceShares,
+ $destinationShares,
+ $output,
+ $path,
+ $finalTarget,
+ $move
+ );
+
+ $destinationPath = $finalTarget . '/' . $path;
// restore the shares
$this->restoreShares(
$sourceUid,
$destinationUid,
+ $destinationPath,
$shares,
$output
);
+ if ($sizeDifference !== 0) {
+ $output->writeln("Transferred folder have a size difference of: $sizeDifference Bytes which means the transfer may be incomplete. Please check the logs if there was any issue during the transfer operation.");
+ }
+ }
+
+ private function sanitizeFolderName(string $name): string {
+ // Remove some characters which are prone to cause errors
+ $name = str_replace(['\\', '/', ':', '.', '?', '#', '\'', '"'], '-', $name);
+ // Replace multiple dashes with one dash
+ return preg_replace('/-{2,}/s', '-', $name);
}
private function walkFiles(View $view, $path, Closure $callBack) {
@@ -194,84 +214,130 @@ class OwnershipTransferService {
/**
* @param OutputInterface $output
*
- * @throws \Exception
+ * @throws TransferOwnershipException
*/
- protected function analyse(string $sourceUid,
- string $destinationUid,
- string $sourcePath,
- View $view,
- OutputInterface $output): void {
+ protected function analyse(
+ string $sourceUid,
+ string $destinationUid,
+ string $sourcePath,
+ View $view,
+ OutputInterface $output,
+ bool $includeExternalStorage = false,
+ ): void {
$output->writeln('Validating quota');
- $size = $view->getFileInfo($sourcePath, false)->getSize(false);
+ $sourceFileInfo = $view->getFileInfo($sourcePath, false);
+ if ($sourceFileInfo === false) {
+ throw new TransferOwnershipException("Unknown path provided: $sourcePath", 1);
+ }
+ $size = $sourceFileInfo->getSize(false);
$freeSpace = $view->free_space($destinationUid . '/files/');
if ($size > $freeSpace && $freeSpace !== FileInfo::SPACE_UNKNOWN) {
- $output->writeln('<error>Target user does not have enough free space available.</error>');
- throw new \Exception('Execution terminated.');
+ throw new TransferOwnershipException('Target user does not have enough free space available.', 1);
}
$output->writeln("Analysing files of $sourceUid ...");
$progress = new ProgressBar($output);
$progress->start();
+ if ($this->encryptionManager->isEnabled()) {
+ $masterKeyEnabled = Server::get(Util::class)->isMasterKeyEnabled();
+ } else {
+ $masterKeyEnabled = false;
+ }
$encryptedFiles = [];
- $this->walkFiles($view, $sourcePath,
- function (FileInfo $fileInfo) use ($progress) {
- if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
- // only analyze into folders from main storage,
- if (!$fileInfo->getStorage()->instanceOfStorage(IHomeStorage::class)) {
- return false;
- }
- return true;
- }
- $progress->advance();
- if ($fileInfo->isEncrypted()) {
- $encryptedFiles[] = $fileInfo;
- }
- return true;
- });
+ if ($sourceFileInfo->getType() === FileInfo::TYPE_FOLDER) {
+ if ($sourceFileInfo->isEncrypted()) {
+ /* Encrypted folder means e2ee encrypted */
+ $encryptedFiles[] = $sourceFileInfo;
+ } else {
+ $this->walkFiles($view, $sourcePath,
+ function (FileInfo $fileInfo) use ($progress, $masterKeyEnabled, &$encryptedFiles, $includeExternalStorage) {
+ if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
+ $mount = $fileInfo->getMountPoint();
+ // only analyze into folders from main storage,
+ if (
+ $mount->getMountProvider() instanceof IHomeMountProvider
+ || ($includeExternalStorage && $mount->getMountProvider() instanceof ConfigAdapter)
+ ) {
+ if ($fileInfo->isEncrypted()) {
+ /* Encrypted folder means e2ee encrypted, we cannot transfer it */
+ $encryptedFiles[] = $fileInfo;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ $progress->advance();
+ if ($fileInfo->isEncrypted() && !$masterKeyEnabled) {
+ /* Encrypted file means SSE, we can only transfer it if master key is enabled */
+ $encryptedFiles[] = $fileInfo;
+ }
+ return true;
+ });
+ }
+ } elseif ($sourceFileInfo->isEncrypted() && !$masterKeyEnabled) {
+ /* Encrypted file means SSE, we can only transfer it if master key is enabled */
+ $encryptedFiles[] = $sourceFileInfo;
+ }
$progress->finish();
$output->writeln('');
// no file is allowed to be encrypted
if (!empty($encryptedFiles)) {
- $output->writeln("<error>Some files are encrypted - please decrypt them first.</error>");
+ $output->writeln('<error>Some files are encrypted - please decrypt them first.</error>');
foreach ($encryptedFiles as $encryptedFile) {
/** @var FileInfo $encryptedFile */
- $output->writeln(" " . $encryptedFile->getPath());
+ $output->writeln(' ' . $encryptedFile->getPath());
}
- throw new \Exception('Execution terminated.');
+ throw new TransferOwnershipException('Some files are encrypted - please decrypt them first.', 1);
}
}
- private function collectUsersShares(string $sourceUid,
- OutputInterface $output,
- View $view,
- string $path): array {
+ /**
+ * @return array<array{share: IShare, suffix: string}>
+ */
+ private function collectUsersShares(
+ string $sourceUid,
+ OutputInterface $output,
+ View $view,
+ string $path,
+ ): array {
$output->writeln("Collecting all share information for files and folders of $sourceUid ...");
$shares = [];
$progress = new ProgressBar($output);
- foreach ([IShare::TYPE_GROUP, IShare::TYPE_USER, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_ROOM, IShare::TYPE_EMAIL, IShare::TYPE_CIRCLE, IShare::TYPE_DECK] as $shareType) {
+
+ $normalizedPath = Filesystem::normalizePath($path);
+
+ $supportedShareTypes = [
+ IShare::TYPE_GROUP,
+ IShare::TYPE_USER,
+ IShare::TYPE_LINK,
+ IShare::TYPE_REMOTE,
+ IShare::TYPE_ROOM,
+ IShare::TYPE_EMAIL,
+ IShare::TYPE_CIRCLE,
+ IShare::TYPE_DECK,
+ IShare::TYPE_SCIENCEMESH,
+ ];
+
+ foreach ($supportedShareTypes as $shareType) {
$offset = 0;
while (true) {
- $sharePage = $this->shareManager->getSharesBy($sourceUid, $shareType, null, true, 50, $offset);
+ $sharePage = $this->shareManager->getSharesBy($sourceUid, $shareType, null, true, 50, $offset, onlyValid: false);
$progress->advance(count($sharePage));
if (empty($sharePage)) {
break;
}
if ($path !== "$sourceUid/files") {
- $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $path) {
+ $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $normalizedPath) {
try {
- $relativePath = $view->getPath($share->getNodeId());
- $singleFileTranfer = $view->is_file($path);
- if ($singleFileTranfer) {
- return Filesystem::normalizePath($relativePath) === Filesystem::normalizePath($path);
- }
+ $sourceNode = $share->getNode();
+ $relativePath = $view->getRelativePath($sourceNode->getPath());
- return mb_strpos(
- Filesystem::normalizePath($relativePath . '/', false),
- Filesystem::normalizePath($path . '/', false)) === 0;
- } catch (\Exception $e) {
+ return str_starts_with($relativePath . '/', $normalizedPath . '/');
+ } catch (Exception $e) {
return false;
}
});
@@ -283,17 +349,75 @@ class OwnershipTransferService {
$progress->finish();
$output->writeln('');
+
+ return array_values(array_filter(array_map(function (IShare $share) use ($view, $normalizedPath, $output, $sourceUid) {
+ try {
+ $nodePath = $view->getRelativePath($share->getNode()->getPath());
+ } catch (NotFoundException $e) {
+ $output->writeln("<error>Failed to find path for shared file {$share->getNodeId()} for user $sourceUid, skipping</error>");
+ return null;
+ }
+
+ return [
+ 'share' => $share,
+ 'suffix' => substr(Filesystem::normalizePath($nodePath), strlen($normalizedPath)),
+ ];
+ }, $shares)));
+ }
+
+ private function collectIncomingShares(
+ string $sourceUid,
+ OutputInterface $output,
+ ?string $path,
+ ): array {
+ $output->writeln("Collecting all incoming share information for files and folders of $sourceUid ...");
+
+ $shares = [];
+ $progress = new ProgressBar($output);
+ $normalizedPath = Filesystem::normalizePath($path);
+
+ $offset = 0;
+ while (true) {
+ $sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset);
+ $progress->advance(count($sharePage));
+ if (empty($sharePage)) {
+ break;
+ }
+
+ if ($path !== null && $path !== "$sourceUid/files") {
+ $sharePage = array_filter($sharePage, static function (IShare $share) use ($sourceUid, $normalizedPath) {
+ try {
+ return str_starts_with(Filesystem::normalizePath($sourceUid . '/files' . $share->getTarget() . '/', false), $normalizedPath . '/');
+ } catch (Exception) {
+ return false;
+ }
+ });
+ }
+
+ foreach ($sharePage as $share) {
+ $shares[$share->getNodeId()] = $share;
+ }
+
+ $offset += 50;
+ }
+
+
+ $progress->finish();
+ $output->writeln('');
return $shares;
}
/**
* @throws TransferOwnershipException
*/
- protected function transferFiles(string $sourceUid,
- string $sourcePath,
- string $finalTarget,
- View $view,
- OutputInterface $output): void {
+ protected function transferFiles(
+ string $sourceUid,
+ string $sourcePath,
+ string $finalTarget,
+ View $view,
+ OutputInterface $output,
+ bool $includeExternalStorage,
+ ): void {
$output->writeln("Transferring files to $finalTarget ...");
// This change will help user to transfer the folder specified using --path option.
@@ -302,26 +426,69 @@ class OwnershipTransferService {
$view->mkdir($finalTarget);
$finalTarget = $finalTarget . '/' . basename($sourcePath);
}
- if ($view->rename($sourcePath, $finalTarget) === false) {
- throw new TransferOwnershipException("Could not transfer files.", 1);
+ $sourceInfo = $view->getFileInfo($sourcePath);
+
+ /// handle the external storages mounted at the root, or the admin specifying an external storage with --path
+ if ($sourceInfo->getInternalPath() === '' && $includeExternalStorage) {
+ $this->moveMountContents($view, $sourcePath, $finalTarget);
+ } else {
+ if ($view->rename($sourcePath, $finalTarget, ['checkSubMounts' => false]) === false) {
+ throw new TransferOwnershipException('Could not transfer files.', 1);
+ }
+ }
+
+ if ($includeExternalStorage) {
+ $nestedMounts = $this->mountManager->findIn($sourcePath);
+ foreach ($nestedMounts as $mount) {
+ if ($mount->getMountProvider() === ConfigAdapter::class) {
+ $relativePath = substr(trim($mount->getMountPoint(), '/'), strlen($sourcePath));
+ $this->moveMountContents($view, $mount->getMountPoint(), $finalTarget . $relativePath);
+ }
+ }
}
+
if (!is_dir("$sourceUid/files")) {
// because the files folder is moved away we need to recreate it
$view->mkdir("$sourceUid/files");
}
}
- private function restoreShares(string $sourceUid,
- string $destinationUid,
- array $shares,
- OutputInterface $output) {
- $output->writeln("Restoring shares ...");
+ private function moveMountContents(View $rootView, string $source, string $target) {
+ if ($rootView->copy($source, $target)) {
+ // just doing `rmdir` on the mountpoint would cause it to try and unmount the storage
+ // we need to empty the contents instead
+ $content = $rootView->getDirectoryContent($source);
+ foreach ($content as $item) {
+ if ($item->getType() === FileInfo::TYPE_FOLDER) {
+ $rootView->rmdir($item->getPath());
+ } else {
+ $rootView->unlink($item->getPath());
+ }
+ }
+ } else {
+ throw new TransferOwnershipException("Could not transfer $source to $target");
+ }
+ }
+
+ /**
+ * @param string $targetLocation New location of the transfered node
+ * @param array<array{share: IShare, suffix: string}> $shares previously collected share information
+ */
+ private function restoreShares(
+ string $sourceUid,
+ string $destinationUid,
+ string $targetLocation,
+ array $shares,
+ OutputInterface $output,
+ ):void {
+ $output->writeln('Restoring shares ...');
$progress = new ProgressBar($output, count($shares));
- foreach ($shares as $share) {
+ foreach ($shares as ['share' => $share, 'suffix' => $suffix]) {
try {
- if ($share->getShareType() === IShare::TYPE_USER &&
- $share->getSharedWith() === $destinationUid) {
+ $output->writeln('Transfering share ' . $share->getId() . ' of type ' . $share->getShareType(), OutputInterface::VERBOSITY_VERBOSE);
+ if ($share->getShareType() === IShare::TYPE_USER
+ && $share->getSharedWith() === $destinationUid) {
// Unmount the shares before deleting, so we don't try to get the storage later on.
$shareMountPoint = $this->mountManager->find('/' . $destinationUid . '/files' . $share->getTarget());
if ($shareMountPoint) {
@@ -336,15 +503,115 @@ class OwnershipTransferService {
$share->setSharedBy($destinationUid);
}
+ if ($share->getShareType() === IShare::TYPE_USER
+ && !$this->userManager->userExists($share->getSharedWith())) {
+ // stray share with deleted user
+ $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted user "' . $share->getSharedWith() . '", deleting</error>');
+ $this->shareManager->deleteShare($share);
+ continue;
+ } else {
+ // trigger refetching of the node so that the new owner and mountpoint are taken into account
+ // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
+ $this->userMountCache->clear();
+
+ try {
+ // Try to get the "old" id.
+ // Normally the ID is preserved,
+ // but for transferes between different storages the ID might change
+ $newNodeId = $share->getNode()->getId();
+ } catch (NotFoundException) {
+ // ID has changed due to transfer between different storages
+ // Try to get the new ID from the target path and suffix of the share
+ $node = $this->rootFolder->get(Filesystem::normalizePath($targetLocation . '/' . $suffix));
+ $newNodeId = $node->getId();
+ $output->writeln('Had to change node id to ' . $newNodeId, OutputInterface::VERBOSITY_VERY_VERBOSE);
+ }
+ $share->setNodeId($newNodeId);
+
+ $this->shareManager->updateShare($share, onlyValid: false);
+ }
+ }
+ } catch (NotFoundException $e) {
+ $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
+ } catch (\Throwable $e) {
+ $output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
+ }
+ $progress->advance();
+ }
+ $progress->finish();
+ $output->writeln('');
+ }
+ private function transferIncomingShares(string $sourceUid,
+ string $destinationUid,
+ array $sourceShares,
+ array $destinationShares,
+ OutputInterface $output,
+ string $path,
+ string $finalTarget,
+ bool $move): void {
+ $output->writeln('Restoring incoming shares ...');
+ $progress = new ProgressBar($output, count($sourceShares));
+ $prefix = "$destinationUid/files";
+ $finalShareTarget = '';
+ if (str_starts_with($finalTarget, $prefix)) {
+ $finalShareTarget = substr($finalTarget, strlen($prefix));
+ }
+ foreach ($sourceShares as $share) {
+ try {
+ // Only restore if share is in given path.
+ $pathToCheck = '/';
+ if (trim($path, '/') !== '') {
+ $pathToCheck = '/' . trim($path) . '/';
+ }
+ if (!str_starts_with($share->getTarget(), $pathToCheck)) {
+ continue;
+ }
+ $shareTarget = $share->getTarget();
+ $shareTarget = $finalShareTarget . $shareTarget;
+ if ($share->getShareType() === IShare::TYPE_USER
+ && $share->getSharedBy() === $destinationUid) {
+ $this->shareManager->deleteShare($share);
+ } elseif (isset($destinationShares[$share->getNodeId()])) {
+ $destinationShare = $destinationShares[$share->getNodeId()];
+ // Keep the share which has the most permissions and discard the other one.
+ if ($destinationShare->getPermissions() < $share->getPermissions()) {
+ $this->shareManager->deleteShare($destinationShare);
+ $share->setSharedWith($destinationUid);
+ // trigger refetching of the node so that the new owner and mountpoint are taken into account
+ // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
+ $this->userMountCache->clear();
+ $share->setNodeId($share->getNode()->getId());
+ $this->shareManager->updateShare($share);
+ // The share is already transferred.
+ $progress->advance();
+ if ($move) {
+ continue;
+ }
+ $share->setTarget($shareTarget);
+ $this->shareManager->moveShare($share, $destinationUid);
+ continue;
+ }
+ $this->shareManager->deleteShare($share);
+ } elseif ($share->getShareOwner() === $destinationUid) {
+ $this->shareManager->deleteShare($share);
+ } else {
+ $share->setSharedWith($destinationUid);
+ $share->setNodeId($share->getNode()->getId());
+ $this->shareManager->updateShare($share);
// trigger refetching of the node so that the new owner and mountpoint are taken into account
// otherwise the checks on the share update will fail due to the original node not being available in the new user scope
$this->userMountCache->clear();
- $share->setNodeId($share->getNode()->getId());
-
- $this->shareManager->updateShare($share);
+ // The share is already transferred.
+ $progress->advance();
+ if ($move) {
+ continue;
+ }
+ $share->setTarget($shareTarget);
+ $this->shareManager->moveShare($share, $destinationUid);
+ continue;
}
- } catch (\OCP\Files\NotFoundException $e) {
+ } catch (NotFoundException $e) {
$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
} catch (\Throwable $e) {
$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');