From 796d97231d7f6598413d00f43f8eed206adb4a74 Mon Sep 17 00:00:00 2001 From: Alexander Ratajczak Date: Sat, 15 Aug 2026 23:23:28 +0200 Subject: [PATCH] Added a Setting to set the Model Download Folder Signed-off-by: Alexander Ratajczak --- appinfo/info.xml | 3 ++ lib/Migration/MoveDefaultModelFolder.php | 68 ++++++++++++++++++++++++ lib/Service/DownloadModelsService.php | 15 +++--- lib/Service/SettingsService.php | 31 ++++++++++- lib/Settings/AdminSettings.php | 6 ++- package-lock.json | 4 +- src/components/ViewAdmin.vue | 31 ++++++++--- 7 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 lib/Migration/MoveDefaultModelFolder.php diff --git a/appinfo/info.xml b/appinfo/info.xml index aecd1dbb7..6a2864e81 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -102,6 +102,9 @@ The app does not send any sensitive data to cloud providers or similar services. OCA\Recognize\Migration\InstallDeps + + OCA\Recognize\Migration\MoveDefaultModelFolder + OCA\Recognize\Migration\RemoveDuplicateFaceDetections diff --git a/lib/Migration/MoveDefaultModelFolder.php b/lib/Migration/MoveDefaultModelFolder.php new file mode 100644 index 000000000..ff6cca539 --- /dev/null +++ b/lib/Migration/MoveDefaultModelFolder.php @@ -0,0 +1,68 @@ + + * @copyright Copyright (c) 2021, Marcel Klehr + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Recognize\Migration; + +use OCA\Recognize\Service\SettingsService; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; +use function Safe\rename; +use function Safe\scandir; + +final class MoveDefaultModelFolder implements IRepairStep { + + public function __construct( + private SettingsService $settingsService, + private LoggerInterface $logger, + ) { + } + + public function getName(): string { + return 'Try to move the default Model Folder'; + } + + public function run(IOutput $output): void { + $oldModelTargetPath = __DIR__ . '/../../models'; + $oldModelArchivePath = __DIR__ . '/../../models.tar.gz'; + $newPath = $this->settingsService->getSetting('models_target_path'); + $newModelTargetPath = $newPath . '/models'; + $newModelArchivePath = $newPath . '/models.tar.gz'; + + if (is_dir($oldModelTargetPath)) { + $filesToMove = scandir($oldModelTargetPath); + $filesToMove = array_filter($filesToMove, fn ($value) => $value !== '.' && $value === '..'); + $filesToMove = array_map(fn ($value) => $oldModelTargetPath.'/'.$value, $filesToMove); + mkdir($newModelTargetPath); + foreach ($filesToMove as $file) { + rename($file, $newModelTargetPath.'/'.basename($file)); + } + } + + if (is_file($oldModelArchivePath)) { + rename($oldModelArchivePath, $newModelArchivePath); + } + } +} diff --git a/lib/Service/DownloadModelsService.php b/lib/Service/DownloadModelsService.php index 64e422382..9f2b1d063 100644 --- a/lib/Service/DownloadModelsService.php +++ b/lib/Service/DownloadModelsService.php @@ -16,10 +16,12 @@ final class DownloadModelsService { private IClientService $clientService; private bool $isCLI; + private SettingsService $settingsService; - public function __construct(IClientService $clientService, bool $isCLI) { + public function __construct(IClientService $clientService, bool $isCLI, SettingsService $settingsService) { $this->clientService = $clientService; $this->isCLI = $isCLI; + $this->settingsService = $settingsService; } /** @@ -27,10 +29,11 @@ public function __construct(IClientService $clientService, bool $isCLI) { * @throws \Exception */ public function download() : void { - $targetPath = __DIR__ . '/../../models'; - if (file_exists($targetPath)) { + $targetPath = $this->settingsService->getSetting('models_target_path'); + $modelPath = $targetPath . '/models'; + if (file_exists($modelPath)) { // remove models directory - $it = new RecursiveDirectoryIterator($targetPath, FilesystemIterator::SKIP_DOTS); + $it = new RecursiveDirectoryIterator($modelPath, FilesystemIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { @@ -40,11 +43,11 @@ public function download() : void { unlink($file->getRealPath()); } } - rmdir($targetPath); + rmdir($modelPath); } $archiveUrl = $this->getArchiveUrl($this->getNeededArchiveRef()); - $archivePath = __DIR__ . '/../../models.tar.gz'; + $archivePath = $targetPath . '/models.tar.gz'; $timeout = $this->isCLI ? 0 : 480; $this->clientService->newClient()->get($archiveUrl, ['sink' => $archivePath, 'timeout' => $timeout]); $tarManager = new TAR($archivePath); diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index 8ea9d217b..7075cdfd9 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -61,6 +61,7 @@ final class SettingsService { 'nice_value' => '0', 'concurrency.enabled' => 'false', 'ffmpeg_binary' => '', + 'models_target_path' => '../../models_cache', ]; /** @var array */ @@ -94,7 +95,13 @@ final class SettingsService { 'landmarks.batchSize', 'movinet.batchSize', 'musicnn.batchSize', - 'concurrency.enabled' + 'concurrency.enabled', + 'models_target_path', + 'models_archive_file', + ]; + + private const PATH_SETTINGS = [ + 'models_target_path', ]; private IAppConfig $config; @@ -121,6 +128,14 @@ public function getSetting(string $key): string { if (in_array($key, self::LAZY_SETTINGS, true)) { $lazy = true; } + + if (in_array($key, self::PATH_SETTINGS, true)) { + $path = $this->config->getAppValueString($key, self::DEFAULTS[$key], lazy: $lazy); + if (!$this->isPathAbsolute($path)) { + $path = __DIR__ .'/'. $path; + } + return $path; + } return $this->config->getAppValueString($key, self::DEFAULTS[$key], lazy: $lazy); } @@ -182,6 +197,9 @@ public function setSetting(string $key, string $value): void { if (in_array($key, self::LAZY_SETTINGS, true)) { $lazy = true; } + if (in_array($key, self::PATH_SETTINGS) && $value === '') { + $value = self::DEFAULTS[$key]; + } $this->config->setAppValueString($key, $value, lazy: $lazy); } @@ -195,4 +213,15 @@ public function getAll(): array { } return $settings; } + + private function isPathAbsolute(string $path): bool { + if ($path === '') { + return false; + } + if ($path[0] === '/') { + return true; + } + + return false; + } } diff --git a/lib/Settings/AdminSettings.php b/lib/Settings/AdminSettings.php index 31f133706..4afe42e5f 100644 --- a/lib/Settings/AdminSettings.php +++ b/lib/Settings/AdminSettings.php @@ -28,9 +28,11 @@ public function getForm(): TemplateResponse { $settings = $this->settingsService->getAll(); $this->initialState->provideInitialState('settings', $settings); - $modelsPath = __DIR__ . '/../../models'; - $modelsDownloaded = file_exists($modelsPath); + $targetPath = $this->settingsService->getSetting('models_target_path'); + $modelsDownloaded = file_exists($targetPath .' /models'); + $modelsTargetPathWritable = is_writable($targetPath); $this->initialState->provideInitialState('modelsDownloaded', $modelsDownloaded); + $this->initialState->provideInitialState('modelsTargetPathWritable', $modelsTargetPathWritable); $tagsEnabled = $this->appManager->isEnabledForAnyone('systemtags'); $this->initialState->provideInitialState('tagsEnabled', $tagsEnabled); diff --git a/package-lock.json b/package-lock.json index 853f9c3b7..61836d351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "recognize", - "version": "12.1.0-dev.0", + "version": "13.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "recognize", - "version": "12.1.0-dev.0", + "version": "13.0.0", "license": "MIT", "dependencies": { "@exifer/gps": "^1.0.0-beta.2", diff --git a/src/components/ViewAdmin.vue b/src/components/ViewAdmin.vue index bc465d77e..589a6dbfc 100644 --- a/src/components/ViewAdmin.vue +++ b/src/components/ViewAdmin.vue @@ -38,6 +38,23 @@

+ + + {{ t('recognize', 'The Model Target Path is Writable') }} + + + {{ t('recognize', 'Model Target Path is not Writable') }} + +

+ +

+

+ {{ t('recognize', 'Changing this Value will require you to redownload the Models. Also the models in the old Directory need to be deleted manually') }} +

+
{{ t('recognize', 'The recognize_backend ExApp is installed; TaskProcessing mode is recommended.') }} @@ -81,8 +98,8 @@ {{ t('recognize', 'Enable face recognition (groups photos by faces that appear in them; UI is in the photos app)') }}