mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
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 <vincent@nextcloud.com>
This commit is contained in:
parent
95662a1070
commit
1f42657bb9
2 changed files with 180 additions and 4 deletions
|
|
@ -31,12 +31,14 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Files\Command;
|
namespace OCA\Files\Command;
|
||||||
|
|
||||||
use OCA\Files\Exception\TransferOwnershipException;
|
use OCA\Files\Exception\TransferOwnershipException;
|
||||||
use OCA\Files\Service\OwnershipTransferService;
|
use OCA\Files\Service\OwnershipTransferService;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
|
use OCP\IConfig;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
@ -51,17 +53,22 @@ class TransferOwnership extends Command {
|
||||||
/** @var OwnershipTransferService */
|
/** @var OwnershipTransferService */
|
||||||
private $transferService;
|
private $transferService;
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
|
|
||||||
public function __construct(IUserManager $userManager,
|
public function __construct(IUserManager $userManager,
|
||||||
OwnershipTransferService $transferService) {
|
OwnershipTransferService $transferService,
|
||||||
|
IConfig $config) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
$this->transferService = $transferService;
|
$this->transferService = $transferService;
|
||||||
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure() {
|
protected function configure() {
|
||||||
$this
|
$this
|
||||||
->setName('files:transfer-ownership')
|
->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(
|
->addArgument(
|
||||||
'source-user',
|
'source-user',
|
||||||
InputArgument::REQUIRED,
|
InputArgument::REQUIRED,
|
||||||
|
|
@ -83,6 +90,12 @@ class TransferOwnership extends Command {
|
||||||
null,
|
null,
|
||||||
InputOption::VALUE_NONE,
|
InputOption::VALUE_NONE,
|
||||||
'move data from source user to root directory of destination user, which must be empty'
|
'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 {
|
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(
|
$this->transferService->transfer(
|
||||||
$sourceUserObject,
|
$sourceUserObject,
|
||||||
$destinationUserObject,
|
$destinationUserObject,
|
||||||
ltrim($input->getOption('path'), '/'),
|
ltrim($input->getOption('path'), '/'),
|
||||||
$output,
|
$output,
|
||||||
$input->getOption('move') === true
|
$input->getOption('move') === true,
|
||||||
|
false,
|
||||||
|
$includeIncoming
|
||||||
);
|
);
|
||||||
} catch (TransferOwnershipException $e) {
|
} catch (TransferOwnershipException $e) {
|
||||||
$output->writeln("<error>" . $e->getMessage() . "</error>");
|
$output->writeln("<error>" . $e->getMessage() . "</error>");
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace OCA\Files\Service;
|
namespace OCA\Files\Service;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
|
@ -93,7 +94,8 @@ class OwnershipTransferService {
|
||||||
string $path,
|
string $path,
|
||||||
?OutputInterface $output = null,
|
?OutputInterface $output = null,
|
||||||
bool $move = false,
|
bool $move = false,
|
||||||
bool $firstLogin = false): void {
|
bool $firstLogin = false,
|
||||||
|
bool $transferIncomingShares = false): void {
|
||||||
$output = $output ?? new NullOutput();
|
$output = $output ?? new NullOutput();
|
||||||
$sourceUid = $sourceUser->getUID();
|
$sourceUid = $sourceUser->getUID();
|
||||||
$destinationUid = $destinationUser->getUID();
|
$destinationUid = $destinationUser->getUID();
|
||||||
|
|
@ -180,6 +182,31 @@ class OwnershipTransferService {
|
||||||
$shares,
|
$shares,
|
||||||
$output
|
$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) {
|
private function walkFiles(View $view, $path, Closure $callBack) {
|
||||||
|
|
@ -253,6 +280,7 @@ class OwnershipTransferService {
|
||||||
|
|
||||||
$shares = [];
|
$shares = [];
|
||||||
$progress = new ProgressBar($output);
|
$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) {
|
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;
|
$offset = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -288,6 +316,41 @@ class OwnershipTransferService {
|
||||||
return $shares;
|
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
|
* @throws TransferOwnershipException
|
||||||
*/
|
*/
|
||||||
|
|
@ -356,4 +419,80 @@ class OwnershipTransferService {
|
||||||
$progress->finish();
|
$progress->finish();
|
||||||
$output->writeln('');
|
$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('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue