diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2021-09-15 11:26:56 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-15 11:26:56 +0300 |
commit | 995aa6518357b9f371ced5ba8941dfd2a02b0d97 (patch) | |
tree | 21b762102590459d36432f1622e984f68d3c2f1b /apps | |
parent | 208a7b688d50b357e031697cf7ae1c5188c15ba8 (diff) | |
parent | 5b664e02d8179608541cce97a2343eea36aec4f7 (diff) |
Merge pull request #28118 from nextcloud/transfer-incoming-shares
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/lib/Command/TransferOwnership.php | 43 | ||||
-rw-r--r-- | apps/files/lib/Service/OwnershipTransferService.php | 141 |
2 files changed, 180 insertions, 4 deletions
diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php index 71853d632e7..50aa0b21a5f 100644 --- a/apps/files/lib/Command/TransferOwnership.php +++ b/apps/files/lib/Command/TransferOwnership.php @@ -31,12 +31,14 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OCA\Files\Command; use OCA\Files\Exception\TransferOwnershipException; use OCA\Files\Service\OwnershipTransferService; use OCP\IUser; use OCP\IUserManager; +use OCP\IConfig; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -51,17 +53,22 @@ class TransferOwnership extends Command { /** @var OwnershipTransferService */ private $transferService; + /** @var IConfig */ + private $config; + public function __construct(IUserManager $userManager, - OwnershipTransferService $transferService) { + OwnershipTransferService $transferService, + IConfig $config) { parent::__construct(); $this->userManager = $userManager; $this->transferService = $transferService; + $this->config = $config; } protected function configure() { $this ->setName('files:transfer-ownership') - ->setDescription('All files and folders are moved to another user - shares are moved as well.') + ->setDescription('All files and folders are moved to another user - outgoing shares and incoming user file shares (optionally) are moved as well.') ->addArgument( 'source-user', InputArgument::REQUIRED, @@ -83,6 +90,12 @@ class TransferOwnership extends Command { null, InputOption::VALUE_NONE, 'move data from source user to root directory of destination user, which must be empty' + )->addOption( + 'transfer-incoming-shares', + null, + InputOption::VALUE_OPTIONAL, + 'transfer incoming user file shares to destination user. Usage: --transfer-incoming-shares=1 (value required)', + '2' ); } @@ -111,12 +124,36 @@ class TransferOwnership extends Command { } try { + $includeIncomingArgument = $input->getOption('transfer-incoming-shares'); + + switch ($includeIncomingArgument) { + case '0': + $includeIncoming = false; + break; + case '1': + $includeIncoming = true; + break; + case '2': + $includeIncoming = $this->config->getSystemValue('transferIncomingShares', false); + if (gettype($includeIncoming) !== 'boolean') { + $output->writeln("<error> config.php: 'transfer-incoming-shares': wrong usage. Transfer aborted.</error>"); + return 1; + } + break; + default: + $output->writeln("<error>Option --transfer-incoming-shares: wrong usage. Transfer aborted.</error>"); + return 1; + break; + } + $this->transferService->transfer( $sourceUserObject, $destinationUserObject, ltrim($input->getOption('path'), '/'), $output, - $input->getOption('move') === true + $input->getOption('move') === true, + false, + $includeIncoming ); } catch (TransferOwnershipException $e) { $output->writeln("<error>" . $e->getMessage() . "</error>"); diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php index cc54df50541..ecb2365ef1b 100644 --- a/apps/files/lib/Service/OwnershipTransferService.php +++ b/apps/files/lib/Service/OwnershipTransferService.php @@ -29,6 +29,7 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files\Service; use Closure; @@ -93,7 +94,8 @@ class OwnershipTransferService { string $path, ?OutputInterface $output = null, bool $move = false, - bool $firstLogin = false): void { + bool $firstLogin = false, + bool $transferIncomingShares = false): void { $output = $output ?? new NullOutput(); $sourceUid = $sourceUser->getUID(); $destinationUid = $destinationUser->getUID(); @@ -180,6 +182,31 @@ class OwnershipTransferService { $shares, $output ); + + // transfer the incoming shares + if ($transferIncomingShares === true) { + $sourceShares = $this->collectIncomingShares( + $sourceUid, + $output, + $view + ); + $destinationShares = $this->collectIncomingShares( + $destinationUid, + $output, + $view, + true + ); + $this->transferIncomingShares( + $sourceUid, + $destinationUid, + $sourceShares, + $destinationShares, + $output, + $path, + $finalTarget, + $move + ); + } } private function walkFiles(View $view, $path, Closure $callBack) { @@ -253,6 +280,7 @@ class OwnershipTransferService { $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) { $offset = 0; while (true) { @@ -288,6 +316,41 @@ class OwnershipTransferService { return $shares; } + private function collectIncomingShares(string $sourceUid, + OutputInterface $output, + View $view, + bool $addKeys = false): array { + $output->writeln("Collecting all incoming share information for files and folders of $sourceUid ..."); + + $shares = []; + $progress = new ProgressBar($output); + + $offset = 0; + while (true) { + $sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset); + $progress->advance(count($sharePage)); + if (empty($sharePage)) { + break; + } + if ($addKeys) { + foreach ($sharePage as $singleShare) { + $shares[$singleShare->getNodeId()] = $singleShare; + } + } else { + foreach ($sharePage as $singleShare) { + $shares[] = $singleShare; + } + } + + $offset += 50; + } + + + $progress->finish(); + $output->writeln(''); + return $shares; + } + /** * @throws TransferOwnershipException */ @@ -356,4 +419,80 @@ class OwnershipTransferService { $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"; + if (substr($finalTarget, 0, strlen($prefix)) === $prefix) { + $finalShareTarget = substr($finalTarget, strlen($prefix)); + } + foreach ($sourceShares as $share) { + try { + // Only restore if share is in given path. + $pathToCheck = '/' . trim($path) . '/'; + if (substr($share->getTarget(), 0, strlen($pathToCheck)) !== $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(); + // The share is already transferred. + $progress->advance(); + if ($move) { + continue; + } + $share->setTarget($shareTarget); + $this->shareManager->moveShare($share, $destinationUid); + continue; + } + } catch (\OCP\Files\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>'); + } + $progress->advance(); + } + $progress->finish(); + $output->writeln(''); + } } |