2019-12-03 13:57:53 -05:00
< ? php
declare ( strict_types = 1 );
2019-11-20 05:41:31 -05:00
/**
2024-05-28 10:42:42 -04:00
* SPDX - FileCopyrightText : 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2019-11-20 05:41:31 -05:00
*/
2021-08-27 11:55:37 -04:00
2019-11-20 05:41:31 -05:00
namespace OCA\Files\Service ;
use Closure ;
2025-06-23 02:24:13 -04:00
use Exception ;
2019-11-20 05:41:31 -05:00
use OC\Files\Filesystem ;
use OC\Files\View ;
2024-10-18 06:04:22 -04:00
use OC\User\NoUserException ;
2024-10-10 06:40:31 -04:00
use OCA\Encryption\Util ;
2019-11-20 05:41:31 -05:00
use OCA\Files\Exception\TransferOwnershipException ;
2025-04-23 11:28:05 -04:00
use OCA\Files_External\Config\ConfigAdapter ;
2019-11-20 05:41:31 -05:00
use OCP\Encryption\IManager as IEncryptionManager ;
2026-01-22 13:15:29 -05:00
use OCP\EventDispatcher\IEventDispatcher ;
2025-04-23 11:28:05 -04:00
use OCP\Files\Config\IHomeMountProvider ;
2020-07-31 05:10:48 -04:00
use OCP\Files\Config\IUserMountCache ;
2025-06-23 02:24:13 -04:00
use OCP\Files\File ;
2019-11-20 05:41:31 -05:00
use OCP\Files\FileInfo ;
2020-04-08 09:41:20 -04:00
use OCP\Files\InvalidPathException ;
2024-04-17 16:27:32 -04:00
use OCP\Files\IRootFolder ;
2019-11-20 05:41:31 -05:00
use OCP\Files\Mount\IMountManager ;
2024-10-10 06:40:31 -04:00
use OCP\Files\NotFoundException ;
2019-11-20 05:41:31 -05:00
use OCP\IUser ;
2021-10-04 05:04:04 -04:00
use OCP\IUserManager ;
2024-04-17 17:50:18 -04:00
use OCP\L10N\IFactory ;
2024-10-10 06:40:31 -04:00
use OCP\Server ;
2026-01-22 13:15:29 -05:00
use OCP\Share\Events\ShareTransferredEvent ;
2019-11-20 05:41:31 -05:00
use OCP\Share\IManager as IShareManager ;
2020-06-24 10:49:16 -04:00
use OCP\Share\IShare ;
2019-11-20 05:41:31 -05:00
use Symfony\Component\Console\Helper\ProgressBar ;
use Symfony\Component\Console\Output\NullOutput ;
use Symfony\Component\Console\Output\OutputInterface ;
use function array_merge ;
use function basename ;
use function count ;
use function date ;
use function is_dir ;
use function rtrim ;
class OwnershipTransferService {
2024-04-17 16:27:32 -04:00
public function __construct (
2024-10-18 06:04:22 -04:00
private IEncryptionManager $encryptionManager ,
2024-04-17 16:27:32 -04:00
private IShareManager $shareManager ,
private IMountManager $mountManager ,
private IUserMountCache $userMountCache ,
private IUserManager $userManager ,
2024-04-17 17:50:18 -04:00
private IFactory $l10nFactory ,
2024-06-27 14:40:05 -04:00
private IRootFolder $rootFolder ,
2026-01-22 13:15:29 -05:00
private IEventDispatcher $eventDispatcher ,
2024-04-17 16:27:32 -04:00
) {
2019-11-20 05:41:31 -05:00
}
/**
* @ param IUser $sourceUser
* @ param IUser $destinationUser
* @ param string $path
*
2020-01-03 02:43:39 -05:00
* @ param OutputInterface | null $output
* @ param bool $move
2019-11-20 05:41:31 -05:00
* @ throws TransferOwnershipException
2024-10-18 06:04:22 -04:00
* @ throws NoUserException
2019-11-20 05:41:31 -05:00
*/
2024-04-17 16:27:32 -04:00
public function transfer (
IUser $sourceUser ,
2019-11-20 05:41:31 -05:00
IUser $destinationUser ,
string $path ,
2020-01-03 02:43:39 -05:00
? OutputInterface $output = null ,
2020-01-31 05:04:43 -05:00
bool $move = false ,
2021-08-27 11:55:37 -04:00
bool $firstLogin = false ,
2025-04-23 11:28:05 -04:00
bool $includeExternalStorage = false ,
2025-11-25 09:45:40 -05:00
bool $useUserId = false ,
2024-04-17 16:27:32 -04:00
) : void {
2019-11-20 05:41:31 -05:00
$output = $output ? ? new NullOutput ();
$sourceUid = $sourceUser -> getUID ();
$destinationUid = $destinationUser -> getUID ();
$sourcePath = rtrim ( $sourceUid . '/files/' . $path , '/' );
2021-03-16 12:35:17 -04:00
// 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 )) {
2019-12-06 08:30:05 -05:00
throw new TransferOwnershipException ( 'The target user is not ready to accept files. The user has at least to have logged in once.' , 2 );
2019-11-20 05:41:31 -05:00
}
2020-04-08 09:41:20 -04:00
// setup filesystem
2021-03-16 12:35:17 -04:00
// Requesting the user folder will set it up if the user hasn't logged in before
2023-04-06 14:51:56 -04:00
// 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
2024-06-25 19:18:30 -04:00
\OC_Util :: setupFS ( $sourceUser -> getUID ());
2023-04-06 14:51:56 -04:00
\OC_Util :: setupFS ( $destinationUser -> getUID ());
2024-06-27 14:40:05 -04:00
$this -> rootFolder -> getUserFolder ( $sourceUser -> getUID ());
$this -> rootFolder -> getUserFolder ( $destinationUser -> getUID ());
2020-04-08 09:41:20 -04:00
Filesystem :: initMountPoints ( $sourceUid );
Filesystem :: initMountPoints ( $destinationUid );
$view = new View ();
2020-01-03 02:43:39 -05:00
if ( $move ) {
$finalTarget = " $destinationUid /files/ " ;
} else {
2024-04-17 17:50:18 -04:00
$l = $this -> l10nFactory -> get ( 'files' , $this -> l10nFactory -> getUserLanguage ( $destinationUser ));
2020-01-03 02:43:39 -05:00
$date = date ( 'Y-m-d H-i-s' );
2019-11-20 05:41:31 -05:00
2025-11-25 09:45:40 -05:00
if ( $useUserId ) {
$cleanUserName = $sourceUid ;
} else {
2025-12-01 05:36:18 -05:00
$cleanUserName = $this -> sanitizeFolderName ( $sourceUser -> getDisplayName ());
2025-12-01 07:52:59 -05:00
if ( $cleanUserName === '' ) {
2025-12-01 05:36:18 -05:00
$cleanUserName = $sourceUid ;
}
2025-11-25 09:45:40 -05:00
}
2025-12-01 05:36:18 -05:00
2024-04-17 17:50:18 -04:00
$finalTarget = " $destinationUid /files/ " . $this -> sanitizeFolderName ( $l -> t ( 'Transferred from %1$s on %2$s' , [ $cleanUserName , $date ]));
2020-04-08 09:41:20 -04:00
try {
$view -> verifyPath ( dirname ( $finalTarget ), basename ( $finalTarget ));
} catch ( InvalidPathException $e ) {
2024-04-17 17:50:18 -04:00
$finalTarget = " $destinationUid /files/ " . $this -> sanitizeFolderName ( $l -> t ( 'Transferred from %1$s on %2$s' , [ $sourceUid , $date ]));
2020-04-08 09:41:20 -04:00
}
}
2019-11-20 05:41:31 -05:00
2019-11-27 05:47:26 -05:00
if ( ! ( $view -> is_dir ( $sourcePath ) || $view -> is_file ( $sourcePath ))) {
2019-11-20 05:41:31 -05:00
throw new TransferOwnershipException ( " Unknown path provided: $path " , 1 );
}
2022-02-01 04:47:40 -05:00
if ( $move && ! $view -> is_dir ( $finalTarget )) {
// Initialize storage
\OC_Util :: setupFS ( $destinationUser -> getUID ());
}
if ( $move && ! $firstLogin && count ( $view -> getDirectoryContent ( $finalTarget )) > 0 ) {
2020-01-03 02:43:39 -05:00
throw new TransferOwnershipException ( 'Destination path does not exists or is not empty' , 1 );
}
2019-11-20 05:41:31 -05:00
// analyse source folder
$this -> analyse (
$sourceUid ,
$destinationUid ,
$sourcePath ,
$view ,
$output
);
// collect all the shares
$shares = $this -> collectUsersShares (
$sourceUid ,
2020-07-31 05:10:48 -04:00
$output ,
$view ,
$sourcePath
2019-11-20 05:41:31 -05:00
);
2025-02-05 07:08:39 -05:00
$sourceSize = $view -> getFileInfo ( $sourcePath ) -> getSize ();
2019-11-20 05:41:31 -05:00
// transfer the files
$this -> transferFiles (
$sourceUid ,
$sourcePath ,
$finalTarget ,
$view ,
2025-04-23 11:28:05 -04:00
$output ,
$includeExternalStorage ,
2019-11-20 05:41:31 -05:00
);
2025-02-05 07:08:39 -05:00
$sizeDifference = $sourceSize - $view -> getFileInfo ( $finalTarget ) -> getSize ();
2019-11-20 05:41:31 -05:00
2021-08-27 11:55:37 -04:00
// transfer the incoming shares
2025-06-03 10:16:19 -04:00
$sourceShares = $this -> collectIncomingShares (
$sourceUid ,
$output ,
$sourcePath ,
);
$destinationShares = $this -> collectIncomingShares (
$destinationUid ,
$output ,
null ,
);
$this -> transferIncomingShares (
$sourceUid ,
$destinationUid ,
$sourceShares ,
$destinationShares ,
$output ,
$path ,
$finalTarget ,
$move
);
2024-08-26 05:31:39 -04:00
$destinationPath = $finalTarget . '/' . $path ;
// restore the shares
$this -> restoreShares (
$sourceUid ,
$destinationUid ,
$destinationPath ,
$shares ,
$output
);
2025-02-05 07:08:39 -05:00
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. " );
}
2019-11-20 05:41:31 -05:00
}
2024-04-17 17:50:18 -04:00
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 );
}
2019-11-20 05:41:31 -05:00
private function walkFiles ( View $view , $path , Closure $callBack ) {
foreach ( $view -> getDirectoryContent ( $path ) as $fileInfo ) {
if ( ! $callBack ( $fileInfo )) {
return ;
}
if ( $fileInfo -> getType () === FileInfo :: TYPE_FOLDER ) {
$this -> walkFiles ( $view , $fileInfo -> getPath (), $callBack );
}
}
}
/**
* @ param OutputInterface $output
*
2024-08-27 06:53:38 -04:00
* @ throws TransferOwnershipException
2019-11-20 05:41:31 -05:00
*/
2025-04-23 11:28:05 -04:00
protected function analyse (
string $sourceUid ,
2019-11-20 05:41:31 -05:00
string $destinationUid ,
string $sourcePath ,
View $view ,
2025-04-23 11:28:05 -04:00
OutputInterface $output ,
bool $includeExternalStorage = false ,
) : void {
2019-11-20 05:41:31 -05:00
$output -> writeln ( 'Validating quota' );
2024-08-27 06:53:38 -04:00
$sourceFileInfo = $view -> getFileInfo ( $sourcePath , false );
if ( $sourceFileInfo === false ) {
throw new TransferOwnershipException ( " Unknown path provided: $sourcePath " , 1 );
}
$size = $sourceFileInfo -> getSize ( false );
2019-11-20 05:41:31 -05:00
$freeSpace = $view -> free_space ( $destinationUid . '/files/' );
2020-08-10 09:45:03 -04:00
if ( $size > $freeSpace && $freeSpace !== FileInfo :: SPACE_UNKNOWN ) {
2024-08-27 06:53:38 -04:00
throw new TransferOwnershipException ( 'Target user does not have enough free space available.' , 1 );
2019-11-20 05:41:31 -05:00
}
$output -> writeln ( " Analysing files of $sourceUid ... " );
$progress = new ProgressBar ( $output );
$progress -> start ();
2024-08-27 06:53:38 -04:00
if ( $this -> encryptionManager -> isEnabled ()) {
2024-10-10 06:40:31 -04:00
$masterKeyEnabled = Server :: get ( Util :: class ) -> isMasterKeyEnabled ();
2024-08-27 06:53:38 -04:00
} else {
$masterKeyEnabled = false ;
}
2019-11-20 05:41:31 -05:00
$encryptedFiles = [];
2024-08-27 06:53:38 -04:00
if ( $sourceFileInfo -> getType () === FileInfo :: TYPE_FOLDER ) {
if ( $sourceFileInfo -> isEncrypted ()) {
/* Encrypted folder means e2ee encrypted */
$encryptedFiles [] = $sourceFileInfo ;
} else {
$this -> walkFiles ( $view , $sourcePath ,
2025-04-23 11:28:05 -04:00
function ( FileInfo $fileInfo ) use ( $progress , $masterKeyEnabled , & $encryptedFiles , $includeExternalStorage ) {
2024-08-27 06:53:38 -04:00
if ( $fileInfo -> getType () === FileInfo :: TYPE_FOLDER ) {
2025-04-23 11:28:05 -04:00
$mount = $fileInfo -> getMountPoint ();
2024-08-27 06:53:38 -04:00
// only analyze into folders from main storage,
2025-04-23 11:28:05 -04:00
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 {
2024-08-27 06:53:38 -04:00
return false ;
}
}
$progress -> advance ();
if ( $fileInfo -> isEncrypted () && ! $masterKeyEnabled ) {
2024-09-03 03:46:36 -04:00
/* Encrypted file means SSE, we can only transfer it if master key is enabled */
2024-08-27 06:53:38 -04:00
$encryptedFiles [] = $fileInfo ;
}
return true ;
});
}
} elseif ( $sourceFileInfo -> isEncrypted () && ! $masterKeyEnabled ) {
2024-09-03 03:46:36 -04:00
/* Encrypted file means SSE, we can only transfer it if master key is enabled */
2024-08-27 06:53:38 -04:00
$encryptedFiles [] = $sourceFileInfo ;
}
2019-11-20 05:41:31 -05:00
$progress -> finish ();
$output -> writeln ( '' );
// no file is allowed to be encrypted
if ( ! empty ( $encryptedFiles )) {
2019-12-06 08:30:05 -05:00
$output -> writeln ( '<error>Some files are encrypted - please decrypt them first.</error>' );
2019-11-20 05:41:31 -05:00
foreach ( $encryptedFiles as $encryptedFile ) {
/** @var FileInfo $encryptedFile */
$output -> writeln ( ' ' . $encryptedFile -> getPath ());
}
2024-08-27 06:53:38 -04:00
throw new TransferOwnershipException ( 'Some files are encrypted - please decrypt them first.' , 1 );
2019-11-20 05:41:31 -05:00
}
}
2024-04-17 16:27:32 -04:00
/**
* @ return array < array { share : IShare , suffix : string } >
*/
private function collectUsersShares (
string $sourceUid ,
2020-07-31 05:10:48 -04:00
OutputInterface $output ,
View $view ,
2024-04-17 16:27:32 -04:00
string $path ,
) : array {
2019-12-06 08:30:05 -05:00
$output -> writeln ( " Collecting all share information for files and folders of $sourceUid ... " );
2019-11-20 05:41:31 -05:00
$shares = [];
$progress = new ProgressBar ( $output );
2021-08-27 11:55:37 -04:00
2024-04-17 16:27:32 -04:00
$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 ,
];
foreach ( $supportedShareTypes as $shareType ) {
2019-11-20 05:41:31 -05:00
$offset = 0 ;
while ( true ) {
2024-11-14 01:41:41 -05:00
$sharePage = $this -> shareManager -> getSharesBy ( $sourceUid , $shareType , null , true , 50 , $offset , onlyValid : false );
2019-11-20 05:41:31 -05:00
$progress -> advance ( count ( $sharePage ));
if ( empty ( $sharePage )) {
break ;
}
2020-09-07 11:35:29 -04:00
if ( $path !== " $sourceUid /files " ) {
2024-04-17 16:27:32 -04:00
$sharePage = array_filter ( $sharePage , function ( IShare $share ) use ( $view , $normalizedPath ) {
2020-07-31 05:10:48 -04:00
try {
2025-08-13 05:55:51 -04:00
$sourceNode = $share -> getNode ();
$relativePath = $view -> getRelativePath ( $sourceNode -> getPath ());
2020-07-31 05:10:48 -04:00
2025-08-13 05:55:51 -04:00
return str_starts_with ( $relativePath . '/' , $normalizedPath . '/' );
2025-06-23 02:24:13 -04:00
} catch ( Exception $e ) {
2020-07-31 05:10:48 -04:00
return false ;
}
});
}
2019-11-20 05:41:31 -05:00
$shares = array_merge ( $shares , $sharePage );
$offset += 50 ;
}
}
$progress -> finish ();
$output -> writeln ( '' );
2024-04-17 16:27:32 -04:00
2024-12-10 12:23:02 -05:00
return array_values ( array_filter ( array_map ( function ( IShare $share ) use ( $view , $normalizedPath , $output , $sourceUid ) {
try {
2025-08-13 05:55:51 -04:00
$nodePath = $view -> getRelativePath ( $share -> getNode () -> getPath ());
2024-12-10 12:23:02 -05:00
} 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 )));
2019-11-20 05:41:31 -05:00
}
2025-06-23 02:24:13 -04:00
private function collectIncomingShares (
string $sourceUid ,
2021-08-27 11:55:37 -04:00
OutputInterface $output ,
2025-06-23 02:24:13 -04:00
? string $path ,
) : array {
2021-08-27 11:55:37 -04:00
$output -> writeln ( " Collecting all incoming share information for files and folders of $sourceUid ... " );
$shares = [];
$progress = new ProgressBar ( $output );
2025-06-23 02:24:13 -04:00
$normalizedPath = Filesystem :: normalizePath ( $path );
2021-08-27 11:55:37 -04:00
$offset = 0 ;
while ( true ) {
$sharePage = $this -> shareManager -> getSharedWith ( $sourceUid , IShare :: TYPE_USER , null , 50 , $offset );
$progress -> advance ( count ( $sharePage ));
if ( empty ( $sharePage )) {
break ;
}
2025-06-23 02:24:13 -04:00
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 ;
2021-08-27 11:55:37 -04:00
}
$offset += 50 ;
}
$progress -> finish ();
$output -> writeln ( '' );
return $shares ;
}
2019-11-27 03:21:17 -05:00
/**
* @ throws TransferOwnershipException
*/
2025-04-23 11:28:05 -04:00
protected function transferFiles (
string $sourceUid ,
2019-11-20 05:41:31 -05:00
string $sourcePath ,
string $finalTarget ,
View $view ,
2025-04-23 11:28:05 -04:00
OutputInterface $output ,
bool $includeExternalStorage ,
) : void {
2019-11-20 05:41:31 -05:00
$output -> writeln ( " Transferring files to $finalTarget ... " );
// This change will help user to transfer the folder specified using --path option.
// Else only the content inside folder is transferred which is not correct.
if ( $sourcePath !== " $sourceUid /files " ) {
$view -> mkdir ( $finalTarget );
$finalTarget = $finalTarget . '/' . basename ( $sourcePath );
}
2025-04-23 14:32:19 -04:00
$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 );
}
2019-11-27 03:21:17 -05:00
}
2025-04-23 11:28:05 -04:00
if ( $includeExternalStorage ) {
$nestedMounts = $this -> mountManager -> findIn ( $sourcePath );
foreach ( $nestedMounts as $mount ) {
if ( $mount -> getMountProvider () === ConfigAdapter :: class ) {
2025-04-23 14:32:19 -04:00
$relativePath = substr ( trim ( $mount -> getMountPoint (), '/' ), strlen ( $sourcePath ));
$this -> moveMountContents ( $view , $mount -> getMountPoint (), $finalTarget . $relativePath );
2025-04-23 11:28:05 -04:00
}
}
}
2019-11-20 05:41:31 -05:00
if ( ! is_dir ( " $sourceUid /files " )) {
// because the files folder is moved away we need to recreate it
$view -> mkdir ( " $sourceUid /files " );
}
}
2025-04-23 14:32:19 -04:00
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 " );
}
}
2024-04-17 16:27:32 -04:00
/**
* @ 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 ,
2019-11-20 05:41:31 -05:00
string $destinationUid ,
2024-04-17 16:27:32 -04:00
string $targetLocation ,
2019-11-20 05:41:31 -05:00
array $shares ,
2024-04-17 16:27:32 -04:00
OutputInterface $output ,
) : void {
2019-11-20 05:41:31 -05:00
$output -> writeln ( 'Restoring shares ...' );
$progress = new ProgressBar ( $output , count ( $shares ));
2024-04-17 16:27:32 -04:00
foreach ( $shares as [ 'share' => $share , 'suffix' => $suffix ]) {
2019-11-20 05:41:31 -05:00
try {
2024-10-07 09:35:47 -04:00
$output -> writeln ( 'Transfering share ' . $share -> getId () . ' of type ' . $share -> getShareType (), OutputInterface :: VERBOSITY_VERBOSE );
2020-06-24 10:49:16 -04:00
if ( $share -> getShareType () === IShare :: TYPE_USER
2019-11-20 05:41:31 -05:00
&& $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 ) {
$this -> mountManager -> removeMount ( $shareMountPoint -> getMountPoint ());
}
$this -> shareManager -> deleteShare ( $share );
} else {
if ( $share -> getShareOwner () === $sourceUid ) {
$share -> setShareOwner ( $destinationUid );
}
if ( $share -> getSharedBy () === $sourceUid ) {
$share -> setSharedBy ( $destinationUid );
}
2021-10-04 05:04:04 -04:00
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 ();
2024-04-17 16:27:32 -04:00
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 ();
2024-10-10 06:40:31 -04:00
} catch ( NotFoundException ) {
2024-04-17 16:27:32 -04:00
// ID has changed due to transfer between different storages
// Try to get the new ID from the target path and suffix of the share
2024-06-27 14:40:05 -04:00
$node = $this -> rootFolder -> get ( Filesystem :: normalizePath ( $targetLocation . '/' . $suffix ));
2024-04-17 16:27:32 -04:00
$newNodeId = $node -> getId ();
2024-10-07 09:35:47 -04:00
$output -> writeln ( 'Had to change node id to ' . $newNodeId , OutputInterface :: VERBOSITY_VERY_VERBOSE );
2024-04-17 16:27:32 -04:00
}
$share -> setNodeId ( $newNodeId );
2021-10-04 05:04:04 -04:00
2024-11-14 01:41:41 -05:00
$this -> shareManager -> updateShare ( $share , onlyValid : false );
2021-10-04 05:04:04 -04:00
}
2019-11-20 05:41:31 -05:00
}
2024-10-10 06:40:31 -04:00
} catch ( NotFoundException $e ) {
2019-11-20 05:41:31 -05:00
$output -> writeln ( '<error>Share with id ' . $share -> getId () . ' points at deleted file, skipping</error>' );
2020-01-03 02:50:37 -05:00
} catch ( \Throwable $e ) {
2021-08-24 03:19:52 -04:00
$output -> writeln ( '<error>Could not restore share with id ' . $share -> getId () . ':' . $e -> getMessage () . ' : ' . $e -> getTraceAsString () . '</error>' );
2019-11-20 05:41:31 -05:00
}
2026-01-22 13:15:29 -05:00
$this -> eventDispatcher -> dispatchTyped ( new ShareTransferredEvent ( $share ));
2019-11-20 05:41:31 -05:00
$progress -> advance ();
}
$progress -> finish ();
$output -> writeln ( '' );
}
2021-08-27 11:55:37 -04:00
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 " ;
2022-02-10 03:50:21 -05:00
$finalShareTarget = '' ;
2023-07-06 21:15:34 -04:00
if ( str_starts_with ( $finalTarget , $prefix )) {
2021-08-27 11:55:37 -04:00
$finalShareTarget = substr ( $finalTarget , strlen ( $prefix ));
}
foreach ( $sourceShares as $share ) {
try {
// Only restore if share is in given path.
2022-02-10 03:50:21 -05:00
$pathToCheck = '/' ;
if ( trim ( $path , '/' ) !== '' ) {
$pathToCheck = '/' . trim ( $path ) . '/' ;
}
2023-07-06 21:15:34 -04:00
if ( ! str_starts_with ( $share -> getTarget (), $pathToCheck )) {
2021-08-27 11:55:37 -04:00
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 ;
}
2024-10-10 06:40:31 -04:00
} catch ( NotFoundException $e ) {
2021-08-27 11:55:37 -04:00
$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 ( '' );
}
2019-11-20 05:41:31 -05:00
}