Skip to content

Commit

Permalink
Add DAV endpoint for location grouping
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Feb 23, 2023
1 parent 4ed1fc1 commit 5bd766c
Show file tree
Hide file tree
Showing 11 changed files with 620 additions and 171 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<collection>OCA\Photos\Sabre\PublicRootCollection</collection>
</collections>
<plugins>
<plugin>OCA\Photos\Sabre\Album\PropFindPlugin</plugin>
<plugin>OCA\Photos\Sabre\PropFindPlugin</plugin>
</plugins>
</sabre>

Expand Down
90 changes: 78 additions & 12 deletions lib/DB/Location/LocationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;

class LocationMapper {
Expand All @@ -43,19 +44,19 @@ public function __construct(

/** @return LocationInfo[] */
public function findLocationsForUser(string $userId): array {
$mountId = $this->rootFolder
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
->getMountId();
->getNumericStorageId();

$mimepart = $this->mimeTypeLoader->getId('image');

$qb = $this->connection->getQueryBuilder();

$rows = $qb->selectDistinct('meta.metadata')
->from('mounts', 'mount')
->join('mount', 'filecache', 'file', $qb->expr()->eq('file.storage', 'mount.storage_id', IQueryBuilder::PARAM_INT))
->join('file', 'file_metadata', 'meta', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('mount.id', $qb->createNamedParameter($mountId), IQueryBuilder::PARAM_INT))
->from('file_metadata', 'meta')
->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->executeQuery()
Expand All @@ -64,21 +65,49 @@ public function findLocationsForUser(string $userId): array {
return array_map(fn ($row) => new LocationInfo($userId, $row['metadata']), $rows);
}

/** @return LocationInfo */
public function findLocationForUser(string $userId, string $location): LocationInfo {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
->getNumericStorageId();

$mimepart = $this->mimeTypeLoader->getId('image');

$qb = $this->connection->getQueryBuilder();

$rows = $qb->selectDistinct('meta.metadata')
->from('file_metadata', 'meta')
->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
->executeQuery()
->fetchAll();

if (count($rows) !== 1) {
throw new NotFoundException();
}

return new LocationInfo($userId, $rows[0]['metadata']);
}

/** @return LocationFile[] */
public function findFilesForUserAndLocation(string $userId, string $location) {
$mountId = $this->rootFolder
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
->getMountId();
->getNumericStorageId();

$mimepart = $this->mimeTypeLoader->getId('image');

$qb = $this->connection->getQueryBuilder();

$rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta.metadata')
->from('mounts', 'mount')
->join('mount', 'filecache', 'file', $qb->expr()->eq('file.storage', 'mount.storage_id', IQueryBuilder::PARAM_INT))
->join('file', 'file_metadata', 'meta', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('mount.id', $qb->createNamedParameter($mountId), IQueryBuilder::PARAM_INT))
->from('file_metadata', 'meta')
->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
Expand All @@ -99,6 +128,43 @@ public function findFilesForUserAndLocation(string $userId, string $location) {
);
}

public function findFileForUserAndLocation(string $userId, string $location, string $fileId, string $fileName): LocationFile {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
->getNumericStorageId();

$mimepart = $this->mimeTypeLoader->getId('image');

$qb = $this->connection->getQueryBuilder();

$rows = $qb->select('file.fileid', 'file.name', 'file.mimetype', 'file.size', 'file.mtime', 'file.etag', 'meta.metadata')
->from('file_metadata', 'meta')
->join('meta', 'filecache', 'file', $qb->expr()->eq('file.fileid', 'meta.id', IQueryBuilder::PARAM_INT))
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.fileid', $qb->createNamedParameter($fileId)))
->andWhere($qb->expr()->eq('file.name', $qb->createNamedParameter($fileName)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
->executeQuery()
->fetchAll();

if (count($rows) !== 1) {
throw new NotFoundException();
}

return new LocationFile(
(int)$rows[0]['fileid'],
$rows[0]['name'],
$this->mimeTypeLoader->getMimetypeById($rows[0]['mimetype']),
(int)$rows[0]['size'],
(int)$rows[0]['mtime'],
$rows[0]['etag'],
$rows[0]['metadata']
);
}

public function setLocationForFile(string $location, int $fileId): void {
try {
$query = $this->connection->getQueryBuilder();
Expand Down
130 changes: 22 additions & 108 deletions lib/Sabre/Album/AlbumPhoto.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,90 +26,36 @@
use OCA\Photos\Album\AlbumFile;
use OCA\Photos\Album\AlbumInfo;
use OCA\Photos\Album\AlbumMapper;
use OCA\Photos\Sabre\CollectionPhoto;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IFile;

class AlbumPhoto implements IFile {
private AlbumMapper $albumMapper;
private AlbumInfo $album;
private AlbumFile $albumFile;
private IRootFolder $rootFolder;

public const TAG_FAVORITE = '_$!<Favorite>!$_';

public function __construct(AlbumMapper $albumMapper, AlbumInfo $album, AlbumFile $albumFile, IRootFolder $rootFolder) {
$this->albumMapper = $albumMapper;
$this->album = $album;
$this->albumFile = $albumFile;
$this->rootFolder = $rootFolder;
class AlbumPhoto extends CollectionPhoto implements IFile {
public function __construct(
private AlbumMapper $albumMapper,
private AlbumInfo $album,
private AlbumFile $albumFile,
private IRootFolder $rootFolder,
Folder $userFolder,
) {
parent::__construct($albumFile, $userFolder);
}

/**
* @return void
*/
public function delete() {
$this->albumMapper->removeFile($this->album->getId(), $this->albumFile->getFileId());
}

public function getName() {
return $this->albumFile->getFileId() . "-" . $this->albumFile->getName();
}

/**
* @return never
*/
public function setName($name) {
throw new Forbidden('Can\'t rename photos trough the album api');
}

public function getLastModified() {
return $this->albumFile->getMTime();
}

public function put($data) {
$nodes = $this->userFolder->getById($this->file->getFileId());
$node = current($nodes);
if ($node) {
/** @var Node $node */
if ($node instanceof File) {
return $node->putContent($data);
} else {
throw new NotFoundException("Photo is a folder");
}
} else {
throw new NotFoundException("Photo not found for user");
}
}

public function get() {
$nodes = $this->rootFolder
->getUserFolder($this->albumFile->getOwner() ?: $this->album->getUserId())
->getById($this->albumFile->getFileId());
$node = current($nodes);
if ($node) {
/** @var Node $node */
if ($node instanceof File) {
return $node->fopen('r');
} else {
throw new NotFoundException("Photo is a folder");
}
} else {
throw new NotFoundException("Photo not found for user");
}
}

public function getFileId(): int {
return $this->albumFile->getFileId();
$this->albumMapper->removeFile($this->album->getId(), $this->file->getFileId());
}

public function getFileInfo(): Node {
private function getNode(): Node {
$nodes = $this->rootFolder
->getUserFolder($this->albumFile->getOwner() ?: $this->album->getUserId())
->getById($this->albumFile->getFileId());
->getById($this->file->getFileId());
$node = current($nodes);
if ($node) {
return $node;
Expand All @@ -118,48 +64,16 @@ public function getFileInfo(): Node {
}
}

public function getContentType() {
return $this->albumFile->getMimeType();
}

public function getETag() {
return $this->albumFile->getEtag();
}

public function getSize() {
return $this->albumFile->getSize();
}

public function getFile(): AlbumFile {
return $this->albumFile;
}

public function isFavorite(): bool {
$tagManager = \OCP\Server::get(\OCP\ITagManager::class);
$tagger = $tagManager->load('files');
if ($tagger === null) {
return false;
}
$tags = $tagger->getTagsForObjects([$this->getFileId()]);

if ($tags === false || empty($tags)) {
return false;
public function get() {
$node = $this->getNode();
if ($node instanceof File) {
return $node->fopen('r');
} else {
throw new NotFoundException("Photo is a folder");
}

return array_search(self::TAG_FAVORITE, current($tags)) !== false;
}

public function setFavoriteState($favoriteState): bool {
$tagManager = \OCP\Server::get(\OCP\ITagManager::class);
$tagger = $tagManager->load('files');

switch ($favoriteState) {
case "0":
return $tagger->removeFromFavorites($this->albumFile->getFileId());
case "1":
return $tagger->addToFavorites($this->albumFile->getFileId());
default:
new \Exception('Favorite state is invalide, should be 0 or 1.');
}
public function getFileInfo(): Node {
return $this->getNode();
}
}
4 changes: 2 additions & 2 deletions lib/Sabre/Album/AlbumRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ public function createDirectory($name) {

public function getChildren(): array {
return array_map(function (AlbumFile $file) {
return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->rootFolder);
return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId));
}, $this->album->getFiles());
}

public function getChild($name): AlbumPhoto {
foreach ($this->album->getFiles() as $file) {
if ($file->getFileId() . "-" . $file->getName() === $name) {
return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->rootFolder);
return new AlbumPhoto($this->albumMapper, $this->album->getAlbum(), $file, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId));
}
}
throw new NotFound("$name not found");
Expand Down
Loading

0 comments on commit 5bd766c

Please sign in to comment.