Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/AuditLog/AuditEventFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
declare(strict_types=1);
namespace OCA\Recognize\AuditLog;

use OCP\Log\Audit\CriticalActionPerformedEvent;

final class AuditEventFactory {
/**
* The operation is only passed on to servers whose CriticalActionPerformedEvent supports it
*
* @param array<string, mixed> $parameters values for the placeholders of $message, in order, optionally followed by more context
*/
public static function create(Operation $operation, string $message, array $parameters = []): CriticalActionPerformedEvent {
$arguments = [$message, $parameters, false];
if (method_exists(CriticalActionPerformedEvent::class, 'getOperation')) {
$arguments[] = $operation->value;
}
return new CriticalActionPerformedEvent(...$arguments);
}
}
37 changes: 37 additions & 0 deletions lib/AuditLog/Operation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
declare(strict_types=1);
namespace OCA\Recognize\AuditLog;

/**
* Operations Recognize writes to the admin audit log, in the form `app.entity.action`.
* The values are matched by log consumers (filters, alert rules, SIEM imports),
* so changing one is a breaking change for them. Case names may be renamed.
*/
enum Operation: string {
case SettingChanged = 'recognize.setting.changed';
case ClassificationsReset = 'recognize.classifications.reset';
case FaceClustersDeletedAll = 'recognize.face_clusters.deleted_all';
case FaceDetectionsDeletedAll = 'recognize.face_detections.deleted_all';
case EmptyTagsDeleted = 'recognize.empty_tags.deleted';
case LegacyTagsDeleted = 'recognize.legacy_tags.deleted';
case QueuesCleared = 'recognize.queues.cleared';
case RecrawlScheduled = 'recognize.recrawl.scheduled';

public function description(): string {
return match ($this) {
self::SettingChanged => 'An admin changed a Recognize setting to a different value.',
self::ClassificationsReset => 'Recognize classification tags were removed from all files of all users.',
self::FaceClustersDeletedAll => 'All face clusters (persons) of all users were deleted.',
self::FaceDetectionsDeletedAll => 'All face detections of all users were deleted.',
self::EmptyTagsDeleted => 'System tags that were assigned to no file were deleted, including tags not created by Recognize.',
self::LegacyTagsDeleted => 'System tags created by old Recognize versions were deleted.',
self::QueuesCleared => 'All Recognize queues and scheduled background jobs were cleared.',
self::RecrawlScheduled => 'All Recognize queues and background jobs were cleared and a new crawl of all files was scheduled.',
};
}
}
25 changes: 2 additions & 23 deletions lib/Command/ClearBackgroundJobs.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,19 @@
declare(strict_types=1);
namespace OCA\Recognize\Command;

use OCA\Recognize\BackgroundJobs\ClassifyFacesJob;
use OCA\Recognize\BackgroundJobs\ClassifyImagenetJob;
use OCA\Recognize\BackgroundJobs\ClassifyLandmarksJob;
use OCA\Recognize\BackgroundJobs\ClassifyMovinetJob;
use OCA\Recognize\BackgroundJobs\ClassifyMusicnnJob;
use OCA\Recognize\BackgroundJobs\SchedulerJob;
use OCA\Recognize\BackgroundJobs\StorageCrawlJob;
use OCA\Recognize\Service\QueueService;
use OCP\BackgroundJob\IJobList;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class ClearBackgroundJobs extends Command {
private IJobList $jobList;
private LoggerInterface $logger;

private QueueService $queue;

public function __construct(IJobList $jobList, LoggerInterface $logger, QueueService $queue) {
public function __construct(LoggerInterface $logger, QueueService $queue) {
parent::__construct();
$this->jobList = $jobList;
$this->logger = $logger;
$this->queue = $queue;
}
Expand All @@ -54,18 +44,7 @@ protected function configure() {
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$this->queue->clearQueue('imagenet');
$this->queue->clearQueue('faces');
$this->queue->clearQueue('landmarks');
$this->queue->clearQueue('movinet');
$this->queue->clearQueue('musicnn');
$this->jobList->remove(ClassifyFacesJob::class);
$this->jobList->remove(ClassifyImagenetJob::class);
$this->jobList->remove(ClassifyLandmarksJob::class);
$this->jobList->remove(ClassifyMusicnnJob::class);
$this->jobList->remove(ClassifyMovinetJob::class);
$this->jobList->remove(SchedulerJob::class);
$this->jobList->remove(StorageCrawlJob::class);
$this->queue->clearAllJobs();
} catch (\Exception $ex) {
$output->writeln('<error>Failed to clear background jobs</error>');
$output->writeln($ex->getMessage());
Expand Down
28 changes: 2 additions & 26 deletions lib/Command/Recrawl.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,18 @@
declare(strict_types=1);
namespace OCA\Recognize\Command;

use OCA\Recognize\BackgroundJobs\ClassifyFacesJob;
use OCA\Recognize\BackgroundJobs\ClassifyImagenetJob;
use OCA\Recognize\BackgroundJobs\ClassifyLandmarksJob;
use OCA\Recognize\BackgroundJobs\ClassifyMovinetJob;
use OCA\Recognize\BackgroundJobs\ClassifyMusicnnJob;
use OCA\Recognize\BackgroundJobs\ClusterFacesJob;
use OCA\Recognize\BackgroundJobs\SchedulerJob;
use OCA\Recognize\BackgroundJobs\StorageCrawlJob;
use OCA\Recognize\Service\QueueService;
use OCP\BackgroundJob\IJobList;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class Recrawl extends Command {
private IJobList $jobList;
private LoggerInterface $logger;
private QueueService $queue;

public function __construct(IJobList $jobList, LoggerInterface $logger, QueueService $queue) {
public function __construct(LoggerInterface $logger, QueueService $queue) {
parent::__construct();
$this->jobList = $jobList;
$this->logger = $logger;
$this->queue = $queue;
}
Expand All @@ -54,20 +43,7 @@ protected function configure() {
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$this->queue->clearQueue('imagenet');
$this->queue->clearQueue('faces');
$this->queue->clearQueue('landmarks');
$this->queue->clearQueue('movinet');
$this->queue->clearQueue('musicnn');
$this->jobList->remove(ClassifyFacesJob::class);
$this->jobList->remove(ClassifyImagenetJob::class);
$this->jobList->remove(ClassifyLandmarksJob::class);
$this->jobList->remove(ClassifyMusicnnJob::class);
$this->jobList->remove(ClassifyMovinetJob::class);
$this->jobList->remove(ClusterFacesJob::class);
$this->jobList->remove(SchedulerJob::class);
$this->jobList->remove(StorageCrawlJob::class);
$this->jobList->add(SchedulerJob::class);
$this->queue->scheduleRecrawl();
} catch (\Exception $ex) {
$output->writeln('<error>Failed to schedule recrawl</error>');
$output->writeln($ex->getMessage());
Expand Down
19 changes: 2 additions & 17 deletions lib/Controller/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
use OCA\Recognize\BackgroundJobs\ClassifyMovinetJob;
use OCA\Recognize\BackgroundJobs\ClassifyMusicnnJob;
use OCA\Recognize\BackgroundJobs\ClusterFacesJob;
use OCA\Recognize\BackgroundJobs\SchedulerJob;
use OCA\Recognize\BackgroundJobs\StorageCrawlJob;
use OCA\Recognize\Db\FaceClusterMapper;
use OCA\Recognize\Db\FaceDetectionMapper;
use OCA\Recognize\Service\QueueService;
Expand Down Expand Up @@ -63,25 +61,12 @@ public function reset(): JSONResponse {
}

public function clearAllJobs(): JSONResponse {
$this->queue->clearQueue('imagenet');
$this->queue->clearQueue('faces');
$this->queue->clearQueue('landmarks');
$this->queue->clearQueue('movinet');
$this->queue->clearQueue('musicnn');
$this->jobList->remove(ClassifyFacesJob::class);
$this->jobList->remove(ClassifyImagenetJob::class);
$this->jobList->remove(ClassifyLandmarksJob::class);
$this->jobList->remove(ClassifyMusicnnJob::class);
$this->jobList->remove(ClassifyMovinetJob::class);
$this->jobList->remove(ClusterFacesJob::class);
$this->jobList->remove(SchedulerJob::class);
$this->jobList->remove(StorageCrawlJob::class);
$this->queue->clearAllJobs();
return new JSONResponse([]);
}

public function recrawl(): JSONResponse {
$this->clearAllJobs();
$this->jobList->add(SchedulerJob::class);
$this->queue->scheduleRecrawl();
return new JSONResponse([]);
}

Expand Down
17 changes: 15 additions & 2 deletions lib/Db/FaceClusterMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@
declare(strict_types=1);
namespace OCA\Recognize\Db;

use OCA\Recognize\AuditLog\AuditEventFactory;
use OCA\Recognize\AuditLog\Operation;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;

/**
* @template-extends QBMapper<FaceCluster>
*/
final class FaceClusterMapper extends QBMapper {
public function __construct(IDBConnection $db) {
public function __construct(
IDBConnection $db,
private IEventDispatcher $eventDispatcher,
) {
parent::__construct($db, 'recognize_face_clusters', FaceCluster::class);
$this->db = $db;
}
Expand Down Expand Up @@ -104,7 +110,14 @@ public function delete(Entity $entity): Entity {
*/
public function deleteAll(): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('recognize_face_clusters')
$count = $qb->delete('recognize_face_clusters')
->executeStatement();
if ($count > 0) {
$this->eventDispatcher->dispatchTyped(AuditEventFactory::create(
Operation::FaceClustersDeletedAll,
'Deleted all %s Recognize face clusters',
['count' => $count],
));
}
}
}
16 changes: 14 additions & 2 deletions lib/Db/FaceDetectionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
declare(strict_types=1);
namespace OCA\Recognize\Db;

use OCA\Recognize\AuditLog\AuditEventFactory;
use OCA\Recognize\AuditLog\Operation;
use OCA\Recognize\Classifiers\TaskProcessing\ImageFaceRecognitionClassifier;
use OCA\Recognize\Service\FaceClusterAnalyzer;
use OCA\Recognize\Service\SettingsService;
Expand All @@ -15,6 +17,7 @@
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;

Expand All @@ -24,12 +27,14 @@
final class FaceDetectionMapper extends QBMapper {
private IConfig $config;
private SettingsService $settingsService;
private IEventDispatcher $eventDispatcher;

public function __construct(IDBConnection $db, IConfig $config, SettingsService $settingsService) {
public function __construct(IDBConnection $db, IConfig $config, SettingsService $settingsService, IEventDispatcher $eventDispatcher) {
parent::__construct($db, 'recognize_face_detections', FaceDetection::class);
$this->db = $db;
$this->config = $config;
$this->settingsService = $settingsService;
$this->eventDispatcher = $eventDispatcher;
}

private function getMinDetectionSize(): float {
Expand Down Expand Up @@ -91,8 +96,15 @@ public function insertWithoutDeduplication(Entity $entity): FaceDetection {
*/
public function deleteAll(): void {
$qb = $this->db->getQueryBuilder();
$qb->delete('recognize_face_detections')
$count = $qb->delete('recognize_face_detections')
->executeStatement();
if ($count > 0) {
$this->eventDispatcher->dispatchTyped(AuditEventFactory::create(
Operation::FaceDetectionsDeletedAll,
'Deleted all %s Recognize face detections',
['count' => $count],
));
}
}

/**
Expand Down
8 changes: 6 additions & 2 deletions lib/Db/QueueMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,13 @@ public function insertIntoQueue(string $model, QueueFile $file) : QueueFile {
return $insertedFile;
}

public function clearQueue(string $model): void {
/**
* @return int the number of removed queue entries
* @throws \OCP\DB\Exception
*/
public function clearQueue(string $model): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getQueueTable($model))->executeStatement();
return $qb->delete($this->getQueueTable($model))->executeStatement();
}

/**
Expand Down
Loading
Loading