Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f7557ef
chore: Mark tensorflow.purejs config entry as lazy-loaded
marcelklehr Jan 29, 2026
feec072
chore: Mark node_binary config entry as lazy-loaded
marcelklehr Jan 29, 2026
e820b5a
chore: Mark nice_binary config entry as lazy-loaded
marcelklehr Jan 29, 2026
1ef2401
chore: Mark nice_value config entry as lazy-loaded
marcelklehr Jan 29, 2026
04967a1
chore: Mark ffmpeg_binary config entry as lazy-loaded
marcelklehr Jan 29, 2026
9fea2f4
chore: Mark status config entries as lazy-loaded
marcelklehr Jan 29, 2026
65711e5
chore: Mark lastFile config entries as lazy-loaded
marcelklehr Jan 29, 2026
f56c51d
chore: Mark 'concurrency.enabled' config entriy as lazy-loaded
marcelklehr Jan 29, 2026
336497a
fix: Add migration changing non-lazy config entries to lazy
marcelklehr Jan 29, 2026
a85b657
fix: Introduce internal require_api_key setting
marcelklehr Jan 29, 2026
bffc362
Fix: Run cs:fix
marcelklehr Jan 29, 2026
cd02d7a
chore: Update vendor-bin
marcelklehr Jan 29, 2026
06cde84
chore: Update php-scoper
marcelklehr Mar 30, 2026
e5601ce
chore: Update psalm and php-cs-fixer
marcelklehr Mar 30, 2026
cf71b27
chore: Fix deprecated API usage
marcelklehr Mar 30, 2026
b21a9de
chore: Fix barmani-bin interaction with php-scoper
marcelklehr Mar 30, 2026
dd51bb2
chore: Fix barmani php-scoper interaction
marcelklehr Mar 30, 2026
4c457c4
fix: fix some psalm issues
marcelklehr Mar 30, 2026
84d28ae
fix: fix psalm issues
marcelklehr Mar 30, 2026
6d6dc76
fix: drop nc 33
marcelklehr Mar 30, 2026
50a2e15
fix: Don't run barmani update in composer post-install script
marcelklehr Mar 30, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/cluster-faces-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@ jobs:

- name: Set config
run: |
./occ config:app:set --value ${{ matrix.pure-js-mode }} recognize tensorflow.purejs
./occ config:app:set --lazy --value ${{ matrix.pure-js-mode }} recognize tensorflow.purejs
./occ config:app:set --value true recognize faces.enabled
# Don't force API key usage to allow tests to run
./occ config:app:set --value false recognize require_api_key
# only use one core. GH actions has 2
./occ config:app:set --value 1 recognize tensorflow.cores
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/files-scan-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ jobs:
- name: Set imagenet.enabled and concurrency.enabled
run: |
./occ config:app:set --value true recognize imagenet.enabled
./occ config:app:set --value true recognize concurrency.enabled
./occ config:app:set --lazy --value true recognize concurrency.enabled
- name: Run scan
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/full-run-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ jobs:
- name: Set pure-js mode
run: |
./occ config:app:set --value ${{ matrix.pure-js-mode }} recognize tensorflow.purejs
./occ config:app:set --lazy --value ${{ matrix.pure-js-mode }} recognize tensorflow.purejs
- name: Set imagenet.enabled
run: |
Expand Down
4 changes: 2 additions & 2 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Requirements:
The app does not send any sensitive data to cloud providers or similar services. All processing is done on your Nextcloud machine, using Tensorflow.js running in Node.js.

]]></description>
<version>11.1.0-dev.0</version>
<version>12.0.0-dev.0</version>
<licence>agpl</licence>
<author mail="mklehr@gmx.net">Marcel Klehr</author>
<types>
Expand All @@ -92,7 +92,7 @@ The app does not send any sensitive data to cloud providers or similar services.
<screenshot>https://raw.githubusercontent.com/nextcloud/recognize/main/screenshots/Logo.png</screenshot>
<screenshot>https://raw.githubusercontent.com/nextcloud/recognize/main/screenshots/imagenet_examples.jpg</screenshot>
<dependencies>
<nextcloud min-version="33" max-version="34" />
<nextcloud min-version="34" max-version="34" />
</dependencies>
<background-jobs>
<job>OCA\Recognize\BackgroundJobs\MaintenanceJob</job>
Expand Down
90 changes: 47 additions & 43 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
{
"require": {
"php": ">=8.2",
"ext-json": "*",
"ext-pdo": "*",
"rubix/ml": "2.x",
"amphp\/parallel": "1.4.x",
"wamania/php-stemmer": "4.0 as 3.0",
"bamarni/composer-bin-plugin": "^1.8"
},
"autoload": {
"psr-4": {
"OCA\\Recognize\\": "lib/"
}
"require": {
"php": ">=8.2",
"ext-json": "*",
"ext-pdo": "*",
"rubix/ml": "2.x",
"amphp/parallel": "1.4.x",
"wamania/php-stemmer": "4.0 as 3.0",
"bamarni/composer-bin-plugin": "^1.8"
},
"autoload": {
"psr-4": {
"OCA\\Recognize\\": "lib/"
}
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm",
"psalm:update-baseline": "psalm --threads=1 --update-baseline",
"psalm:update-baseline:force": "psalm --threads=1 --update-baseline --set-baseline=psalm-baseline.xml",
"test:unit": "phpunit --config tests/phpunit.xml",
"post-install-cmd": [
"@composer bin all install --ansi",
"grep -r 'OCA\\\\Recognize\\\\Vendor\\\\Rubix' ./vendor/rubix/ml/ || vendor/bin/php-scoper add-prefix --prefix='OCA\\Recognize\\Vendor' --output-dir=\".\" --working-dir=\"./vendor/\" -f --config=\"../scoper.inc.php\"",
"composer install --no-scripts",
"@composer bin all install --ansi",
"composer dump-autoload"
],
"post-update-cmd": [
"@composer bin all update --ansi",
"grep -r 'OCA\\\\Recognize\\\\Vendor\\\\Rubix' ./vendor/rubix/ml/ || vendor/bin/php-scoper add-prefix --prefix='OCA\\Recognize\\Vendor' --output-dir=\".\" --working-dir=\"./vendor/\" -f --config=\"../scoper.inc.php\"",
"composer install --no-scripts",
"@composer bin all update --ansi",
"composer dump-autoload"
]
},
"config": {
"platform": {
"php": "8.2.0"
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm",
"psalm:update-baseline": "psalm --threads=1 --update-baseline",
"psalm:update-baseline:force": "psalm --threads=1 --update-baseline --set-baseline=psalm-baseline.xml",
"test:unit": "phpunit --config tests/phpunit.xml",
"post-install-cmd": [
"@composer bin all install --ansi",
"grep -r 'OCA\\\\Recognize\\\\Vendor\\\\Rubix' ./vendor/rubix/ml/ || vendor/bin/php-scoper add-prefix --prefix='OCA\\Recognize\\Vendor' --output-dir=\".\" --working-dir=\"./vendor/\" -f --config=\"../scoper.inc.php\"",
"composer dump-autoload"
],
"post-update-cmd": [
"@composer bin all update --ansi",
"grep -r 'OCA\\\\Recognize\\\\Vendor\\\\Rubix' ./vendor/rubix/ml/ || vendor/bin/php-scoper add-prefix --prefix='OCA\\Recognize\\Vendor' --output-dir=\".\" --working-dir=\"./vendor/\" -f --config=\"../scoper.inc.php\"",
"composer dump-autoload"
]
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true
},
"config": {
"platform": {
"php": "8.2.0"
},
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true
},
"autoloader-suffix": "Recognize",
"optimize-autoloader": true,
"sort-packages": true
}
"autoloader-suffix": "Recognize",
"optimize-autoloader": true,
"sort-packages": true
}
}
2 changes: 1 addition & 1 deletion lib/BackgroundJobs/StorageCrawlJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ protected function run($argument): void {
$this->queue->insertIntoQueue(ImagenetClassifier::MODEL_NAME, $queueFile);
}
if (!in_array(ImagenetClassifier::MODEL_NAME, $models) && in_array(LandmarksClassifier::MODEL_NAME, $models)) {
$tags = $this->tagManager->getTagsForFiles([$queueFile->getFileId()]);
$tags = $this->tagManager->getTagsForFiles([(string)$queueFile->getFileId()]);
$fileTags = $tags[$queueFile->getFileId()];
$landmarkTags = array_filter($fileTags, function ($tag) {
return in_array($tag->getName(), LandmarksClassifier::PRECONDITION_TAGS);
Expand Down
6 changes: 3 additions & 3 deletions lib/Classifiers/Audio/MusicnnClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function __construct(Logger $logger, IAppConfig $config, TagManager $tagM

#[Override]
public function classify(array $queueFiles): void {
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
$timeout = self::AUDIO_PUREJS_TIMEOUT;
} else {
$timeout = self::AUDIO_TIMEOUT;
Expand All @@ -43,8 +43,8 @@ public function classify(array $queueFiles): void {
*/
foreach ($classifierProcess as $queueFile => $results) {
$this->tagManager->assignTags($queueFile->getFileId(), $results);
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true');
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time());
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true', lazy: true);
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time(), lazy: true);
}
}
}
10 changes: 5 additions & 5 deletions lib/Classifiers/Classifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,15 @@ public function classifyFiles(string $model, array $queueFiles, int $timeout): \
$this->logger->debug('Classifying '.var_export($paths, true));

$command = [
$this->config->getAppValueString('node_binary'),
$this->config->getAppValueString('node_binary', lazy: true),
dirname(__DIR__, 2) . '/src/classifier_'.$model.'.js',
'-'
];

if (trim($this->config->getAppValueString('nice_binary', '')) !== '') {
if (trim($this->config->getAppValueString('nice_binary', '', lazy: true)) !== '') {
$command = [
$this->config->getAppValueString('nice_binary'),
"-" . $this->config->getAppValueString('nice_value', '0'),
$this->config->getAppValueString('nice_binary', lazy: true),
"-" . $this->config->getAppValueString('nice_value', '0', lazy: true),
...$command,
];
}
Expand All @@ -179,7 +179,7 @@ public function classifyFiles(string $model, array $queueFiles, int $timeout): \
if ($this->config->getAppValueString('tensorflow.gpu', 'false') === 'true') {
$env['RECOGNIZE_GPU'] = 'true';
}
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
$env['RECOGNIZE_PUREJS'] = 'true';
}
// Set cores
Expand Down
6 changes: 3 additions & 3 deletions lib/Classifiers/Images/ClusteringFaceClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ private function getUsersWithFileAccess(Node $node): array {

#[Override]
public function classify(array $queueFiles): void {
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
$timeout = self::IMAGE_PUREJS_TIMEOUT;
} else {
$timeout = self::IMAGE_TIMEOUT;
Expand Down Expand Up @@ -134,8 +134,8 @@ public function classify(array $queueFiles): void {
$this->logger->debug('scheduling ClusterFacesJob for user ' . $userId);
$this->jobList->add(ClusterFacesJob::class, ['userId' => $userId]);
}
$this->config->setAppValueString(self::MODEL_NAME . '.status', 'true');
$this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time());
$this->config->setAppValueString(self::MODEL_NAME . '.status', 'true', lazy: true);
$this->config->setAppValueString(self::MODEL_NAME . '.lastFile', (string)time(), lazy: true);
}
}
$this->logger->debug('face classifier end');
Expand Down
6 changes: 3 additions & 3 deletions lib/Classifiers/Images/ImagenetClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __construct(Logger $logger, IAppConfig $config, TagManager $tagM

#[Override]
public function classify(array $queueFiles): void {
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
$timeout = self::IMAGE_PUREJS_TIMEOUT;
} else {
$timeout = self::IMAGE_TIMEOUT;
Expand All @@ -47,8 +47,8 @@ public function classify(array $queueFiles): void {
$landmarkTags = array_filter($results, static function ($tagName) {
return in_array($tagName, LandmarksClassifier::PRECONDITION_TAGS);
});
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true');
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time());
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true', lazy: true);
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time(), lazy: true);

if (count($landmarkTags) > 0) {
try {
Expand Down
6 changes: 3 additions & 3 deletions lib/Classifiers/Images/LandmarksClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __construct(Logger $logger, IAppConfig $config, TagManager $tagM

#[Override]
public function classify(array $queueFiles): void {
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
$timeout = self::IMAGE_PUREJS_TIMEOUT;
} else {
$timeout = self::IMAGE_TIMEOUT;
Expand All @@ -44,8 +44,8 @@ public function classify(array $queueFiles): void {
/** @var list<string> $results */
foreach ($classifierProcess as $queueFile => $results) {
$this->tagManager->assignTags($queueFile->getFileId(), $results);
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true');
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time());
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true', lazy: true);
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time(), lazy: true);
}
}
}
6 changes: 3 additions & 3 deletions lib/Classifiers/Video/MovinetClassifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function __construct(Logger $logger, IAppConfig $config, TagManager $tagM

#[Override]
public function classify(array $queueFiles): void {
if ($this->config->getAppValueString('tensorflow.purejs', 'false') === 'true') {
if ($this->config->getAppValueString('tensorflow.purejs', 'false', lazy: true) === 'true') {
throw new Exception('Movinet does not support WASM mode');
} else {
$timeout = self::VIDEO_TIMEOUT;
Expand All @@ -42,8 +42,8 @@ public function classify(array $queueFiles): void {
/** @var list<string> $results */
foreach ($classifierProcess as $queueFile => $results) {
$this->tagManager->assignTags($queueFile->getFileId(), $results);
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true');
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time());
$this->config->setAppValueString(self::MODEL_NAME.'.status', 'true', lazy: true);
$this->config->setAppValueString(self::MODEL_NAME.'.lastFile', (string)time(), lazy: true);
}
}
}
2 changes: 1 addition & 1 deletion lib/Command/Classify.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
// if retry flag is set, skip other classifiers for tagged files
if ($input->getOption('retry')) {
$fileTags = $this->tagManager->getTagsForFiles([$lastFileId]);
$fileTags = $this->tagManager->getTagsForFiles([(string)$lastFileId]);
// check if processed tag is already in the tags
if (in_array($processedTag, $fileTags[$lastFileId])) {
continue;
Expand Down
8 changes: 4 additions & 4 deletions lib/Controller/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,17 @@ public function musl(): JSONResponse {

public function nice(): JSONResponse {
/* use nice binary from settings if available */
if ($this->config->getAppValueString('nice_binary', '') !== '') {
$nice_path = $this->config->getAppValueString('nice_binary');
if ($this->config->getAppValueString('nice_binary', '', lazy: true) !== '') {
$nice_path = $this->config->getAppValueString('nice_binary', lazy: true);
} else {
/* returns the path to the nice binary or false if not found */
$nice_path = $this->binaryFinder->findBinaryPath('nice');
}

if ($nice_path !== false) {
$this->config->setAppValueString('nice_binary', $nice_path);
$this->config->setAppValueString('nice_binary', $nice_path, lazy: true);
} else {
$this->config->setAppValueString('nice_binary', '');
$this->config->setAppValueString('nice_binary', '', lazy: true);
return new JSONResponse(['nice' => false]);
}

Expand Down
3 changes: 3 additions & 0 deletions lib/Dav/Faces/FacePhoto.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ public function hasPreview(): bool {

public function isFavorite(): bool {
$tagger = $this->tagManager->load('files');
if ($tagger === null) {
return false;
}
$tags = $tagger->getTagsForObjects([$this->getFile()->getId()]);

if ($tags === false || empty($tags)) {
Expand Down
9 changes: 7 additions & 2 deletions lib/Dav/Faces/PropFindPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use OCA\Recognize\Db\FaceDetectionWithTitle;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\Files\DavUtil;
Expand Down Expand Up @@ -47,6 +48,7 @@ public function __construct(
private ICrypto $crypto,
private LoggerInterface $logger,
private ITimeFactory $timeFactory,
private IAppConfig $appConfig,
) {
}

Expand Down Expand Up @@ -76,12 +78,12 @@ public function propFind(PropFind $propFind, INode $node): void {
$propFind->handle(TagsPlugin::FAVORITE_PROPERTYNAME, fn () => $node->isFavorite() ? 1 : 0);
$propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, fn () => json_encode($this->previewManager->isAvailable($node->getFile()->getFileInfo())));
$propFind->handle(FilesPlugin::PERMISSIONS_PROPERTYNAME, function () use ($node): string {
$permissions = DavUtil::getDavPermissions($node->getFile()->getFileInfo());
$permissions = DavUtil::getDavPermissions($node->getFile(), $node->getFile()->getParent());
$filteredPermissions = str_replace('R', '', $permissions);
return $filteredPermissions;
});

foreach ($node->getFile()->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
foreach ($node->getFile()->getMetadata() as $metadataKey => $metadataValue) {
/** @var string $metadataKey */
$propFind->handle(FilesPlugin::FILE_METADATA_PREFIX.$metadataKey, $metadataValue);
}
Expand Down Expand Up @@ -130,6 +132,9 @@ public function beforeMethod(RequestInterface $request, ResponseInterface $respo
if (!str_starts_with($request->getPath(), 'recognize')) {
return;
}
if ($this->appConfig->getAppValueString('require_api_key', 'true') !== 'true') {
return;
}
$key = $request->getHeader('X-Recognize-Api-Key');
if ($key === null) {
throw new Forbidden('You must provide a valid X-Recognize-Api-Key');
Expand Down
Loading
Loading