diff --git a/lib/Dav/Faces/FacePhoto.php b/lib/Dav/Faces/FacePhoto.php index a8098e60..b274f05b 100644 --- a/lib/Dav/Faces/FacePhoto.php +++ b/lib/Dav/Faces/FacePhoto.php @@ -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; @@ -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; @@ -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()); diff --git a/lib/Dav/Faces/FaceRoot.php b/lib/Dav/Faces/FaceRoot.php index 0a64cd3a..e7f64d20 100644 --- a/lib/Dav/Faces/FaceRoot.php +++ b/lib/Dav/Faces/FaceRoot.php @@ -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; @@ -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 { @@ -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'); } /** diff --git a/lib/Dav/Faces/PropFindPlugin.php b/lib/Dav/Faces/PropFindPlugin.php index 1d5d08c6..065bbb1e 100644 --- a/lib/Dav/Faces/PropFindPlugin.php +++ b/lib/Dav/Faces/PropFindPlugin.php @@ -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())); } diff --git a/lib/Dav/Faces/UnassignedFacePhoto.php b/lib/Dav/Faces/UnassignedFacePhoto.php new file mode 100644 index 00000000..3f5d27f3 --- /dev/null +++ b/lib/Dav/Faces/UnassignedFacePhoto.php @@ -0,0 +1,30 @@ +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; + } +} diff --git a/lib/Dav/RecognizeHome.php b/lib/Dav/RecognizeHome.php index 62f2a81e..393e2ae3 100644 --- a/lib/Dav/RecognizeHome.php +++ b/lib/Dav/RecognizeHome.php @@ -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; @@ -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(); } @@ -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 { diff --git a/lib/Db/FaceClusterMapper.php b/lib/Db/FaceClusterMapper.php index 4d53e37a..22155d42 100644 --- a/lib/Db/FaceClusterMapper.php +++ b/lib/Db/FaceClusterMapper.php @@ -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); } diff --git a/lib/Db/FaceDetectionMapper.php b/lib/Db/FaceDetectionMapper.php index 5ac9e264..712356bb 100644 --- a/lib/Db/FaceDetectionMapper.php +++ b/lib/Db/FaceDetectionMapper.php @@ -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