diff options
-rw-r--r-- | appinfo/info.xml | 10 | ||||
-rw-r--r-- | lib/BackgroundJob/ExpireGroupVersions.php | 42 | ||||
-rw-r--r-- | lib/Command/ExpireGroupVersions.php | 58 | ||||
-rw-r--r-- | lib/Versions/ExpireManager.php | 134 | ||||
-rw-r--r-- | lib/Versions/GroupVersionsExpireManager.php | 64 | ||||
-rw-r--r-- | lib/Versions/VersionsBackend.php | 21 |
6 files changed, 328 insertions, 1 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml index 3f4c755d..20ec8ac4 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -10,7 +10,7 @@ Folders can be configured from *Group folders* in the admin settings. After a folder is created, the admin can give access to the folder to one or more groups, control their write/sharing permissions and assign a quota for the folder. Note: encrypting the contents of group folders is currently not supported.]]></description> - <version>1.3.3</version> + <version>1.3.5</version> <licence>agpl</licence> <author>Robin Appelman</author> <namespace>GroupFolders</namespace> @@ -32,6 +32,14 @@ Note: encrypting the contents of group folders is currently not supported.]]></d <nextcloud min-version="13" max-version="15"/> </dependencies> + <commands> + <command>OCA\GroupFolders\Command\ExpireGroupVersions</command> + </commands> + + <background-jobs> + <job>OCA\GroupFolders\BackgroundJob\ExpireGroupVersions</job> + </background-jobs> + <settings> <admin>OCA\GroupFolders\Settings\Admin</admin> <admin-section>OCA\GroupFolders\Settings\Section</admin-section> diff --git a/lib/BackgroundJob/ExpireGroupVersions.php b/lib/BackgroundJob/ExpireGroupVersions.php new file mode 100644 index 00000000..773671c3 --- /dev/null +++ b/lib/BackgroundJob/ExpireGroupVersions.php @@ -0,0 +1,42 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\GroupFolders\BackgroundJob; + +use OCA\GroupFolders\Versions\GroupVersionsExpireManager; + +class ExpireGroupVersions extends \OC\BackgroundJob\TimedJob { + + const ITEMS_PER_SESSION = 1000; + + private $expireManager; + + public function __construct(GroupVersionsExpireManager $expireManager) { + // Run once per hour + $this->setInterval(60 * 60); + + $this->expireManager = $expireManager; + } + + protected function run($argument) { + $this->expireManager->expireAll(); + } +} diff --git a/lib/Command/ExpireGroupVersions.php b/lib/Command/ExpireGroupVersions.php new file mode 100644 index 00000000..00b8bf17 --- /dev/null +++ b/lib/Command/ExpireGroupVersions.php @@ -0,0 +1,58 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\GroupFolders\Command; + + +use OC\Core\Command\Base; +use OCA\Files_Versions\Versions\IVersion; +use OCA\GroupFolders\Versions\GroupVersionsExpireManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ExpireGroupVersions extends Base { + private $expireManager; + + public function __construct(GroupVersionsExpireManager $expireManager) { + parent::__construct(); + $this->expireManager = $expireManager; + } + + protected function configure() { + $this + ->setName('groupfolders:expire') + ->setDescription('Trigger expiry of versions for files stored in group folders'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $this->expireManager->listen(GroupVersionsExpireManager::class, 'enterFolder', function(array $folder) use ($output) { + $output->writeln("<info>Expiring version in '${folder['mount_point']}'</info>"); + }); + $this->expireManager->listen(GroupVersionsExpireManager::class, 'deleteVersion', function(IVersion $version) use ($output) { + $id = $version->getRevisionId(); + $file = $version->getSourceFileName(); + $output->writeln("<info>Expiring version $id for '$file'</info>"); + }); + + $this->expireManager->expireAll(); + } +} diff --git a/lib/Versions/ExpireManager.php b/lib/Versions/ExpireManager.php new file mode 100644 index 00000000..f431f518 --- /dev/null +++ b/lib/Versions/ExpireManager.php @@ -0,0 +1,134 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\GroupFolders\Versions; + +use OCA\Files_Versions\Expiration; +use OCA\Files_Versions\Versions\IVersion; + +/** + * TODO: move this to files_versions to be reused to apps in nc16 + */ +class ExpireManager { + const MAX_VERSIONS_PER_INTERVAL = [ + //first 10sec, one version every 2sec + 1 => ['intervalEndsAfter' => 10, 'step' => 2], + //next minute, one version every 10sec + 2 => ['intervalEndsAfter' => 60, 'step' => 10], + //next hour, one version every minute + 3 => ['intervalEndsAfter' => 3600, 'step' => 60], + //next 24h, one version every hour + 4 => ['intervalEndsAfter' => 86400, 'step' => 3600], + //next 30days, one version per day + 5 => ['intervalEndsAfter' => 2592000, 'step' => 86400], + //until the end one version per week + 6 => ['intervalEndsAfter' => -1, 'step' => 604800], + ]; + + /** @var Expiration */ + private $expiration; + + public function __construct(Expiration $expiration) { + $this->expiration = $expiration; + } + + /** + * get list of files we want to expire + * + * @param integer $time + * @param IVersion[] $versions + * @return IVersion[] + */ + protected function getAutoExpireList(int $time, $versions) { + if (!$versions) { + return []; + } + $toDelete = []; // versions we want to delete + + // ensure the versions are sorted newest first + usort($versions, function (IVersion $a, IVersion $b) { + return $b->getTimestamp() <=> $a->getTimestamp(); + }); + + $interval = 1; + $step = self::MAX_VERSIONS_PER_INTERVAL[$interval]['step']; + if (self::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter'] === -1) { + $nextInterval = -1; + } else { + $nextInterval = $time - self::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter']; + } + + /** @var IVersion $firstVersion */ + $firstVersion = array_shift($versions); + $prevTimestamp = $firstVersion->getTimestamp(); + $nextVersion = $firstVersion->getTimestamp() - $step; + + foreach ($versions as $version) { + $newInterval = true; + while ($newInterval) { + if ($nextInterval === -1 || $prevTimestamp > $nextInterval) { + if ($version->getTimestamp() > $nextVersion) { + //distance between two version too small, mark to delete + $toDelete[] = $version; + } else { + $nextVersion = $version->getTimestamp() - $step; + $prevTimestamp = $version->getTimestamp(); + } + $newInterval = false; // version checked so we can move to the next one + } else { // time to move on to the next interval + $interval++; + $step = self::MAX_VERSIONS_PER_INTERVAL[$interval]['step']; + $nextVersion = $prevTimestamp - $step; + if (self::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter'] === -1) { + $nextInterval = -1; + } else { + $nextInterval = $time - self::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter']; + } + $newInterval = true; // we changed the interval -> check same version with new interval + } + } + } + + return $toDelete; + } + + /** + * @param IVersion[] $versions + * @param int $time + * @param boolean $quotaExceeded + * @return IVersion[] + */ + public function getExpiredVersion($versions, int $time, bool $quotaExceeded) { + if ($this->expiration->shouldAutoExpire()) { + $autoExpire = $this->getAutoExpireList($time, $versions); + } else { + $autoExpire = []; + } + + $versionsLeft = array_diff($versions, $autoExpire); + + $expired = array_filter($versionsLeft, function (IVersion $version) use ($quotaExceeded) { + return $this->expiration->isExpired($version->getTimestamp(), $quotaExceeded); + }); + + return array_merge($autoExpire, $expired); + } +} diff --git a/lib/Versions/GroupVersionsExpireManager.php b/lib/Versions/GroupVersionsExpireManager.php new file mode 100644 index 00000000..c1d2787c --- /dev/null +++ b/lib/Versions/GroupVersionsExpireManager.php @@ -0,0 +1,64 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\GroupFolders\Versions; + + +use OC\Hooks\BasicEmitter; +use OC\User\User; +use OCA\GroupFolders\Folder\FolderManager; +use OCP\AppFramework\Utility\ITimeFactory; + +class GroupVersionsExpireManager extends BasicEmitter { + private $folderManager; + private $expireManager; + private $versionsBackend; + private $timeFactory; + + public function __construct(FolderManager $folderManager, ExpireManager $expireManager, VersionsBackend $versionsBackend, ITimeFactory $timeFactory) { + $this->folderManager = $folderManager; + $this->expireManager = $expireManager; + $this->versionsBackend = $versionsBackend; + $this->timeFactory = $timeFactory; + } + + public function expireAll() { + $folders = $this->folderManager->getAllFolders(); + foreach($folders as $folder) { + $this->emit(self::class, 'enterFolder', [$folder]); + $this->expireFolder($folder); + } + } + + public function expireFolder($folder) { + $files = $this->versionsBackend->getAllVersionedFiles($folder); + $dummyUser = new User('', null); + foreach($files as $file) { + $versions = $this->versionsBackend->getVersionsForFile($dummyUser, $file); + $expireVersions = $this->expireManager->getExpiredVersion($versions, $this->timeFactory->getTime(), false); + foreach($expireVersions as $version) { + /** @var GroupVersion $version */ + $this->emit(self::class, 'deleteVersion', [$version]); + $version->getVersionFile()->delete(); + } + } + } +} diff --git a/lib/Versions/VersionsBackend.php b/lib/Versions/VersionsBackend.php index e96043c8..9ed3d0bb 100644 --- a/lib/Versions/VersionsBackend.php +++ b/lib/Versions/VersionsBackend.php @@ -30,6 +30,7 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\Folder; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IUser; @@ -146,6 +147,26 @@ class VersionsBackend implements IVersionBackend { } /** + * @param array $folder + * @return FileInfo[] + * @throws NotFoundException + */ + public function getAllVersionedFiles(array $folder) { + $versionsFolder = $this->getVersionsFolder($folder['id']); + $mount = $this->mountProvider->getMount($folder['id'], '/dummyuser/files/' . $folder['mount_point'], $folder['permissions'], $folder['quota']); + $contents = $versionsFolder->getDirectoryListing(); + $files = array_map(function (Node $node) use ($mount) { + $cacheEntry = $mount->getStorage()->getCache()->get((int)$node->getName()); + if ($cacheEntry) { + return new \OC\Files\FileInfo($mount->getMountPoint() . '/' . $cacheEntry->getPath(), $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount); + } else { + return null; + } + }, $contents); + return array_values(array_filter($files)); + } + + /** * @param $folderId * @return Folder */ |