Skip to content

Commit

Permalink
feat(dav): Add an unassigned-faces DAV collection
Browse files Browse the repository at this point in the history
fixes #442

also see nextcloud/photos#1832

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
  • Loading branch information
marcelklehr committed May 28, 2023
1 parent 810737e commit 44f91a6
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 16 deletions.
11 changes: 2 additions & 9 deletions lib/Dav/Faces/FacePhoto.php
Expand Up @@ -8,7 +8,6 @@

use \Rubix\ML\Kernels\Distance\Euclidean;
use OC\Metadata\IMetadataManager;
use OCA\Recognize\Db\FaceCluster;
use OCA\Recognize\Db\FaceDetection;
use OCA\Recognize\Db\FaceDetectionMapper;
use OCA\Recognize\Service\FaceClusterAnalyzer;
Expand All @@ -24,16 +23,14 @@
class FacePhoto implements IFile {
private FaceDetectionMapper $detectionMapper;
private FaceDetection $faceDetection;
private FaceCluster $cluster;
private Folder $userFolder;
private ?File $file = null;
private ITagManager $tagManager;
private IMetadataManager $metadataManager;
private IPreview $preview;

public function __construct(FaceDetectionMapper $detectionMapper, FaceCluster $cluster, FaceDetection $faceDetection, Folder $userFolder, ITagManager $tagManager, IMetadataManager $metadataManager, IPreview $preview) {
public function __construct(FaceDetectionMapper $detectionMapper, FaceDetection $faceDetection, Folder $userFolder, ITagManager $tagManager, IMetadataManager $metadataManager, IPreview $preview) {
$this->detectionMapper = $detectionMapper;
$this->cluster = $cluster;
$this->faceDetection = $faceDetection;
$this->userFolder = $userFolder;
$this->tagManager = $tagManager;
Expand Down Expand Up @@ -80,11 +77,7 @@ public function setName($name) {
public function put($data) {
throw new Forbidden('Can\'t write to photos trough the faces api');
}

public function getCluster() : FaceCluster {
return $this->cluster;
}


public function getFile() : File {
if ($this->file === null) {
$nodes = $this->userFolder->getById($this->faceDetection->getFileId());
Expand Down
6 changes: 3 additions & 3 deletions lib/Dav/Faces/FaceRoot.php
Expand Up @@ -89,7 +89,7 @@ public function createFile($name, $data = null) {
public function getChildren(): array {
if (count($this->children) === 0) {
$this->children = array_map(function (FaceDetection $detection) {
return new FacePhoto($this->detectionMapper, $this->cluster, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
return new FacePhoto($this->detectionMapper, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
}, $this->detectionMapper->findByClusterId($this->cluster->getId()));
}
return $this->children;
Expand All @@ -110,7 +110,7 @@ public function getChild($name): FacePhoto {
} catch (DoesNotExistException $e) {
throw new NotFound();
}
return new FacePhoto($this->detectionMapper, $this->cluster, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
return new FacePhoto($this->detectionMapper, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
}

public function childExists($name): bool {
Expand All @@ -128,7 +128,7 @@ public function moveInto($targetName, $sourcePath, INode $sourceNode) {
$this->detectionMapper->update($sourceNode->getFaceDetection());
return true;
}
throw new Forbidden('Not a photo with a detected face, you can only move photos from the faces collection here');
throw new Forbidden('Not a photo with a detected face, you can only move photos from the faces collection or the unassigned-faces here');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Dav/Faces/PropFindPlugin.php
Expand Up @@ -59,7 +59,7 @@ public function propFind(PropFind $propFind, INode $node) {
$propFind->handle(TagsPlugin::FAVORITE_PROPERTYNAME, fn () => $node->isFavorite() ? 1 : 0);
}

if ($node instanceof FaceRoot) {
if ($node instanceof FaceRoot || $node instanceof UnassignedFacesHome) {
$propFind->handle(self::NBITEMS_PROPERTYNAME, fn () => count($node->getChildren()));
}

Expand Down
30 changes: 30 additions & 0 deletions lib/Dav/Faces/UnassignedFacePhoto.php
@@ -0,0 +1,30 @@
<?php
/*
* Copyright (c) 2022 The Recognize contributors.
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
*/

namespace OCA\Recognize\Dav\Faces;

use OC\Metadata\IMetadataManager;
use OCA\Recognize\Db\FaceDetection;
use OCA\Recognize\Db\FaceDetectionMapper;
use OCP\Files\Folder;
use OCP\IPreview;
use OCP\ITagManager;
use Sabre\DAV\Exception\Forbidden;

class UnassignedFacePhoto extends FacePhoto {

public function __construct(FaceDetectionMapper $detectionMapper, FaceDetection $faceDetection, Folder $userFolder, ITagManager $tagManager, IMetadataManager $metadataManager, IPreview $preview) {
parent::__construct($detectionMapper, $faceDetection, $userFolder, $tagManager, $metadataManager, $preview);
}

/**
* @inheritDoc
* @throws \OCP\DB\Exception
*/
public function delete() {
throw new Forbidden('Cannot delete unassigned photos');
}
}
115 changes: 115 additions & 0 deletions lib/Dav/Faces/UnassignedFacesHome.php
@@ -0,0 +1,115 @@
<?php
/*
* Copyright (c) 2022 The Recognize contributors.
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
*/

namespace OCA\Recognize\Dav\Faces;

use OC\Metadata\IMetadataManager;
use OCA\Recognize\Db\FaceDetection;
use OCA\Recognize\Db\FaceDetectionMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\IRootFolder;
use OCP\IPreview;
use OCP\ITagManager;
use OCP\IUser;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;

class UnassignedFacesHome implements ICollection {
private IUser $user;
private FaceDetectionMapper $faceDetectionMapper;
private IRootFolder $rootFolder;
private array $children = [];
private ITagManager $tagManager;
private IMetadataManager $metadataManager;
private IPreview $previewManager;

public function __construct(IUser $user, FaceDetectionMapper $faceDetectionMapper, IRootFolder $rootFolder, ITagManager $tagManager, IMetadataManager $metadataManager, IPreview $previewManager) {
$this->user = $user;
$this->faceDetectionMapper = $faceDetectionMapper;
$this->rootFolder = $rootFolder;
$this->tagManager = $tagManager;
$this->metadataManager = $metadataManager;
$this->previewManager = $previewManager;
}

public function delete() {
throw new Forbidden();
}

public function getName(): string {
return 'unassigned-faces';
}

public function setName($name) {
throw new Forbidden('Permission denied to rename this folder');
}

public function createDirectory($name) {
throw new Forbidden('Not allowed to create directories in this folder');
}

public function createFile($name, $data = null) {
throw new Forbidden('Not allowed to create files in this folder');
}

/**
* @param $name
* @return FacePhoto
* @throws NotFound
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
* @throws \OCP\Files\NotPermittedException
* @throws \OC\User\NoUserException
*/
public function getChild($name): FacePhoto {
if (count($this->children) !== 0) {
foreach ($this->getChildren() as $child) {
if ($child->getName() === $name) {
return $child;
}
}
throw new NotFound("$name not found");
}
[$detectionId,] = explode('-', $name);
try {
$detection = $this->faceDetectionMapper->find((int)$detectionId);
} catch (DoesNotExistException $e) {
throw new NotFound();
}
if ($detection->getClusterId() !== -1) {
throw new NotFound();
}

return new UnassignedFacePhoto($this->faceDetectionMapper, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
}

public function childExists($name): bool {
try {
$this->getChild($name);
return true;
} catch (NotFound $e) {
return false;
}
}

/**
* @throws \OCP\Files\NotPermittedException
* @throws \OC\User\NoUserException
*/
public function getChildren(): array {
if (count($this->children) !== 0) {
return $this->children;
}
return $this->children = array_map(function (FaceDetection $detection) {
return new UnassignedFacePhoto($this->faceDetectionMapper, $detection, $this->rootFolder->getUserFolder($this->user->getUID()), $this->tagManager, $this->metadataManager, $this->previewManager);
}, $this->faceDetectionMapper->findRejectedByUserId($this->user->getUID()));
}

public function getLastModified(): int {
return 0;
}
}
12 changes: 10 additions & 2 deletions lib/Dav/RecognizeHome.php
Expand Up @@ -8,6 +8,7 @@

use OC\Metadata\IMetadataManager;
use OCA\Recognize\Dav\Faces\FacesHome;
use OCA\Recognize\Dav\Faces\UnassignedFacesHome;
use OCA\Recognize\Db\FaceClusterMapper;
use OCA\Recognize\Db\FaceDetectionMapper;
use OCP\Files\IRootFolder;
Expand Down Expand Up @@ -64,10 +65,17 @@ private function getFacesHome() {
return new FacesHome($this->faceClusterMapper, $this->user, $this->faceDetectionMapper, $this->rootFolder, $this->tagManager, $this->metadataManager, $this->previewManager);
}

private function getUnassignedFacesHome() {
return new UnassignedFacesHome($this->user, $this->faceDetectionMapper, $this->rootFolder, $this->tagManager, $this->metadataManager, $this->previewManager);
}

public function getChild($name) {
if ($name === 'faces') {
return $this->getFacesHome();
}
if ($name === 'unassigned-faces') {
return $this->getUnassignedFacesHome();
}

throw new NotFound();
}
Expand All @@ -76,11 +84,11 @@ public function getChild($name) {
* @return FacesHome[]
*/
public function getChildren(): array {
return [$this->getFacesHome()];
return [$this->getFacesHome(), $this->getUnassignedFacesHome()];
}

public function childExists($name): bool {
return $name === 'faces';
return $name === 'faces' || $name === 'unassigned-faces';
}

public function getLastModified(): int {
Expand Down
4 changes: 3 additions & 1 deletion lib/Db/FaceClusterMapper.php
Expand Up @@ -83,7 +83,9 @@ public function findByDetectionId(int $detectionId): array {

public function delete(Entity $entity): Entity {
$qb = $this->db->getQueryBuilder();
$qb->delete('recognize_face_detections')->where($qb->expr()->eq('cluster_id', $qb->createPositionalParameter($entity->getId())));
$qb->update('recognize_face_detections')
->set('cluster_id', -1)
->where($qb->expr()->eq('cluster_id', $qb->createPositionalParameter($entity->getId())));
$qb->executeStatement();
return parent::delete($entity);
}
Expand Down
14 changes: 14 additions & 0 deletions lib/Db/FaceDetectionMapper.php
Expand Up @@ -182,6 +182,20 @@ public function findUnclusteredByUserId(string $userId, int $limit = 0, float $m
return $this->findEntities($qb);
}

/**
* @param string $userId
* @return \OCA\Recognize\Db\FaceDetection[]
* @throws \OCP\DB\Exception
*/
public function findRejectedByUserId(string $userId) : array {
$qb = $this->db->getQueryBuilder();
$qb->select(FaceDetection::$columns)
->from('recognize_face_detections')
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
->andWhere($qb->expr()->eq('cluster_id', $qb->createPositionalParameter(-1)));
return $this->findEntities($qb);
}

/**
* @param int $clusterId
* @param int $n
Expand Down

0 comments on commit 44f91a6

Please sign in to comment.