From 21bd10732864283169f018a94156c5007b59a066 Mon Sep 17 00:00:00 2001 From: Jonas Meurer <jonas@freesources.org> Date: Sat, 22 Aug 2020 12:26:08 +0200 Subject: [PATCH] Implement skeleton folder support (#69) When creating a new collective, copy over the skeleton folder instead of creating an empty folder. --- README.md | 15 +++ lib/AppInfo/Application.php | 6 +- lib/Mount/CollectiveFolderManager.php | 101 ++++++++++++++++++ lib/Mount/CollectiveMountPoint.php | 26 ++--- lib/Mount/CollectiveRootPathHelper.php | 28 ----- lib/Mount/MountProvider.php | 78 ++------------ lib/Service/CollectiveService.php | 35 +++--- skeleton/README.md | 23 ++++ tests/Integration/features/collective.feature | 2 + 9 files changed, 184 insertions(+), 130 deletions(-) create mode 100644 lib/Mount/CollectiveFolderManager.php delete mode 100644 lib/Mount/CollectiveRootPathHelper.php create mode 100644 skeleton/README.md diff --git a/README.md b/README.md index 0d152c5c1..f3f920bf0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,21 @@ organize together. Come and gather in collectives to build shared knowledge. * **Well-known [Markdown](https://en.wikipedia.org/wiki/Markdown) syntax** for page formatting +## Usage + +### Custom skeletons for new collectives + +It's possible to customize the skeletons for new collectives by putting files +in the app skeleton directory at `data/app_<INSTANCE_ID>/collectives/skeleton`. +New collectives get the contents of this skeleton directory copied over. + +`README.md` is the landing page that is opened automatically when entering a +collective. + +If the skeleton directory doesn't contain a `README.md`, the default landing +page from `collectives/skeleton/README.md` will be copied into the collectives +directory instead. + ## Dependencies For installation (see below), the following tools need to be available: diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 3f58eb99e..e2427e0a3 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -5,7 +5,7 @@ namespace OCA\Collectives\AppInfo; use Closure; -use OCA\Collectives\Mount\CollectiveRootPathHelper; +use OCA\Collectives\Mount\CollectiveFolderManager; use OCA\Collectives\Mount\MountProvider; use OCA\Collectives\Service\CollectiveHelper; use OCP\AppFramework\App; @@ -13,7 +13,6 @@ use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\Files\Config\IMountProviderCollection; -use OCP\Files\IRootFolder; use OCP\IUserSession; use Psr\Container\ContainerInterface; @@ -31,8 +30,7 @@ public function register(IRegistrationContext $context): void { $context->registerService(MountProvider::class, function (ContainerInterface $c) { return new MountProvider( $c->get(CollectiveHelper::class), - $c->get(CollectiveRootPathHelper::class), - $c->get(IRootFolder::class), + $c->get(CollectiveFolderManager::class), $c->get(IUserSession::class) ); }); diff --git a/lib/Mount/CollectiveFolderManager.php b/lib/Mount/CollectiveFolderManager.php new file mode 100644 index 000000000..7f40173a0 --- /dev/null +++ b/lib/Mount/CollectiveFolderManager.php @@ -0,0 +1,101 @@ +<?php + +namespace OCA\Collectives\Mount; + +use OC\Files\Node\LazyFolder; +use OC\SystemConfig; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class CollectiveFolderManager { + public const SKELETON_DIR = 'skeleton'; + public const LANDING_PAGE = 'README.md'; + + /** @var SystemConfig */ + private $systemConfig; + + /** @var IRootFolder */ + private $rootFolder; + + /** + * MountProvider constructor. + * + * @param SystemConfig $systemConfig + * @param IRootFolder $rootFolder + */ + public function __construct( + IRootFolder $rootFolder, + SystemConfig $systemConfig) { + $this->systemConfig = $systemConfig; + $this->rootFolder = $rootFolder; + } + + public function getRootPath(): string { + $instanceId = $this->systemConfig->getValue('instanceid', null); + if (null === $instanceId) { + throw new \RuntimeException('no instance id!'); + } + + return 'appdata_' . $instanceId . '/collectives'; + } + + /** + * @return Folder + */ + public function getRootFolder(): Folder { + $rootFolder = $this->rootFolder; + return (new LazyFolder(function () use ($rootFolder) { + try { + return $rootFolder->get($this->getRootPath()); + } catch (NotFoundException $e) { + return $rootFolder->newFolder($this->getRootPath()); + } + })); + } + + /** + * @param Folder $folder + * + * @return Folder + * @throws NotPermittedException + */ + public function getSkeletonFolder(Folder $folder): Folder { + try { + $skeletonFolder = $folder->get(self::SKELETON_DIR); + if (!$skeletonFolder instanceof Folder) { + throw new NotFoundException('Not a folder: ' . $skeletonFolder->getPath()); + } + } catch (NotFoundException $e) { + $skeletonFolder = $folder->newFolder(self::SKELETON_DIR); + } + + return $skeletonFolder; + } + + /** + * @param int $id + * @param bool $create + * + * @return Folder|null + * @throws NotPermittedException + */ + public function getFolder(int $id, bool $create = true): ?Folder { + try { + $folder = $this->getRootFolder()->get((string)$id); + if (!$folder instanceof Folder) { + return null; + } + } catch (NotFoundException $e) { + if (!$create) { + return null; + } + + $folder = $this->getSkeletonFolder($this->getRootFolder()) + ->copy($this->getRootFolder()->getPath() . '/' . (string)$id); + } + + return $folder; + } +} diff --git a/lib/Mount/CollectiveMountPoint.php b/lib/Mount/CollectiveMountPoint.php index 85df9e6f4..e714fe6d8 100644 --- a/lib/Mount/CollectiveMountPoint.php +++ b/lib/Mount/CollectiveMountPoint.php @@ -9,25 +9,25 @@ class CollectiveMountPoint extends MountPoint { /** @var int */ private $folderId; - /** @var CollectiveRootPathHelper */ - private $collectiveRootPathHelper; + /** @var CollectiveFolderManager */ + private $collectiveFolderManager; /** * CollectiveMountPoint constructor. * - * @param int|null $folderId - * @param CollectiveRootPathHelper $collectiveRootPathHelper - * @param CollectiveStorage $storage - * @param string $mountPoint - * @param array|null $arguments - * @param IStorageFactory|null $loader - * @param array|null $mountOptions - * @param int|null $mountId + * @param int|null $folderId + * @param CollectiveFolderManager $collectiveFolderManager + * @param CollectiveStorage $storage + * @param string $mountPoint + * @param array|null $arguments + * @param IStorageFactory|null $loader + * @param array|null $mountOptions + * @param int|null $mountId * * @throws \Exception */ public function __construct(?int $folderId, - CollectiveRootPathHelper $collectiveRootPathHelper, + CollectiveFolderManager $collectiveFolderManager, CollectiveStorage $storage, string $mountPoint, array $arguments = null, @@ -35,7 +35,7 @@ public function __construct(?int $folderId, array $mountOptions = null, int $mountId = null) { $this->folderId = $folderId; - $this->collectiveRootPathHelper = $collectiveRootPathHelper; + $this->collectiveFolderManager = $collectiveFolderManager; parent::__construct($storage, $mountPoint, $arguments, $loader, $mountOptions, $mountId); } @@ -77,6 +77,6 @@ public function getFolderId(): int { * @return string */ public function getSourcePath(): string { - return '/' . $this->collectiveRootPathHelper->get() . '/' . $this->getFolderId(); + return '/' . $this->collectiveFolderManager->getRootFolder()->getPath() . '/' . $this->getFolderId(); } } diff --git a/lib/Mount/CollectiveRootPathHelper.php b/lib/Mount/CollectiveRootPathHelper.php deleted file mode 100644 index 4a431993b..000000000 --- a/lib/Mount/CollectiveRootPathHelper.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -namespace OCA\Collectives\Mount; - -use OC\SystemConfig; - -class CollectiveRootPathHelper { - /** @var SystemConfig */ - private $systemConfig; - - /** - * CollectiveRootPathHelper constructor. - * - * @param SystemConfig $systemConfig - */ - public function __construct(SystemConfig $systemConfig) { - $this->systemConfig = $systemConfig; - } - - public function get(): string { - $instanceId = $this->systemConfig->getValue('instanceid', null); - if (null === $instanceId) { - throw new \RuntimeException('no instance id!'); - } - - return 'appdata_' . $instanceId . '/collectives'; - } -} diff --git a/lib/Mount/MountProvider.php b/lib/Mount/MountProvider.php index bb11c6cba..9db00bf9c 100644 --- a/lib/Mount/MountProvider.php +++ b/lib/Mount/MountProvider.php @@ -2,12 +2,9 @@ namespace OCA\Collectives\Mount; -use OC\Files\Node\LazyFolder; use OC\Files\Storage\Wrapper\Jail; use OCA\Collectives\Service\CollectiveHelper; use OCP\Files\Config\IMountProvider; -use OCP\Files\Folder; -use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; @@ -16,16 +13,11 @@ use OCP\IUserSession; class MountProvider implements IMountProvider { - private const LANDING_PAGE = 'README.md'; - /** @var CollectiveHelper */ private $collectiveHelper; - /** @var CollectiveRootPathHelper */ - private $collectiveRootPathHelper; - - /** @var IRootFolder */ - private $rootFolder; + /** @var CollectiveFolderManager */ + private $collectiveFolderManager; /** @var IUserSession */ private $userSession; @@ -34,18 +26,15 @@ class MountProvider implements IMountProvider { * MountProvider constructor. * * @param CollectiveHelper $collectiveHelper - * @param CollectiveRootPathHelper $collectiveRootPathHelper - * @param IRootFolder $rootFolder + * @param CollectiveFolderManager $collectiveFolderManager * @param IUserSession $userSession */ public function __construct( CollectiveHelper $collectiveHelper, - CollectiveRootPathHelper $collectiveRootPathHelper, - IRootFolder $rootFolder, + CollectiveFolderManager $collectiveFolderManager, IUserSession $userSession) { $this->collectiveHelper = $collectiveHelper; - $this->collectiveRootPathHelper = $collectiveRootPathHelper; - $this->rootFolder = $rootFolder; + $this->collectiveFolderManager = $collectiveFolderManager; $this->userSession = $userSession; } @@ -81,14 +70,6 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { }, $folders); } - /** - * @return string|null - */ - private function getCurrentUID(): ?string { - $user = $this->userSession->getUser(); - return $user ? $user->getUID() : null; - } - /** * @param int $id * @param string $mountPoint @@ -107,10 +88,10 @@ public function getMount(int $id, IUser $user = null): IMountPoint { if (!$cacheEntry) { // trigger folder creation - $this->getFolder($id); + $this->collectiveFolderManager->getFolder($id); } - $storage = $this->getCollectivesRootFolder()->getStorage(); + $storage = $this->collectiveFolderManager->getRootFolder()->getStorage(); $rootPath = $this->getJailPath($id); @@ -128,7 +109,7 @@ public function getMount(int $id, return new CollectiveMountPoint( $id, - $this->collectiveRootPathHelper, + $this->collectiveFolderManager, $collectiveStorage, $mountPoint, null, @@ -142,47 +123,6 @@ public function getMount(int $id, * @return string */ public function getJailPath(int $folderId): string { - return $this->getCollectivesRootFolder()->getInternalPath() . '/' . $folderId; - } - - /** - * @return Folder - */ - private function getCollectivesRootFolder(): Folder { - $rootFolder = $this->rootFolder; - return (new LazyFolder(function () use ($rootFolder) { - try { - return $rootFolder->get($this->collectiveRootPathHelper->get()); - } catch (NotFoundException $e) { - return $rootFolder->newFolder($this->collectiveRootPathHelper->get()); - } - })); - } - - /** - * @param int $id - * @param bool $create - * - * @return Folder|null - * @throws NotPermittedException - */ - public function getFolder(int $id, bool $create = true): ?Folder { - try { - $folder = $this->getCollectivesRootFolder()->get((string)$id); - if (!$folder instanceof Folder) { - return null; - } - } catch (NotFoundException $e) { - if ($create) { - $folder = $this->getCollectivesRootFolder()->newFolder((string)$id); - } - return null; - } - - if ($create && !$folder->nodeExists(self::LANDING_PAGE)) { - $folder->newFile(self::LANDING_PAGE); - } - - return $folder; + return $this->collectiveFolderManager->getRootFolder()->getInternalPath() . '/' . $folderId; } } diff --git a/lib/Service/CollectiveService.php b/lib/Service/CollectiveService.php index fb937a7d8..4e7c753d1 100644 --- a/lib/Service/CollectiveService.php +++ b/lib/Service/CollectiveService.php @@ -6,10 +6,9 @@ use OCA\Collectives\Db\Collective; use OCA\Collectives\Db\CollectiveMapper; use OCA\Collectives\Fs\NodeHelper; -use OCA\Collectives\Mount\CollectiveRootPathHelper; +use OCA\Collectives\Mount\CollectiveFolderManager; use OCP\AppFramework\QueryException; use OCP\Files\InvalidPathException; -use OCP\Files\IRootFolder; use OCP\Files\NotPermittedException; class CollectiveService { @@ -19,10 +18,8 @@ class CollectiveService { private $collectiveHelper; /** @var NodeHelper */ private $nodeHelper; - /** @var IRootFolder */ - private $rootFolder; - /** @var CollectiveRootPathHelper */ - private $collectiveRootPathHelper; + /** @var CollectiveFolderManager */ + private $collectiveFolderManager; /** * CollectiveService constructor. @@ -30,21 +27,17 @@ class CollectiveService { * @param CollectiveMapper $collectiveMapper * @param CollectiveHelper $collectiveHelper * @param NodeHelper $nodeHelper - * @param IRootFolder $rootFolder - * @param CollectiveRootPathHelper $collectiveRootPathHelper + * @param CollectiveFolderManager $collectiveFolderManager */ public function __construct( CollectiveMapper $collectiveMapper, CollectiveHelper $collectiveHelper, NodeHelper $nodeHelper, - IRootFolder $rootFolder, - CollectiveRootPathHelper $collectiveRootPathHelper - ) { + CollectiveFolderManager $collectiveFolderManager) { $this->collectiveMapper = $collectiveMapper; $this->collectiveHelper = $collectiveHelper; $this->nodeHelper = $nodeHelper; - $this->rootFolder = $rootFolder; - $this->collectiveRootPathHelper = $collectiveRootPathHelper; + $this->collectiveFolderManager = $collectiveFolderManager; } /** @@ -62,6 +55,7 @@ public function getCollectives(string $userId): array { * @param string $name * * @return Collective + * @throws NotPermittedException */ public function createCollective(string $userId, string $name): Collective { if (empty($name)) { @@ -87,6 +81,15 @@ public function createCollective(string $userId, string $name): Collective { $collective->setCircleUniqueId($circle->getUniqueId()); $collective = $this->collectiveMapper->insert($collective); + // Create folder for collective and optionally copy default landing page + $collectiveFolder = $this->collectiveFolderManager->getFolder($collective->getId()); + if (null !== $collectiveFolder && + !$collectiveFolder->nodeExists(CollectiveFolderManager::LANDING_PAGE)) { + if (false !== $content = file_get_contents(__DIR__ . '/../../skeleton/' . CollectiveFolderManager::LANDING_PAGE)) { + $collectiveFolder->newFile(CollectiveFolderManager::LANDING_PAGE, $content); + } + } + return $collective; } @@ -101,7 +104,6 @@ public function deleteCollective(string $userId, int $id): Collective { if (null === $collective = $this->collectiveMapper->findById($id, $userId)) { throw new NotFoundException('Collective not found: '. $id); } - $folder = $this->collectiveMapper->getCollectiveFolder($collective, $userId); try { Circles::destroyCircle($collective->getCircleUniqueId()); @@ -110,8 +112,9 @@ public function deleteCollective(string $userId, int $id): Collective { } try { - $collectiveFolder = $this->rootFolder->get($this->collectiveRootPathHelper->get() . '/' . $collective->getId()); - $collectiveFolder->delete(); + if (null !== $collectiveFolder = $this->collectiveFolderManager->getFolder($collective->getId(), false)) { + $collectiveFolder->delete(); + } } catch (InvalidPathException | \OCP\Files\NotFoundException | NotPermittedException $e) { throw new NotFoundException('Failed to delete collective folder'); } diff --git a/skeleton/README.md b/skeleton/README.md new file mode 100644 index 000000000..5034015f2 --- /dev/null +++ b/skeleton/README.md @@ -0,0 +1,23 @@ +# Welcome to your new collective + +*Come, organize and build shared knowledge!* + + +### 🐾 Add your comrades to the collective + +Members can be managed in the [Circle App](/index.php/apps/circles/) + +### 🌱 Bring life to your collective + +Create pages and share the knowledge that really matters + +### 🛋️ Edit this landing page to feel like home + +Push the pencil button to get started ↗️ + + +## Also good to know + +* Link local pages by selecting text and choosing "link file" +* Multiple people can edit the same page simultaneously +* Find out more about this App in the [documentation](https://gitlab.com/collectivecloud/collectives) diff --git a/tests/Integration/features/collective.feature b/tests/Integration/features/collective.feature index be9cc2c32..83b609365 100644 --- a/tests/Integration/features/collective.feature +++ b/tests/Integration/features/collective.feature @@ -5,6 +5,8 @@ Feature: collective And user "alice" is member of circle "mycollective" with admin "jane" Then user "jane" sees collective "mycollective" And user "alice" sees collective "mycollective" + And user "jane" sees page "README" in "mycollective" + And user "alice" sees page "README" in "mycollective" And user "john" doesn't see collective "mycollective" Scenario: Fail to delete a foreign collective