diff --git a/appinfo/info.xml b/appinfo/info.xml index 8b3e63f2..d8948710 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -9,7 +9,7 @@ Recommendations Shows recommended files Shows recommended files for quick access of files and folders with recent activity - 5.0.0-dev.0 + 5.0.0-dev.1 agpl Christoph Wurst Jan-Christoph Borchardt @@ -19,7 +19,19 @@ + + + + OCA\Recommendations\Command\GetRecommendations + + + OCA\Recommendations\Sabre\RootCollection + + + OCA\Recommendations\Sabre\PropFindPlugin + + diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 909dd4c8..16bd7c98 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,6 +9,7 @@ namespace OCA\Recommendations\AppInfo; +use OCA\DAV\Connector\Sabre\Principal; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Recommendations\Capabilities; use OCA\Recommendations\Dashboard\RecommendationWidget; @@ -29,6 +30,7 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(LoadAdditionalScriptsEvent::class, FilesLoadAdditionalScriptsListener::class); $context->registerDashboardWidget(RecommendationWidget::class); $context->registerCapability(Capabilities::class); + $context->registerServiceAlias('principalBackend', Principal::class); } public function boot(IBootContext $context): void { diff --git a/lib/Command/GetRecommendations.php b/lib/Command/GetRecommendations.php index 2ab7523e..5bc17b5d 100644 --- a/lib/Command/GetRecommendations.php +++ b/lib/Command/GetRecommendations.php @@ -49,12 +49,12 @@ public function execute(InputInterface $input, OutputInterface $output) { ); if (is_null($user)) { - $output->writeln("user does not exist"); + $output->writeln('user does not exist'); return 1; } if ($input->getArgument('max')) { - $recommendations = $this->recommendationService->getRecommendations($user, (int) $input->getArgument('max')); + $recommendations = $this->recommendationService->getRecommendations($user, (int)$input->getArgument('max')); } else { $recommendations = $this->recommendationService->getRecommendations($user); } diff --git a/lib/Controller/RecommendationController.php b/lib/Controller/RecommendationController.php index 3d23701e..0aef9969 100644 --- a/lib/Controller/RecommendationController.php +++ b/lib/Controller/RecommendationController.php @@ -50,7 +50,7 @@ public function __construct(IRequest $request, public function index(): DataResponse { $user = $this->userSession->getUser(); if (is_null($user)) { - throw new Exception("Not logged in"); + throw new Exception('Not logged in'); } $response = []; $response['enabled'] = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled', 'true') === 'true'; @@ -73,7 +73,7 @@ public function index(): DataResponse { public function always(): DataResponse { $user = $this->userSession->getUser(); if (is_null($user)) { - throw new Exception("Not logged in"); + throw new Exception('Not logged in'); } $response = [ 'enabled' => $this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled', 'true') === 'true', diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index ad9836fb..b1e6837d 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -40,7 +40,7 @@ public function __construct($appName, public function getSettings(): JSONResponse { $user = $this->userSession->getUser(); if (!$user instanceof IUser) { - throw new Exception("Not logged in"); + throw new Exception('Not logged in'); } return new JSONResponse([ 'enabled' => $this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled', 'true') === 'true', @@ -55,7 +55,7 @@ public function getSettings(): JSONResponse { public function setSetting(string $key, string $value): JSONResponse { $user = $this->userSession->getUser(); if (!$user instanceof IUser) { - throw new Exception("Not logged in"); + throw new Exception('Not logged in'); } $availableSettings = ['enabled']; if (!in_array($key, $availableSettings)) { diff --git a/lib/Dashboard/RecommendationWidget.php b/lib/Dashboard/RecommendationWidget.php index 3d0de16c..9575d4dd 100644 --- a/lib/Dashboard/RecommendationWidget.php +++ b/lib/Dashboard/RecommendationWidget.php @@ -37,7 +37,7 @@ public function __construct( IURLGenerator $urlGenerator, IMimeTypeDetector $mimeTypeDetector, RecommendationService $recommendationService, - IUserManager $userManager + IUserManager $userManager, ) { $this->userSession = $userSession; $this->l10n = $l10n; diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index b240498c..140bda83 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -17,6 +17,7 @@ * mimeType: string, * hasPreview: bool, * reason: string, + * reasonLabel: string, * } * * @psalm-suppress UnusedClass diff --git a/lib/Sabre/IRecommendationNode.php b/lib/Sabre/IRecommendationNode.php new file mode 100644 index 00000000..5f6fbd81 --- /dev/null +++ b/lib/Sabre/IRecommendationNode.php @@ -0,0 +1,14 @@ +on('propFind', $this->propFind(...)); + } + + public function propFind(PropFind $propFind, INode $node): void { + if ($node instanceof RecommendationDirectory || $node instanceof RecommendationFile) { + $propFind->handle( + self::RECOMMENDATION_REASON, + /** @psalm-suppress PossiblyNullReference Null already checked above */ + fn () => $node->getRecommendationReason(), + ); + $propFind->handle( + self::RECOMMENDATION_REASON_LABEL, + /** @psalm-suppress PossiblyNullReference Null already checked above */ + fn () => $node->getRecommendationReasonLabel(), + ); + $propFind->handle( + self::RECOMMENDATION_ORIGINAL_LOCATION, + /** @psalm-suppress PossiblyNullReference Null already checked above */ + fn () => $node->getPath(), + ); + } + } +} diff --git a/lib/Sabre/RecommendationDirectory.php b/lib/Sabre/RecommendationDirectory.php new file mode 100644 index 00000000..6ca5f687 --- /dev/null +++ b/lib/Sabre/RecommendationDirectory.php @@ -0,0 +1,37 @@ +recommendationReason; + } + + public function getRecommendationReasonLabel(): string { + return $this->recommendationReasonLabel; + } +} diff --git a/lib/Sabre/RecommendationFile.php b/lib/Sabre/RecommendationFile.php new file mode 100644 index 00000000..8e5f50f6 --- /dev/null +++ b/lib/Sabre/RecommendationFile.php @@ -0,0 +1,38 @@ +recommendationReason; + } + + public function getRecommendationReasonLabel(): string { + return $this->recommendationReasonLabel; + } +} diff --git a/lib/Sabre/RecommendationsHome.php b/lib/Sabre/RecommendationsHome.php new file mode 100644 index 00000000..59022f5c --- /dev/null +++ b/lib/Sabre/RecommendationsHome.php @@ -0,0 +1,132 @@ + */ + private ?array $cachedRecommendations = null; + + private View $fileView; + + public function __construct( + private array $principalInfo, + private IUser $user, + private IConfig $config, + private RecommendationService $recommendationService, + private IManager $shareManager, + private IRequest $request, + private IL10N $l10n, + ) { + $this->fileView = Filesystem::getView(); + } + + public function delete() { + throw new Forbidden(); + } + + public function getName(): string { + [, $name] = \Sabre\Uri\split($this->principalInfo['uri']); + return $name; + } + + public function setName($name) { + throw new Forbidden(); + } + + public function createFile($name, $data = null) { + throw new Forbidden(); + } + + public function createDirectory($name) { + throw new Forbidden(); + } + + public function getChild($name) { + if (!$this->isEnabled()) { + throw new NotFound('Recommendations are disabled'); + } + + $recommendations = $this->getChildren(); + foreach ($recommendations as $child) { + if ($child->getName() === $name) { + return $child; + } + } + + throw new NotFound("Child '$name' not found in recommendations"); + } + + public function getChildren(): array { + if (!$this->isEnabled()) { + return []; + } + + if ($this->cachedRecommendations === null) { + $this->cachedRecommendations = $this->recommendationService->getRecommendations($this->user); + } + + return array_map( + function (IRecommendation $recommendation) { + $fileInfo = $recommendation->getNode()->getFileInfo(); + if ($recommendation->getNode()->getType() === FileInfo::TYPE_FOLDER) { + return new RecommendationDirectory( + $recommendation->getReason(), + $recommendation->getReasonLabel(), + $this->fileView, + $fileInfo, + null, + $this->shareManager, + ); + } + return new RecommendationFile( + $recommendation->getReason(), + $recommendation->getReasonLabel(), + $this->fileView, + $fileInfo, + $this->shareManager, + $this->request, + $this->l10n + ); + }, + $this->cachedRecommendations + ); + } + + public function childExists($name): bool { + if (!$this->isEnabled()) { + return false; + } + // TODO: map the recommendations to a Sabre node type + return true; + } + + public function getLastModified(): int { + return 0; + } + + private function isEnabled(): bool { + return $this->config->getUserValue($this->user->getUID(), Application::APP_ID, 'enabled', 'true') === 'true'; + } +} diff --git a/lib/Sabre/RootCollection.php b/lib/Sabre/RootCollection.php new file mode 100644 index 00000000..031533fa --- /dev/null +++ b/lib/Sabre/RootCollection.php @@ -0,0 +1,67 @@ +disableListing = !$config->getSystemValue('debug', false); + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return INode + */ + public function getChildForPrincipal(array $principalInfo): RecommendationsHome { + [, $name] = \Sabre\Uri\split($principalInfo['uri']); + $user = $this->userSession->getUser(); + if (is_null($user) || $name !== $user->getUID()) { + throw new Forbidden(); + } + return new RecommendationsHome( + $principalInfo, + $user, + $this->config, + $this->recommendationService, + $this->shareManager, + $this->request, + $this->l10n, + ); + } + + public function getName(): string { + return Application::APP_ID; + } +} diff --git a/lib/Service/IRecommendation.php b/lib/Service/IRecommendation.php index 080fd600..491ddea8 100644 --- a/lib/Service/IRecommendation.php +++ b/lib/Service/IRecommendation.php @@ -24,4 +24,6 @@ public function hasPreview(): bool; public function setHasPreview(bool $state); public function getReason(): string; + + public function getReasonLabel(): string; } diff --git a/lib/Service/RecentlyCommentedFilesSource.php b/lib/Service/RecentlyCommentedFilesSource.php index 9f912d1b..75ad049e 100644 --- a/lib/Service/RecentlyCommentedFilesSource.php +++ b/lib/Service/RecentlyCommentedFilesSource.php @@ -1,4 +1,5 @@ getNode(), $file->getComment()->getCreationDateTime()->getTimestamp(), self::REASON, + $this->l10n->t('Recently commented') ); }, $this->getNMostRecentlyCommenedFiles($all, $max)); } diff --git a/lib/Service/RecentlyEditedFilesSource.php b/lib/Service/RecentlyEditedFilesSource.php index 71afae05..9a3dc140 100644 --- a/lib/Service/RecentlyEditedFilesSource.php +++ b/lib/Service/RecentlyEditedFilesSource.php @@ -48,6 +48,7 @@ public function getMostRecentRecommendation(IUser $user, int $max): array { $node, $node->getMTime(), self::REASON, + $this->l10n->t('Recently edited'), ); } catch (StorageNotAvailableException $e) { return null; diff --git a/lib/Service/RecentlySharedFilesSource.php b/lib/Service/RecentlySharedFilesSource.php index 2b9aa1b5..0b4d72b6 100644 --- a/lib/Service/RecentlySharedFilesSource.php +++ b/lib/Service/RecentlySharedFilesSource.php @@ -99,11 +99,13 @@ public function getMostRecentRecommendation(IUser $user, int $max): array { return array_filter(array_map(function (IShare $share) use ($userFolder): ?RecommendedFile { try { + $node = $userFolder->get($share->getTarget()); return new RecommendedFile( - $userFolder->getRelativePath($userFolder->get($share->getTarget())->getParent()->getPath()), - $share->getNode(), + $userFolder->getRelativePath($node->getParent()->getPath()), + $node, $share->getShareTime()->getTimestamp(), self::REASON, + $this->l10n->t('Recently shared'), ); } catch (NotFoundException $ex) { return null; diff --git a/lib/Service/RecommendedFile.php b/lib/Service/RecommendedFile.php index c8c3d30b..2bd32806 100644 --- a/lib/Service/RecommendedFile.php +++ b/lib/Service/RecommendedFile.php @@ -16,21 +16,14 @@ * @psalm-import-type RecommendationsRecommendedFile from ResponseDefinitions */ class RecommendedFile implements IRecommendation { - private string $directory; - private Node $node; - private int $timestamp; - private string $reason; - private bool $hasPreview; - - public function __construct(string $directory, - Node $node, - int $timestamp, - string $reason) { - $this->directory = $directory; - $this->node = $node; - $this->reason = $reason; - $this->timestamp = $timestamp; - $this->hasPreview = false; + public function __construct( + private string $directory, + private Node $node, + private int $timestamp, + private string $reason, + private string $reasonLabel, + private bool $hasPreview = false, + ) { } public function getId(): string { @@ -61,6 +54,10 @@ public function setHasPreview(bool $state) { $this->hasPreview = $state; } + public function getReasonLabel(): string { + return $this->reasonLabel; + } + /** * @return RecommendationsRecommendedFile */ @@ -75,6 +72,7 @@ public function jsonSerialize() { 'mimeType' => $this->node->getMimetype(), 'hasPreview' => $this->hasPreview(), 'reason' => $this->getReason(), + 'reasonLabel' => $this->getReasonLabel(), ]; } } diff --git a/openapi.json b/openapi.json index c72e5f6c..ea0a485c 100644 --- a/openapi.json +++ b/openapi.json @@ -70,7 +70,8 @@ "extension", "mimeType", "hasPreview", - "reason" + "reason", + "reasonLabel" ], "properties": { "id": { @@ -97,6 +98,9 @@ }, "reason": { "type": "string" + }, + "reasonLabel": { + "type": "string" } } }