Passed
Push — master ( 30dd39...cd44ee )
by Julius
15:00 queued 12s
created
apps/files/lib/Service/OwnershipTransferService.php 1 patch
Indentation   +456 added lines, -456 removed lines patch added patch discarded remove patch
@@ -58,460 +58,460 @@
 block discarded – undo
58 58
 
59 59
 class OwnershipTransferService {
60 60
 
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;
86
-	}
87
-
88
-	/**
89
-	 * @param IUser $sourceUser
90
-	 * @param IUser $destinationUser
91
-	 * @param string $path
92
-	 *
93
-	 * @param OutputInterface|null $output
94
-	 * @param bool $move
95
-	 * @throws TransferOwnershipException
96
-	 * @throws \OC\User\NoUserException
97
-	 */
98
-	public function transfer(IUser $sourceUser,
99
-							 IUser $destinationUser,
100
-							 string $path,
101
-							 ?OutputInterface $output = null,
102
-							 bool $move = false,
103
-							 bool $firstLogin = false,
104
-							 bool $transferIncomingShares = false): void {
105
-		$output = $output ?? new NullOutput();
106
-		$sourceUid = $sourceUser->getUID();
107
-		$destinationUid = $destinationUser->getUID();
108
-		$sourcePath = rtrim($sourceUid . '/files/' . $path, '/');
109
-
110
-		// If encryption is on we have to ensure the user has logged in before and that all encryption modules are ready
111
-		if (($this->encryptionManager->isEnabled() && $destinationUser->getLastLogin() === 0)
112
-			|| !$this->encryptionManager->isReadyForUser($destinationUid)) {
113
-			throw new TransferOwnershipException("The target user is not ready to accept files. The user has at least to have logged in once.", 2);
114
-		}
115
-
116
-		// setup filesystem
117
-		// Requesting the user folder will set it up if the user hasn't logged in before
118
-		// We need a setupFS for the full filesystem setup before as otherwise we will just return
119
-		// a lazy root folder which does not create the destination users folder
120
-		\OC_Util::setupFS($destinationUser->getUID());
121
-		\OC::$server->getUserFolder($destinationUser->getUID());
122
-		Filesystem::initMountPoints($sourceUid);
123
-		Filesystem::initMountPoints($destinationUid);
124
-
125
-		$view = new View();
126
-
127
-		if ($move) {
128
-			$finalTarget = "$destinationUid/files/";
129
-		} else {
130
-			$date = date('Y-m-d H-i-s');
131
-
132
-			// Remove some characters which are prone to cause errors
133
-			$cleanUserName = str_replace(['\\', '/', ':', '.', '?', '#', '\'', '"'], '-', $sourceUser->getDisplayName());
134
-			// Replace multiple dashes with one dash
135
-			$cleanUserName = preg_replace('/-{2,}/s', '-', $cleanUserName);
136
-			$cleanUserName = $cleanUserName ?: $sourceUid;
137
-
138
-			$finalTarget = "$destinationUid/files/transferred from $cleanUserName on $date";
139
-			try {
140
-				$view->verifyPath(dirname($finalTarget), basename($finalTarget));
141
-			} catch (InvalidPathException $e) {
142
-				$finalTarget = "$destinationUid/files/transferred from $sourceUid on $date";
143
-			}
144
-		}
145
-
146
-		if (!($view->is_dir($sourcePath) || $view->is_file($sourcePath))) {
147
-			throw new TransferOwnershipException("Unknown path provided: $path", 1);
148
-		}
149
-
150
-		if ($move && !$view->is_dir($finalTarget)) {
151
-			// Initialize storage
152
-			\OC_Util::setupFS($destinationUser->getUID());
153
-		}
154
-
155
-		if ($move && !$firstLogin && count($view->getDirectoryContent($finalTarget)) > 0) {
156
-			throw new TransferOwnershipException("Destination path does not exists or is not empty", 1);
157
-		}
158
-
159
-
160
-		// analyse source folder
161
-		$this->analyse(
162
-			$sourceUid,
163
-			$destinationUid,
164
-			$sourcePath,
165
-			$view,
166
-			$output
167
-		);
168
-
169
-		// collect all the shares
170
-		$shares = $this->collectUsersShares(
171
-			$sourceUid,
172
-			$output,
173
-			$view,
174
-			$sourcePath
175
-		);
176
-
177
-		// transfer the files
178
-		$this->transferFiles(
179
-			$sourceUid,
180
-			$sourcePath,
181
-			$finalTarget,
182
-			$view,
183
-			$output
184
-		);
185
-
186
-		// restore the shares
187
-		$this->restoreShares(
188
-			$sourceUid,
189
-			$destinationUid,
190
-			$shares,
191
-			$output
192
-		);
193
-
194
-		// transfer the incoming shares
195
-		if ($transferIncomingShares === true) {
196
-			$sourceShares = $this->collectIncomingShares(
197
-				$sourceUid,
198
-				$output,
199
-				$view
200
-			);
201
-			$destinationShares = $this->collectIncomingShares(
202
-				$destinationUid,
203
-				$output,
204
-				$view,
205
-				true
206
-			);
207
-			$this->transferIncomingShares(
208
-				$sourceUid,
209
-				$destinationUid,
210
-				$sourceShares,
211
-				$destinationShares,
212
-				$output,
213
-				$path,
214
-				$finalTarget,
215
-				$move
216
-			);
217
-		}
218
-	}
219
-
220
-	private function walkFiles(View $view, $path, Closure $callBack) {
221
-		foreach ($view->getDirectoryContent($path) as $fileInfo) {
222
-			if (!$callBack($fileInfo)) {
223
-				return;
224
-			}
225
-			if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
226
-				$this->walkFiles($view, $fileInfo->getPath(), $callBack);
227
-			}
228
-		}
229
-	}
230
-
231
-	/**
232
-	 * @param OutputInterface $output
233
-	 *
234
-	 * @throws \Exception
235
-	 */
236
-	protected function analyse(string $sourceUid,
237
-							   string $destinationUid,
238
-							   string $sourcePath,
239
-							   View $view,
240
-							   OutputInterface $output): void {
241
-		$output->writeln('Validating quota');
242
-		$size = $view->getFileInfo($sourcePath, false)->getSize(false);
243
-		$freeSpace = $view->free_space($destinationUid . '/files/');
244
-		if ($size > $freeSpace && $freeSpace !== FileInfo::SPACE_UNKNOWN) {
245
-			$output->writeln('<error>Target user does not have enough free space available.</error>');
246
-			throw new \Exception('Execution terminated.');
247
-		}
248
-
249
-		$output->writeln("Analysing files of $sourceUid ...");
250
-		$progress = new ProgressBar($output);
251
-		$progress->start();
252
-
253
-		$encryptedFiles = [];
254
-		$this->walkFiles($view, $sourcePath,
255
-			function (FileInfo $fileInfo) use ($progress) {
256
-				if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
257
-					// only analyze into folders from main storage,
258
-					if (!$fileInfo->getStorage()->instanceOfStorage(IHomeStorage::class)) {
259
-						return false;
260
-					}
261
-					return true;
262
-				}
263
-				$progress->advance();
264
-				if ($fileInfo->isEncrypted()) {
265
-					$encryptedFiles[] = $fileInfo;
266
-				}
267
-				return true;
268
-			});
269
-		$progress->finish();
270
-		$output->writeln('');
271
-
272
-		// no file is allowed to be encrypted
273
-		if (!empty($encryptedFiles)) {
274
-			$output->writeln("<error>Some files are encrypted - please decrypt them first.</error>");
275
-			foreach ($encryptedFiles as $encryptedFile) {
276
-				/** @var FileInfo $encryptedFile */
277
-				$output->writeln("  " . $encryptedFile->getPath());
278
-			}
279
-			throw new \Exception('Execution terminated.');
280
-		}
281
-	}
282
-
283
-	private function collectUsersShares(string $sourceUid,
284
-										OutputInterface $output,
285
-										View $view,
286
-										string $path): array {
287
-		$output->writeln("Collecting all share information for files and folders of $sourceUid ...");
288
-
289
-		$shares = [];
290
-		$progress = new ProgressBar($output);
291
-
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) {
293
-			$offset = 0;
294
-			while (true) {
295
-				$sharePage = $this->shareManager->getSharesBy($sourceUid, $shareType, null, true, 50, $offset);
296
-				$progress->advance(count($sharePage));
297
-				if (empty($sharePage)) {
298
-					break;
299
-				}
300
-				if ($path !== "$sourceUid/files") {
301
-					$sharePage = array_filter($sharePage, function (IShare $share) use ($view, $path) {
302
-						try {
303
-							$relativePath = $view->getPath($share->getNodeId());
304
-							$singleFileTranfer = $view->is_file($path);
305
-							if ($singleFileTranfer) {
306
-								return Filesystem::normalizePath($relativePath) === Filesystem::normalizePath($path);
307
-							}
308
-
309
-							return mb_strpos(
310
-								Filesystem::normalizePath($relativePath . '/', false),
311
-								Filesystem::normalizePath($path . '/', false)) === 0;
312
-						} catch (\Exception $e) {
313
-							return false;
314
-						}
315
-					});
316
-				}
317
-				$shares = array_merge($shares, $sharePage);
318
-				$offset += 50;
319
-			}
320
-		}
321
-
322
-		$progress->finish();
323
-		$output->writeln('');
324
-		return $shares;
325
-	}
326
-
327
-	private function collectIncomingShares(string $sourceUid,
328
-										OutputInterface $output,
329
-										View $view,
330
-										bool $addKeys = false): array {
331
-		$output->writeln("Collecting all incoming share information for files and folders of $sourceUid ...");
332
-
333
-		$shares = [];
334
-		$progress = new ProgressBar($output);
335
-
336
-		$offset = 0;
337
-		while (true) {
338
-			$sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset);
339
-			$progress->advance(count($sharePage));
340
-			if (empty($sharePage)) {
341
-				break;
342
-			}
343
-			if ($addKeys) {
344
-				foreach ($sharePage as $singleShare) {
345
-					$shares[$singleShare->getNodeId()] = $singleShare;
346
-				}
347
-			} else {
348
-				foreach ($sharePage as $singleShare) {
349
-					$shares[] = $singleShare;
350
-				}
351
-			}
352
-
353
-			$offset += 50;
354
-		}
355
-
356
-
357
-		$progress->finish();
358
-		$output->writeln('');
359
-		return $shares;
360
-	}
361
-
362
-	/**
363
-	 * @throws TransferOwnershipException
364
-	 */
365
-	protected function transferFiles(string $sourceUid,
366
-									 string $sourcePath,
367
-									 string $finalTarget,
368
-									 View $view,
369
-									 OutputInterface $output): void {
370
-		$output->writeln("Transferring files to $finalTarget ...");
371
-
372
-		// This change will help user to transfer the folder specified using --path option.
373
-		// Else only the content inside folder is transferred which is not correct.
374
-		if ($sourcePath !== "$sourceUid/files") {
375
-			$view->mkdir($finalTarget);
376
-			$finalTarget = $finalTarget . '/' . basename($sourcePath);
377
-		}
378
-		if ($view->rename($sourcePath, $finalTarget) === false) {
379
-			throw new TransferOwnershipException("Could not transfer files.", 1);
380
-		}
381
-		if (!is_dir("$sourceUid/files")) {
382
-			// because the files folder is moved away we need to recreate it
383
-			$view->mkdir("$sourceUid/files");
384
-		}
385
-	}
386
-
387
-	private function restoreShares(string $sourceUid,
388
-								   string $destinationUid,
389
-								   array $shares,
390
-								   OutputInterface $output) {
391
-		$output->writeln("Restoring shares ...");
392
-		$progress = new ProgressBar($output, count($shares));
393
-
394
-		foreach ($shares as $share) {
395
-			try {
396
-				if ($share->getShareType() === IShare::TYPE_USER &&
397
-					$share->getSharedWith() === $destinationUid) {
398
-					// Unmount the shares before deleting, so we don't try to get the storage later on.
399
-					$shareMountPoint = $this->mountManager->find('/' . $destinationUid . '/files' . $share->getTarget());
400
-					if ($shareMountPoint) {
401
-						$this->mountManager->removeMount($shareMountPoint->getMountPoint());
402
-					}
403
-					$this->shareManager->deleteShare($share);
404
-				} else {
405
-					if ($share->getShareOwner() === $sourceUid) {
406
-						$share->setShareOwner($destinationUid);
407
-					}
408
-					if ($share->getSharedBy() === $sourceUid) {
409
-						$share->setSharedBy($destinationUid);
410
-					}
411
-
412
-					if ($share->getShareType() === IShare::TYPE_USER &&
413
-						!$this->userManager->userExists($share->getSharedWith())) {
414
-						// stray share with deleted user
415
-						$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted user "' . $share->getSharedWith() . '", deleting</error>');
416
-						$this->shareManager->deleteShare($share);
417
-						continue;
418
-					} else {
419
-						// trigger refetching of the node so that the new owner and mountpoint are taken into account
420
-						// otherwise the checks on the share update will fail due to the original node not being available in the new user scope
421
-						$this->userMountCache->clear();
422
-						$share->setNodeId($share->getNode()->getId());
423
-
424
-						$this->shareManager->updateShare($share);
425
-					}
426
-				}
427
-			} catch (\OCP\Files\NotFoundException $e) {
428
-				$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
429
-			} catch (\Throwable $e) {
430
-				$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
431
-			}
432
-			$progress->advance();
433
-		}
434
-		$progress->finish();
435
-		$output->writeln('');
436
-	}
437
-
438
-	private function transferIncomingShares(string $sourceUid,
439
-								   string $destinationUid,
440
-								   array $sourceShares,
441
-								   array $destinationShares,
442
-								   OutputInterface $output,
443
-								   string $path,
444
-								   string $finalTarget,
445
-								   bool $move): void {
446
-		$output->writeln("Restoring incoming shares ...");
447
-		$progress = new ProgressBar($output, count($sourceShares));
448
-		$prefix = "$destinationUid/files";
449
-		$finalShareTarget = '';
450
-		if (substr($finalTarget, 0, strlen($prefix)) === $prefix) {
451
-			$finalShareTarget = substr($finalTarget, strlen($prefix));
452
-		}
453
-		foreach ($sourceShares as $share) {
454
-			try {
455
-				// Only restore if share is in given path.
456
-				$pathToCheck = '/';
457
-				if (trim($path, '/') !== '') {
458
-					$pathToCheck = '/' . trim($path) . '/';
459
-				}
460
-				if (substr($share->getTarget(), 0, strlen($pathToCheck)) !== $pathToCheck) {
461
-					continue;
462
-				}
463
-				$shareTarget = $share->getTarget();
464
-				$shareTarget = $finalShareTarget . $shareTarget;
465
-				if ($share->getShareType() === IShare::TYPE_USER &&
466
-					$share->getSharedBy() === $destinationUid) {
467
-					$this->shareManager->deleteShare($share);
468
-				} elseif (isset($destinationShares[$share->getNodeId()])) {
469
-					$destinationShare = $destinationShares[$share->getNodeId()];
470
-					// Keep the share which has the most permissions and discard the other one.
471
-					if ($destinationShare->getPermissions() < $share->getPermissions()) {
472
-						$this->shareManager->deleteShare($destinationShare);
473
-						$share->setSharedWith($destinationUid);
474
-						// trigger refetching of the node so that the new owner and mountpoint are taken into account
475
-						// otherwise the checks on the share update will fail due to the original node not being available in the new user scope
476
-						$this->userMountCache->clear();
477
-						$share->setNodeId($share->getNode()->getId());
478
-						$this->shareManager->updateShare($share);
479
-						// The share is already transferred.
480
-						$progress->advance();
481
-						if ($move) {
482
-							continue;
483
-						}
484
-						$share->setTarget($shareTarget);
485
-						$this->shareManager->moveShare($share, $destinationUid);
486
-						continue;
487
-					}
488
-					$this->shareManager->deleteShare($share);
489
-				} elseif ($share->getShareOwner() === $destinationUid) {
490
-					$this->shareManager->deleteShare($share);
491
-				} else {
492
-					$share->setSharedWith($destinationUid);
493
-					$share->setNodeId($share->getNode()->getId());
494
-					$this->shareManager->updateShare($share);
495
-					// trigger refetching of the node so that the new owner and mountpoint are taken into account
496
-					// otherwise the checks on the share update will fail due to the original node not being available in the new user scope
497
-					$this->userMountCache->clear();
498
-					// The share is already transferred.
499
-					$progress->advance();
500
-					if ($move) {
501
-						continue;
502
-					}
503
-					$share->setTarget($shareTarget);
504
-					$this->shareManager->moveShare($share, $destinationUid);
505
-					continue;
506
-				}
507
-			} catch (\OCP\Files\NotFoundException $e) {
508
-				$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
509
-			} catch (\Throwable $e) {
510
-				$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');
511
-			}
512
-			$progress->advance();
513
-		}
514
-		$progress->finish();
515
-		$output->writeln('');
516
-	}
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;
86
+    }
87
+
88
+    /**
89
+     * @param IUser $sourceUser
90
+     * @param IUser $destinationUser
91
+     * @param string $path
92
+     *
93
+     * @param OutputInterface|null $output
94
+     * @param bool $move
95
+     * @throws TransferOwnershipException
96
+     * @throws \OC\User\NoUserException
97
+     */
98
+    public function transfer(IUser $sourceUser,
99
+                                IUser $destinationUser,
100
+                                string $path,
101
+                             ?OutputInterface $output = null,
102
+                                bool $move = false,
103
+                                bool $firstLogin = false,
104
+                                bool $transferIncomingShares = false): void {
105
+        $output = $output ?? new NullOutput();
106
+        $sourceUid = $sourceUser->getUID();
107
+        $destinationUid = $destinationUser->getUID();
108
+        $sourcePath = rtrim($sourceUid . '/files/' . $path, '/');
109
+
110
+        // If encryption is on we have to ensure the user has logged in before and that all encryption modules are ready
111
+        if (($this->encryptionManager->isEnabled() && $destinationUser->getLastLogin() === 0)
112
+            || !$this->encryptionManager->isReadyForUser($destinationUid)) {
113
+            throw new TransferOwnershipException("The target user is not ready to accept files. The user has at least to have logged in once.", 2);
114
+        }
115
+
116
+        // setup filesystem
117
+        // Requesting the user folder will set it up if the user hasn't logged in before
118
+        // We need a setupFS for the full filesystem setup before as otherwise we will just return
119
+        // a lazy root folder which does not create the destination users folder
120
+        \OC_Util::setupFS($destinationUser->getUID());
121
+        \OC::$server->getUserFolder($destinationUser->getUID());
122
+        Filesystem::initMountPoints($sourceUid);
123
+        Filesystem::initMountPoints($destinationUid);
124
+
125
+        $view = new View();
126
+
127
+        if ($move) {
128
+            $finalTarget = "$destinationUid/files/";
129
+        } else {
130
+            $date = date('Y-m-d H-i-s');
131
+
132
+            // Remove some characters which are prone to cause errors
133
+            $cleanUserName = str_replace(['\\', '/', ':', '.', '?', '#', '\'', '"'], '-', $sourceUser->getDisplayName());
134
+            // Replace multiple dashes with one dash
135
+            $cleanUserName = preg_replace('/-{2,}/s', '-', $cleanUserName);
136
+            $cleanUserName = $cleanUserName ?: $sourceUid;
137
+
138
+            $finalTarget = "$destinationUid/files/transferred from $cleanUserName on $date";
139
+            try {
140
+                $view->verifyPath(dirname($finalTarget), basename($finalTarget));
141
+            } catch (InvalidPathException $e) {
142
+                $finalTarget = "$destinationUid/files/transferred from $sourceUid on $date";
143
+            }
144
+        }
145
+
146
+        if (!($view->is_dir($sourcePath) || $view->is_file($sourcePath))) {
147
+            throw new TransferOwnershipException("Unknown path provided: $path", 1);
148
+        }
149
+
150
+        if ($move && !$view->is_dir($finalTarget)) {
151
+            // Initialize storage
152
+            \OC_Util::setupFS($destinationUser->getUID());
153
+        }
154
+
155
+        if ($move && !$firstLogin && count($view->getDirectoryContent($finalTarget)) > 0) {
156
+            throw new TransferOwnershipException("Destination path does not exists or is not empty", 1);
157
+        }
158
+
159
+
160
+        // analyse source folder
161
+        $this->analyse(
162
+            $sourceUid,
163
+            $destinationUid,
164
+            $sourcePath,
165
+            $view,
166
+            $output
167
+        );
168
+
169
+        // collect all the shares
170
+        $shares = $this->collectUsersShares(
171
+            $sourceUid,
172
+            $output,
173
+            $view,
174
+            $sourcePath
175
+        );
176
+
177
+        // transfer the files
178
+        $this->transferFiles(
179
+            $sourceUid,
180
+            $sourcePath,
181
+            $finalTarget,
182
+            $view,
183
+            $output
184
+        );
185
+
186
+        // restore the shares
187
+        $this->restoreShares(
188
+            $sourceUid,
189
+            $destinationUid,
190
+            $shares,
191
+            $output
192
+        );
193
+
194
+        // transfer the incoming shares
195
+        if ($transferIncomingShares === true) {
196
+            $sourceShares = $this->collectIncomingShares(
197
+                $sourceUid,
198
+                $output,
199
+                $view
200
+            );
201
+            $destinationShares = $this->collectIncomingShares(
202
+                $destinationUid,
203
+                $output,
204
+                $view,
205
+                true
206
+            );
207
+            $this->transferIncomingShares(
208
+                $sourceUid,
209
+                $destinationUid,
210
+                $sourceShares,
211
+                $destinationShares,
212
+                $output,
213
+                $path,
214
+                $finalTarget,
215
+                $move
216
+            );
217
+        }
218
+    }
219
+
220
+    private function walkFiles(View $view, $path, Closure $callBack) {
221
+        foreach ($view->getDirectoryContent($path) as $fileInfo) {
222
+            if (!$callBack($fileInfo)) {
223
+                return;
224
+            }
225
+            if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
226
+                $this->walkFiles($view, $fileInfo->getPath(), $callBack);
227
+            }
228
+        }
229
+    }
230
+
231
+    /**
232
+     * @param OutputInterface $output
233
+     *
234
+     * @throws \Exception
235
+     */
236
+    protected function analyse(string $sourceUid,
237
+                                string $destinationUid,
238
+                                string $sourcePath,
239
+                                View $view,
240
+                                OutputInterface $output): void {
241
+        $output->writeln('Validating quota');
242
+        $size = $view->getFileInfo($sourcePath, false)->getSize(false);
243
+        $freeSpace = $view->free_space($destinationUid . '/files/');
244
+        if ($size > $freeSpace && $freeSpace !== FileInfo::SPACE_UNKNOWN) {
245
+            $output->writeln('<error>Target user does not have enough free space available.</error>');
246
+            throw new \Exception('Execution terminated.');
247
+        }
248
+
249
+        $output->writeln("Analysing files of $sourceUid ...");
250
+        $progress = new ProgressBar($output);
251
+        $progress->start();
252
+
253
+        $encryptedFiles = [];
254
+        $this->walkFiles($view, $sourcePath,
255
+            function (FileInfo $fileInfo) use ($progress) {
256
+                if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
257
+                    // only analyze into folders from main storage,
258
+                    if (!$fileInfo->getStorage()->instanceOfStorage(IHomeStorage::class)) {
259
+                        return false;
260
+                    }
261
+                    return true;
262
+                }
263
+                $progress->advance();
264
+                if ($fileInfo->isEncrypted()) {
265
+                    $encryptedFiles[] = $fileInfo;
266
+                }
267
+                return true;
268
+            });
269
+        $progress->finish();
270
+        $output->writeln('');
271
+
272
+        // no file is allowed to be encrypted
273
+        if (!empty($encryptedFiles)) {
274
+            $output->writeln("<error>Some files are encrypted - please decrypt them first.</error>");
275
+            foreach ($encryptedFiles as $encryptedFile) {
276
+                /** @var FileInfo $encryptedFile */
277
+                $output->writeln("  " . $encryptedFile->getPath());
278
+            }
279
+            throw new \Exception('Execution terminated.');
280
+        }
281
+    }
282
+
283
+    private function collectUsersShares(string $sourceUid,
284
+                                        OutputInterface $output,
285
+                                        View $view,
286
+                                        string $path): array {
287
+        $output->writeln("Collecting all share information for files and folders of $sourceUid ...");
288
+
289
+        $shares = [];
290
+        $progress = new ProgressBar($output);
291
+
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) {
293
+            $offset = 0;
294
+            while (true) {
295
+                $sharePage = $this->shareManager->getSharesBy($sourceUid, $shareType, null, true, 50, $offset);
296
+                $progress->advance(count($sharePage));
297
+                if (empty($sharePage)) {
298
+                    break;
299
+                }
300
+                if ($path !== "$sourceUid/files") {
301
+                    $sharePage = array_filter($sharePage, function (IShare $share) use ($view, $path) {
302
+                        try {
303
+                            $relativePath = $view->getPath($share->getNodeId());
304
+                            $singleFileTranfer = $view->is_file($path);
305
+                            if ($singleFileTranfer) {
306
+                                return Filesystem::normalizePath($relativePath) === Filesystem::normalizePath($path);
307
+                            }
308
+
309
+                            return mb_strpos(
310
+                                Filesystem::normalizePath($relativePath . '/', false),
311
+                                Filesystem::normalizePath($path . '/', false)) === 0;
312
+                        } catch (\Exception $e) {
313
+                            return false;
314
+                        }
315
+                    });
316
+                }
317
+                $shares = array_merge($shares, $sharePage);
318
+                $offset += 50;
319
+            }
320
+        }
321
+
322
+        $progress->finish();
323
+        $output->writeln('');
324
+        return $shares;
325
+    }
326
+
327
+    private function collectIncomingShares(string $sourceUid,
328
+                                        OutputInterface $output,
329
+                                        View $view,
330
+                                        bool $addKeys = false): array {
331
+        $output->writeln("Collecting all incoming share information for files and folders of $sourceUid ...");
332
+
333
+        $shares = [];
334
+        $progress = new ProgressBar($output);
335
+
336
+        $offset = 0;
337
+        while (true) {
338
+            $sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset);
339
+            $progress->advance(count($sharePage));
340
+            if (empty($sharePage)) {
341
+                break;
342
+            }
343
+            if ($addKeys) {
344
+                foreach ($sharePage as $singleShare) {
345
+                    $shares[$singleShare->getNodeId()] = $singleShare;
346
+                }
347
+            } else {
348
+                foreach ($sharePage as $singleShare) {
349
+                    $shares[] = $singleShare;
350
+                }
351
+            }
352
+
353
+            $offset += 50;
354
+        }
355
+
356
+
357
+        $progress->finish();
358
+        $output->writeln('');
359
+        return $shares;
360
+    }
361
+
362
+    /**
363
+     * @throws TransferOwnershipException
364
+     */
365
+    protected function transferFiles(string $sourceUid,
366
+                                        string $sourcePath,
367
+                                        string $finalTarget,
368
+                                        View $view,
369
+                                        OutputInterface $output): void {
370
+        $output->writeln("Transferring files to $finalTarget ...");
371
+
372
+        // This change will help user to transfer the folder specified using --path option.
373
+        // Else only the content inside folder is transferred which is not correct.
374
+        if ($sourcePath !== "$sourceUid/files") {
375
+            $view->mkdir($finalTarget);
376
+            $finalTarget = $finalTarget . '/' . basename($sourcePath);
377
+        }
378
+        if ($view->rename($sourcePath, $finalTarget) === false) {
379
+            throw new TransferOwnershipException("Could not transfer files.", 1);
380
+        }
381
+        if (!is_dir("$sourceUid/files")) {
382
+            // because the files folder is moved away we need to recreate it
383
+            $view->mkdir("$sourceUid/files");
384
+        }
385
+    }
386
+
387
+    private function restoreShares(string $sourceUid,
388
+                                    string $destinationUid,
389
+                                    array $shares,
390
+                                    OutputInterface $output) {
391
+        $output->writeln("Restoring shares ...");
392
+        $progress = new ProgressBar($output, count($shares));
393
+
394
+        foreach ($shares as $share) {
395
+            try {
396
+                if ($share->getShareType() === IShare::TYPE_USER &&
397
+                    $share->getSharedWith() === $destinationUid) {
398
+                    // Unmount the shares before deleting, so we don't try to get the storage later on.
399
+                    $shareMountPoint = $this->mountManager->find('/' . $destinationUid . '/files' . $share->getTarget());
400
+                    if ($shareMountPoint) {
401
+                        $this->mountManager->removeMount($shareMountPoint->getMountPoint());
402
+                    }
403
+                    $this->shareManager->deleteShare($share);
404
+                } else {
405
+                    if ($share->getShareOwner() === $sourceUid) {
406
+                        $share->setShareOwner($destinationUid);
407
+                    }
408
+                    if ($share->getSharedBy() === $sourceUid) {
409
+                        $share->setSharedBy($destinationUid);
410
+                    }
411
+
412
+                    if ($share->getShareType() === IShare::TYPE_USER &&
413
+                        !$this->userManager->userExists($share->getSharedWith())) {
414
+                        // stray share with deleted user
415
+                        $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted user "' . $share->getSharedWith() . '", deleting</error>');
416
+                        $this->shareManager->deleteShare($share);
417
+                        continue;
418
+                    } else {
419
+                        // trigger refetching of the node so that the new owner and mountpoint are taken into account
420
+                        // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
421
+                        $this->userMountCache->clear();
422
+                        $share->setNodeId($share->getNode()->getId());
423
+
424
+                        $this->shareManager->updateShare($share);
425
+                    }
426
+                }
427
+            } catch (\OCP\Files\NotFoundException $e) {
428
+                $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
429
+            } catch (\Throwable $e) {
430
+                $output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
431
+            }
432
+            $progress->advance();
433
+        }
434
+        $progress->finish();
435
+        $output->writeln('');
436
+    }
437
+
438
+    private function transferIncomingShares(string $sourceUid,
439
+                                    string $destinationUid,
440
+                                    array $sourceShares,
441
+                                    array $destinationShares,
442
+                                    OutputInterface $output,
443
+                                    string $path,
444
+                                    string $finalTarget,
445
+                                    bool $move): void {
446
+        $output->writeln("Restoring incoming shares ...");
447
+        $progress = new ProgressBar($output, count($sourceShares));
448
+        $prefix = "$destinationUid/files";
449
+        $finalShareTarget = '';
450
+        if (substr($finalTarget, 0, strlen($prefix)) === $prefix) {
451
+            $finalShareTarget = substr($finalTarget, strlen($prefix));
452
+        }
453
+        foreach ($sourceShares as $share) {
454
+            try {
455
+                // Only restore if share is in given path.
456
+                $pathToCheck = '/';
457
+                if (trim($path, '/') !== '') {
458
+                    $pathToCheck = '/' . trim($path) . '/';
459
+                }
460
+                if (substr($share->getTarget(), 0, strlen($pathToCheck)) !== $pathToCheck) {
461
+                    continue;
462
+                }
463
+                $shareTarget = $share->getTarget();
464
+                $shareTarget = $finalShareTarget . $shareTarget;
465
+                if ($share->getShareType() === IShare::TYPE_USER &&
466
+                    $share->getSharedBy() === $destinationUid) {
467
+                    $this->shareManager->deleteShare($share);
468
+                } elseif (isset($destinationShares[$share->getNodeId()])) {
469
+                    $destinationShare = $destinationShares[$share->getNodeId()];
470
+                    // Keep the share which has the most permissions and discard the other one.
471
+                    if ($destinationShare->getPermissions() < $share->getPermissions()) {
472
+                        $this->shareManager->deleteShare($destinationShare);
473
+                        $share->setSharedWith($destinationUid);
474
+                        // trigger refetching of the node so that the new owner and mountpoint are taken into account
475
+                        // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
476
+                        $this->userMountCache->clear();
477
+                        $share->setNodeId($share->getNode()->getId());
478
+                        $this->shareManager->updateShare($share);
479
+                        // The share is already transferred.
480
+                        $progress->advance();
481
+                        if ($move) {
482
+                            continue;
483
+                        }
484
+                        $share->setTarget($shareTarget);
485
+                        $this->shareManager->moveShare($share, $destinationUid);
486
+                        continue;
487
+                    }
488
+                    $this->shareManager->deleteShare($share);
489
+                } elseif ($share->getShareOwner() === $destinationUid) {
490
+                    $this->shareManager->deleteShare($share);
491
+                } else {
492
+                    $share->setSharedWith($destinationUid);
493
+                    $share->setNodeId($share->getNode()->getId());
494
+                    $this->shareManager->updateShare($share);
495
+                    // trigger refetching of the node so that the new owner and mountpoint are taken into account
496
+                    // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
497
+                    $this->userMountCache->clear();
498
+                    // The share is already transferred.
499
+                    $progress->advance();
500
+                    if ($move) {
501
+                        continue;
502
+                    }
503
+                    $share->setTarget($shareTarget);
504
+                    $this->shareManager->moveShare($share, $destinationUid);
505
+                    continue;
506
+                }
507
+            } catch (\OCP\Files\NotFoundException $e) {
508
+                $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
509
+            } catch (\Throwable $e) {
510
+                $output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');
511
+            }
512
+            $progress->advance();
513
+        }
514
+        $progress->finish();
515
+        $output->writeln('');
516
+    }
517 517
 }
Please login to merge, or discard this patch.