Skip to content

Commit

Permalink
Added support for transfering incoming file shares.
Browse files Browse the repository at this point in the history
- new option --transfer-incoming-shares=1 | 0
- new config.php option 'transfer-incoming-shares' => true | false

The command line option overrules the config.php option.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
Co-authored-by: Immanuel Pasanec <immanuel.pasanec@compaso.de>
  • Loading branch information
PVince81 and ipasanec committed Sep 15, 2021
1 parent 7c870fa commit 249582a
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 4 deletions.
43 changes: 40 additions & 3 deletions apps/files/lib/Command/TransferOwnership.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@
* 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;
Expand All @@ -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,
Expand All @@ -83,6 +90,12 @@ protected function configure() {
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'
);
}

Expand Down Expand Up @@ -111,12 +124,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

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>");
Expand Down
141 changes: 140 additions & 1 deletion apps/files/lib/Service/OwnershipTransferService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Files\Service;

use Closure;
Expand Down Expand Up @@ -93,7 +94,8 @@ public function transfer(IUser $sourceUser,
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();
Expand Down Expand Up @@ -180,6 +182,31 @@ public function transfer(IUser $sourceUser,
$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) {
Expand Down Expand Up @@ -253,6 +280,7 @@ private function collectUsersShares(string $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) {
$offset = 0;
while (true) {
Expand Down Expand Up @@ -288,6 +316,41 @@ private function collectUsersShares(string $sourceUid,
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
*/
Expand Down Expand Up @@ -356,4 +419,80 @@ private function restoreShares(string $sourceUid,
$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('');
}
}

0 comments on commit 249582a

Please sign in to comment.