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);