Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added support for transfering incoming file shares.
- 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 <[email protected]>
Co-authored-by: Immanuel Pasanec <[email protected]>
  • Loading branch information
PVince81 and ipasanec committed Sep 15, 2021
commit 249582a8116ffc0366c2d07a06ae0a221675e065
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('');
}
}