1212 * @author Roeland Jago Douma <roeland@famdouma.nl>
1313 * @author Sascha Wiswedel <sascha.wiswedel@nextcloud.com>
1414 * @author Tobia De Koninck <LEDfan@users.noreply.github.com>
15+ * @author Ferdinand Thiessen <opensource@fthiessen.de>
1516 *
1617 * @license GNU AGPL version 3 or any later version
1718 *
3334namespace OCA \Files \Service ;
3435
3536use Closure ;
37+ use OC \Encryption \Manager as EncryptionManager ;
3638use OC \Files \Filesystem ;
3739use OC \Files \View ;
3840use OCA \Files \Exception \TransferOwnershipException ;
4143use OCP \Files \FileInfo ;
4244use OCP \Files \IHomeStorage ;
4345use OCP \Files \InvalidPathException ;
46+ use OCP \Files \IRootFolder ;
4447use OCP \Files \Mount \IMountManager ;
4548use OCP \IUser ;
4649use OCP \IUserManager ;
5861
5962class OwnershipTransferService {
6063
61- /** @var IEncryptionManager */
62- private $ encryptionManager ;
63-
64- /** @var IShareManager */
65- private $ shareManager ;
66-
67- /** @var IMountManager */
68- private $ mountManager ;
69-
70- /** @var IUserMountCache */
71- private $ userMountCache ;
72-
73- /** @var IUserManager */
74- private $ userManager ;
75-
76- public function __construct (IEncryptionManager $ manager ,
77- IShareManager $ shareManager ,
78- IMountManager $ mountManager ,
79- IUserMountCache $ userMountCache ,
80- IUserManager $ userManager ) {
81- $ this ->encryptionManager = $ manager ;
82- $ this ->shareManager = $ shareManager ;
83- $ this ->mountManager = $ mountManager ;
84- $ this ->userMountCache = $ userMountCache ;
85- $ this ->userManager = $ userManager ;
64+ public function __construct (
65+ private IEncryptionManager |EncryptionManager $ encryptionManager ,
66+ private IShareManager $ shareManager ,
67+ private IMountManager $ mountManager ,
68+ private IUserMountCache $ userMountCache ,
69+ private IUserManager $ userManager ,
70+ ) {
8671 }
8772
8873 /**
@@ -95,13 +80,15 @@ public function __construct(IEncryptionManager $manager,
9580 * @throws TransferOwnershipException
9681 * @throws \OC\User\NoUserException
9782 */
98- public function transfer (IUser $ sourceUser ,
83+ public function transfer (
84+ IUser $ sourceUser ,
9985 IUser $ destinationUser ,
10086 string $ path ,
10187 ?OutputInterface $ output = null ,
10288 bool $ move = false ,
10389 bool $ firstLogin = false ,
104- bool $ transferIncomingShares = false ): void {
90+ bool $ transferIncomingShares = false ,
91+ ): void {
10592 $ output = $ output ?? new NullOutput ();
10693 $ sourceUid = $ sourceUser ->getUID ();
10794 $ destinationUid = $ destinationUser ->getUID ();
@@ -183,10 +170,12 @@ public function transfer(IUser $sourceUser,
183170 $ output
184171 );
185172
173+ $ destinationPath = $ finalTarget . '/ ' . $ path ;
186174 // restore the shares
187175 $ this ->restoreShares (
188176 $ sourceUid ,
189177 $ destinationUid ,
178+ $ destinationPath ,
190179 $ shares ,
191180 $ output
192181 );
@@ -280,16 +269,35 @@ function (FileInfo $fileInfo) use ($progress) {
280269 }
281270 }
282271
283- private function collectUsersShares (string $ sourceUid ,
272+ /**
273+ * @return array<array{share: IShare, suffix: string}>
274+ */
275+ private function collectUsersShares (
276+ string $ sourceUid ,
284277 OutputInterface $ output ,
285278 View $ view ,
286- string $ path ): array {
279+ string $ path ,
280+ ): array {
287281 $ output ->writeln ("Collecting all share information for files and folders of $ sourceUid ... " );
288282
289283 $ shares = [];
290284 $ progress = new ProgressBar ($ output );
291285
292- 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 , IShare::TYPE_SCIENCEMESH ] as $ shareType ) {
286+ $ normalizedPath = Filesystem::normalizePath ($ path );
287+
288+ $ supportedShareTypes = [
289+ IShare::TYPE_GROUP ,
290+ IShare::TYPE_USER ,
291+ IShare::TYPE_LINK ,
292+ IShare::TYPE_REMOTE ,
293+ IShare::TYPE_ROOM ,
294+ IShare::TYPE_EMAIL ,
295+ IShare::TYPE_CIRCLE ,
296+ IShare::TYPE_DECK ,
297+ IShare::TYPE_SCIENCEMESH ,
298+ ];
299+
300+ foreach ($ supportedShareTypes as $ shareType ) {
293301 $ offset = 0 ;
294302 while (true ) {
295303 $ sharePage = $ this ->shareManager ->getSharesBy ($ sourceUid , $ shareType , null , true , 50 , $ offset );
@@ -298,17 +306,17 @@ private function collectUsersShares(string $sourceUid,
298306 break ;
299307 }
300308 if ($ path !== "$ sourceUid/files " ) {
301- $ sharePage = array_filter ($ sharePage , function (IShare $ share ) use ($ view , $ path ) {
309+ $ sharePage = array_filter ($ sharePage , function (IShare $ share ) use ($ view , $ normalizedPath ) {
302310 try {
303311 $ relativePath = $ view ->getPath ($ share ->getNodeId ());
304- $ singleFileTranfer = $ view ->is_file ($ path );
312+ $ singleFileTranfer = $ view ->is_file ($ normalizedPath );
305313 if ($ singleFileTranfer ) {
306- return Filesystem::normalizePath ($ relativePath ) === Filesystem:: normalizePath ( $ path ) ;
314+ return Filesystem::normalizePath ($ relativePath ) === $ normalizedPath ;
307315 }
308316
309317 return mb_strpos (
310318 Filesystem::normalizePath ($ relativePath . '/ ' , false ),
311- Filesystem:: normalizePath ( $ path . '/ ' , false ) ) === 0 ;
319+ $ normalizedPath . '/ ' ) === 0 ;
312320 } catch (\Exception $ e ) {
313321 return false ;
314322 }
@@ -321,7 +329,11 @@ private function collectUsersShares(string $sourceUid,
321329
322330 $ progress ->finish ();
323331 $ output ->writeln ('' );
324- return $ shares ;
332+
333+ return array_map (fn (IShare $ share ) => [
334+ 'share ' => $ share ,
335+ 'suffix ' => substr (Filesystem::normalizePath ($ view ->getPath ($ share ->getNodeId ())), strlen ($ normalizedPath )),
336+ ], $ shares );
325337 }
326338
327339 private function collectIncomingShares (string $ sourceUid ,
@@ -384,14 +396,22 @@ protected function transferFiles(string $sourceUid,
384396 }
385397 }
386398
387- private function restoreShares (string $ sourceUid ,
399+ /**
400+ * @param string $targetLocation New location of the transfered node
401+ * @param array<array{share: IShare, suffix: string}> $shares previously collected share information
402+ */
403+ private function restoreShares (
404+ string $ sourceUid ,
388405 string $ destinationUid ,
406+ string $ targetLocation ,
389407 array $ shares ,
390- OutputInterface $ output ) {
408+ OutputInterface $ output ,
409+ ) {
391410 $ output ->writeln ("Restoring shares ... " );
392411 $ progress = new ProgressBar ($ output , count ($ shares ));
412+ $ rootFolder = \OCP \Server::get (IRootFolder::class);
393413
394- foreach ($ shares as $ share ) {
414+ foreach ($ shares as [ ' share ' => $ share, ' suffix ' => $ suffix ] ) {
395415 try {
396416 if ($ share ->getShareType () === IShare::TYPE_USER &&
397417 $ share ->getSharedWith () === $ destinationUid ) {
@@ -419,7 +439,19 @@ private function restoreShares(string $sourceUid,
419439 // trigger refetching of the node so that the new owner and mountpoint are taken into account
420440 // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
421441 $ this ->userMountCache ->clear ();
422- $ share ->setNodeId ($ share ->getNode ()->getId ());
442+
443+ try {
444+ // Try to get the "old" id.
445+ // Normally the ID is preserved,
446+ // but for transferes between different storages the ID might change
447+ $ newNodeId = $ share ->getNode ()->getId ();
448+ } catch (\OCP \Files \NotFoundException ) {
449+ // ID has changed due to transfer between different storages
450+ // Try to get the new ID from the target path and suffix of the share
451+ $ node = $ rootFolder ->get (Filesystem::normalizePath ($ targetLocation . '/ ' . $ suffix ));
452+ $ newNodeId = $ node ->getId ();
453+ }
454+ $ share ->setNodeId ($ newNodeId );
423455
424456 $ this ->shareManager ->updateShare ($ share );
425457 }
0 commit comments