Completed
Push — master ( 3ba18f...5a7373 )
by
unknown
28:35
created
apps/files_versions/lib/Versions/LegacyVersionsBackend.php 2 patches
Indentation   +358 added lines, -358 removed lines patch added patch discarded remove patch
@@ -31,362 +31,362 @@
 block discarded – undo
31 31
 use Psr\Log\LoggerInterface;
32 32
 
33 33
 class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend, IVersionsImporterBackend {
34
-	public function __construct(
35
-		private IRootFolder $rootFolder,
36
-		private IUserManager $userManager,
37
-		private VersionsMapper $versionsMapper,
38
-		private IMimeTypeLoader $mimeTypeLoader,
39
-		private IUserSession $userSession,
40
-		private LoggerInterface $logger,
41
-	) {
42
-	}
43
-
44
-	public function useBackendForStorage(IStorage $storage): bool {
45
-		return true;
46
-	}
47
-
48
-	public function getVersionsForFile(IUser $user, FileInfo $file): array {
49
-		$storage = $file->getStorage();
50
-
51
-		if ($storage->instanceOfStorage(ISharedStorage::class)) {
52
-			$owner = $storage->getOwner('');
53
-			if ($owner === false) {
54
-				throw new NotFoundException('No owner for ' . $file->getPath());
55
-			}
56
-
57
-			$user = $this->userManager->get($owner);
58
-
59
-			$fileId = $file->getId();
60
-			if ($fileId === null) {
61
-				throw new NotFoundException("File not found ($fileId)");
62
-			}
63
-
64
-			if ($user === null) {
65
-				throw new NotFoundException("User $owner not found for $fileId");
66
-			}
67
-
68
-			$userFolder = $this->rootFolder->getUserFolder($user->getUID());
69
-
70
-			$file = $userFolder->getFirstNodeById($fileId);
71
-
72
-			if (!$file) {
73
-				throw new NotFoundException('version file not found for share owner');
74
-			}
75
-		} else {
76
-			$userFolder = $this->rootFolder->getUserFolder($user->getUID());
77
-		}
78
-
79
-		$fileId = $file->getId();
80
-		if ($fileId === null) {
81
-			throw new NotFoundException("File not found ($fileId)");
82
-		}
83
-
84
-		// Insert entries in the DB for existing versions.
85
-		$relativePath = $userFolder->getRelativePath($file->getPath());
86
-		if ($relativePath === null) {
87
-			throw new NotFoundException("Relative path not found for file $fileId (" . $file->getPath() . ')');
88
-		}
89
-
90
-		$currentVersion = [
91
-			'version' => (string)$file->getMtime(),
92
-			'size' => $file->getSize(),
93
-			'mimetype' => $file->getMimetype(),
94
-		];
95
-
96
-		$versionsInDB = $this->versionsMapper->findAllVersionsForFileId($file->getId());
97
-		/** @var array<int, array> */
98
-		$versionsInFS = array_values(Storage::getVersions($user->getUID(), $relativePath));
99
-
100
-		/** @var array<int, array{db: ?VersionEntity, fs: ?mixed}> */
101
-		$groupedVersions = [];
102
-		$davVersions = [];
103
-
104
-		foreach ($versionsInDB as $version) {
105
-			$revisionId = $version->getTimestamp();
106
-			$groupedVersions[$revisionId] = $groupedVersions[$revisionId] ?? [];
107
-			$groupedVersions[$revisionId]['db'] = $version;
108
-		}
109
-
110
-		foreach ([$currentVersion, ...$versionsInFS] as $version) {
111
-			$revisionId = $version['version'];
112
-			$groupedVersions[$revisionId] = $groupedVersions[$revisionId] ?? [];
113
-			$groupedVersions[$revisionId]['fs'] = $version;
114
-		}
115
-
116
-		/** @var array<string, array{db: ?VersionEntity, fs: ?mixed}> $groupedVersions */
117
-		foreach ($groupedVersions as $versions) {
118
-			if (empty($versions['db']) && !empty($versions['fs'])) {
119
-				$versions['db'] = new VersionEntity();
120
-				$versions['db']->setFileId($fileId);
121
-				$versions['db']->setTimestamp((int)$versions['fs']['version']);
122
-				$versions['db']->setSize((int)$versions['fs']['size']);
123
-				$versions['db']->setMimetype($this->mimeTypeLoader->getId($versions['fs']['mimetype']));
124
-				$versions['db']->setMetadata([]);
125
-				$this->versionsMapper->insert($versions['db']);
126
-			} elseif (!empty($versions['db']) && empty($versions['fs'])) {
127
-				$this->versionsMapper->delete($versions['db']);
128
-				continue;
129
-			}
130
-
131
-			$version = new Version(
132
-				$versions['db']->getTimestamp(),
133
-				$versions['db']->getTimestamp(),
134
-				$file->getName(),
135
-				$versions['db']->getSize(),
136
-				$this->mimeTypeLoader->getMimetypeById($versions['db']->getMimetype()),
137
-				$userFolder->getRelativePath($file->getPath()),
138
-				$file,
139
-				$this,
140
-				$user,
141
-				$versions['db']->getMetadata() ?? [],
142
-			);
143
-
144
-			array_push($davVersions, $version);
145
-		}
146
-
147
-		return $davVersions;
148
-	}
149
-
150
-	public function createVersion(IUser $user, FileInfo $file) {
151
-		$userFolder = $this->rootFolder->getUserFolder($user->getUID());
152
-		$relativePath = $userFolder->getRelativePath($file->getPath());
153
-		$userView = new View('/' . $user->getUID());
154
-		// create all parent folders
155
-		Storage::createMissingDirectories($relativePath, $userView);
156
-
157
-		Storage::scheduleExpire($user->getUID(), $relativePath);
158
-
159
-		// store a new version of a file
160
-		$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
161
-		// ensure the file is scanned
162
-		$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
163
-	}
164
-
165
-	public function rollback(IVersion $version) {
166
-		if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_UPDATE)) {
167
-			throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.');
168
-		}
169
-
170
-		return Storage::rollback($version->getVersionPath(), $version->getRevisionId(), $version->getUser());
171
-	}
172
-
173
-	private function getVersionFolder(IUser $user): Folder {
174
-		$userRoot = $this->rootFolder->getUserFolder($user->getUID())
175
-			->getParent();
176
-		try {
177
-			/** @var Folder $folder */
178
-			$folder = $userRoot->get('files_versions');
179
-			return $folder;
180
-		} catch (NotFoundException $e) {
181
-			return $userRoot->newFolder('files_versions');
182
-		}
183
-	}
184
-
185
-	public function read(IVersion $version) {
186
-		$versions = $this->getVersionFolder($version->getUser());
187
-		/** @var File $file */
188
-		$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
189
-		return $file->fopen('r');
190
-	}
191
-
192
-	public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File {
193
-		$userFolder = $this->rootFolder->getUserFolder($user->getUID());
194
-		$owner = $sourceFile->getOwner();
195
-		$storage = $sourceFile->getStorage();
196
-
197
-		// Shared files have their versions in the owners root folder so we need to obtain them from there
198
-		if ($storage->instanceOfStorage(ISharedStorage::class) && $owner) {
199
-			/** @var ISharedStorage $storage */
200
-			$userFolder = $this->rootFolder->getUserFolder($owner->getUID());
201
-			$user = $owner;
202
-			$ownerPathInStorage = $sourceFile->getInternalPath();
203
-			$sourceFile = $storage->getShare()->getNode();
204
-			if ($sourceFile instanceof Folder) {
205
-				$sourceFile = $sourceFile->get($ownerPathInStorage);
206
-			}
207
-		}
208
-
209
-		$versionFolder = $this->getVersionFolder($user);
210
-		/** @var File $file */
211
-		$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
212
-		return $file;
213
-	}
214
-
215
-	public function getRevision(Node $node): int {
216
-		return $node->getMTime();
217
-	}
218
-
219
-	public function deleteVersion(IVersion $version): void {
220
-		if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_DELETE)) {
221
-			throw new Forbidden('You cannot delete this version because you do not have delete permissions on the source file.');
222
-		}
223
-
224
-		Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
225
-		$versionEntity = $this->versionsMapper->findVersionForFileId(
226
-			$version->getSourceFile()->getId(),
227
-			$version->getTimestamp(),
228
-		);
229
-		$this->versionsMapper->delete($versionEntity);
230
-	}
231
-
232
-	public function createVersionEntity(File $file): ?VersionEntity {
233
-		$versionEntity = new VersionEntity();
234
-		$versionEntity->setFileId($file->getId());
235
-		$versionEntity->setTimestamp($file->getMTime());
236
-		$versionEntity->setSize($file->getSize());
237
-		$versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
238
-		$versionEntity->setMetadata([]);
239
-
240
-		$tries = 1;
241
-		while ($tries < 5) {
242
-			try {
243
-				$this->versionsMapper->insert($versionEntity);
244
-				return $versionEntity;
245
-			} catch (\OCP\DB\Exception $e) {
246
-				if (!in_array($e->getReason(), [
247
-					\OCP\DB\Exception::REASON_CONSTRAINT_VIOLATION,
248
-					\OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION,
249
-				])
250
-				) {
251
-					throw $e;
252
-				}
253
-				/* Conflict with another version, increase mtime and try again */
254
-				$versionEntity->setTimestamp($versionEntity->getTimestamp() + 1);
255
-				$tries++;
256
-				$this->logger->warning('Constraint violation while inserting version, retrying with increased timestamp', ['exception' => $e]);
257
-			}
258
-		}
259
-
260
-		return null;
261
-	}
262
-
263
-	public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
264
-		$versionEntity = $this->versionsMapper->findVersionForFileId($sourceFile->getId(), $revision);
265
-
266
-		if (isset($properties['timestamp'])) {
267
-			$versionEntity->setTimestamp($properties['timestamp']);
268
-		}
269
-
270
-		if (isset($properties['size'])) {
271
-			$versionEntity->setSize($properties['size']);
272
-		}
273
-
274
-		if (isset($properties['mimetype'])) {
275
-			$versionEntity->setMimetype($properties['mimetype']);
276
-		}
277
-
278
-		$this->versionsMapper->update($versionEntity);
279
-	}
280
-
281
-	public function deleteVersionsEntity(File $file): void {
282
-		$this->versionsMapper->deleteAllVersionsForFileId($file->getId());
283
-	}
284
-
285
-	private function currentUserHasPermissions(FileInfo $sourceFile, int $permissions): bool {
286
-		$currentUserId = $this->userSession->getUser()?->getUID();
287
-
288
-		if ($currentUserId === null) {
289
-			throw new NotFoundException('No user logged in');
290
-		}
291
-
292
-		if ($sourceFile->getOwner()?->getUID() === $currentUserId) {
293
-			return ($sourceFile->getPermissions() & $permissions) === $permissions;
294
-		}
295
-
296
-		$nodes = $this->rootFolder->getUserFolder($currentUserId)->getById($sourceFile->getId());
297
-
298
-		if (count($nodes) === 0) {
299
-			throw new NotFoundException('Version file not accessible by current user');
300
-		}
301
-
302
-		foreach ($nodes as $node) {
303
-			if (($node->getPermissions() & $permissions) === $permissions) {
304
-				return true;
305
-			}
306
-		}
307
-
308
-		return false;
309
-	}
310
-
311
-	public function setMetadataValue(Node $node, int $revision, string $key, string $value): void {
312
-		if (!$this->currentUserHasPermissions($node, Constants::PERMISSION_UPDATE)) {
313
-			throw new Forbidden('You cannot update the version\'s metadata because you do not have update permissions on the source file.');
314
-		}
315
-
316
-		$versionEntity = $this->versionsMapper->findVersionForFileId($node->getId(), $revision);
317
-
318
-		$versionEntity->setMetadataValue($key, $value);
319
-		$this->versionsMapper->update($versionEntity);
320
-	}
321
-
322
-
323
-	/**
324
-	 * @inheritdoc
325
-	 */
326
-	public function importVersionsForFile(IUser $user, Node $source, Node $target, array $versions): void {
327
-		$userFolder = $this->rootFolder->getUserFolder($user->getUID());
328
-		$relativePath = $userFolder->getRelativePath($target->getPath());
329
-
330
-		if ($relativePath === null) {
331
-			throw new \Exception('Target does not have a relative path' . $target->getPath());
332
-		}
333
-
334
-		$userView = new View('/' . $user->getUID());
335
-		// create all parent folders
336
-		Storage::createMissingDirectories($relativePath, $userView);
337
-		Storage::scheduleExpire($user->getUID(), $relativePath);
338
-
339
-		foreach ($versions as $version) {
340
-			// 1. Import the file in its new location.
341
-			// Nothing to do for the current version.
342
-			if ($version->getTimestamp() !== $source->getMTime()) {
343
-				$backend = $version->getBackend();
344
-				$versionFile = $backend->getVersionFile($user, $source, $version->getRevisionId());
345
-				$newVersionPath = 'files_versions/' . $relativePath . '.v' . $version->getTimestamp();
346
-
347
-				$versionContent = $versionFile->fopen('r');
348
-				if ($versionContent === false) {
349
-					$this->logger->warning('Fail to open version file.', ['source' => $source, 'version' => $version, 'versionFile' => $versionFile]);
350
-					continue;
351
-				}
352
-
353
-				$userView->file_put_contents($newVersionPath, $versionContent);
354
-				// ensure the file is scanned
355
-				$userView->getFileInfo($newVersionPath);
356
-			}
357
-
358
-			// 2. Create the entity in the database
359
-			$versionEntity = new VersionEntity();
360
-			$versionEntity->setFileId($target->getId());
361
-			$versionEntity->setTimestamp($version->getTimestamp());
362
-			$versionEntity->setSize($version->getSize());
363
-			$versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimetype()));
364
-			if ($version instanceof IMetadataVersion) {
365
-				$versionEntity->setMetadata($version->getMetadata());
366
-			}
367
-			$this->versionsMapper->insert($versionEntity);
368
-		}
369
-	}
370
-
371
-	/**
372
-	 * @inheritdoc
373
-	 */
374
-	public function clearVersionsForFile(IUser $user, Node $source, Node $target): void {
375
-		$userId = $user->getUID();
376
-		$userFolder = $this->rootFolder->getUserFolder($userId);
377
-
378
-		$relativePath = $userFolder->getRelativePath($source->getPath());
379
-		if ($relativePath === null) {
380
-			throw new Exception('Relative path not found for node with path: ' . $source->getPath());
381
-		}
382
-
383
-		$versionFolder = $this->getVersionFolder($user);
384
-
385
-		$versions = Storage::getVersions($userId, $relativePath);
386
-		foreach ($versions as $version) {
387
-			$versionFolder->get($version['path'] . '.v' . (int)$version['version'])->delete();
388
-		}
389
-
390
-		$this->versionsMapper->deleteAllVersionsForFileId($target->getId());
391
-	}
34
+    public function __construct(
35
+        private IRootFolder $rootFolder,
36
+        private IUserManager $userManager,
37
+        private VersionsMapper $versionsMapper,
38
+        private IMimeTypeLoader $mimeTypeLoader,
39
+        private IUserSession $userSession,
40
+        private LoggerInterface $logger,
41
+    ) {
42
+    }
43
+
44
+    public function useBackendForStorage(IStorage $storage): bool {
45
+        return true;
46
+    }
47
+
48
+    public function getVersionsForFile(IUser $user, FileInfo $file): array {
49
+        $storage = $file->getStorage();
50
+
51
+        if ($storage->instanceOfStorage(ISharedStorage::class)) {
52
+            $owner = $storage->getOwner('');
53
+            if ($owner === false) {
54
+                throw new NotFoundException('No owner for ' . $file->getPath());
55
+            }
56
+
57
+            $user = $this->userManager->get($owner);
58
+
59
+            $fileId = $file->getId();
60
+            if ($fileId === null) {
61
+                throw new NotFoundException("File not found ($fileId)");
62
+            }
63
+
64
+            if ($user === null) {
65
+                throw new NotFoundException("User $owner not found for $fileId");
66
+            }
67
+
68
+            $userFolder = $this->rootFolder->getUserFolder($user->getUID());
69
+
70
+            $file = $userFolder->getFirstNodeById($fileId);
71
+
72
+            if (!$file) {
73
+                throw new NotFoundException('version file not found for share owner');
74
+            }
75
+        } else {
76
+            $userFolder = $this->rootFolder->getUserFolder($user->getUID());
77
+        }
78
+
79
+        $fileId = $file->getId();
80
+        if ($fileId === null) {
81
+            throw new NotFoundException("File not found ($fileId)");
82
+        }
83
+
84
+        // Insert entries in the DB for existing versions.
85
+        $relativePath = $userFolder->getRelativePath($file->getPath());
86
+        if ($relativePath === null) {
87
+            throw new NotFoundException("Relative path not found for file $fileId (" . $file->getPath() . ')');
88
+        }
89
+
90
+        $currentVersion = [
91
+            'version' => (string)$file->getMtime(),
92
+            'size' => $file->getSize(),
93
+            'mimetype' => $file->getMimetype(),
94
+        ];
95
+
96
+        $versionsInDB = $this->versionsMapper->findAllVersionsForFileId($file->getId());
97
+        /** @var array<int, array> */
98
+        $versionsInFS = array_values(Storage::getVersions($user->getUID(), $relativePath));
99
+
100
+        /** @var array<int, array{db: ?VersionEntity, fs: ?mixed}> */
101
+        $groupedVersions = [];
102
+        $davVersions = [];
103
+
104
+        foreach ($versionsInDB as $version) {
105
+            $revisionId = $version->getTimestamp();
106
+            $groupedVersions[$revisionId] = $groupedVersions[$revisionId] ?? [];
107
+            $groupedVersions[$revisionId]['db'] = $version;
108
+        }
109
+
110
+        foreach ([$currentVersion, ...$versionsInFS] as $version) {
111
+            $revisionId = $version['version'];
112
+            $groupedVersions[$revisionId] = $groupedVersions[$revisionId] ?? [];
113
+            $groupedVersions[$revisionId]['fs'] = $version;
114
+        }
115
+
116
+        /** @var array<string, array{db: ?VersionEntity, fs: ?mixed}> $groupedVersions */
117
+        foreach ($groupedVersions as $versions) {
118
+            if (empty($versions['db']) && !empty($versions['fs'])) {
119
+                $versions['db'] = new VersionEntity();
120
+                $versions['db']->setFileId($fileId);
121
+                $versions['db']->setTimestamp((int)$versions['fs']['version']);
122
+                $versions['db']->setSize((int)$versions['fs']['size']);
123
+                $versions['db']->setMimetype($this->mimeTypeLoader->getId($versions['fs']['mimetype']));
124
+                $versions['db']->setMetadata([]);
125
+                $this->versionsMapper->insert($versions['db']);
126
+            } elseif (!empty($versions['db']) && empty($versions['fs'])) {
127
+                $this->versionsMapper->delete($versions['db']);
128
+                continue;
129
+            }
130
+
131
+            $version = new Version(
132
+                $versions['db']->getTimestamp(),
133
+                $versions['db']->getTimestamp(),
134
+                $file->getName(),
135
+                $versions['db']->getSize(),
136
+                $this->mimeTypeLoader->getMimetypeById($versions['db']->getMimetype()),
137
+                $userFolder->getRelativePath($file->getPath()),
138
+                $file,
139
+                $this,
140
+                $user,
141
+                $versions['db']->getMetadata() ?? [],
142
+            );
143
+
144
+            array_push($davVersions, $version);
145
+        }
146
+
147
+        return $davVersions;
148
+    }
149
+
150
+    public function createVersion(IUser $user, FileInfo $file) {
151
+        $userFolder = $this->rootFolder->getUserFolder($user->getUID());
152
+        $relativePath = $userFolder->getRelativePath($file->getPath());
153
+        $userView = new View('/' . $user->getUID());
154
+        // create all parent folders
155
+        Storage::createMissingDirectories($relativePath, $userView);
156
+
157
+        Storage::scheduleExpire($user->getUID(), $relativePath);
158
+
159
+        // store a new version of a file
160
+        $userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
161
+        // ensure the file is scanned
162
+        $userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
163
+    }
164
+
165
+    public function rollback(IVersion $version) {
166
+        if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_UPDATE)) {
167
+            throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.');
168
+        }
169
+
170
+        return Storage::rollback($version->getVersionPath(), $version->getRevisionId(), $version->getUser());
171
+    }
172
+
173
+    private function getVersionFolder(IUser $user): Folder {
174
+        $userRoot = $this->rootFolder->getUserFolder($user->getUID())
175
+            ->getParent();
176
+        try {
177
+            /** @var Folder $folder */
178
+            $folder = $userRoot->get('files_versions');
179
+            return $folder;
180
+        } catch (NotFoundException $e) {
181
+            return $userRoot->newFolder('files_versions');
182
+        }
183
+    }
184
+
185
+    public function read(IVersion $version) {
186
+        $versions = $this->getVersionFolder($version->getUser());
187
+        /** @var File $file */
188
+        $file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
189
+        return $file->fopen('r');
190
+    }
191
+
192
+    public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File {
193
+        $userFolder = $this->rootFolder->getUserFolder($user->getUID());
194
+        $owner = $sourceFile->getOwner();
195
+        $storage = $sourceFile->getStorage();
196
+
197
+        // Shared files have their versions in the owners root folder so we need to obtain them from there
198
+        if ($storage->instanceOfStorage(ISharedStorage::class) && $owner) {
199
+            /** @var ISharedStorage $storage */
200
+            $userFolder = $this->rootFolder->getUserFolder($owner->getUID());
201
+            $user = $owner;
202
+            $ownerPathInStorage = $sourceFile->getInternalPath();
203
+            $sourceFile = $storage->getShare()->getNode();
204
+            if ($sourceFile instanceof Folder) {
205
+                $sourceFile = $sourceFile->get($ownerPathInStorage);
206
+            }
207
+        }
208
+
209
+        $versionFolder = $this->getVersionFolder($user);
210
+        /** @var File $file */
211
+        $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
212
+        return $file;
213
+    }
214
+
215
+    public function getRevision(Node $node): int {
216
+        return $node->getMTime();
217
+    }
218
+
219
+    public function deleteVersion(IVersion $version): void {
220
+        if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_DELETE)) {
221
+            throw new Forbidden('You cannot delete this version because you do not have delete permissions on the source file.');
222
+        }
223
+
224
+        Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
225
+        $versionEntity = $this->versionsMapper->findVersionForFileId(
226
+            $version->getSourceFile()->getId(),
227
+            $version->getTimestamp(),
228
+        );
229
+        $this->versionsMapper->delete($versionEntity);
230
+    }
231
+
232
+    public function createVersionEntity(File $file): ?VersionEntity {
233
+        $versionEntity = new VersionEntity();
234
+        $versionEntity->setFileId($file->getId());
235
+        $versionEntity->setTimestamp($file->getMTime());
236
+        $versionEntity->setSize($file->getSize());
237
+        $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
238
+        $versionEntity->setMetadata([]);
239
+
240
+        $tries = 1;
241
+        while ($tries < 5) {
242
+            try {
243
+                $this->versionsMapper->insert($versionEntity);
244
+                return $versionEntity;
245
+            } catch (\OCP\DB\Exception $e) {
246
+                if (!in_array($e->getReason(), [
247
+                    \OCP\DB\Exception::REASON_CONSTRAINT_VIOLATION,
248
+                    \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION,
249
+                ])
250
+                ) {
251
+                    throw $e;
252
+                }
253
+                /* Conflict with another version, increase mtime and try again */
254
+                $versionEntity->setTimestamp($versionEntity->getTimestamp() + 1);
255
+                $tries++;
256
+                $this->logger->warning('Constraint violation while inserting version, retrying with increased timestamp', ['exception' => $e]);
257
+            }
258
+        }
259
+
260
+        return null;
261
+    }
262
+
263
+    public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
264
+        $versionEntity = $this->versionsMapper->findVersionForFileId($sourceFile->getId(), $revision);
265
+
266
+        if (isset($properties['timestamp'])) {
267
+            $versionEntity->setTimestamp($properties['timestamp']);
268
+        }
269
+
270
+        if (isset($properties['size'])) {
271
+            $versionEntity->setSize($properties['size']);
272
+        }
273
+
274
+        if (isset($properties['mimetype'])) {
275
+            $versionEntity->setMimetype($properties['mimetype']);
276
+        }
277
+
278
+        $this->versionsMapper->update($versionEntity);
279
+    }
280
+
281
+    public function deleteVersionsEntity(File $file): void {
282
+        $this->versionsMapper->deleteAllVersionsForFileId($file->getId());
283
+    }
284
+
285
+    private function currentUserHasPermissions(FileInfo $sourceFile, int $permissions): bool {
286
+        $currentUserId = $this->userSession->getUser()?->getUID();
287
+
288
+        if ($currentUserId === null) {
289
+            throw new NotFoundException('No user logged in');
290
+        }
291
+
292
+        if ($sourceFile->getOwner()?->getUID() === $currentUserId) {
293
+            return ($sourceFile->getPermissions() & $permissions) === $permissions;
294
+        }
295
+
296
+        $nodes = $this->rootFolder->getUserFolder($currentUserId)->getById($sourceFile->getId());
297
+
298
+        if (count($nodes) === 0) {
299
+            throw new NotFoundException('Version file not accessible by current user');
300
+        }
301
+
302
+        foreach ($nodes as $node) {
303
+            if (($node->getPermissions() & $permissions) === $permissions) {
304
+                return true;
305
+            }
306
+        }
307
+
308
+        return false;
309
+    }
310
+
311
+    public function setMetadataValue(Node $node, int $revision, string $key, string $value): void {
312
+        if (!$this->currentUserHasPermissions($node, Constants::PERMISSION_UPDATE)) {
313
+            throw new Forbidden('You cannot update the version\'s metadata because you do not have update permissions on the source file.');
314
+        }
315
+
316
+        $versionEntity = $this->versionsMapper->findVersionForFileId($node->getId(), $revision);
317
+
318
+        $versionEntity->setMetadataValue($key, $value);
319
+        $this->versionsMapper->update($versionEntity);
320
+    }
321
+
322
+
323
+    /**
324
+     * @inheritdoc
325
+     */
326
+    public function importVersionsForFile(IUser $user, Node $source, Node $target, array $versions): void {
327
+        $userFolder = $this->rootFolder->getUserFolder($user->getUID());
328
+        $relativePath = $userFolder->getRelativePath($target->getPath());
329
+
330
+        if ($relativePath === null) {
331
+            throw new \Exception('Target does not have a relative path' . $target->getPath());
332
+        }
333
+
334
+        $userView = new View('/' . $user->getUID());
335
+        // create all parent folders
336
+        Storage::createMissingDirectories($relativePath, $userView);
337
+        Storage::scheduleExpire($user->getUID(), $relativePath);
338
+
339
+        foreach ($versions as $version) {
340
+            // 1. Import the file in its new location.
341
+            // Nothing to do for the current version.
342
+            if ($version->getTimestamp() !== $source->getMTime()) {
343
+                $backend = $version->getBackend();
344
+                $versionFile = $backend->getVersionFile($user, $source, $version->getRevisionId());
345
+                $newVersionPath = 'files_versions/' . $relativePath . '.v' . $version->getTimestamp();
346
+
347
+                $versionContent = $versionFile->fopen('r');
348
+                if ($versionContent === false) {
349
+                    $this->logger->warning('Fail to open version file.', ['source' => $source, 'version' => $version, 'versionFile' => $versionFile]);
350
+                    continue;
351
+                }
352
+
353
+                $userView->file_put_contents($newVersionPath, $versionContent);
354
+                // ensure the file is scanned
355
+                $userView->getFileInfo($newVersionPath);
356
+            }
357
+
358
+            // 2. Create the entity in the database
359
+            $versionEntity = new VersionEntity();
360
+            $versionEntity->setFileId($target->getId());
361
+            $versionEntity->setTimestamp($version->getTimestamp());
362
+            $versionEntity->setSize($version->getSize());
363
+            $versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimetype()));
364
+            if ($version instanceof IMetadataVersion) {
365
+                $versionEntity->setMetadata($version->getMetadata());
366
+            }
367
+            $this->versionsMapper->insert($versionEntity);
368
+        }
369
+    }
370
+
371
+    /**
372
+     * @inheritdoc
373
+     */
374
+    public function clearVersionsForFile(IUser $user, Node $source, Node $target): void {
375
+        $userId = $user->getUID();
376
+        $userFolder = $this->rootFolder->getUserFolder($userId);
377
+
378
+        $relativePath = $userFolder->getRelativePath($source->getPath());
379
+        if ($relativePath === null) {
380
+            throw new Exception('Relative path not found for node with path: ' . $source->getPath());
381
+        }
382
+
383
+        $versionFolder = $this->getVersionFolder($user);
384
+
385
+        $versions = Storage::getVersions($userId, $relativePath);
386
+        foreach ($versions as $version) {
387
+            $versionFolder->get($version['path'] . '.v' . (int)$version['version'])->delete();
388
+        }
389
+
390
+        $this->versionsMapper->deleteAllVersionsForFileId($target->getId());
391
+    }
392 392
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -51,7 +51,7 @@  discard block
 block discarded – undo
51 51
 		if ($storage->instanceOfStorage(ISharedStorage::class)) {
52 52
 			$owner = $storage->getOwner('');
53 53
 			if ($owner === false) {
54
-				throw new NotFoundException('No owner for ' . $file->getPath());
54
+				throw new NotFoundException('No owner for '.$file->getPath());
55 55
 			}
56 56
 
57 57
 			$user = $this->userManager->get($owner);
@@ -84,11 +84,11 @@  discard block
 block discarded – undo
84 84
 		// Insert entries in the DB for existing versions.
85 85
 		$relativePath = $userFolder->getRelativePath($file->getPath());
86 86
 		if ($relativePath === null) {
87
-			throw new NotFoundException("Relative path not found for file $fileId (" . $file->getPath() . ')');
87
+			throw new NotFoundException("Relative path not found for file $fileId (".$file->getPath().')');
88 88
 		}
89 89
 
90 90
 		$currentVersion = [
91
-			'version' => (string)$file->getMtime(),
91
+			'version' => (string) $file->getMtime(),
92 92
 			'size' => $file->getSize(),
93 93
 			'mimetype' => $file->getMimetype(),
94 94
 		];
@@ -118,8 +118,8 @@  discard block
 block discarded – undo
118 118
 			if (empty($versions['db']) && !empty($versions['fs'])) {
119 119
 				$versions['db'] = new VersionEntity();
120 120
 				$versions['db']->setFileId($fileId);
121
-				$versions['db']->setTimestamp((int)$versions['fs']['version']);
122
-				$versions['db']->setSize((int)$versions['fs']['size']);
121
+				$versions['db']->setTimestamp((int) $versions['fs']['version']);
122
+				$versions['db']->setSize((int) $versions['fs']['size']);
123 123
 				$versions['db']->setMimetype($this->mimeTypeLoader->getId($versions['fs']['mimetype']));
124 124
 				$versions['db']->setMetadata([]);
125 125
 				$this->versionsMapper->insert($versions['db']);
@@ -150,16 +150,16 @@  discard block
 block discarded – undo
150 150
 	public function createVersion(IUser $user, FileInfo $file) {
151 151
 		$userFolder = $this->rootFolder->getUserFolder($user->getUID());
152 152
 		$relativePath = $userFolder->getRelativePath($file->getPath());
153
-		$userView = new View('/' . $user->getUID());
153
+		$userView = new View('/'.$user->getUID());
154 154
 		// create all parent folders
155 155
 		Storage::createMissingDirectories($relativePath, $userView);
156 156
 
157 157
 		Storage::scheduleExpire($user->getUID(), $relativePath);
158 158
 
159 159
 		// store a new version of a file
160
-		$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
160
+		$userView->copy('files/'.$relativePath, 'files_versions/'.$relativePath.'.v'.$file->getMtime());
161 161
 		// ensure the file is scanned
162
-		$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
162
+		$userView->getFileInfo('files_versions/'.$relativePath.'.v'.$file->getMtime());
163 163
 	}
164 164
 
165 165
 	public function rollback(IVersion $version) {
@@ -185,7 +185,7 @@  discard block
 block discarded – undo
185 185
 	public function read(IVersion $version) {
186 186
 		$versions = $this->getVersionFolder($version->getUser());
187 187
 		/** @var File $file */
188
-		$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
188
+		$file = $versions->get($version->getVersionPath().'.v'.$version->getRevisionId());
189 189
 		return $file->fopen('r');
190 190
 	}
191 191
 
@@ -208,7 +208,7 @@  discard block
 block discarded – undo
208 208
 
209 209
 		$versionFolder = $this->getVersionFolder($user);
210 210
 		/** @var File $file */
211
-		$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
211
+		$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()).'.v'.$revision);
212 212
 		return $file;
213 213
 	}
214 214
 
@@ -328,10 +328,10 @@  discard block
 block discarded – undo
328 328
 		$relativePath = $userFolder->getRelativePath($target->getPath());
329 329
 
330 330
 		if ($relativePath === null) {
331
-			throw new \Exception('Target does not have a relative path' . $target->getPath());
331
+			throw new \Exception('Target does not have a relative path'.$target->getPath());
332 332
 		}
333 333
 
334
-		$userView = new View('/' . $user->getUID());
334
+		$userView = new View('/'.$user->getUID());
335 335
 		// create all parent folders
336 336
 		Storage::createMissingDirectories($relativePath, $userView);
337 337
 		Storage::scheduleExpire($user->getUID(), $relativePath);
@@ -342,7 +342,7 @@  discard block
 block discarded – undo
342 342
 			if ($version->getTimestamp() !== $source->getMTime()) {
343 343
 				$backend = $version->getBackend();
344 344
 				$versionFile = $backend->getVersionFile($user, $source, $version->getRevisionId());
345
-				$newVersionPath = 'files_versions/' . $relativePath . '.v' . $version->getTimestamp();
345
+				$newVersionPath = 'files_versions/'.$relativePath.'.v'.$version->getTimestamp();
346 346
 
347 347
 				$versionContent = $versionFile->fopen('r');
348 348
 				if ($versionContent === false) {
@@ -377,14 +377,14 @@  discard block
 block discarded – undo
377 377
 
378 378
 		$relativePath = $userFolder->getRelativePath($source->getPath());
379 379
 		if ($relativePath === null) {
380
-			throw new Exception('Relative path not found for node with path: ' . $source->getPath());
380
+			throw new Exception('Relative path not found for node with path: '.$source->getPath());
381 381
 		}
382 382
 
383 383
 		$versionFolder = $this->getVersionFolder($user);
384 384
 
385 385
 		$versions = Storage::getVersions($userId, $relativePath);
386 386
 		foreach ($versions as $version) {
387
-			$versionFolder->get($version['path'] . '.v' . (int)$version['version'])->delete();
387
+			$versionFolder->get($version['path'].'.v'.(int) $version['version'])->delete();
388 388
 		}
389 389
 
390 390
 		$this->versionsMapper->deleteAllVersionsForFileId($target->getId());
Please login to merge, or discard this patch.