diff options
Diffstat (limited to 'apps/files_versions/lib')
42 files changed, 641 insertions, 1050 deletions
diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index f9ab1883a6e..29158276415 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -1,29 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\AppInfo; @@ -33,7 +13,9 @@ use OCA\DAV\Connector\Sabre\Principal; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files\Event\LoadSidebar; use OCA\Files_Versions\Capabilities; +use OCA\Files_Versions\Events\VersionRestoredEvent; use OCA\Files_Versions\Listener\FileEventsListener; +use OCA\Files_Versions\Listener\LegacyRollbackListener; use OCA\Files_Versions\Listener\LoadAdditionalListener; use OCA\Files_Versions\Listener\LoadSidebarListener; use OCA\Files_Versions\Listener\VersionAuthorListener; @@ -63,6 +45,7 @@ use OCP\IServerContainer; use OCP\IUserManager; use OCP\IUserSession; use OCP\L10N\IFactory; +use OCP\Server; use OCP\Share\IManager as IShareManager; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -89,7 +72,7 @@ class Application extends App implements IBootstrap { return new Principal( $server->get(IUserManager::class), $server->get(IGroupManager::class), - \OC::$server->get(IAccountManager::class), + Server::get(IAccountManager::class), $server->get(IShareManager::class), $server->get(IUserSession::class), $server->get(IAppManager::class), @@ -100,9 +83,7 @@ class Application extends App implements IBootstrap { ); }); - $context->registerService(IVersionManager::class, function () { - return new VersionManager(); - }); + $context->registerServiceAlias(IVersionManager::class, VersionManager::class); /** * Register Events @@ -127,7 +108,10 @@ class Application extends App implements IBootstrap { $context->registerEventListener(BeforeNodeRenamedEvent::class, FileEventsListener::class); $context->registerEventListener(BeforeNodeCopiedEvent::class, FileEventsListener::class); - $context->registerEventListener(NodeWrittenEvent::class, VersionAuthorListener::class); + // we add the version author listener with lower priority to make sure new versions already are created by FileEventsListener + $context->registerEventListener(NodeWrittenEvent::class, VersionAuthorListener::class, -1); + + $context->registerEventListener(VersionRestoredEvent::class, LegacyRollbackListener::class); } public function boot(IBootContext $context): void { @@ -135,7 +119,7 @@ class Application extends App implements IBootstrap { } public function registerVersionBackends(ContainerInterface $container, IAppManager $appManager, LoggerInterface $logger): void { - foreach ($appManager->getInstalledApps() as $app) { + foreach ($appManager->getEnabledApps() as $app) { $appInfo = $appManager->getAppInfo($app); if (isset($appInfo['versions'])) { $backends = $appInfo['versions']; diff --git a/apps/files_versions/lib/BackgroundJob/ExpireVersions.php b/apps/files_versions/lib/BackgroundJob/ExpireVersions.php index 7e714a059d0..794cbc5b882 100644 --- a/apps/files_versions/lib/BackgroundJob/ExpireVersions.php +++ b/apps/files_versions/lib/BackgroundJob/ExpireVersions.php @@ -1,30 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\BackgroundJob; +use OC\Files\View; use OCA\Files_Versions\Expiration; use OCA\Files_Versions\Storage; use OCP\AppFramework\Utility\ITimeFactory; @@ -36,18 +19,15 @@ use OCP\IUserManager; class ExpireVersions extends TimedJob { public const ITEMS_PER_SESSION = 1000; - private IConfig $config; - private Expiration $expiration; - private IUserManager $userManager; - - public function __construct(IConfig $config, IUserManager $userManager, Expiration $expiration, ITimeFactory $time) { + public function __construct( + private IConfig $config, + private IUserManager $userManager, + private Expiration $expiration, + ITimeFactory $time, + ) { parent::__construct($time); // Run once per 30 minutes $this->setInterval(60 * 30); - - $this->config = $config; - $this->expiration = $expiration; - $this->userManager = $userManager; } public function run($argument) { @@ -61,7 +41,7 @@ class ExpireVersions extends TimedJob { return; } - $this->userManager->callForSeenUsers(function (IUser $user) { + $this->userManager->callForSeenUsers(function (IUser $user): void { $uid = $user->getUID(); if (!$this->setupFS($uid)) { return; @@ -78,7 +58,7 @@ class ExpireVersions extends TimedJob { \OC_Util::setupFS($user); // Check if this user has a versions directory - $view = new \OC\Files\View('/' . $user); + $view = new View('/' . $user); if (!$view->is_dir('/files_versions')) { return false; } diff --git a/apps/files_versions/lib/Capabilities.php b/apps/files_versions/lib/Capabilities.php index 7091f9c4676..cb6394f0a36 100644 --- a/apps/files_versions/lib/Capabilities.php +++ b/apps/files_versions/lib/Capabilities.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christopher Schäpers <kondou@ts.unde.re> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Tom Needham <tom@owncloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions; @@ -29,15 +12,10 @@ use OCP\Capabilities\ICapability; use OCP\IConfig; class Capabilities implements ICapability { - private IConfig $config; - private IAppManager $appManager; - public function __construct( - IConfig $config, - IAppManager $appManager + private IConfig $config, + private IAppManager $appManager, ) { - $this->config = $config; - $this->appManager = $appManager; } /** diff --git a/apps/files_versions/lib/Command/CleanUp.php b/apps/files_versions/lib/Command/CleanUp.php index eda482357b3..e8c46afef16 100644 --- a/apps/files_versions/lib/Command/CleanUp.php +++ b/apps/files_versions/lib/Command/CleanUp.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Daniel Rudolf <nextcloud.com@daniel-rudolf.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\Command; @@ -67,7 +50,7 @@ class CleanUp extends Command { $path = $input->getOption('path'); if ($path) { if (!preg_match('#^/([^/]+)/files(/.*)?$#', $path, $pathMatches)) { - $output->writeln("<error>Invalid path given</error>"); + $output->writeln('<error>Invalid path given</error>'); return self::FAILURE; } diff --git a/apps/files_versions/lib/Command/Expire.php b/apps/files_versions/lib/Command/Expire.php index 688a6e869e9..a30e623c347 100644 --- a/apps/files_versions/lib/Command/Expire.php +++ b/apps/files_versions/lib/Command/Expire.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Robin Appelman <robin@icewind.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\Command; @@ -28,6 +12,7 @@ use OCA\Files_Versions\Storage; use OCP\Command\ICommand; use OCP\Files\StorageNotAvailableException; use OCP\IUserManager; +use OCP\Server; use Psr\Log\LoggerInterface; class Expire implements ICommand { @@ -41,7 +26,7 @@ class Expire implements ICommand { public function handle(): void { /** @var IUserManager $userManager */ - $userManager = \OC::$server->get(IUserManager::class); + $userManager = Server::get(IUserManager::class); if (!$userManager->userExists($this->user)) { // User has been deleted already return; @@ -53,7 +38,7 @@ class Expire implements ICommand { // In case of external storage and session credentials, the expiration // fails because the command does not have those credentials - $logger = \OC::$server->get(LoggerInterface::class); + $logger = Server::get(LoggerInterface::class); $logger->warning($e->getMessage(), [ 'exception' => $e, 'uid' => $this->user, diff --git a/apps/files_versions/lib/Command/ExpireVersions.php b/apps/files_versions/lib/Command/ExpireVersions.php index 24cc6a896d5..d3f341a21d2 100644 --- a/apps/files_versions/lib/Command/ExpireVersions.php +++ b/apps/files_versions/lib/Command/ExpireVersions.php @@ -1,30 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud GmbH. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud GmbH. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\Command; +use OC\Files\View; use OCA\Files_Versions\Expiration; use OCA\Files_Versions\Storage; use OCP\IUser; @@ -57,7 +40,7 @@ class ExpireVersions extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { $maxAge = $this->expiration->getMaxAgeAsTimestamp(); if (!$maxAge) { - $output->writeln("Auto expiration is configured - expiration will be handled automatically according to the expiration patterns detailed at the following link https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/file_versioning.html."); + $output->writeln('Auto expiration is configured - expiration will be handled automatically according to the expiration patterns detailed at the following link https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/file_versioning.html.'); return self::FAILURE; } @@ -78,7 +61,7 @@ class ExpireVersions extends Command { $p = new ProgressBar($output); $p->start(); - $this->userManager->callForSeenUsers(function (IUser $user) use ($p) { + $this->userManager->callForSeenUsers(function (IUser $user) use ($p): void { $p->advance(); $this->expireVersionsForUser($user); }); @@ -103,7 +86,7 @@ class ExpireVersions extends Command { \OC_Util::setupFS($user); // Check if this user has a version directory - $view = new \OC\Files\View('/' . $user); + $view = new View('/' . $user); if (!$view->is_dir('/files_versions')) { return false; } diff --git a/apps/files_versions/lib/Controller/PreviewController.php b/apps/files_versions/lib/Controller/PreviewController.php index 1365e7e50fa..2c3ff8da70d 100644 --- a/apps/files_versions/lib/Controller/PreviewController.php +++ b/apps/files_versions/lib/Controller/PreviewController.php @@ -1,104 +1,89 @@ <?php + /** - * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Morris Jobke <hey@morrisjobke.de> - * @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/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Controller; use OCA\Files_Versions\Versions\IVersionManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; +use OCP\AppFramework\Http\RedirectResponse; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\IPreview; use OCP\IRequest; use OCP\IUserSession; +use OCP\Preview\IMimeIconProvider; +#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] class PreviewController extends Controller { - /** @var IRootFolder */ - private $rootFolder; - - /** @var IUserSession */ - private $userSession; - - /** @var IVersionManager */ - private $versionManager; - - /** @var IPreview */ - private $previewManager; - public function __construct( string $appName, IRequest $request, - IRootFolder $rootFolder, - IUserSession $userSession, - IVersionManager $versionManager, - IPreview $previewManager + private IRootFolder $rootFolder, + private IUserSession $userSession, + private IVersionManager $versionManager, + private IPreview $previewManager, + private IMimeIconProvider $mimeIconProvider, ) { parent::__construct($appName, $request); - - $this->rootFolder = $rootFolder; - $this->userSession = $userSession; - $this->versionManager = $versionManager; - $this->previewManager = $previewManager; } /** - * @NoAdminRequired - * @NoCSRFRequired - * * Get the preview for a file version * * @param string $file Path of the file * @param int $x Width of the preview * @param int $y Height of the preview * @param string $version Version of the file to get the preview for - * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array<empty>, array{}> + * @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available + * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}> * * 200: Preview returned + * 303: Redirect to the mime icon url if mimeFallback is true * 400: Getting preview is not possible * 404: Preview not found */ + #[NoAdminRequired] + #[NoCSRFRequired] public function getPreview( string $file = '', int $x = 44, int $y = 44, - string $version = '' + string $version = '', + bool $mimeFallback = false, ) { if ($file === '' || $version === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } + $versionFile = null; try { $user = $this->userSession->getUser(); $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $file = $userFolder->get($file); $versionFile = $this->versionManager->getVersionFile($user, $file, $version); $preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype()); - return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); + $response = new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); + $response->cacheFor(3600 * 24, false, true); + return $response; } catch (NotFoundException $e) { + // If we have no preview enabled, we can redirect to the mime icon if any + if ($mimeFallback && $versionFile !== null) { + $url = $this->mimeIconProvider->getMimeIconUrl($versionFile->getMimeType()); + if ($url !== null) { + return new RedirectResponse($url); + } + } + return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (\InvalidArgumentException $e) { return new DataResponse([], Http::STATUS_BAD_REQUEST); diff --git a/apps/files_versions/lib/Db/VersionEntity.php b/apps/files_versions/lib/Db/VersionEntity.php index dda88817b55..10f1dc8cbba 100644 --- a/apps/files_versions/lib/Db/VersionEntity.php +++ b/apps/files_versions/lib/Db/VersionEntity.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> - * - * @author 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Db; diff --git a/apps/files_versions/lib/Db/VersionsMapper.php b/apps/files_versions/lib/Db/VersionsMapper.php index 9dc15dd60ab..318dd8f0d82 100644 --- a/apps/files_versions/lib/Db/VersionsMapper.php +++ b/apps/files_versions/lib/Db/VersionsMapper.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> - * - * @author 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Db; @@ -45,8 +28,8 @@ class VersionsMapper extends QBMapper { $qb = $this->db->getQueryBuilder(); $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))); + ->from($this->getTableName()) + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))); return $this->findEntities($qb); } @@ -58,10 +41,10 @@ class VersionsMapper extends QBMapper { $qb = $this->db->getQueryBuilder(); $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) - ->orderBy('timestamp', 'DESC') - ->setMaxResults(1); + ->from($this->getTableName()) + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) + ->orderBy('timestamp', 'DESC') + ->setMaxResults(1); return $this->findEntity($qb); } @@ -70,9 +53,9 @@ class VersionsMapper extends QBMapper { $qb = $this->db->getQueryBuilder(); $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) - ->andWhere($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp))); + ->from($this->getTableName()) + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) + ->andWhere($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp))); return $this->findEntity($qb); } @@ -81,8 +64,8 @@ class VersionsMapper extends QBMapper { $qb = $this->db->getQueryBuilder(); return $qb->delete($this->getTableName()) - ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) - ->executeStatement(); + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) + ->executeStatement(); } public function deleteAllVersionsForUser(int $storageId, ?string $path = null): void { diff --git a/apps/files_versions/lib/Events/CreateVersionEvent.php b/apps/files_versions/lib/Events/CreateVersionEvent.php index d1a5bb00c35..92ed26b2dd6 100644 --- a/apps/files_versions/lib/Events/CreateVersionEvent.php +++ b/apps/files_versions/lib/Events/CreateVersionEvent.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Events; @@ -40,17 +22,15 @@ class CreateVersionEvent extends Event { /** @var bool */ private $createVersion; - /** @var Node */ - private $node; - /** * CreateVersionEvent constructor. * * @param Node $node */ - public function __construct(Node $node) { + public function __construct( + private Node $node, + ) { $this->createVersion = true; - $this->node = $node; } /** diff --git a/apps/files_versions/lib/Events/VersionCreatedEvent.php b/apps/files_versions/lib/Events/VersionCreatedEvent.php new file mode 100644 index 00000000000..4dc7a7cb505 --- /dev/null +++ b/apps/files_versions/lib/Events/VersionCreatedEvent.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_Versions\Events; + +use OCA\Files_Versions\Versions\IVersion; +use OCP\EventDispatcher\Event; +use OCP\Files\Node; + +/** + * Event dispatched after a successful creation of a version + */ +class VersionCreatedEvent extends Event { + public function __construct( + private Node $node, + private IVersion $version, + ) { + parent::__construct(); + } + + /** + * Node of the file that has been versioned + */ + public function getNode(): Node { + return $this->node; + } + + /** + * Version of the file that was created + */ + public function getVersion(): IVersion { + return $this->version; + } +} diff --git a/apps/files_versions/lib/Events/VersionRestoredEvent.php b/apps/files_versions/lib/Events/VersionRestoredEvent.php new file mode 100644 index 00000000000..12e91bd258d --- /dev/null +++ b/apps/files_versions/lib/Events/VersionRestoredEvent.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_Versions\Events; + +use OCA\Files_Versions\Versions\IVersion; +use OCP\EventDispatcher\Event; + +/** + * Class VersionRestoredEvent + * + * Event that is called after a successful restore of a previous version + * + * @package OCA\Files_Versions + */ +class VersionRestoredEvent extends Event { + public function __construct( + private IVersion $version, + ) { + } + + /** + * Version that was restored + */ + public function getVersion(): IVersion { + return $this->version; + } +} diff --git a/apps/files_versions/lib/Expiration.php b/apps/files_versions/lib/Expiration.php index f18a577d80e..1e04d93379f 100644 --- a/apps/files_versions/lib/Expiration.php +++ b/apps/files_versions/lib/Expiration.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions; @@ -33,9 +16,6 @@ class Expiration { // how long do we keep files a version if no other value is defined in the config file (unit: days) public const NO_OBLIGATION = -1; - /** @var ITimeFactory */ - private $timeFactory; - /** @var string */ private $retentionObligation; @@ -48,12 +28,11 @@ class Expiration { /** @var bool */ private $canPurgeToSaveSpace; - /** @var LoggerInterface */ - private $logger; - - public function __construct(IConfig $config, ITimeFactory $timeFactory, LoggerInterface $logger) { - $this->timeFactory = $timeFactory; - $this->logger = $logger; + public function __construct( + IConfig $config, + private ITimeFactory $timeFactory, + private LoggerInterface $logger, + ) { $this->retentionObligation = $config->getSystemValue('versions_retention_obligation', 'auto'); if ($this->retentionObligation !== 'disabled') { @@ -121,6 +100,20 @@ class Expiration { } /** + * Get minimal retention obligation as a timestamp + * + * @return int|false + */ + public function getMinAgeAsTimestamp() { + $minAge = false; + if ($this->isEnabled() && $this->minAge !== self::NO_OBLIGATION) { + $time = $this->timeFactory->getTime(); + $minAge = $time - ($this->minAge * 86400); + } + return $minAge; + } + + /** * Get maximal retention obligation as a timestamp * * @return int|false diff --git a/apps/files_versions/lib/Listener/FileEventsListener.php b/apps/files_versions/lib/Listener/FileEventsListener.php index 3273f1f9c40..969ca4ded45 100644 --- a/apps/files_versions/lib/Listener/FileEventsListener.php +++ b/apps/files_versions/lib/Listener/FileEventsListener.php @@ -1,32 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bart Visscher <bartv@thisnet.nl> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Louis Chmn <louis@chmn.me> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions\Listener; @@ -35,6 +12,7 @@ use OC\DB\Exceptions\DbalException; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; use OC\Files\Node\NonExistingFile; +use OC\Files\Node\NonExistingFolder; use OC\Files\View; use OCA\Files_Versions\Storage; use OCA\Files_Versions\Versions\INeedSyncVersionBackend; @@ -59,6 +37,7 @@ use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; use OCP\Files\Node; +use OCP\Files\NotFoundException; use OCP\IUserSession; use Psr\Log\LoggerInterface; @@ -147,6 +126,22 @@ class FileEventsListener implements IEventListener { } public function touch_hook(Node $node): void { + // Do not handle folders. + if ($node instanceof Folder) { + return; + } + + if ($node instanceof NonExistingFile) { + $this->logger->error( + 'Failed to create or update version for {path}, node does not exist', + [ + 'path' => $node->getPath(), + ] + ); + + return; + } + $previousNode = $this->nodesTouched[$node->getId()] ?? null; if ($previousNode === null) { @@ -157,8 +152,10 @@ class FileEventsListener implements IEventListener { try { if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) { + $revision = $this->versionManager->getRevision($previousNode); + // We update the timestamp of the version entity associated with the previousNode. - $this->versionManager->updateVersionEntity($node, $previousNode->getMTime(), ['timestamp' => $node->getMTime()]); + $this->versionManager->updateVersionEntity($node, $revision, ['timestamp' => $node->getMTime()]); } } catch (DbalException $ex) { // Ignore UniqueConstraintViolationException, as we are probably in the middle of a rollback @@ -174,7 +171,22 @@ class FileEventsListener implements IEventListener { public function created(Node $node): void { // Do not handle folders. - if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) { + if (!($node instanceof File)) { + return; + } + + if ($node instanceof NonExistingFile) { + $this->logger->error( + 'Failed to create version for {path}, node does not exist', + [ + 'path' => $node->getPath(), + ] + ); + + return; + } + + if ($this->versionManager instanceof INeedSyncVersionBackend) { $this->versionManager->createVersionEntity($node); } } @@ -212,6 +224,17 @@ class FileEventsListener implements IEventListener { return; } + if ($node instanceof NonExistingFile) { + $this->logger->error( + 'Failed to create or update version for {path}, node does not exist', + [ + 'path' => $node->getPath(), + ] + ); + + return; + } + $writeHookInfo = $this->writeHookInfo[$node->getId()] ?? null; if ($writeHookInfo === null) { @@ -219,8 +242,8 @@ class FileEventsListener implements IEventListener { } if ( - $writeHookInfo['versionCreated'] && - $node->getMTime() !== $writeHookInfo['previousNode']->getMTime() + $writeHookInfo['versionCreated'] + && $node->getMTime() !== $writeHookInfo['previousNode']->getMTime() ) { // If a new version was created, insert a version in the DB for the current content. // If both versions have the same mtime, it means the latest version file simply got overrode, @@ -231,9 +254,11 @@ class FileEventsListener implements IEventListener { // If no new version was stored in the FS, no new version should be added in the DB. // So we simply update the associated version. if ($node instanceof File && $this->versionManager instanceof INeedSyncVersionBackend) { + $revision = $this->versionManager->getRevision($writeHookInfo['previousNode']); + $this->versionManager->updateVersionEntity( $node, - $writeHookInfo['previousNode']->getMtime(), + $revision, [ 'timestamp' => $node->getMTime(), 'size' => $node->getSize(), @@ -241,6 +266,15 @@ class FileEventsListener implements IEventListener { ], ); } + } catch (DoesNotExistException $e) { + // This happens if the versions app was not enabled while the file was created or updated the last time. + // meaning there is no such revision and we need to create this file. + if ($writeHookInfo['versionCreated']) { + $this->created($node); + } else { + // Normally this should not happen so we re-throw the exception to not hide any potential issues. + throw $e; + } } catch (Exception $e) { $this->logger->error('Failed to update existing version for ' . $node->getPath(), [ 'exception' => $e, @@ -264,7 +298,7 @@ class FileEventsListener implements IEventListener { /** * Erase versions of deleted file * - * This function is connected to the delete signal of OC_Filesystem + * This function is connected to the NodeDeletedEvent event * cleanup the versions directory if the actual file gets deleted */ public function remove_hook(Node $node): void { @@ -296,7 +330,7 @@ class FileEventsListener implements IEventListener { /** * rename/move versions of renamed/moved files * - * This function is connected to the rename signal of OC_Filesystem and adjust the name and location + * This function is connected to the NodeRenamedEvent event and adjust the name and location * of the stored versions along the actual file */ public function rename_hook(Node $source, Node $target): void { @@ -315,7 +349,7 @@ class FileEventsListener implements IEventListener { /** * copy versions of copied files * - * This function is connected to the copy signal of OC_Filesystem and copies the + * This function is connected to the NodeCopiedEvent event and copies the * the stored versions to the new location */ public function copy_hook(Node $source, Node $target): void { @@ -346,11 +380,19 @@ class FileEventsListener implements IEventListener { return; } - // if we rename a movable mount point, then the versions don't have - // to be renamed + // if we rename a movable mount point, then the versions don't have to be renamed $oldPath = $this->getPathForNode($source); $newPath = $this->getPathForNode($target); - $absOldPath = Filesystem::normalizePath('/' . \OC_User::getUser() . '/files' . $oldPath); + if ($oldPath === null || $newPath === null) { + return; + } + + $user = $this->userSession->getUser()?->getUID(); + if ($user === null) { + return; + } + + $absOldPath = Filesystem::normalizePath('/' . $user . '/files' . $oldPath); $manager = Filesystem::getMountManager(); $mount = $manager->find($absOldPath); $internalPath = $mount->getInternalPath($absOldPath); @@ -358,7 +400,7 @@ class FileEventsListener implements IEventListener { return; } - $view = new View(\OC_User::getUser() . '/files'); + $view = new View($user . '/files'); if ($view->file_exists($newPath)) { Storage::store($newPath); } else { @@ -382,7 +424,11 @@ class FileEventsListener implements IEventListener { } } - $owner = $node->getOwner()?->getUid(); + try { + $owner = $node->getOwner()?->getUid(); + } catch (NotFoundException) { + $owner = null; + } // If no owner, extract it from the path. // e.g. /user/files/foobar.txt @@ -403,6 +449,24 @@ class FileEventsListener implements IEventListener { } } + if (!($node instanceof NonExistingFile) && !($node instanceof NonExistingFolder)) { + $this->logger->debug('Failed to compute path for node', [ + 'node' => [ + 'path' => $node->getPath(), + 'owner' => $owner, + 'fileid' => $node->getId(), + 'size' => $node->getSize(), + 'mtime' => $node->getMTime(), + ] + ]); + } else { + $this->logger->debug('Failed to compute path for node', [ + 'node' => [ + 'path' => $node->getPath(), + 'owner' => $owner, + ] + ]); + } return null; } } diff --git a/apps/files_versions/lib/Listener/LegacyRollbackListener.php b/apps/files_versions/lib/Listener/LegacyRollbackListener.php new file mode 100644 index 00000000000..072c1511caa --- /dev/null +++ b/apps/files_versions/lib/Listener/LegacyRollbackListener.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_Versions\Listener; + +use OCA\Files_Versions\Events\VersionRestoredEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +/** + * This listener is designed to be compatible with third-party code + * that can still use a hook. This listener will be removed in + * the next version and the rollback hook will stop working. + * + * @deprecated 32.0.0 + * @template-implements IEventListener<VersionRestoredEvent> + */ +class LegacyRollbackListener implements IEventListener { + public function handle(Event $event): void { + if (!($event instanceof VersionRestoredEvent)) { + return; + } + $version = $event->getVersion(); + \OC_Hook::emit('\OCP\Versions', 'rollback', [ + 'path' => $version->getVersionPath(), + 'revision' => $version->getRevisionId(), + 'node' => $version->getSourceFile(), + ]); + } +} diff --git a/apps/files_versions/lib/Listener/LoadAdditionalListener.php b/apps/files_versions/lib/Listener/LoadAdditionalListener.php index 379b5fcc36d..cb955629c0f 100644 --- a/apps/files_versions/lib/Listener/LoadAdditionalListener.php +++ b/apps/files_versions/lib/Listener/LoadAdditionalListener.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Listener; diff --git a/apps/files_versions/lib/Listener/LoadSidebarListener.php b/apps/files_versions/lib/Listener/LoadSidebarListener.php index 8f8e8c67ee4..b8d13fa4810 100644 --- a/apps/files_versions/lib/Listener/LoadSidebarListener.php +++ b/apps/files_versions/lib/Listener/LoadSidebarListener.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Listener; diff --git a/apps/files_versions/lib/Listener/VersionAuthorListener.php b/apps/files_versions/lib/Listener/VersionAuthorListener.php index 158f039d555..9b93b1f888b 100644 --- a/apps/files_versions/lib/Listener/VersionAuthorListener.php +++ b/apps/files_versions/lib/Listener/VersionAuthorListener.php @@ -3,26 +3,13 @@ declare(strict_types=1); /** - * @author Eduardo Morales emoral435@gmail.com> - * - * @license GNU AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Listener; use OC\Files\Node\Folder; +use OCA\Files_Versions\Sabre\Plugin; use OCA\Files_Versions\Versions\IMetadataVersionBackend; use OCA\Files_Versions\Versions\IVersionManager; use OCP\EventDispatcher\Event; @@ -61,8 +48,9 @@ class VersionAuthorListener implements IEventListener { } // check if our version manager supports setting the metadata if ($this->versionManager instanceof IMetadataVersionBackend) { + $revision = $this->versionManager->getRevision($node); $author = $user->getUID(); - $this->versionManager->setMetadataValue($node, $node->getMTime(), 'author', $author); + $this->versionManager->setMetadataValue($node, $revision, Plugin::AUTHOR, $author); } } } diff --git a/apps/files_versions/lib/Listener/VersionStorageMoveListener.php b/apps/files_versions/lib/Listener/VersionStorageMoveListener.php index 1648bd403d2..d0a0bcf4a92 100644 --- a/apps/files_versions/lib/Listener/VersionStorageMoveListener.php +++ b/apps/files_versions/lib/Listener/VersionStorageMoveListener.php @@ -3,29 +3,15 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me> - * - * @author Louis Chmn <louis@chmn.me> - * - * @license GNU AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OCA\Files_Versions\Listener; use Exception; use OC\Files\Node\NonExistingFile; +use OC\Files\Node\NonExistingFolder; use OCA\Files_Versions\Versions\IVersionBackend; use OCA\Files_Versions\Versions\IVersionManager; use OCA\Files_Versions\Versions\IVersionsImporterBackend; @@ -79,7 +65,7 @@ class VersionStorageMoveListener implements IEventListener { $user = $this->userSession->getUser() ?? $source->getOwner(); if ($user === null) { - throw new Exception("Cannot move versions across storages without a user."); + throw new Exception('Cannot move versions across storages without a user.'); } if ($event instanceof BeforeNodeRenamedEvent) { @@ -145,7 +131,7 @@ class VersionStorageMoveListener implements IEventListener { } private function getNodeStorage(Node $node): IStorage { - if ($node instanceof NonExistingFile) { + if ($node instanceof NonExistingFile || $node instanceof NonExistingFolder) { return $node->getParent()->getStorage(); } else { return $node->getStorage(); diff --git a/apps/files_versions/lib/Migration/Version1020Date20221114144058.php b/apps/files_versions/lib/Migration/Version1020Date20221114144058.php index f0d58284c9f..77c8c2201f3 100644 --- a/apps/files_versions/lib/Migration/Version1020Date20221114144058.php +++ b/apps/files_versions/lib/Migration/Version1020Date20221114144058.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> - * - * @author 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Migration; @@ -46,11 +29,11 @@ class Version1020Date20221114144058 extends SimpleMigrationStep { /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); - if ($schema->hasTable("files_versions")) { + if ($schema->hasTable('files_versions')) { return null; } - $table = $schema->createTable("files_versions"); + $table = $schema->createTable('files_versions'); $table->addColumn('id', Types::BIGINT, [ 'autoincrement' => true, 'notnull' => true, diff --git a/apps/files_versions/lib/Sabre/Plugin.php b/apps/files_versions/lib/Sabre/Plugin.php index c50d650c958..984c4a36e5b 100644 --- a/apps/files_versions/lib/Sabre/Plugin.php +++ b/apps/files_versions/lib/Sabre/Plugin.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -42,6 +24,10 @@ use Sabre\HTTP\ResponseInterface; class Plugin extends ServerPlugin { private Server $server; + public const LABEL = 'label'; + + public const AUTHOR = 'author'; + public const VERSION_LABEL = '{http://nextcloud.org/ns}version-label'; public const VERSION_AUTHOR = '{http://nextcloud.org/ns}version-author'; // dav property for author @@ -94,9 +80,12 @@ class Plugin extends ServerPlugin { public function propFind(PropFind $propFind, INode $node): void { if ($node instanceof VersionFile) { - $propFind->handle(self::VERSION_LABEL, fn () => $node->getMetadataValue('label')); - $propFind->handle(self::VERSION_AUTHOR, fn () => $node->getMetadataValue("author")); - $propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, fn () => $this->previewManager->isMimeSupported($node->getContentType())); + $propFind->handle(self::VERSION_LABEL, fn () => $node->getMetadataValue(self::LABEL)); + $propFind->handle(self::VERSION_AUTHOR, fn () => $node->getMetadataValue(self::AUTHOR)); + $propFind->handle( + FilesPlugin::HAS_PREVIEW_PROPERTYNAME, + fn (): string => $this->previewManager->isMimeSupported($node->getContentType()) ? 'true' : 'false', + ); } } @@ -104,7 +93,7 @@ class Plugin extends ServerPlugin { $node = $this->server->tree->getNodeForPath($path); if ($node instanceof VersionFile) { - $propPatch->handle(self::VERSION_LABEL, fn (string $label) => $node->setMetadataValue('label', $label)); + $propPatch->handle(self::VERSION_LABEL, fn (string $label) => $node->setMetadataValue(self::LABEL, $label)); } } } diff --git a/apps/files_versions/lib/Sabre/RestoreFolder.php b/apps/files_versions/lib/Sabre/RestoreFolder.php index 31d87cc7112..7904b098a4f 100644 --- a/apps/files_versions/lib/Sabre/RestoreFolder.php +++ b/apps/files_versions/lib/Sabre/RestoreFolder.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; diff --git a/apps/files_versions/lib/Sabre/RootCollection.php b/apps/files_versions/lib/Sabre/RootCollection.php index 835df5930bc..1e7129f23da 100644 --- a/apps/files_versions/lib/Sabre/RootCollection.php +++ b/apps/files_versions/lib/Sabre/RootCollection.php @@ -1,25 +1,8 @@ <?php + /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -34,33 +17,16 @@ use Sabre\DAVACL\PrincipalBackend; class RootCollection extends AbstractPrincipalCollection { - /** @var IRootFolder */ - private $rootFolder; - - /** @var IUserManager */ - private $userManager; - - /** @var IVersionManager */ - private $versionManager; - - /** @var IUserSession */ - private $userSession; - public function __construct( PrincipalBackend\BackendInterface $principalBackend, - IRootFolder $rootFolder, + private IRootFolder $rootFolder, IConfig $config, - IUserManager $userManager, - IVersionManager $versionManager, - IUserSession $userSession + private IUserManager $userManager, + private IVersionManager $versionManager, + private IUserSession $userSession, ) { parent::__construct($principalBackend, 'principals/users'); - $this->rootFolder = $rootFolder; - $this->userManager = $userManager; - $this->versionManager = $versionManager; - $this->userSession = $userSession; - $this->disableListing = !$config->getSystemValue('debug', false); } diff --git a/apps/files_versions/lib/Sabre/VersionCollection.php b/apps/files_versions/lib/Sabre/VersionCollection.php index 946ac9baad7..375d5cf99f2 100644 --- a/apps/files_versions/lib/Sabre/VersionCollection.php +++ b/apps/files_versions/lib/Sabre/VersionCollection.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -36,19 +18,11 @@ use Sabre\DAV\ICollection; class VersionCollection implements ICollection { - /** @var File */ - private $file; - - /** @var IUser */ - private $user; - - /** @var IVersionManager */ - private $versionManager; - - public function __construct(File $file, IUser $user, IVersionManager $versionManager) { - $this->file = $file; - $this->user = $user; - $this->versionManager = $versionManager; + public function __construct( + private File $file, + private IUser $user, + private IVersionManager $versionManager, + ) { } public function createFile($name, $data = null) { diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index ef89a033c29..faa03473648 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -41,7 +23,7 @@ use Sabre\DAV\IFile; class VersionFile implements IFile { public function __construct( private IVersion $version, - private IVersionManager $versionManager + private IVersionManager $versionManager, ) { } @@ -93,7 +75,7 @@ class VersionFile implements IFile { $backend = $this->version->getBackend(); if ($backend instanceof IMetadataVersionBackend) { - $backend->setMetadataValue($this->version->getSourceFile(), $this->version->getRevisionId(), $key, $value); + $backend->setMetadataValue($this->version->getSourceFile(), $this->version->getTimestamp(), $key, $value); return true; } elseif ($key === 'label' && $backend instanceof INameableVersionBackend) { $backend->setVersionLabel($this->version, $value); diff --git a/apps/files_versions/lib/Sabre/VersionHome.php b/apps/files_versions/lib/Sabre/VersionHome.php index 13505d9a96c..07ac491f2a1 100644 --- a/apps/files_versions/lib/Sabre/VersionHome.php +++ b/apps/files_versions/lib/Sabre/VersionHome.php @@ -1,25 +1,8 @@ <?php + /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -32,23 +15,12 @@ use Sabre\DAV\ICollection; class VersionHome implements ICollection { - /** @var array */ - private $principalInfo; - - /** @var IRootFolder */ - private $rootFolder; - - /** @var IUserManager */ - private $userManager; - - /** @var IVersionManager */ - private $versionManager; - - public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) { - $this->principalInfo = $principalInfo; - $this->rootFolder = $rootFolder; - $this->userManager = $userManager; - $this->versionManager = $versionManager; + public function __construct( + private array $principalInfo, + private IRootFolder $rootFolder, + private IUserManager $userManager, + private IVersionManager $versionManager, + ) { } private function getUser() { diff --git a/apps/files_versions/lib/Sabre/VersionRoot.php b/apps/files_versions/lib/Sabre/VersionRoot.php index 2ae1bf04203..7f7014fbee3 100644 --- a/apps/files_versions/lib/Sabre/VersionRoot.php +++ b/apps/files_versions/lib/Sabre/VersionRoot.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Sabre; @@ -36,19 +18,11 @@ use Sabre\DAV\ICollection; class VersionRoot implements ICollection { - /** @var IUser */ - private $user; - - /** @var IRootFolder */ - private $rootFolder; - - /** @var IVersionManager */ - private $versionManager; - - public function __construct(IUser $user, IRootFolder $rootFolder, IVersionManager $versionManager) { - $this->user = $user; - $this->rootFolder = $rootFolder; - $this->versionManager = $versionManager; + public function __construct( + private IUser $user, + private IRootFolder $rootFolder, + private IVersionManager $versionManager, + ) { } public function delete() { diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 44ce909e03b..6d53a19a518 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -1,50 +1,20 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bart Visscher <bartv@thisnet.nl> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Felix Moeller <mail@felixmoeller.de> - * @author Felix Nieuwenhuizen <felix@tdlrali.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Liam JACK <liamjack@users.noreply.github.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Victor Dubiniuk <dubiniuk@owncloud.com> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Versions; use OC\Files\Filesystem; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\Search\SearchBinaryOperator; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; use OC\Files\View; +use OC\User\NoUserException; use OC_User; use OCA\Files_Sharing\SharedMount; use OCA\Files_Versions\AppInfo\Application; @@ -55,20 +25,25 @@ use OCA\Files_Versions\Versions\IVersionManager; use OCP\AppFramework\Db\DoesNotExistException; use OCP\Command\IBus; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files; use OCP\Files\FileInfo; use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; +use OCP\Files\Storage\IWriteStreamStorage; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; use OCP\Lock\ILockingProvider; +use OCP\Server; +use OCP\Util; use Psr\Log\LoggerInterface; class Storage { @@ -100,7 +75,7 @@ class Storage { 6 => ['intervalEndsAfter' => -1, 'step' => 604800], ]; - /** @var \OCA\Files_Versions\AppInfo\Application */ + /** @var Application */ private static $application; /** @@ -109,11 +84,11 @@ class Storage { * * @param string $filename * @return array - * @throws \OC\User\NoUserException + * @throws NoUserException */ public static function getUidAndFilename($filename) { $uid = Filesystem::getOwner($filename); - $userManager = \OC::$server->get(IUserManager::class); + $userManager = Server::get(IUserManager::class); // if the user with the UID doesn't exists, e.g. because the UID points // to a remote user with a federated cloud ID we use the current logged-in // user. We need a valid local user to create the versions @@ -123,7 +98,7 @@ class Storage { Filesystem::initMountPoints($uid); if ($uid !== OC_User::getUser()) { $info = Filesystem::getFileInfo($filename); - $ownerView = new View('/'.$uid.'/files'); + $ownerView = new View('/' . $uid . '/files'); try { $filename = $ownerView->getPath($info['fileid']); // make sure that the file name doesn't end with a trailing slash @@ -196,10 +171,10 @@ class Storage { $uid = Filesystem::getView()->getOwner(''); /** @var IRootFolder $rootFolder */ - $rootFolder = \OC::$server->get(IRootFolder::class); + $rootFolder = Server::get(IRootFolder::class); $userFolder = $rootFolder->getUserFolder($uid); - $eventDispatcher = \OC::$server->get(IEventDispatcher::class); + $eventDispatcher = Server::get(IEventDispatcher::class); try { $file = $userFolder->get($filename); } catch (NotFoundException $e) { @@ -217,7 +192,7 @@ class Storage { } /** @var IUserManager $userManager */ - $userManager = \OC::$server->get(IUserManager::class); + $userManager = Server::get(IUserManager::class); $user = $userManager->get($uid); if (!$user) { @@ -236,7 +211,7 @@ class Storage { } /** @var IVersionManager $versionManager */ - $versionManager = \OC::$server->get(IVersionManager::class); + $versionManager = Server::get(IVersionManager::class); $versionManager->createVersion($user, $file); } @@ -308,9 +283,9 @@ class Storage { * Rename or copy versions of a file of the given paths * * @param string $sourcePath source path of the file to move, relative to - * the currently logged in user's "files" folder + * the currently logged in user's "files" folder * @param string $targetPath target path of the file to move, relative to - * the currently logged in user's "files" folder + * the currently logged in user's "files" folder * @param string $operation can be 'copy' or 'rename' */ public static function renameOrCopy($sourcePath, $targetPath, $operation) { @@ -335,7 +310,7 @@ class Storage { // does the directory exists for versions too ? if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) { // create missing dirs if necessary - self::createMissingDirectories($targetPath, new View('/'. $targetOwner)); + self::createMissingDirectories($targetPath, new View('/' . $targetOwner)); // move the directory containing the versions $rootView->$operation( @@ -345,13 +320,13 @@ class Storage { } } elseif ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) { // create missing dirs if necessary - self::createMissingDirectories($targetPath, new View('/'. $targetOwner)); + self::createMissingDirectories($targetPath, new View('/' . $targetOwner)); foreach ($versions as $v) { // move each version one by one to the target directory $rootView->$operation( - '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'], - '/' . $targetOwner . '/files_versions/' . $targetPath.'.v' . $v['version'] + '/' . $sourceOwner . '/files_versions/' . $sourcePath . '.v' . $v['version'], + '/' . $targetOwner . '/files_versions/' . $targetPath . '.v' . $v['version'] ); } } @@ -374,11 +349,11 @@ class Storage { $filename = '/' . ltrim($file, '/'); // Fetch the userfolder to trigger view hooks - $root = \OC::$server->get(IRootFolder::class); + $root = Server::get(IRootFolder::class); $userFolder = $root->getUserFolder($user->getUID()); - $users_view = new View('/'.$user->getUID()); - $files_view = new View('/'. $user->getUID().'/files'); + $users_view = new View('/' . $user->getUID()); + $files_view = new View('/' . $user->getUID() . '/files'); $versionCreated = false; @@ -390,9 +365,9 @@ class Storage { } //first create a new version - $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename); + $version = 'files_versions' . $filename . '.v' . $users_view->filemtime('files' . $filename); if (!$users_view->file_exists($version)) { - $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename)); + $users_view->copy('files' . $filename, 'files_versions' . $filename . '.v' . $users_view->filemtime('files' . $filename)); $versionCreated = true; } @@ -407,7 +382,8 @@ class Storage { $fileInfo->getId(), [ 'encrypted' => $oldVersion, 'encryptedVersion' => $oldVersion, - 'size' => $oldFileInfo->getSize() + 'size' => $oldFileInfo->getData()['size'], + 'unencrypted_size' => $oldFileInfo->getData()['unencrypted_size'], ] ); @@ -416,8 +392,6 @@ class Storage { $files_view->touch($file, $revision); Storage::scheduleExpire($user->getUID(), $file); - $node = $userFolder->get($file); - return true; } elseif ($versionCreated) { self::deleteVersion($users_view, $version); @@ -446,12 +420,31 @@ class Storage { try { // TODO add a proper way of overwriting a file while maintaining file ids - if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) { + if ($storage1->instanceOfStorage(ObjectStoreStorage::class) + || $storage2->instanceOfStorage(ObjectStoreStorage::class) + ) { $source = $storage1->fopen($internalPath1, 'r'); - $target = $storage2->fopen($internalPath2, 'w'); - [, $result] = \OC_Helper::streamCopy($source, $target); - fclose($source); - fclose($target); + $result = $source !== false; + if ($result) { + if ($storage2->instanceOfStorage(IWriteStreamStorage::class)) { + /** @var IWriteStreamStorage $storage2 */ + $storage2->writeStream($internalPath2, $source); + } else { + $target = $storage2->fopen($internalPath2, 'w'); + $result = $target !== false; + if ($result) { + [, $result] = Files::streamCopy($source, $target, true); + } + // explicit check as S3 library closes streams already + if (is_resource($target)) { + fclose($target); + } + } + } + // explicit check as S3 library closes streams already + if (is_resource($source)) { + fclose($source); + } if ($result !== false) { $storage1->unlink($internalPath1); @@ -505,7 +498,7 @@ class Storage { $pathparts = pathinfo($entryName); $timestamp = substr($pathparts['extension'] ?? '', 1); if (!is_numeric($timestamp)) { - \OC::$server->get(LoggerInterface::class)->error( + Server::get(LoggerInterface::class)->error( 'Version file {path} has incorrect name format', [ 'path' => $entryName, @@ -522,14 +515,14 @@ class Storage { $versions[$key]['preview'] = ''; } else { /** @var IURLGenerator $urlGenerator */ - $urlGenerator = \OC::$server->get(IURLGenerator::class); + $urlGenerator = Server::get(IURLGenerator::class); $versions[$key]['preview'] = $urlGenerator->linkToRoute('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]); } $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename); $versions[$key]['name'] = $versionedFile; $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName); - $versions[$key]['mimetype'] = \OC::$server->get(IMimeTypeDetector::class)->detectPath($versionedFile); + $versions[$key]['mimetype'] = Server::get(IMimeTypeDetector::class)->detectPath($versionedFile); } } } @@ -549,7 +542,7 @@ class Storage { */ public static function expireOlderThanMaxForUser($uid) { /** @var IRootFolder $root */ - $root = \OC::$server->get(IRootFolder::class); + $root = Server::get(IRootFolder::class); try { /** @var Folder $versionsRoot */ $versionsRoot = $root->get('/' . $uid . '/files_versions'); @@ -573,7 +566,7 @@ class Storage { )); /** @var VersionsMapper $versionsMapper */ - $versionsMapper = \OC::$server->get(VersionsMapper::class); + $versionsMapper = Server::get(VersionsMapper::class); $userFolder = $root->getUserFolder($uid); $versionEntities = []; @@ -594,7 +587,7 @@ class Storage { } try { - $node = $userFolder->get(substr($path, 0, -strlen('.v'.$version))); + $node = $userFolder->get(substr($path, 0, -strlen('.v' . $version))); $versionEntity = $versionsMapper->findVersionForFileId($node->getId(), $version); $versionEntities[$info->getId()] = $versionEntity; @@ -604,7 +597,7 @@ class Storage { } catch (NotFoundException $e) { // Original node not found, delete the version return true; - } catch (StorageNotAvailableException | StorageInvalidException $e) { + } catch (StorageNotAvailableException|StorageInvalidException $e) { // Storage can't be used, but it might only be temporary so we can't always delete the version // since we can't determine if the version is named we take the safe route and don't expire return false; @@ -626,8 +619,12 @@ class Storage { $versionsMapper->delete($versionEntity); } - $version->delete(); - \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $internalPath, 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]); + try { + $version->delete(); + \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $internalPath, 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]); + } catch (NotPermittedException $e) { + Server::get(LoggerInterface::class)->error("Missing permissions to delete version: {$internalPath}", ['app' => 'files_versions', 'exception' => $e]); + } } } @@ -641,19 +638,19 @@ class Storage { $diff = time() - $timestamp; if ($diff < 60) { // first minute - return $diff . " seconds ago"; + return $diff . ' seconds ago'; } elseif ($diff < 3600) { //first hour - return round($diff / 60) . " minutes ago"; + return round($diff / 60) . ' minutes ago'; } elseif ($diff < 86400) { // first day - return round($diff / 3600) . " hours ago"; + return round($diff / 3600) . ' hours ago'; } elseif ($diff < 604800) { //first week - return round($diff / 86400) . " days ago"; + return round($diff / 86400) . ' days ago'; } elseif ($diff < 2419200) { //first month - return round($diff / 604800) . " weeks ago"; + return round($diff / 604800) . ' weeks ago'; } elseif ($diff < 29030400) { // first year - return round($diff / 2419200) . " months ago"; + return round($diff / 2419200) . ' months ago'; } else { - return round($diff / 29030400) . " years ago"; + return round($diff / 29030400) . ' years ago'; } } @@ -696,7 +693,7 @@ class Storage { ]; foreach ($versions as $key => $value) { - $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']); + $size = $view->filesize(self::VERSIONS_ROOT . '/' . $value['path'] . '.v' . $value['timestamp']); $filename = $value['path']; $result['all'][$key]['version'] = $value['timestamp']; @@ -722,7 +719,15 @@ class Storage { $expiration = self::getExpiration(); if ($expiration->shouldAutoExpire()) { - [$toDelete, $size] = self::getAutoExpireList($time, $versions); + // Exclude versions that are newer than the minimum age from the auto expiration logic. + $minAge = $expiration->getMinAgeAsTimestamp(); + if ($minAge !== false) { + $versionsToAutoExpire = array_filter($versions, fn ($version) => $version['version'] < $minAge); + } else { + $versionsToAutoExpire = $versions; + } + + [$toDelete, $size] = self::getAutoExpireList($time, $versionsToAutoExpire); } else { $size = 0; $toDelete = []; // versions we want to delete @@ -730,8 +735,8 @@ class Storage { foreach ($versions as $key => $version) { if (!is_numeric($version['version'])) { - \OC::$server->get(LoggerInterface::class)->error( - 'Found a non-numeric timestamp version: '. json_encode($version), + Server::get(LoggerInterface::class)->error( + 'Found a non-numeric timestamp version: ' . json_encode($version), ['app' => 'files_versions']); continue; } @@ -781,7 +786,7 @@ class Storage { //distance between two version too small, mark to delete $toDelete[$key] = $version['path'] . '.v' . $version['version']; $size += $version['size']; - \OC::$server->get(LoggerInterface::class)->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']); + Server::get(LoggerInterface::class)->info('Mark to expire ' . $version['path'] . ' next version should be ' . $nextVersion . ' or smaller. (prevTimestamp: ' . $prevTimestamp . '; step: ' . $step, ['app' => 'files_versions']); } else { $nextVersion = $version['version'] - $step; $prevTimestamp = $version['version']; @@ -816,7 +821,7 @@ class Storage { if ($expiration->isEnabled()) { $command = new Expire($uid, $fileName); /** @var IBus $bus */ - $bus = \OC::$server->get(IBus::class); + $bus = Server::get(IBus::class); $bus->push($command); } } @@ -835,14 +840,14 @@ class Storage { $expiration = self::getExpiration(); /** @var LoggerInterface $logger */ - $logger = \OC::$server->get(LoggerInterface::class); + $logger = Server::get(LoggerInterface::class); if ($expiration->isEnabled()) { // get available disk space for user - $user = \OC::$server->get(IUserManager::class)->get($uid); + $user = Server::get(IUserManager::class)->get($uid); if (is_null($user)) { $logger->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']); - throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid); + throw new NoUserException('Backends provided no user object for ' . $uid); } \OC_Util::setupFS($uid); @@ -861,7 +866,7 @@ class Storage { // file maybe renamed or deleted return false; } - $versionsFileview = new View('/'.$uid.'/files_versions'); + $versionsFileview = new View('/' . $uid . '/files_versions'); $softQuota = true; $quota = $user->getQuota(); @@ -869,7 +874,7 @@ class Storage { $quota = Filesystem::free_space('/'); $softQuota = false; } else { - $quota = \OCP\Util::computerFileSize($quota); + $quota = Util::computerFileSize($quota); } // make sure that we have the current size of the version history @@ -879,7 +884,7 @@ class Storage { // subtract size of files and current versions size from quota if ($quota >= 0) { if ($softQuota) { - $root = \OC::$server->get(IRootFolder::class); + $root = Server::get(IRootFolder::class); $userFolder = $root->getUserFolder($uid); if (is_null($userFolder)) { $availableSpace = 0; @@ -924,12 +929,12 @@ class Storage { // Make sure to cleanup version table relations as expire does not pass deleteVersion try { /** @var VersionsMapper $versionsMapper */ - $versionsMapper = \OC::$server->get(VersionsMapper::class); - $file = \OC::$server->get(IRootFolder::class)->getUserFolder($uid)->get($filename); + $versionsMapper = Server::get(VersionsMapper::class); + $file = Server::get(IRootFolder::class)->getUserFolder($uid)->get($filename); $pathparts = pathinfo($path); $timestamp = (int)substr($pathparts['extension'] ?? '', 1); $versionEntity = $versionsMapper->findVersionForFileId($file->getId(), $timestamp); - if ($versionEntity->getMetadataValue('label') !== '') { + if ($versionEntity->getMetadataValue('label') !== null && $versionEntity->getMetadataValue('label') !== '') { continue; } $versionsMapper->delete($versionEntity); @@ -953,10 +958,10 @@ class Storage { reset($allVersions); while ($availableSpace < 0 && $i < $numOfVersions) { $version = current($allVersions); - \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); + \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']); - \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); - $logger->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']); + \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED]); + $logger->info('running out of space! Delete oldest version: ' . $version['path'] . '.v' . $version['version'], ['app' => 'files_versions']); $versionsSize -= $version['size']; $availableSpace += $version['size']; next($allVersions); @@ -974,13 +979,13 @@ class Storage { * that match the given path to a file. * * @param string $filename $path to a file, relative to the user's - * "files" folder + * "files" folder * @param View $view view on data/user/ */ public static function createMissingDirectories($filename, $view) { $dirname = Filesystem::normalizePath(dirname($filename)); $dirParts = explode('/', $dirname); - $dir = "/files_versions"; + $dir = '/files_versions'; foreach ($dirParts as $part) { $dir = $dir . '/' . $part; if (!$view->file_exists($dir)) { @@ -995,7 +1000,7 @@ class Storage { */ protected static function getExpiration() { if (self::$application === null) { - self::$application = \OC::$server->get(Application::class); + self::$application = Server::get(Application::class); } return self::$application->getContainer()->get(Expiration::class); } diff --git a/apps/files_versions/lib/Versions/BackendNotFoundException.php b/apps/files_versions/lib/Versions/BackendNotFoundException.php index 151957c116a..f1fbecb852a 100644 --- a/apps/files_versions/lib/Versions/BackendNotFoundException.php +++ b/apps/files_versions/lib/Versions/BackendNotFoundException.php @@ -1,24 +1,8 @@ <?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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/IDeletableVersionBackend.php b/apps/files_versions/lib/Versions/IDeletableVersionBackend.php index abb43d09d90..fefc038864f 100644 --- a/apps/files_versions/lib/Versions/IDeletableVersionBackend.php +++ b/apps/files_versions/lib/Versions/IDeletableVersionBackend.php @@ -3,23 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/IMetadataVersion.php b/apps/files_versions/lib/Versions/IMetadataVersion.php index 40ee827012a..bc4cd77138b 100644 --- a/apps/files_versions/lib/Versions/IMetadataVersion.php +++ b/apps/files_versions/lib/Versions/IMetadataVersion.php @@ -3,23 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2024 Eduardo Morales <emoral435@gmail.com> - * - * @license GNU AGPL-3.0-or-later - * - * 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/>. - * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/IMetadataVersionBackend.php b/apps/files_versions/lib/Versions/IMetadataVersionBackend.php index 348596c050c..79db85e460b 100644 --- a/apps/files_versions/lib/Versions/IMetadataVersionBackend.php +++ b/apps/files_versions/lib/Versions/IMetadataVersionBackend.php @@ -3,23 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2024 Eduardo Morales <emoral435@gmail.com> - * - * @license GNU AGPL-3.0-or-later - * - * 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/>. - * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/INameableVersion.php b/apps/files_versions/lib/Versions/INameableVersion.php index 7752247c791..a470239f128 100644 --- a/apps/files_versions/lib/Versions/INameableVersion.php +++ b/apps/files_versions/lib/Versions/INameableVersion.php @@ -3,23 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/INameableVersionBackend.php b/apps/files_versions/lib/Versions/INameableVersionBackend.php index 5df2ce57865..d2ab7ed8135 100644 --- a/apps/files_versions/lib/Versions/INameableVersionBackend.php +++ b/apps/files_versions/lib/Versions/INameableVersionBackend.php @@ -3,23 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/INeedSyncVersionBackend.php b/apps/files_versions/lib/Versions/INeedSyncVersionBackend.php index f5112ab11dd..e52e2f8e8bc 100644 --- a/apps/files_versions/lib/Versions/INeedSyncVersionBackend.php +++ b/apps/files_versions/lib/Versions/INeedSyncVersionBackend.php @@ -3,33 +3,23 @@ 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/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; +use OCA\Files_Versions\Db\VersionEntity; use OCP\Files\File; /** * @since 28.0.0 */ interface INeedSyncVersionBackend { - public function createVersionEntity(File $file): void; + /** + * TODO: Convert return type to strong type once all implementations are fixed. + * @return null|VersionEntity + */ + public function createVersionEntity(File $file); public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void; public function deleteVersionsEntity(File $file): void; } diff --git a/apps/files_versions/lib/Versions/IVersion.php b/apps/files_versions/lib/Versions/IVersion.php index 8480658fa30..e5fd53d0157 100644 --- a/apps/files_versions/lib/Versions/IVersion.php +++ b/apps/files_versions/lib/Versions/IVersion.php @@ -3,25 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/IVersionBackend.php b/apps/files_versions/lib/Versions/IVersionBackend.php index 39e75672266..18f8c17f0ac 100644 --- a/apps/files_versions/lib/Versions/IVersionBackend.php +++ b/apps/files_versions/lib/Versions/IVersionBackend.php @@ -3,29 +3,12 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; +use OC\Files\Node\Node; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\NotFoundException; @@ -96,4 +79,11 @@ interface IVersionBackend { * @since 15.0.0 */ public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File; + + /** + * Get the revision for a node + * + * @since 32.0.0 + */ + public function getRevision(Node $node): int; } diff --git a/apps/files_versions/lib/Versions/IVersionManager.php b/apps/files_versions/lib/Versions/IVersionManager.php index ee5d7abeb0c..ecd424d0cc1 100644 --- a/apps/files_versions/lib/Versions/IVersionManager.php +++ b/apps/files_versions/lib/Versions/IVersionManager.php @@ -3,25 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/IVersionsImporterBackend.php b/apps/files_versions/lib/Versions/IVersionsImporterBackend.php index 45649268107..db9349328e9 100644 --- a/apps/files_versions/lib/Versions/IVersionsImporterBackend.php +++ b/apps/files_versions/lib/Versions/IVersionsImporterBackend.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me> - * - * @author 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/>. - * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php index deb8833c87b..48d69d31629 100644 --- a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php +++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php @@ -3,26 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; @@ -30,11 +12,10 @@ namespace OCA\Files_Versions\Versions; use Exception; 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\Constants; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\Folder; @@ -42,6 +23,7 @@ use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\Files\Storage\ISharedStorage; use OCP\Files\Storage\IStorage; use OCP\IUser; use OCP\IUserManager; @@ -66,8 +48,12 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend public function getVersionsForFile(IUser $user, FileInfo $file): array { $storage = $file->getStorage(); - if ($storage->instanceOfStorage(SharedStorage::class)) { + if ($storage->instanceOfStorage(ISharedStorage::class)) { $owner = $storage->getOwner(''); + if ($owner === false) { + throw new NotFoundException('No owner for ' . $file->getPath()); + } + $user = $this->userManager->get($owner); $fileId = $file->getId(); @@ -84,7 +70,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend $file = $userFolder->getFirstNodeById($fileId); if (!$file) { - throw new NotFoundException("version file not found for share owner"); + throw new NotFoundException('version file not found for share owner'); } } else { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); @@ -177,7 +163,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend } public function rollback(IVersion $version) { - if (!$this->currentUserHasPermissions($version->getSourceFile(), \OCP\Constants::PERMISSION_UPDATE)) { + if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_UPDATE)) { throw new Forbidden('You cannot restore this version because you do not have update permissions on the source file.'); } @@ -210,7 +196,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend // 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 */ + /** @var ISharedStorage $storage */ $userFolder = $this->rootFolder->getUserFolder($owner->getUID()); $user = $owner; $ownerPathInStorage = $sourceFile->getInternalPath(); @@ -226,8 +212,12 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend return $file; } + public function getRevision(Node $node): int { + return $node->getMTime(); + } + public function deleteVersion(IVersion $version): void { - if (!$this->currentUserHasPermissions($version->getSourceFile(), \OCP\Constants::PERMISSION_DELETE)) { + if (!$this->currentUserHasPermissions($version->getSourceFile(), Constants::PERMISSION_DELETE)) { throw new Forbidden('You cannot delete this version because you do not have delete permissions on the source file.'); } @@ -239,14 +229,35 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend $this->versionsMapper->delete($versionEntity); } - public function createVersionEntity(File $file): void { + public function createVersionEntity(File $file): ?VersionEntity { $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); + + $tries = 1; + while ($tries < 5) { + try { + $this->versionsMapper->insert($versionEntity); + return $versionEntity; + } catch (\OCP\DB\Exception $e) { + if (!in_array($e->getReason(), [ + \OCP\DB\Exception::REASON_CONSTRAINT_VIOLATION, + \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION, + ]) + ) { + throw $e; + } + /* Conflict with another version, increase mtime and try again */ + $versionEntity->setTimestamp($versionEntity->getTimestamp() + 1); + $tries++; + $this->logger->warning('Constraint violation while inserting version, retrying with increased timestamp', ['exception' => $e]); + } + } + + return null; } public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void { @@ -275,7 +286,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend $currentUserId = $this->userSession->getUser()?->getUID(); if ($currentUserId === null) { - throw new NotFoundException("No user logged in"); + throw new NotFoundException('No user logged in'); } if ($sourceFile->getOwner()?->getUID() === $currentUserId) { @@ -285,7 +296,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend $nodes = $this->rootFolder->getUserFolder($currentUserId)->getById($sourceFile->getId()); if (count($nodes) === 0) { - throw new NotFoundException("Version file not accessible by current user"); + throw new NotFoundException('Version file not accessible by current user'); } foreach ($nodes as $node) { @@ -298,7 +309,7 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend } public function setMetadataValue(Node $node, int $revision, string $key, string $value): void { - if (!$this->currentUserHasPermissions($node, \OCP\Constants::PERMISSION_UPDATE)) { + if (!$this->currentUserHasPermissions($node, Constants::PERMISSION_UPDATE)) { throw new Forbidden('You cannot update the version\'s metadata because you do not have update permissions on the source file.'); } @@ -361,16 +372,20 @@ class LegacyVersionsBackend implements IVersionBackend, IDeletableVersionBackend * @inheritdoc */ public function clearVersionsForFile(IUser $user, Node $source, Node $target): void { - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $userId = $user->getUID(); + $userFolder = $this->rootFolder->getUserFolder($userId); $relativePath = $userFolder->getRelativePath($source->getPath()); if ($relativePath === null) { - throw new Exception("Relative path not found for node with path: " . $source->getPath()); + throw new Exception('Relative path not found for node with path: ' . $source->getPath()); + } + + $versionFolder = $this->rootFolder->get($userId . '/files_versions'); + if (!$versionFolder instanceof Folder) { + throw new Exception('User versions folder does not exist'); } - $versions = Storage::getVersions($user->getUID(), $relativePath); - /** @var Folder versionFolder */ - $versionFolder = $this->rootFolder->get('admin/files_versions'); + $versions = Storage::getVersions($userId, $relativePath); foreach ($versions as $version) { $versionFolder->get($version['path'] . '.v' . (int)$version['version'])->delete(); } diff --git a/apps/files_versions/lib/Versions/Version.php b/apps/files_versions/lib/Versions/Version.php index 83d27db2f7e..e202a69b7d7 100644 --- a/apps/files_versions/lib/Versions/Version.php +++ b/apps/files_versions/lib/Versions/Version.php @@ -3,25 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; diff --git a/apps/files_versions/lib/Versions/VersionManager.php b/apps/files_versions/lib/Versions/VersionManager.php index 754dd8523c9..9acea8c6513 100644 --- a/apps/files_versions/lib/Versions/VersionManager.php +++ b/apps/files_versions/lib/Versions/VersionManager.php @@ -3,28 +3,15 @@ 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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Versions; +use OCA\Files_Versions\Db\VersionEntity; +use OCA\Files_Versions\Events\VersionCreatedEvent; +use OCA\Files_Versions\Events\VersionRestoredEvent; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\IRootFolder; @@ -35,11 +22,18 @@ use OCP\Files\Node; use OCP\Files\Storage\IStorage; use OCP\IUser; use OCP\Lock\ManuallyLockedException; +use OCP\Server; class VersionManager implements IVersionManager, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend { + /** @var (IVersionBackend[])[] */ private $backends = []; + public function __construct( + private IEventDispatcher $dispatcher, + ) { + } + public function registerBackend(string $storageType, IVersionBackend $backend) { if (!isset($this->backends[$storageType])) { $this->backends[$storageType] = []; @@ -68,8 +62,8 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed foreach ($backends as $type => $backendsForType) { if ( - $storage->instanceOfStorage($type) && - ($foundType === '' || is_subclass_of($type, $foundType)) + $storage->instanceOfStorage($type) + && ($foundType === '' || is_subclass_of($type, $foundType)) ) { foreach ($backendsForType as $backend) { /** @var IVersionBackend $backend */ @@ -103,11 +97,7 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed $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(), - ]); + $this->dispatcher->dispatchTyped(new VersionRestoredEvent($version)); } return $result; } @@ -122,6 +112,11 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed return $backend->getVersionFile($user, $sourceFile, $revision); } + public function getRevision(Node $node): int { + $backend = $this->getBackendForStorage($node->getStorage()); + return $backend->getRevision($node); + } + public function useBackendForStorage(IStorage $storage): bool { return false; } @@ -136,7 +131,16 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed public function createVersionEntity(File $file): void { $backend = $this->getBackendForStorage($file->getStorage()); if ($backend instanceof INeedSyncVersionBackend) { - $backend->createVersionEntity($file); + $versionEntity = $backend->createVersionEntity($file); + + if ($versionEntity instanceof VersionEntity) { + foreach ($backend->getVersionsForFile($file->getOwner(), $file) as $version) { + if ($version->getRevisionId() === $versionEntity->getTimestamp()) { + $this->dispatcher->dispatchTyped(new VersionCreatedEvent($file, $version)); + break; + } + } + } } } @@ -184,8 +188,8 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed try { return $callback(); } catch (ManuallyLockedException $e) { - $owner = (string) $e->getOwner(); - $appsThatHandleUpdates = ["text", "richdocuments"]; + $owner = (string)$e->getOwner(); + $appsThatHandleUpdates = ['text', 'richdocuments']; if (!in_array($owner, $appsThatHandleUpdates)) { throw $e; } @@ -193,11 +197,11 @@ class VersionManager implements IVersionManager, IDeletableVersionBackend, INeed // 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); + $root = Server::get(IRootFolder::class); $lockContext = new LockContext($root, ILock::TYPE_APP, $owner); - $lockManager = \OC::$server->get(ILockManager::class); + $lockManager = Server::get(ILockManager::class); $result = null; - $lockManager->runInScope($lockContext, function () use ($callback, &$result) { + $lockManager->runInScope($lockContext, function () use ($callback, &$result): void { $result = $callback(); }); return $result; |