Completed
Pull Request — master (#32545)
by Tom
09:55
created

TransferOwnershipService::restoreShares()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 24
nop 0
dl 0
loc 41
rs 8.3306
c 0
b 0
f 0
1
<?php
2
3
namespace OCA\Files\Service\TransferOwnership;
4
5
use OC\Encryption\Manager;
6
use OC\Files\Filesystem;
7
use OC\Files\View;
8
use OC\Share20\ProviderFactory;
9
use OCP\Files\FileInfo;
10
use OCP\Files\NotFoundException;
11
use OCP\IConfig;
12
use OCP\ILogger;
13
use OCP\IUser;
14
use OCP\IUserManager;
15
use OCP\Share\IManager;
16
use OCP\Share\IShare;
17
18
class TransferOwnershipService {
19
20
	/** @var bool */
21
	private $filesExist = false;
22
	/** @var bool */
23
	private $foldersExist = false;
24
	/** @var FileInfo[] */
25
	private $encryptedFiles = [];
26
	/** @var IShare[] */
27
	private $shares = [];
28
	/** @var string */
29
	private $sourceUser;
30
	/** @var string */
31
	private $destinationUser;
32
	/** @var string */
33
	private $inputPath;
34
	/** @var string */
35
	private $finalTarget;
36
37
	/** @var Manager  */
38
	protected $encryptionManager;
39
	/** @var IUserManager  */
40
	protected $userManager;
41
	/** @var ILogger */
42
	protected $logger;
43
	/** @var IConfig  */
44
	protected $config;
45
	/** @var ProviderFactory  */
46
	private $shareProviderFactory;
47
	/** @var IManager */
48
	protected $shareManager;
49
50
	public function __construct(
51
		Manager $encryptionManager,
52
		IUserManager $userManager,
53
		ILogger $logger,
54
		IConfig $config,
55
		ProviderFactory $shareProviderFactory,
56
		IManager $shareManager
57
	) {
58
		$this->encryptionManager = $encryptionManager;
59
		$this->userManager = $userManager;
60
		$this->loggger = $logger;
0 ignored issues
show
Bug introduced by
The property loggger does not seem to exist. Did you mean logger?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
61
		$this->config = $config;
62
		$this->shareProviderFactory = $shareProviderFactory;
63
		$this->shareManager = $shareManager;
64
	}
65
66
	public function transfer(IUser $sourceUser, IUser $destinationUser, $sourcePath = '/') {
67
68
		$this->inputPath = $sourcePath;
69
		$this->destinationUser = $destinationUser->getUID();
70
		$this->sourceUser = $sourceUser->getUID();
71
72
		// target user has to be ready
73
		if (!$this->encryptionManager->isReadyForUser($this->destinationUser)) {
74
			throw new \Exception('The target user is not ready to accept files. The user has at least to be logged in once.');
75
		}
76
77
		// use a date format compatible across client OS
78
		$date = \date('Ymd_his');
79
		$this->finalTarget = "$this->destinationUser/files/transferred from $this->sourceUser on $date";
80
81
		// setup filesystem
82
		Filesystem::initMountPoints($this->sourceUser);
83
		Filesystem::initMountPoints($this->destinationUser);
84
85
		if (\strlen($this->inputPath) >= 1) {
86
			$view = new View();
87
			$unknownDir = $this->inputPath;
88
			$this->inputPath = $this->sourceUser . "/files/" . $this->inputPath;
89
			if (!$view->is_dir($this->inputPath)) {
90
				throw new NotFoundException("$unknownDir not found");
91
			}
92
		}
93
94
		// analyse source folder
95
		$this->analyse();
96
97
		if (!$this->filesExist and !$this->foldersExist) {
98
			throw new NotFoundException("No files/folders found for transfer");
99
		}
100
101
		// collect all the shares
102
		$this->collectUsersShares();
103
104
		// transfer the files
105
		$this->doTransfer();
106
107
		// restore the shares
108
		$status = $this->restoreShares();
109
110
		if ($status !== 0) {
111
			throw new \Exception('Failed to complete transfer');
112
		}
113
114
		return true;
115
	}
116
117
	private function walkFiles(View $view, $path, \Closure $callBack) {
118
		foreach ($view->getDirectoryContent($path) as $fileInfo) {
119
			if (!$callBack($fileInfo)) {
120
				return;
121
			}
122
			if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
123
				$this->walkFiles($view, $fileInfo->getPath(), $callBack);
124
			}
125
		}
126
	}
127
128
	/**
129
	 * @throws \Exception
130
	 */
131
	protected function analyse() {
132
		$view = new View();
133
		$walkPath = "$this->sourceUser/files";
134
		if (\strlen($this->inputPath) > 0) {
135
			if ($this->inputPath !== "$this->sourceUser/files") {
136
				$walkPath = $this->inputPath;
137
				$this->foldersExist = true;
138
			}
139
		}
140
141
		$this->walkFiles($view, $walkPath,
142
			function (FileInfo $fileInfo) {
143
				if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
144
					// only analyze into folders from main storage,
145
					// sub-storages have an empty internal path
146
					if ($fileInfo->getInternalPath() === '' && $fileInfo->getPath() !== '') {
147
						return false;
148
					}
149
150
					$this->foldersExist = true;
151
					return true;
152
				}
153
				$this->filesExist = true;
154
				if ($fileInfo->isEncrypted()) {
155
					if ($this->config->getAppValue('encryption', 'useMasterKey', 0) !== 0) {
156
						/**
157
						 * We are not going to add this to encryptedFiles array.
158
						 * Because its encrypted with masterKey and hence it doesn't
159
						 * require user's specific password.
160
						 */
161
						return true;
162
					}
163
					$this->encryptedFiles[] = $fileInfo;
164
				}
165
				return true;
166
			});
167
168
		// no file is allowed to be encrypted
169
		if (!empty($this->encryptedFiles)) {
170
			throw new \Exception('Encrypted files found - decrypt them first');
171
		}
172
	}
173
174
	/**
175
	 */
176
	private function collectUsersShares() {
177
		foreach ([\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE] as $shareType) {
178
			$offset = 0;
179
			while (true) {
180
				$sharePage = $this->shareManager->getSharesBy($this->sourceUser, $shareType, null, true, 50, $offset);
181
				if (empty($sharePage)) {
182
					break;
183
				}
184
185
				$this->shares = \array_merge($this->shares, $sharePage);
186
				$offset += 50;
187
			}
188
		}
189
190
	}
191
192
	/**
193
	 */
194
	protected function doTransfer() {
195
		$view = new View();
196
		$sourcePath = (\strlen($this->inputPath) > 0) ? $this->inputPath : "$this->sourceUser/files";
197
		// This change will help user to transfer the folder specified using --path option.
198
		// Else only the content inside folder is transferred which is not correct.
199
		if (\strlen($this->inputPath) > 0) {
200
			if ($this->inputPath !== \ltrim("$this->sourceUser/files", '/')) {
201
				$view->mkdir($this->finalTarget);
202
				$this->finalTarget = $this->finalTarget . '/' . \basename($sourcePath);
203
			}
204
		}
205
206
		$view->rename($sourcePath, $this->finalTarget);
207
208
		if (!\is_dir("$this->sourceUser/files")) {
209
			// because the files folder is moved away we need to recreate it
210
			$view->mkdir("$this->sourceUser/files");
211
		}
212
	}
213
214
	/**
215
	 */
216
	private function restoreShares() {
217
		$status = 0;
218
219
		$childShares = [];
220
		foreach ($this->shares as $share) {
221
			try {
222
				/**
223
				 * Do not process children which are already processed.
224
				 * This piece of code populates the childShare array
225
				 * with the child shares which will be processed. And
226
				 * hence will avoid further processing of same share
227
				 * again.
228
				 */
229
				if ($share->getSharedWith() === $this->destinationUser) {
230
					$provider = $this->shareProviderFactory->getProviderForType($share->getShareType());
231
					foreach ($provider->getChildren($share) as $child) {
232
						$childShares[] = $child->getId();
233
					}
234
				} else {
235
					/**
236
					 * Before doing handover to transferShare, check if the share
237
					 * id is present in the childShares. If so then just ignore
238
					 * this share and continue. If not ignored, the child shares
239
					 * would be processed again, if their parent share was shared
240
					 * with destination user. And hence we can safely avoid the
241
					 * duplicate processing of shares here.
242
					 */
243
					if (\in_array($share->getId(), $childShares, true)) {
244
						continue;
245
					}
246
				}
247
				$this->shareManager->transferShare($share, $this->sourceUser, $this->destinationUser, $this->finalTarget);
248
			} catch (\OCP\Files\NotFoundException $e) {
249
				$this->logger->error('Share with id ' . $share->getId() . ' points at deleted file, skipping');
250
			} catch (\Exception $e) {
251
				$this->logger->error('Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');
252
				$status = 1;
253
			}
254
		}
255
		return $status;
256
	}
257
258
}