From c8bf3a52d6affb175b17ed3380a6f39746e1c28a Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Wed, 27 Jul 2022 11:47:27 +0200 Subject: Add Albums view Signed-off-by: Louis Chemineau --- lib/Album/AlbumFile.php | 4 +-- lib/Album/AlbumWithFiles.php | 2 +- lib/Exception/AlreadyInAlbumException.php | 1 - lib/Migration/Version20000Date20220727125801.php | 3 +- lib/Sabre/Album/AlbumPhoto.php | 34 ++++++++++++++++++ lib/Sabre/Album/AlbumRoot.php | 40 +++++++++++++++++++++ lib/Sabre/Album/AlbumsHome.php | 26 +++++++++++--- lib/Sabre/Album/PropFindPlugin.php | 44 +++++++++++++++--------- lib/Sabre/PhotosHome.php | 9 +++++ lib/Sabre/RootCollection.php | 2 -- 10 files changed, 136 insertions(+), 29 deletions(-) (limited to 'lib') diff --git a/lib/Album/AlbumFile.php b/lib/Album/AlbumFile.php index 86863e09..8eb2482f 100644 --- a/lib/Album/AlbumFile.php +++ b/lib/Album/AlbumFile.php @@ -33,7 +33,7 @@ class AlbumFile { private int $mtime; private string $etag; private int $added; - /** @var array */ + /** @var array */ private array $metaData = []; public function __construct( @@ -74,7 +74,7 @@ class AlbumFile { return $this->mtime; } - public function getEtag() { + public function getEtag(): string { return $this->etag; } diff --git a/lib/Album/AlbumWithFiles.php b/lib/Album/AlbumWithFiles.php index cc6d0d1a..0bb630d6 100644 --- a/lib/Album/AlbumWithFiles.php +++ b/lib/Album/AlbumWithFiles.php @@ -48,7 +48,7 @@ class AlbumWithFiles { * @return int[] */ public function getFileIds(): array { - return array_map(function(AlbumFile $file) { + return array_map(function (AlbumFile $file) { return $file->getFileId(); }, $this->files); } diff --git a/lib/Exception/AlreadyInAlbumException.php b/lib/Exception/AlreadyInAlbumException.php index 9c7d7fe3..a8a89b05 100644 --- a/lib/Exception/AlreadyInAlbumException.php +++ b/lib/Exception/AlreadyInAlbumException.php @@ -24,5 +24,4 @@ declare(strict_types=1); namespace OCA\Photos\Exception; class AlreadyInAlbumException extends \Exception { - } diff --git a/lib/Migration/Version20000Date20220727125801.php b/lib/Migration/Version20000Date20220727125801.php index 81284e35..5641a540 100644 --- a/lib/Migration/Version20000Date20220727125801.php +++ b/lib/Migration/Version20000Date20220727125801.php @@ -25,7 +25,6 @@ namespace OCA\Photos\Migration; use Closure; use Doctrine\DBAL\Types\Types; -use OC\DB\SchemaWrapper; use OCP\DB\ISchemaWrapper; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -35,7 +34,7 @@ use OCP\Migration\SimpleMigrationStep; */ class Version20000Date20220727125801 extends SimpleMigrationStep { public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { - /** @var SchemaWrapper $schema */ + /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); if (!$schema->hasTable("photos_albums")) { diff --git a/lib/Sabre/Album/AlbumPhoto.php b/lib/Sabre/Album/AlbumPhoto.php index 01532725..0c208574 100644 --- a/lib/Sabre/Album/AlbumPhoto.php +++ b/lib/Sabre/Album/AlbumPhoto.php @@ -39,6 +39,8 @@ class AlbumPhoto implements IFile { private AlbumFile $file; private Folder $userFolder; + public const TAG_FAVORITE = '_$!!$_'; + public function __construct(AlbumMapper $albumMapper, AlbumInfo $album, AlbumFile $file, Folder $userFolder) { $this->albumMapper = $albumMapper; $this->album = $album; @@ -46,6 +48,9 @@ class AlbumPhoto implements IFile { $this->userFolder = $userFolder; } + /** + * @return void + */ public function delete() { $this->albumMapper->removeFile($this->album->getId(), $this->file->getFileId()); } @@ -54,6 +59,9 @@ class AlbumPhoto implements IFile { return $this->file->getFileId() . "-" . $this->file->getName(); } + /** + * @return never + */ public function setName($name) { throw new Forbidden('Can\'t rename photos trough the album api'); } @@ -81,6 +89,20 @@ class AlbumPhoto implements IFile { } } + public function getFileId(): int { + return $this->file->getFileId(); + } + + public function getFileInfo(): Node { + $nodes = $this->userFolder->getById($this->file->getFileId()); + $node = current($nodes); + if ($node) { + return $node; + } else { + throw new NotFoundException("Photo not found for user"); + } + } + public function getContentType() { return $this->file->getMimeType(); } @@ -96,4 +118,16 @@ class AlbumPhoto implements IFile { public function getFile(): AlbumFile { return $this->file; } + + public function isFavorite(): bool { + $tagManager = \OCP\Server::get(\OCP\ITagManager::class); + $tagger = $tagManager->load('files'); + $tags = $tagger->getTagsForObjects([$this->getFileId()]); + + if ($tags === false || empty($tags)) { + return false; + } + + return array_search(self::TAG_FAVORITE, current($tags)) !== false; + } } diff --git a/lib/Sabre/Album/AlbumRoot.php b/lib/Sabre/Album/AlbumRoot.php index 4cb132c0..ffe5d2a2 100644 --- a/lib/Sabre/Album/AlbumRoot.php +++ b/lib/Sabre/Album/AlbumRoot.php @@ -52,6 +52,9 @@ class AlbumRoot implements ICollection, ICopyTarget { $this->user = $user; } + /** + * @return void + */ public function delete() { $this->albumMapper->delete($this->album->getAlbum()->getId()); } @@ -60,6 +63,9 @@ class AlbumRoot implements ICollection, ICopyTarget { return basename($this->album->getAlbum()->getTitle()); } + /** + * @return void + */ public function setName($name) { $this->albumMapper->rename($this->album->getAlbum()->getId(), $name); } @@ -68,6 +74,9 @@ class AlbumRoot implements ICollection, ICopyTarget { throw new Forbidden('Not allowed to create files in this folder, copy files into this folder instead'); } + /** + * @return never + */ public function createDirectory($name) { throw new Forbidden('Not allowed to create directories in this folder'); } @@ -118,4 +127,35 @@ class AlbumRoot implements ICollection, ICopyTarget { public function getAlbum(): AlbumWithFiles { return $this->album; } + + public function getDateRange(): array { + $earliestDate = null; + $latestDate = null; + + foreach ($this->getChildren() as $child) { + $childCreationDate = $child->getFileInfo()->getMtime(); + if ($childCreationDate < $earliestDate || $earliestDate === null) { + $earliestDate = $childCreationDate; + } + + if ($childCreationDate > $earliestDate || $latestDate === null) { + $latestDate = $childCreationDate; + } + } + + return ['start' => $earliestDate, 'end' => $latestDate]; + } + + /** + * @return int|null + */ + public function getCover() { + $children = $this->getChildren(); + + if (count($children) > 0) { + return $children[0]->getFileId(); + } else { + return null; + } + } } diff --git a/lib/Sabre/Album/AlbumsHome.php b/lib/Sabre/Album/AlbumsHome.php index 1c070b59..a2bc2fe7 100644 --- a/lib/Sabre/Album/AlbumsHome.php +++ b/lib/Sabre/Album/AlbumsHome.php @@ -39,6 +39,11 @@ class AlbumsHome implements ICollection { private IRootFolder $rootFolder; private Folder $userFolder; + /** + * @var AlbumRoot[] + */ + private ?array $children = null; + public function __construct( array $principalInfo, AlbumMapper $albumMapper, @@ -52,6 +57,9 @@ class AlbumsHome implements ICollection { $this->userFolder = $rootFolder->getUserFolder($user->getUID()); } + /** + * @return never + */ public function delete() { throw new Forbidden(); } @@ -60,6 +68,9 @@ class AlbumsHome implements ICollection { return 'albums'; } + /** + * @return never + */ public function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } @@ -68,6 +79,9 @@ class AlbumsHome implements ICollection { throw new Forbidden('Not allowed to create files in this folder'); } + /** + * @return void + */ public function createDirectory($name) { $uid = $this->user->getUID(); $this->albumMapper->create($uid, $name); @@ -87,10 +101,14 @@ class AlbumsHome implements ICollection { * @return AlbumRoot[] */ public function getChildren(): array { - $folders = $this->albumMapper->getForUserWithFiles($this->user->getUID()); - return array_map(function (AlbumWithFiles $folder) { - return new AlbumRoot($this->albumMapper, $folder, $this->rootFolder, $this->userFolder, $this->user); - }, $folders); + if ($this->children === null) { + $folders = $this->albumMapper->getForUserWithFiles($this->user->getUID()); + $this->children = array_map(function (AlbumWithFiles $folder) { + return new AlbumRoot($this->albumMapper, $folder, $this->rootFolder, $this->userFolder, $this->user); + }, $folders); + } + + return $this->children; } public function childExists($name): bool { diff --git a/lib/Sabre/Album/PropFindPlugin.php b/lib/Sabre/Album/PropFindPlugin.php index ce7f0eff..040ef5ac 100644 --- a/lib/Sabre/Album/PropFindPlugin.php +++ b/lib/Sabre/Album/PropFindPlugin.php @@ -27,6 +27,7 @@ use OC\Metadata\IMetadataManager; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\Photos\Album\AlbumMapper; use OCP\IConfig; +use OCP\IPreview; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; @@ -36,11 +37,18 @@ use Sabre\DAV\Tree; class PropFindPlugin extends ServerPlugin { public const FILE_NAME_PROPERTYNAME = '{http://nextcloud.org/ns}file-name'; + public const REALPATH_PROPERTYNAME = '{http://nextcloud.org/ns}realpath'; + public const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite'; + public const DATE_RANGE_PROPERTYNAME = '{http://nextcloud.org/ns}dateRange'; public const LOCATION_PROPERTYNAME = '{http://nextcloud.org/ns}location'; public const LAST_PHOTO_PROPERTYNAME = '{http://nextcloud.org/ns}last-photo'; + public const NBITEMS_PROPERTYNAME = '{http://nextcloud.org/ns}nbItems'; + + public const TAG_FAVORITE = '_$!!$_'; private IConfig $config; private IMetadataManager $metadataManager; + private IPreview $previewManager; private bool $metadataEnabled; private ?Tree $tree; private AlbumMapper $albumMapper; @@ -48,31 +56,35 @@ class PropFindPlugin extends ServerPlugin { public function __construct( IConfig $config, IMetadataManager $metadataManager, + IPreview $previewManager, AlbumMapper $albumMapper ) { $this->config = $config; $this->metadataManager = $metadataManager; + $this->previewManager = $previewManager; $this->albumMapper = $albumMapper; $this->metadataEnabled = $this->config->getSystemValueBool('enable_file_metadata', true); } - + /** + * @return void + */ public function initialize(Server $server) { $this->tree = $server->tree; $server->on('propFind', [$this, 'propFind']); $server->on('propPatch', [$this, 'handleUpdateProperties']); } - public function propFind(PropFind $propFind, INode $node) { + public function propFind(PropFind $propFind, INode $node): void { if ($node instanceof AlbumPhoto) { - $propFind->handle(self::FILE_NAME_PROPERTYNAME, function () use ($node) { - return $node->getFile()->getName(); - }); - $propFind->handle(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) { - return $node->getFile()->getFileId(); - }); - $propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node): string { - return $node->getETag(); + $propFind->handle(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, fn () => $node->getFile()->getFileId()); + $propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, fn () => $node->getETag()); + $propFind->handle(self::FILE_NAME_PROPERTYNAME, fn () => $node->getFile()->getName()); + $propFind->handle(self::REALPATH_PROPERTYNAME, fn () => $node->getFileInfo()->getPath()); + $propFind->handle(self::FAVORITE_PROPERTYNAME, fn () => $node->isFavorite() ? 1 : 0); + + $propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, function () use ($node) { + return json_encode($this->previewManager->isAvailable($node->getFileInfo())); }); if ($this->metadataEnabled) { @@ -93,12 +105,10 @@ class PropFindPlugin extends ServerPlugin { } if ($node instanceof AlbumRoot) { - $propFind->handle(self::LOCATION_PROPERTYNAME, function () use ($node) { - return $node->getAlbum()->getAlbum()->getLocation(); - }); - $propFind->handle(self::LAST_PHOTO_PROPERTYNAME, function () use ($node) { - return $node->getAlbum()->getAlbum()->getLastAddedPhoto(); - }); + $propFind->handle(self::LAST_PHOTO_PROPERTYNAME, fn () => $node->getAlbum()->getAlbum()->getLastAddedPhoto()); + $propFind->handle(self::NBITEMS_PROPERTYNAME, fn () => count($node->getChildren())); + $propFind->handle(self::LOCATION_PROPERTYNAME, fn () => $node->getAlbum()->getAlbum()->getLocation()); + $propFind->handle(self::DATE_RANGE_PROPERTYNAME, fn () => json_encode($node->getDateRange())); // TODO detect dynamically which metadata groups are requested and // preload all of them and not just size @@ -115,7 +125,7 @@ class PropFindPlugin extends ServerPlugin { } } - public function handleUpdateProperties($path, PropPatch $propPatch) { + public function handleUpdateProperties($path, PropPatch $propPatch): void { $node = $this->tree->getNodeForPath($path); if ($node instanceof AlbumRoot) { $propPatch->handle(self::LOCATION_PROPERTYNAME, function ($location) use ($node) { diff --git a/lib/Sabre/PhotosHome.php b/lib/Sabre/PhotosHome.php index d37c8a11..d3cd1c94 100644 --- a/lib/Sabre/PhotosHome.php +++ b/lib/Sabre/PhotosHome.php @@ -52,6 +52,9 @@ class PhotosHome implements ICollection { $this->userFolder = $rootFolder->getUserFolder($user->getUID()); } + /** + * @return never + */ public function delete() { throw new Forbidden(); } @@ -61,6 +64,9 @@ class PhotosHome implements ICollection { return $name; } + /** + * @return never + */ public function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } @@ -69,6 +75,9 @@ class PhotosHome implements ICollection { throw new Forbidden('Not allowed to create files in this folder'); } + /** + * @return never + */ public function createDirectory($name) { throw new Forbidden('Permission denied to create folders in this folder'); } diff --git a/lib/Sabre/RootCollection.php b/lib/Sabre/RootCollection.php index d2478991..c5cc1e82 100644 --- a/lib/Sabre/RootCollection.php +++ b/lib/Sabre/RootCollection.php @@ -26,7 +26,6 @@ namespace OCA\Photos\Sabre; use OCA\Photos\Album\AlbumMapper; use OCP\Files\IRootFolder; use OCP\IUserSession; -use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAVACL\PrincipalBackend; @@ -56,7 +55,6 @@ class RootCollection extends AbstractPrincipalCollection { * supplied by the authentication backend. * * @param array $principalInfo - * @return INode */ public function getChildForPrincipal(array $principalInfo): PhotosHome { [, $name] = \Sabre\Uri\split($principalInfo['uri']); -- cgit v1.2.3