migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
class BackendNotFoundException extends \Exception {
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
/**
* @since 26.0.0
*/
interface IDeletableVersionBackend {
/**
* Delete a version.
*
* @since 26.0.0
*/
public function deleteVersion(IVersion $version): void;
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
/**
* @since 26.0.0
*/
interface INameableVersion {
/**
* Get the user created label
*
* @return string
* @since 26.0.0
*/
public function getLabel(): string;
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
/**
* @since 26.0.0
*/
interface INameableVersionBackend {
/**
* Set the label for a version.
*
* @since 26.0.0
*/
public function setVersionLabel(IVersion $version, string $label): void;
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\File;
/**
* @since 28.0.0
*/
interface INeedSyncVersionBackend {
public function createVersionEntity(File $file): void;
public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void;
public function deleteVersionsEntity(File $file): void;
}
@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersion {
/**
* @return IVersionBackend
* @since 15.0.0
*/
public function getBackend(): IVersionBackend;
/**
* Get the file info of the source file
*
* @return FileInfo
* @since 15.0.0
*/
public function getSourceFile(): FileInfo;
/**
* Get the id of the revision for the file
*
* @return int|string
* @since 15.0.0
*/
public function getRevisionId();
/**
* Get the timestamp this version was created
*
* @return int
* @since 15.0.0
*/
public function getTimestamp(): int;
/**
* Get the size of this version
*
* @return int|float
* @since 15.0.0
*/
public function getSize(): int|float;
/**
* Get the name of the source file at the time of making this version
*
* @return string
* @since 15.0.0
*/
public function getSourceFileName(): string;
/**
* Get the mimetype of this version
*
* @return string
* @since 15.0.0
*/
public function getMimeType(): string;
/**
* Get the path of this version
*
* @return string
* @since 15.0.0
*/
public function getVersionPath(): string;
/**
* @return IUser
* @since 15.0.0
*/
public function getUser(): IUser;
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
/**
* @since 15.0.0
*/
interface IVersionBackend {
/**
* Whether or not this version backend should be used for a storage
*
* If false is returned then the next applicable backend will be used
*
* @param IStorage $storage
* @return bool
* @since 17.0.0
*/
public function useBackendForStorage(IStorage $storage): bool;
/**
* Get all versions for a file
*
* @param IUser $user
* @param FileInfo $file
* @return IVersion[]
* @since 15.0.0
*/
public function getVersionsForFile(IUser $user, FileInfo $file): array;
/**
* Create a new version for a file
*
* @param IUser $user
* @param FileInfo $file
* @since 15.0.0
*/
public function createVersion(IUser $user, FileInfo $file);
/**
* Restore this version
*
* @param IVersion $version
* @since 15.0.0
*/
public function rollback(IVersion $version);
/**
* Open the file for reading
*
* @param IVersion $version
* @return resource|false
* @throws NotFoundException
* @since 15.0.0
*/
public function read(IVersion $version);
/**
* Get the preview for a specific version of a file
*
* @param IUser $user
* @param FileInfo $sourceFile
* @param int|string $revision
*
* @return File
*
* @since 15.0.0
*/
public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File;
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
/**
* @since 15.0.0
*/
interface IVersionManager extends IVersionBackend {
/**
* Register a new backend
*
* @param string $storageType
* @param IVersionBackend $backend
* @since 15.0.0
*/
public function registerBackend(string $storageType, IVersionBackend $backend);
}
@@ -0,0 +1,309 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OC\Files\View;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\Files_Sharing\ISharedStorage;
use OCA\Files_Sharing\SharedStorage;
use OCA\Files_Versions\Db\VersionEntity;
use OCA\Files_Versions\Db\VersionsMapper;
use OCA\Files_Versions\Storage;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\IMimeTypeLoader;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend {
private IRootFolder $rootFolder;
private IUserManager $userManager;
private VersionsMapper $versionsMapper;
private IMimeTypeLoader $mimeTypeLoader;
private IUserSession $userSession;
public function __construct(
IRootFolder $rootFolder,
IUserManager $userManager,
VersionsMapper $versionsMapper,
IMimeTypeLoader $mimeTypeLoader,
IUserSession $userSession,
) {
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
$this->versionsMapper = $versionsMapper;
$this->mimeTypeLoader = $mimeTypeLoader;
$this->userSession = $userSession;
}
public function useBackendForStorage(IStorage $storage): bool {
return true;
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$storage = $file->getStorage();
if ($storage->instanceOfStorage(SharedStorage::class)) {
$owner = $storage->getOwner('');
$user = $this->userManager->get($owner);
$fileId = $file->getId();
if ($fileId === null) {
throw new NotFoundException("File not found ($fileId)");
}
if ($user === null) {
throw new NotFoundException("User $owner not found for $fileId");
}
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$nodes = $userFolder->getById($fileId);
$file = array_pop($nodes);
if (!$file) {
throw new NotFoundException("version file not found for share owner");
}
} else {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
}
$fileId = $file->getId();
if ($fileId === null) {
throw new NotFoundException("File not found ($fileId)");
}
$versions = $this->getVersionsForFileFromDB($file, $user);
// Early exit if we find any version in the database.
// Else we continue to populate the DB from what's on disk.
if (count($versions) > 0) {
return $versions;
}
// Insert the entry in the DB for the current version.
$versionEntity = new VersionEntity();
$versionEntity->setFileId($fileId);
$versionEntity->setTimestamp($file->getMTime());
$versionEntity->setSize($file->getSize());
$versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
$versionEntity->setMetadata([]);
$this->versionsMapper->insert($versionEntity);
// Insert entries in the DB for existing versions.
$relativePath = $userFolder->getRelativePath($file->getPath());
if ($relativePath === null) {
throw new NotFoundException("Relative path not found for file $fileId (" . $file->getPath() . ')');
}
$versionsOnFS = Storage::getVersions($user->getUID(), $relativePath);
foreach ($versionsOnFS as $version) {
$versionEntity = new VersionEntity();
$versionEntity->setFileId($fileId);
$versionEntity->setTimestamp((int)$version['version']);
$versionEntity->setSize((int)$version['size']);
$versionEntity->setMimetype($this->mimeTypeLoader->getId($version['mimetype']));
$versionEntity->setMetadata([]);
$this->versionsMapper->insert($versionEntity);
}
return $this->getVersionsForFileFromDB($file, $user);
}
/**
* @return IVersion[]
*/
private function getVersionsForFileFromDB(FileInfo $file, IUser $user): array {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
return array_map(
fn (VersionEntity $versionEntity) => new Version(
$versionEntity->getTimestamp(),
$versionEntity->getTimestamp(),
$file->getName(),
$versionEntity->getSize(),
$this->mimeTypeLoader->getMimetypeById($versionEntity->getMimetype()),
$userFolder->getRelativePath($file->getPath()),
$file,
$this,
$user,
$versionEntity->getLabel(),
),
$this->versionsMapper->findAllVersionsForFileId($file->getId())
);
}
public function createVersion(IUser $user, FileInfo $file) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$relativePath = $userFolder->getRelativePath($file->getPath());
$userView = new View('/' . $user->getUID());
// create all parent folders
Storage::createMissingDirectories($relativePath, $userView);
Storage::scheduleExpire($user->getUID(), $relativePath);
// store a new version of a file
$userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
// ensure the file is scanned
$userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
}
public function rollback(IVersion $version) {
if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_UPDATE)) {
throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.');
}
return Storage::rollback($version->getVersionPath(), $version->getRevisionId(), $version->getUser());
}
private function getVersionFolder(IUser $user): Folder {
$userRoot = $this->rootFolder->getUserFolder($user->getUID())
->getParent();
try {
/** @var Folder $folder */
$folder = $userRoot->get('files_versions');
return $folder;
} catch (NotFoundException $e) {
return $userRoot->newFolder('files_versions');
}
}
public function read(IVersion $version) {
$versions = $this->getVersionFolder($version->getUser());
/** @var File $file */
$file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
return $file->fopen('r');
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$owner = $sourceFile->getOwner();
$storage = $sourceFile->getStorage();
// Shared files have their versions in the owners root folder so we need to obtain them from there
if ($storage->instanceOfStorage(ISharedStorage::class) && $owner) {
/** @var SharedStorage $storage */
$userFolder = $this->rootFolder->getUserFolder($owner->getUID());
$user = $owner;
$ownerPathInStorage = $sourceFile->getInternalPath();
$sourceFile = $storage->getShare()->getNode();
if ($sourceFile instanceof Folder) {
$sourceFile = $sourceFile->get($ownerPathInStorage);
}
}
$versionFolder = $this->getVersionFolder($user);
/** @var File $file */
$file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
return $file;
}
public function setVersionLabel(IVersion $version, string $label): void {
if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_UPDATE)) {
throw new Forbidden('You cannot label this version because you do not have update permissions on the source file.');
}
$versionEntity = $this->versionsMapper->findVersionForFileId(
$version->getSourceFile()->getId(),
$version->getTimestamp(),
);
if (trim($label) === '') {
$label = null;
}
$versionEntity->setLabel($label ?? '');
$this->versionsMapper->update($versionEntity);
}
public function deleteVersion(IVersion $version): void {
if (!$this->currentUserHasPermissions($version, \OCP\Constants::PERMISSION_DELETE)) {
throw new Forbidden('You cannot delete this version because you do not have delete permissions on the source file.');
}
Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
$versionEntity = $this->versionsMapper->findVersionForFileId(
$version->getSourceFile()->getId(),
$version->getTimestamp(),
);
$this->versionsMapper->delete($versionEntity);
}
public function createVersionEntity(File $file): void {
$versionEntity = new VersionEntity();
$versionEntity->setFileId($file->getId());
$versionEntity->setTimestamp($file->getMTime());
$versionEntity->setSize($file->getSize());
$versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
$versionEntity->setMetadata([]);
$this->versionsMapper->insert($versionEntity);
}
public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
$versionEntity = $this->versionsMapper->findVersionForFileId($sourceFile->getId(), $revision);
if (isset($properties['timestamp'])) {
$versionEntity->setTimestamp($properties['timestamp']);
}
if (isset($properties['size'])) {
$versionEntity->setSize($properties['size']);
}
if (isset($properties['mimetype'])) {
$versionEntity->setMimetype($properties['mimetype']);
}
$this->versionsMapper->update($versionEntity);
}
public function deleteVersionsEntity(File $file): void {
$this->versionsMapper->deleteAllVersionsForFileId($file->getId());
}
private function currentUserHasPermissions(IVersion $version, int $permissions): bool {
$sourceFile = $version->getSourceFile();
$currentUserId = $this->userSession->getUser()?->getUID();
if ($currentUserId === null) {
throw new NotFoundException("No user logged in");
}
if ($sourceFile->getOwner()?->getUID() !== $currentUserId) {
$nodes = $this->rootFolder->getUserFolder($currentUserId)->getById($sourceFile->getId());
$sourceFile = array_pop($nodes);
if (!$sourceFile) {
throw new NotFoundException("Version file not accessible by current user");
}
}
return ($sourceFile->getPermissions() & $permissions) === $permissions;
}
}
@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\FileInfo;
use OCP\IUser;
class Version implements IVersion, INameableVersion {
/** @var int */
private $timestamp;
/** @var int|string */
private $revisionId;
/** @var string */
private $name;
private string $label;
/** @var int|float */
private $size;
/** @var string */
private $mimetype;
/** @var string */
private $path;
/** @var FileInfo */
private $sourceFileInfo;
/** @var IVersionBackend */
private $backend;
/** @var IUser */
private $user;
public function __construct(
int $timestamp,
$revisionId,
string $name,
int|float $size,
string $mimetype,
string $path,
FileInfo $sourceFileInfo,
IVersionBackend $backend,
IUser $user,
string $label = ''
) {
$this->timestamp = $timestamp;
$this->revisionId = $revisionId;
$this->name = $name;
$this->label = $label;
$this->size = $size;
$this->mimetype = $mimetype;
$this->path = $path;
$this->sourceFileInfo = $sourceFileInfo;
$this->backend = $backend;
$this->user = $user;
}
public function getBackend(): IVersionBackend {
return $this->backend;
}
public function getSourceFile(): FileInfo {
return $this->sourceFileInfo;
}
public function getRevisionId() {
return $this->revisionId;
}
public function getTimestamp(): int {
return $this->timestamp;
}
public function getSize(): int|float {
return $this->size;
}
public function getSourceFileName(): string {
return $this->name;
}
public function getLabel(): string {
return $this->label;
}
public function getMimeType(): string {
return $this->mimetype;
}
public function getVersionPath(): string {
return $this->path;
}
public function getUser(): IUser {
return $this->user;
}
}
@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Files_Versions\Versions;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Lock\ILock;
use OCP\Files\Lock\ILockManager;
use OCP\Files\Lock\LockContext;
use OCP\Files\Storage\IStorage;
use OCP\IUser;
use OCP\Lock\ManuallyLockedException;
class VersionManager implements IVersionManager, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend {
/** @var (IVersionBackend[])[] */
private $backends = [];
public function registerBackend(string $storageType, IVersionBackend $backend) {
if (!isset($this->backends[$storageType])) {
$this->backends[$storageType] = [];
}
$this->backends[$storageType][] = $backend;
}
/**
* @return (IVersionBackend[])[]
*/
private function getBackends(): array {
return $this->backends;
}
/**
* @param IStorage $storage
* @return IVersionBackend
* @throws BackendNotFoundException
*/
public function getBackendForStorage(IStorage $storage): IVersionBackend {
$fullType = get_class($storage);
$backends = $this->getBackends();
$foundType = '';
$foundBackend = null;
foreach ($backends as $type => $backendsForType) {
if (
$storage->instanceOfStorage($type) &&
($foundType === '' || is_subclass_of($type, $foundType))
) {
foreach ($backendsForType as $backend) {
/** @var IVersionBackend $backend */
if ($backend->useBackendForStorage($storage)) {
$foundBackend = $backend;
$foundType = $type;
}
}
}
}
if ($foundType === '' || $foundBackend === null) {
throw new BackendNotFoundException("Version backend for $fullType not found");
} else {
return $foundBackend;
}
}
public function getVersionsForFile(IUser $user, FileInfo $file): array {
$backend = $this->getBackendForStorage($file->getStorage());
return $backend->getVersionsForFile($user, $file);
}
public function createVersion(IUser $user, FileInfo $file) {
$backend = $this->getBackendForStorage($file->getStorage());
$backend->createVersion($user, $file);
}
public function rollback(IVersion $version) {
$backend = $version->getBackend();
$result = self::handleAppLocks(fn (): ?bool => $backend->rollback($version));
// rollback doesn't have a return type yet and some implementations don't return anything
if ($result === null || $result === true) {
\OC_Hook::emit('\OCP\Versions', 'rollback', [
'path' => $version->getVersionPath(),
'revision' => $version->getRevisionId(),
'node' => $version->getSourceFile(),
]);
}
return $result;
}
public function read(IVersion $version) {
$backend = $version->getBackend();
return $backend->read($version);
}
public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File {
$backend = $this->getBackendForStorage($sourceFile->getStorage());
return $backend->getVersionFile($user, $sourceFile, $revision);
}
public function useBackendForStorage(IStorage $storage): bool {
return false;
}
public function setVersionLabel(IVersion $version, string $label): void {
$backend = $this->getBackendForStorage($version->getSourceFile()->getStorage());
if ($backend instanceof INameableVersionBackend) {
$backend->setVersionLabel($version, $label);
}
}
public function deleteVersion(IVersion $version): void {
$backend = $this->getBackendForStorage($version->getSourceFile()->getStorage());
if ($backend instanceof IDeletableVersionBackend) {
$backend->deleteVersion($version);
}
}
public function createVersionEntity(File $file): void {
$backend = $this->getBackendForStorage($file->getStorage());
if ($backend instanceof INeedSyncVersionBackend) {
$backend->createVersionEntity($file);
}
}
public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
$backend = $this->getBackendForStorage($sourceFile->getStorage());
if ($backend instanceof INeedSyncVersionBackend) {
$backend->updateVersionEntity($sourceFile, $revision, $properties);
}
}
public function deleteVersionsEntity(File $file): void {
$backend = $this->getBackendForStorage($file->getStorage());
if ($backend instanceof INeedSyncVersionBackend) {
$backend->deleteVersionsEntity($file);
}
}
/**
* Catch ManuallyLockedException and retry in app context if possible.
*
* Allow users to go back to old versions via the versions tab in the sidebar
* even when the file is opened in the viewer next to it.
*
* Context: If a file is currently opened for editing
* the files_lock app will throw ManuallyLockedExceptions.
* This prevented the user from rolling an opened file back to a previous version.
*
* Text and Richdocuments can handle changes of open files.
* So we execute the rollback under their lock context
* to let them handle the conflict.
*
* @param callable $callback function to run with app locks handled
* @return bool|null
* @throws ManuallyLockedException
*
*/
private static function handleAppLocks(callable $callback): ?bool {
try {
return $callback();
} catch (ManuallyLockedException $e) {
$owner = (string) $e->getOwner();
$appsThatHandleUpdates = array("text", "richdocuments");
if (!in_array($owner, $appsThatHandleUpdates)) {
throw $e;
}
// The LockWrapper in the files_lock app only compares the lock type and owner
// when checking the lock against the current scope.
// So we do not need to get the actual node here
// and use the root node instead.
$root = \OC::$server->get(IRootFolder::class);
$lockContext = new LockContext($root, ILock::TYPE_APP, $owner);
$lockManager = \OC::$server->get(ILockManager::class);
$result = null;
$lockManager->runInScope($lockContext, function () use ($callback, &$result) {
$result = $callback();
});
return $result;
}
}
}