diff --git a/appinfo/info.xml b/appinfo/info.xml index 07ed378e..f2ede556 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -94,6 +94,9 @@ The app does not send any sensitive data to cloud providers or similar services. + + OCA\Recognize\BackgroundJobs\MaintenanceJob + diff --git a/lib/BackgroundJobs/MaintenanceJob.php b/lib/BackgroundJobs/MaintenanceJob.php new file mode 100644 index 00000000..87b44f60 --- /dev/null +++ b/lib/BackgroundJobs/MaintenanceJob.php @@ -0,0 +1,45 @@ +setInterval(60 * 60 * 12); + $this->setTimeSensitivity(self::TIME_INSENSITIVE); + } + + /** + * @param mixed $argument + * @return void + */ + protected function run($argument) { + // Trigger clustering in case it's stuck + try { + $users = $this->faceDetectionMapper->getUsersForUnclustered(); + } catch (Exception $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + return; + } + foreach ($users as $userId) { + $this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]); + } + } +} diff --git a/lib/Classifiers/Images/ClusteringFaceClassifier.php b/lib/Classifiers/Images/ClusteringFaceClassifier.php index 115e382b..d3744031 100644 --- a/lib/Classifiers/Images/ClusteringFaceClassifier.php +++ b/lib/Classifiers/Images/ClusteringFaceClassifier.php @@ -60,12 +60,12 @@ private function getUsersWithFileAccess(Node $node): array { return array_values(array_unique($userIds)); } - /** - * @param string $user - * @param \OCA\Recognize\Db\QueueFile[] $queueFiles - * @return void - * @throws \ErrorException - */ + /** + * @param string $user + * @param \OCA\Recognize\Db\QueueFile[] $queueFiles + * @return void + * @throws \ErrorException + */ public function classify(array $queueFiles): void { if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') { $timeout = self::IMAGE_PUREJS_TIMEOUT; @@ -94,61 +94,61 @@ public function classify(array $queueFiles): void { } $usersToCluster = []; - try { - $classifierProcess = $this->classifyFiles(self::MODEL_NAME, $filteredQueueFiles, $timeout); + try { + $classifierProcess = $this->classifyFiles(self::MODEL_NAME, $filteredQueueFiles, $timeout); - /** - * @var list $faces - */ - foreach ($classifierProcess as $queueFile => $faces) { - $this->logger->debug('Face results for ' . $queueFile->getFileId() . ' are in'); - foreach ($faces as $face) { - if ($face['score'] < self::MIN_FACE_RECOGNITION_SCORE) { - $this->logger->debug('Face score too low. continuing with next face.'); - continue; - } - if (abs($face['angle']['roll']) > self::MAX_FACE_ROLL || abs($face['angle']['yaw']) > self::MAX_FACE_YAW) { - $this->logger->debug('Face is not straight. continuing with next face.'); - continue; - } + /** + * @var list $faces + */ + foreach ($classifierProcess as $queueFile => $faces) { + $this->logger->debug('Face results for ' . $queueFile->getFileId() . ' are in'); + foreach ($faces as $face) { + if ($face['score'] < self::MIN_FACE_RECOGNITION_SCORE) { + $this->logger->debug('Face score too low. continuing with next face.'); + continue; + } + if (abs($face['angle']['roll']) > self::MAX_FACE_ROLL || abs($face['angle']['yaw']) > self::MAX_FACE_YAW) { + $this->logger->debug('Face is not straight. continuing with next face.'); + continue; + } - try { - $node = $this->rootFolder->getFirstNodeById($queueFile->getFileId()); - $userIds = $node !== null ? $this->getUsersWithFileAccess($node) : []; - } catch (InvalidPathException|NotFoundException $e) { - $userIds = []; - } + try { + $node = $this->rootFolder->getFirstNodeById($queueFile->getFileId()); + $userIds = $node !== null ? $this->getUsersWithFileAccess($node) : []; + } catch (InvalidPathException|NotFoundException $e) { + $userIds = []; + } - // Insert face detection for all users with access - foreach ($userIds as $userId) { - $this->logger->debug('preparing face detection for user ' . $userId); - $faceDetection = new FaceDetection(); - $faceDetection->setX($face['x']); - $faceDetection->setY($face['y']); - $faceDetection->setWidth($face['width']); - $faceDetection->setHeight($face['height']); - $faceDetection->setVector($face['vector']); - $faceDetection->setFileId($queueFile->getFileId()); - $faceDetection->setUserId($userId); - try { - $this->faceDetections->insert($faceDetection); - } catch (Exception $e) { - $this->logger->error('Could not store face detection in database', ['exception' => $e]); - continue; - } - $usersToCluster[$userId] = true; - } - $this->config->setAppValueString(self::MODEL_NAME . '.status', 'true'); - $this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time()); - } - } - } finally { - $usersToCluster = array_keys($usersToCluster); - foreach ($usersToCluster as $userId) { - $this->logger->debug('scheduling ClusterFacesJob for user ' . $userId); - $this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]); - } - $this->logger->debug('face classifier end'); - } + // Insert face detection for all users with access + foreach ($userIds as $userId) { + $this->logger->debug('preparing face detection for user ' . $userId); + $faceDetection = new FaceDetection(); + $faceDetection->setX($face['x']); + $faceDetection->setY($face['y']); + $faceDetection->setWidth($face['width']); + $faceDetection->setHeight($face['height']); + $faceDetection->setVector($face['vector']); + $faceDetection->setFileId($queueFile->getFileId()); + $faceDetection->setUserId($userId); + try { + $this->faceDetections->insert($faceDetection); + } catch (Exception $e) { + $this->logger->error('Could not store face detection in database', ['exception' => $e]); + continue; + } + $usersToCluster[$userId] = true; + } + $this->config->setAppValueString(self::MODEL_NAME . '.status', 'true'); + $this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time()); + } + } + } finally { + $usersToCluster = array_keys($usersToCluster); + foreach ($usersToCluster as $userId) { + $this->logger->debug('scheduling ClusterFacesJob for user ' . $userId); + $this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]); + } + $this->logger->debug('face classifier end'); + } } } diff --git a/lib/Db/FaceDetectionMapper.php b/lib/Db/FaceDetectionMapper.php index 672143d2..75faf836 100644 --- a/lib/Db/FaceDetectionMapper.php +++ b/lib/Db/FaceDetectionMapper.php @@ -306,6 +306,24 @@ public function countUnclustered(): int { return (int) $count; } + /** + * @return array + * @throws \OCP\DB\Exception + */ + public function getUsersForUnclustered(): array { + $qb = $this->db->getQueryBuilder(); + $qb->selectDistinct('user_id') + ->from('recognize_face_detections') + ->where($qb->expr()->isNull('cluster_id')) + ->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE))) + ->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE))); + $result = $qb->executeQuery(); + /** @var array $users */ + $users = $result->fetchAll(\PDO::FETCH_COLUMN); + $result->closeCursor(); + return $users; + } + protected function mapRowToEntity(array $row): Entity { try { return parent::mapRowToEntity($row);