diff --git a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php index c5b83f02..4710c46e 100644 --- a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php +++ b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php @@ -28,14 +28,13 @@ use OCP\Files\File; use OCP\Files\Folder; -use OCP\Files\IHomeStorage; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext; use OCA\FaceRecognition\Db\Image; use OCA\FaceRecognition\Db\ImageMapper; -use OCA\FaceRecognition\Helper\Requirements; use OCA\FaceRecognition\Migration\AddDefaultFaceModel; +use OCA\FaceRecognition\Service\FileService; /** * Task that, for each user, crawls for all images in filesystem and insert them in database. @@ -51,14 +50,21 @@ class AddMissingImagesTask extends FaceRecognitionBackgroundTask { /** @var ImageMapper Image mapper */ private $imageMapper; + /** @var FileService */ + private $fileService; + /** * @param IConfig $config Config * @param ImageMapper $imageMapper Image mapper + * @param FileService $fileService File Service */ - public function __construct(IConfig $config, ImageMapper $imageMapper) { + public function __construct(IConfig $config, + ImageMapper $imageMapper, + FileService $fileService) { parent::__construct(); - $this->config = $config; + $this->config = $config; $this->imageMapper = $imageMapper; + $this->fileService = $fileService; } /** @@ -120,11 +126,10 @@ public function execute(FaceRecognitionContext $context) { */ private function addMissingImagesForUser(string $userId, int $model): int { $this->logInfo(sprintf('Finding missing images for user %s', $userId)); - \OC_Util::tearDownFS(); - \OC_Util::setupFS($userId); + $this->fileService->setupFS($userId); $userFolder = $this->context->rootFolder->getUserFolder($userId); - return $this->parseUserFolder($model, $userFolder); + return $this->parseUserFolder($userId, $model, $userFolder); } /** @@ -134,14 +139,14 @@ private function addMissingImagesForUser(string $userId, int $model): int { * @param Folder $folder Folder to recursively search images in * @return int Number of missing images found */ - private function parseUserFolder(int $model, Folder $folder): int { + private function parseUserFolder(string $userId, int $model, Folder $folder): int { $insertedImages = 0; - $nodes = $this->getPicturesFromFolder($folder); + $nodes = $this->fileService->getPicturesFromFolder($folder); foreach ($nodes as $file) { $this->logDebug('Found ' . $file->getPath()); $image = new Image(); - $image->setUser($file->getOwner()->getUid()); + $image->setUser($userId); $image->setFile($file->getId()); $image->setModel($model); // todo: this check/insert logic for each image is so inefficient it hurts my mind @@ -155,32 +160,4 @@ private function parseUserFolder(int $model, Folder $folder): int { return $insertedImages; } - /** - * Return all images from a given folder. - * - * TODO: It is inefficient since it copies the array recursively. - * - * @param Folder $folder Folder to get images from - * @return array List of all images and folders to continue recursive crawling - */ - private function getPicturesFromFolder(Folder $folder, $results = array()) { - // todo: should we also care about this too: instanceOfStorage(ISharedStorage::class); - if ($folder->getStorage()->instanceOfStorage(IHomeStorage::class) === false) { - return $results; - } - - $nodes = $folder->getDirectoryListing(); - - foreach ($nodes as $node) { - if ($node instanceof Folder and !$node->nodeExists('.nomedia')) { - $results = $this->getPicturesFromFolder($node, $results); - } else if ($node instanceof File) { - if (Requirements::isImageTypeSupported($node->getMimeType())) { - $results[] = $node; - } - } - } - - return $results; - } } diff --git a/lib/BackgroundJob/Tasks/ImageProcessingTask.php b/lib/BackgroundJob/Tasks/ImageProcessingTask.php index 150941ed..85d77a1b 100644 --- a/lib/BackgroundJob/Tasks/ImageProcessingTask.php +++ b/lib/BackgroundJob/Tasks/ImageProcessingTask.php @@ -28,7 +28,6 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\IConfig; -use OCP\ITempManager; use OCP\IUser; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask; @@ -39,6 +38,8 @@ use OCA\FaceRecognition\Helper\Requirements; use OCA\FaceRecognition\Migration\AddDefaultFaceModel; +use OCA\FaceRecognition\Service\FileService; + /** * Plain old PHP object holding all information * that are needed to process all faces from one image @@ -115,20 +116,25 @@ class ImageProcessingTask extends FaceRecognitionBackgroundTask { /** @var ImageMapper Image mapper*/ protected $imageMapper; - /** @var ITempManager */ - private $tempManager; + /** @var FileService */ + private $fileService; /** @var int|null Maximum image area (cached, so it is not recalculated for each image) */ private $maxImageAreaCached; /** + * @param IConfig $config * @param ImageMapper $imageMapper Image mapper + * @param FileService $fileService */ - public function __construct(IConfig $config, ImageMapper $imageMapper, ITempManager $tempManager) { + public function __construct(IConfig $config, + ImageMapper $imageMapper, + FileService $fileService) + { parent::__construct(); - $this->config = $config; - $this->imageMapper = $imageMapper; - $this->tempManager = $tempManager; + $this->config = $config; + $this->imageMapper = $imageMapper; + $this->fileService = $fileService; $this->maxImageAreaCached = null; } @@ -148,7 +154,6 @@ public function execute(FaceRecognitionContext $context) { $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); $requirements = new Requirements($context->modelService, $model); - $dataDir = rtrim($context->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'), '/'); $images = $context->propertyBag['images']; $cfd = new \CnnFaceDetection($requirements->getFaceDetectionModel()); @@ -160,11 +165,11 @@ public function execute(FaceRecognitionContext $context) { foreach($images as $image) { yield; - $imageProcessingContext = null; $startMillis = round(microtime(true) * 1000); try { - $imageProcessingContext = $this->findFaces($cfd, $dataDir, $image); + $imageProcessingContext = $this->findFaces($cfd, $image); + if (($imageProcessingContext !== null) && ($imageProcessingContext->getSkipDetection() === false)) { $this->populateDescriptors($fld, $fr, $imageProcessingContext); } @@ -184,7 +189,7 @@ public function execute(FaceRecognitionContext $context) { $this->logDebug($e); $this->imageMapper->imageProcessed($image, array(), 0, $e); } finally { - $this->tempManager->clean(); + $this->fileService->clean(); } } @@ -197,22 +202,22 @@ public function execute(FaceRecognitionContext $context) { * If there is any error, throws exception * * @param \CnnFaceDetection $cfd Face detection model - * @param string $dataDir Directory where data is stored * @param Image $image Image to find faces on * @return ImageProcessingContext|null Generated context that hold all information needed later for this image */ - private function findFaces(\CnnFaceDetection $cfd, string $dataDir, Image $image) { + private function findFaces(\CnnFaceDetection $cfd, Image $image) { // todo: check if this hits I/O (database, disk...), consider having lazy caching to return user folder from user - $userFolder = $this->context->rootFolder->getUserFolder($image->user); - $userRoot = $userFolder->getParent(); - $file = $userRoot->getById($image->file); + $file = $this->fileService->getFileById($image->getFile(), $image->getUser()); + if (empty($file)) { + // If we cannot find a file probably it was deleted out of our control and we must clean our tables. + $this->config->setUserValue($image->user, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); $this->logInfo('File with ID ' . $image->file . ' doesn\'t exist anymore, skipping it'); return null; } - // todo: this concat is wrong with shared files. - $imagePath = $dataDir . $file[0]->getPath(); + $imagePath = $this->fileService->getLocalFile($file); + $this->logInfo('Processing image ' . $imagePath); $imageProcessingContext = $this->prepareImage($imagePath); if ($imageProcessingContext->getSkipDetection() === true) { @@ -248,6 +253,7 @@ private function prepareImage(string $imagePath) { $image = new OCP_Image(null, $this->context->logger->getLogger(), $this->context->config); $image->loadFromFile($imagePath); $image->fixOrientation(); + if (!$image->valid()) { throw new \RuntimeException("Image is not valid, probably cannot be loaded"); } @@ -261,8 +267,9 @@ private function prepareImage(string $imagePath) { $maxImageArea = $this->getMaxImageArea(); $ratio = $this->resizeImage($image, $maxImageArea); - $tempfile = $this->tempManager->getTemporaryFile(pathinfo($imagePath, PATHINFO_EXTENSION)); + $tempfile = $this->fileService->getTemporaryFile(pathinfo($imagePath, PATHINFO_EXTENSION)); $image->save($tempfile); + return new ImageProcessingContext($imagePath, $tempfile, $ratio, false); } @@ -270,7 +277,7 @@ private function prepareImage(string $imagePath) { * Resizes the image to reach max image area, but preserving ratio. * Stolen and adopted from OC_Image->resize() (difference is that this returns ratio of resize.) * - * @param OC_Image $image Image to resize + * @param Image $image Image to resize * @param int $maxImageArea The maximum size of image we can handle (in pixels^2). * * @return float Ratio of resize. 1 if there was no resize @@ -383,4 +390,5 @@ private function calculateMaxImageArea(): int { $maxImageArea = intval((0.75 * $allowedMemory) / 1024); // in pixels^2 return $maxImageArea; } + } \ No newline at end of file diff --git a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php index 3a08a039..ac263073 100644 --- a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php +++ b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php @@ -28,7 +28,7 @@ use OCP\Files\File; use OCP\Files\Folder; -use OCP\Files\IHomeStorage; +use OCP\Files\Node; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext; @@ -37,6 +37,7 @@ use OCA\FaceRecognition\Db\FaceMapper; use OCA\FaceRecognition\Db\PersonMapper; use OCA\FaceRecognition\Migration\AddDefaultFaceModel; +use OCA\FaceRecognition\Service\FileService; /** * Task that, for each user, crawls for all images in database, @@ -59,18 +60,27 @@ class StaleImagesRemovalTask extends FaceRecognitionBackgroundTask { /** @var PersonMapper Person mapper */ private $personMapper; + /** @var FileService */ + private $fileService; + /** * @param IConfig $config Config * @param ImageMapper $imageMapper Image mapper * @param FaceMapper $faceMapper Face mapper * @param PersonMapper $personMapper Person mapper + * @param FileService $fileService File Service */ - public function __construct(IConfig $config, ImageMapper $imageMapper, FaceMapper $faceMapper, PersonMapper $personMapper) { + public function __construct(IConfig $config, + ImageMapper $imageMapper, + FaceMapper $faceMapper, + PersonMapper $personMapper, + FileService $fileService) { parent::__construct(); $this->config = $config; - $this->imageMapper = $imageMapper; - $this->faceMapper = $faceMapper; + $this->imageMapper = $imageMapper; + $this->faceMapper = $faceMapper; $this->personMapper = $personMapper; + $this->fileService = $fileService; } /** @@ -134,8 +144,8 @@ public function execute(FaceRecognitionContext $context) { * which represent number of stale images removed */ private function staleImagesRemovalForUser(string $userId, int $model) { - \OC_Util::tearDownFS(); - \OC_Util::setupFS($userId); + + $this->fileService->setupFS($userId); $this->logDebug(sprintf('Getting all images for user %s', $userId)); $allImages = $this->imageMapper->findImages($userId, $model); @@ -165,17 +175,19 @@ private function staleImagesRemovalForUser(string $userId, int $model) { yield; // Now iterate and check remaining images - $userFolder = $this->context->rootFolder->getUserFolder($userId); $processed = 0; $imagesRemoved = 0; foreach ($allImages as $image) { - // Delete image doesn't exist anymore in filesystem or it is under .nomedia - $mount = $this->getHomeMount($userFolder, $image); + // Try to get the file to ensure that exist. + try { + $file = $this->fileService->getFileById($image->getFile(), $userId); + } catch (\OCP\Files\NotFoundException $e) { + $file = null; + } - if ($mount === null) { - $this->deleteImage($image, $userId); - $imagesRemoved++; - } else if ($this->isUnderNoMedia($mount)) { + // Delete image doesn't exist anymore in filesystem or it is under .nomedia + if (($file === null) || (!$this->fileService->isAllowedNode($file)) || + ($this->fileService->isUnderNoDetection($file))) { $this->deleteImage($image, $userId); $imagesRemoved++; } @@ -197,47 +209,6 @@ private function staleImagesRemovalForUser(string $userId, int $model) { return $imagesRemoved; } - /** - * For a given image, tries to find home mount. Returns null if it is not found (equivalent of image does not exist). - * - * @param Folder $userFolder User folder to search in - * @param Image $image Image to find home mount for - * - * @return File|null File if image file node is found, null otherwise. - */ - private function getHomeMount(Folder $userFolder, Image $image) { - $allMounts = $userFolder->getById($image->file); - $homeMounts = array_filter($allMounts, function ($m) { - return $m->getStorage()->instanceOfStorage(IHomeStorage::class); - }); - - if (count($homeMounts) === 0) { - return null; - } else { - return $homeMounts[0]; - } - } - - /** - * Checks if this file is located somewhere under .nomedia file and should be therefore ignored. - * TODO: same method is in Watcher.php, find a place for both methods - * - * @param File $file File to search for - * @return bool True if file is located under .nomedia, false otherwise - */ - private function isUnderNoMedia(File $file): bool { - // If we detect .nomedia file anywhere on the path to root folder (id===null), bail out - $parentNode = $file->getParent(); - while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) { - if ($parentNode->nodeExists('.nomedia')) { - return true; - } - $parentNode = $parentNode->getParent(); - } - - return false; - } - private function deleteImage(Image $image, string $userId) { $this->logInfo(sprintf('Removing stale image %d for user %s', $image->id, $userId)); // note that invalidatePersons depends on existence of faces for a given image, diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php new file mode 100644 index 00000000..4d2a875f --- /dev/null +++ b/lib/Service/FileService.php @@ -0,0 +1,232 @@ + + * + * @author Matias De lellis + * + * @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\FaceRecognition\Service; + +use OCA\FaceRecognition\Helper\Requirements; + +use OCP\IConfig; +use OCP\Files\IRootFolder; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\ITempManager; + +use OCP\Files\IHomeStorage; +use OCP\Files\NotFoundException; + +use OCA\Files_Sharing\External\Storage as SharingExternalStorage; + +class FileService { + + const NOMEDIA_FILE = ".nomedia"; + + const FACERECOGNITION_SETTINGS_FILE = ".facerecognition.json"; + + /** @var string|null */ + private $userId; + + /** @var IConfig Config */ + private $config; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var ITempManager */ + private $tempManager; + + public function __construct($userId, + IConfig $config, + IRootFolder $rootFolder, + ITempManager $tempManager) + { + $this->userId = $userId; + $this->config = $config; + $this->rootFolder = $rootFolder; + $this->tempManager = $tempManager; + } + + /** + * TODO: Describe exactly when necessary. + */ + public function setupFS(string $userId) { + \OC_Util::tearDownFS(); + \OC_Util::setupFS($userId); + + $this->userId = $userId; + } + + /** + * @return Node + * @throws NotFoundException + */ + public function getFileById($fileId, $userId = null): Node { + $files = $this->rootFolder->getUserFolder($this->userId ?? $userId)->getById($fileId); + if (count($files) === 0) { + throw new NotFoundException(); + } + + return $files[0]; + } + + /** + * Checks if this file is located somewhere under .nomedia file and should be therefore ignored. + * Or with an .facerecognition.json setting file that disable tha analysis + * + * @param File $file File to search for + * @return bool True if file is located under .nomedia or .facerecognition.json that disabled + * analysis, false otherwise + */ + public function isUnderNoDetection(Node $node): bool { + // If we detect .nomedia file anywhere on the path to root folder (id===null), bail out + $parentNode = $node->getParent(); + while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) { + $allowDetection = $this->allowsChildDetection($parentNode); + if (!$allowDetection) + return true; + $parentNode = $parentNode->getParent(); + } + return false; + } + + /** + * Checks if this folder has .nomedia file an .facerecognition.json setting file that + * disable that analysis. + * + * @param Folder $folder Folder to search for + * @return bool true if folder dont have an .nomedia file or .facerecognition.json that disabled + * analysis, false otherwise + */ + public function allowsChildDetection(Folder $folder): bool { + if ($folder->nodeExists(FileService::NOMEDIA_FILE)) { + return false; + } + if ($folder->nodeExists(FileService::FACERECOGNITION_SETTINGS_FILE)) { + $file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE); + $localPath = $this->getLocalFile($file); + + $settings = json_decode(file_get_contents($localPath), true); + if ($settings === null || !array_key_exists('detection', $settings)) + return true; + + if ($settings['detection'] === 'off') + return false; + } + + return true; + } + + /** + * Returns if the file is inside a shared storage. + */ + public function isSharedFile(Node $node): bool { + return $node->getStorage()->instanceOfStorage(SharingExternalStorage::class); + } + + /** + * Returns if the file is inside HomeStorage. + */ + public function isUserFile(Node $node): bool { + return $node->getStorage()->instanceOfStorage(IHomeStorage::class); + } + + /** + * Returns if the Node is allowed based on preferences. + */ + public function isAllowedNode(Node $node): bool { + if ($this->isUserFile($node)) { + return true; + } else if ($this->isSharedFile($node)) { + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); + return ($handleSharedFiles === 'true'); + } + return false; + } + + /** + * Get a path to either the local file or temporary file + * + * @param File $file + * @param int $maxSize maximum size for temporary files + * @return string + */ + public function getLocalFile(File $file, int $maxSize = null): string { + $useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal(); + if ($useTempFile) { + $absPath = $this->tempManager->getTemporaryFile(); + + $content = $file->fopen('r'); + if ($maxSize !== null) { + $content = stream_get_contents($content, $maxSize); + } + file_put_contents($absPath, $content); + + return $absPath; + } else { + return $file->getStorage()->getLocalFile($file->getInternalPath()); + } + } + + /** + * Return all images from a given folder. + * + * TODO: It is inefficient since it copies the array recursively. + * + * @param Folder $folder Folder to get images from + * @return array List of all images and folders to continue recursive crawling + */ + public function getPicturesFromFolder(Folder $folder, $results = array()) { + $nodes = $folder->getDirectoryListing(); + foreach ($nodes as $node) { + if (!$this->isAllowedNode($node)) { + continue; + } + if ($node instanceof Folder && $this->allowsChildDetection($node)) { + $results = $this->getPicturesFromFolder($node, $results); + } + else if ($node instanceof File) { + if (Requirements::isImageTypeSupported($node->getMimeType())) { + $results[] = $node; + } + } + } + return $results; + } + + /** + * Create a temporary file and return the path + */ + public function getTemporaryFile(string $postFix = ''): string { + return $this->tempManager->getTemporaryFile($postFix); + } + + /** + * Remove any temporary file from the service. + */ + public function clean() { + $this->tempManager->clean(); + } + +} diff --git a/lib/Watcher.php b/lib/Watcher.php index 2a063b86..01500b51 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -28,11 +28,11 @@ use OCP\Files\IHomeStorage; use OCP\Files\Node; use OCP\IConfig; -use OCP\IDBConnection; use OCP\ILogger; use OCP\IUserManager; use OCA\FaceRecognition\FaceManagementService; +use OCA\FaceRecognition\Service\FileService; use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask; use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask; @@ -52,9 +52,6 @@ class Watcher { /** @var ILogger Logger */ private $logger; - /** @var IDBConnection */ - private $connection; - /** @var IUserManager */ private $userManager; @@ -67,6 +64,9 @@ class Watcher { /** @var PersonMapper */ private $personMapper; + /** @var FileService */ + private $fileService; + /** @var FaceManagementService */ private $faceManagementService; @@ -75,29 +75,29 @@ class Watcher { * * @param IConfig $config * @param ILogger $logger - * @param IDBConnection $connection * @param IUserManager $userManager * @param FaceMapper $faceMapper * @param ImageMapper $imageMapper * @param PersonMapper $personMapper + * @param FileService $fileService * @param FaceManagementService $faceManagementService */ public function __construct(IConfig $config, ILogger $logger, - IDBConnection $connection, IUserManager $userManager, FaceMapper $faceMapper, ImageMapper $imageMapper, PersonMapper $personMapper, + FileService $fileService, FaceManagementService $faceManagementService) { - $this->config = $config; - $this->logger = $logger; - $this->connection = $connection; - $this->userManager = $userManager; - $this->faceMapper = $faceMapper; - $this->imageMapper = $imageMapper; - $this->personMapper = $personMapper; + $this->config = $config; + $this->logger = $logger; + $this->userManager = $userManager; + $this->faceMapper = $faceMapper; + $this->imageMapper = $imageMapper; + $this->personMapper = $personMapper; + $this->fileService = $fileService; $this->faceManagementService = $faceManagementService; } @@ -108,10 +108,8 @@ public function __construct(IConfig $config, * @param Node $node */ public function postWrite(Node $node) { - $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); - - // todo: should we also care about this too: instanceOfStorage(ISharedStorage::class); - if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) { + if (!$this->fileService->isAllowedNode($node)) { + // Nextcloud sends the Hooks when create thumbnails for example. return; } @@ -119,7 +117,12 @@ public function postWrite(Node $node) { return; } - $owner = $node->getOwner()->getUid(); + $owner = \OC::$server->getUserSession()->getUser()->getUID(); + if (!$this->userManager->userExists($owner)) { + $this->logger->debug( + "Skipping inserting image " . $node->getName() . " because it seems that user " . $owner . " doesn't exist"); + return; + } $enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); if ($enabled !== 'true') { @@ -127,37 +130,34 @@ public function postWrite(Node $node) { return; } - if ($node->getName() === '.nomedia') { + if ($node->getName() === FileService::NOMEDIA_FILE) { // If user added this file, it means all images in this and all child directories should be removed. // Instead of doing that here, it's better to just add flag that image removal should be done. $this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); return; } - if (!Requirements::isImageTypeSupported($node->getMimeType())) { + if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) { + // This file can enable or disable the analysis, so I have to look for new files and forget others. + $this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); + $this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); return; } - if (!$this->userManager->userExists($owner)) { - $this->logger->debug( - "Skipping inserting image " . $node->getName() . " because it seems that user " . $owner . " doesn't exist"); + if (!Requirements::isImageTypeSupported($node->getMimeType())) { return; } - // If we detect .nomedia file anywhere on the path to root folder (id===null), bail out - $parentNode = $node->getParent(); - while (($parentNode instanceof Folder) && ($parentNode->getId() !== null)) { - if ($parentNode->nodeExists('.nomedia')) { - $this->logger->debug( - "Skipping inserting image " . $node->getName() . " because directory " . $parentNode->getName() . " contains .nomedia file"); - return; - } - - $parentNode = $parentNode->getParent(); + if ($this->fileService->isUnderNoDetection($node)) { + $this->logger->debug( + "Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file"); + return; } $this->logger->debug("Inserting/updating image " . $node->getName() . " for face recognition"); + $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); + $image = new Image(); $image->setUser($owner); $image->setFile($node->getId()); @@ -193,10 +193,8 @@ public function postWrite(Node $node) { * @param Node $node */ public function postDelete(Node $node) { - $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); - - // todo: should we also care about this too: instanceOfStorage(ISharedStorage::class); - if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) { + if (!$this->fileService->isAllowedNode($node)) { + // Nextcloud sends the Hooks when create thumbnails for example. return; } @@ -204,15 +202,14 @@ public function postDelete(Node $node) { return; } - $owner = $node->getOwner()->getUid(); - + $owner = \OC::$server->getUserSession()->getUser()->getUID(); $enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); if ($enabled !== 'true') { $this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); return; } - if ($node->getName() === '.nomedia') { + if ($node->getName() === FileService::NOMEDIA_FILE) { // If user deleted file named .nomedia, that means all images in this and all child directories should be added. // But, instead of doing that here, better option seem to be to just reset flag that image scan is not done. // This will trigger another round of image crawling in AddMissingImagesTask for this user and those images will be added. @@ -220,12 +217,21 @@ public function postDelete(Node $node) { return; } + if ($node->getName() === FileService::FACERECOGNITION_SETTINGS_FILE) { + // This file can enable or disable the analysis, so I have to look for new files and forget others. + $this->config->setUserValue($owner, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); + $this->config->setUserValue($owner, 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); + return; + } + if (!Requirements::isImageTypeSupported($node->getMimeType())) { return; } $this->logger->debug("Deleting image " . $node->getName() . " from face recognition"); + $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); + $image = new Image(); $image->setUser($owner); $image->setFile($node->getId()); diff --git a/tests/Integration/AddMissingImagesTaskTest.php b/tests/Integration/AddMissingImagesTaskTest.php index 43ddf7e9..47651c6d 100644 --- a/tests/Integration/AddMissingImagesTaskTest.php +++ b/tests/Integration/AddMissingImagesTaskTest.php @@ -116,7 +116,8 @@ private function doMissingImageScan($contextUser = null) { $this->config->setUserValue($this->user->getUID(), 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); - $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper, $fileService); $this->assertNotEquals("", $addMissingImagesTask->description()); // Set user for which to do scanning, if any diff --git a/tests/Integration/DisabledUserRemovalTaskTest.php b/tests/Integration/DisabledUserRemovalTaskTest.php index dc89b0d5..6e35eb25 100644 --- a/tests/Integration/DisabledUserRemovalTaskTest.php +++ b/tests/Integration/DisabledUserRemovalTaskTest.php @@ -58,7 +58,8 @@ public function testNoMediaImageRemoval() { // Create these two images in database by calling add missing images task $this->config->setUserValue($this->user->getUID(), 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); - $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper, $fileService); $this->context->user = $this->user; $generator = $addMissingImagesTask->execute($this->context); foreach ($generator as $_) { diff --git a/tests/Integration/ImageProcessingTaskTest.php b/tests/Integration/ImageProcessingTaskTest.php index c3e21800..f6e13332 100644 --- a/tests/Integration/ImageProcessingTaskTest.php +++ b/tests/Integration/ImageProcessingTaskTest.php @@ -146,8 +146,8 @@ private function genericTestImageProcessing($imgData, $expectingError, $expected */ private function doImageProcessing($imgData, $contextUser = null) { $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); - $tempManager = $this->container->query('OCP\ITempManager'); - $imageProcessingTask = new ImageProcessingTask($this->config, $imageMapper, $tempManager); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + $imageProcessingTask = new ImageProcessingTask($this->config, $imageMapper, $fileService); $this->assertNotEquals("", $imageProcessingTask->description()); // Set user for which to do processing, if any @@ -179,7 +179,8 @@ private function doMissingImageScan($contextUser = null) { $this->config->setUserValue($this->user->getUID(), 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); - $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper, $fileService); // Set user for which to do scanning, if any $this->context->user = $contextUser; diff --git a/tests/Integration/StaleImagesRemovalTaskTest.php b/tests/Integration/StaleImagesRemovalTaskTest.php index b12b91f5..e3e77ece 100644 --- a/tests/Integration/StaleImagesRemovalTaskTest.php +++ b/tests/Integration/StaleImagesRemovalTaskTest.php @@ -90,7 +90,8 @@ public function testNoMediaImageRemoval() { // Create these two images in database by calling add missing images task $this->config->setUserValue($this->user->getUID(), 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); - $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + $addMissingImagesTask = new AddMissingImagesTask($this->config, $imageMapper, $fileService); $this->context->user = $this->user; $generator = $addMissingImagesTask->execute($this->context); foreach ($generator as $_) { @@ -140,6 +141,7 @@ private function createStaleImagesRemovalTask() { $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); $faceMapper = $this->container->query('OCA\FaceRecognition\Db\FaceMapper'); $personMapper = $this->container->query('OCA\FaceRecognition\Db\PersonMapper'); - return new StaleImagesRemovalTask($this->config, $imageMapper, $faceMapper, $personMapper); + $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); + return new StaleImagesRemovalTask($this->config, $imageMapper, $faceMapper, $personMapper, $fileService); } } \ No newline at end of file diff --git a/tests/unit/ResizeTest.php b/tests/unit/ResizeTest.php index 3b663723..4dd4a5c8 100644 --- a/tests/unit/ResizeTest.php +++ b/tests/unit/ResizeTest.php @@ -27,7 +27,6 @@ use OCP\IConfig; use OCP\ILogger; -use OCP\ITempManager; use OCP\IUserManager; use OCP\App\IAppManager; @@ -37,6 +36,7 @@ use OCA\FaceRecognition\BackgroundJob\FaceRecognitionLogger; use OCA\FaceRecognition\BackgroundJob\Tasks\ImageProcessingTask; use OCA\FaceRecognition\Db\ImageMapper; +use OCA\FaceRecognition\Service\FileService; use OCA\FaceRecognition\Service\ModelService; use Test\TestCase; @@ -62,8 +62,8 @@ public function setUp() { public function testResize() { $config = $this->createMock(IConfig::class); $imageMapper = $this->createMock(ImageMapper::class); - $tempManager = $this->createMock(ITempManager::class); - $imageProcessingTask = new ImageProcessingTask($config, $imageMapper, $tempManager); + $fileService = $this->createMock(FileService::class); + $imageProcessingTask = new ImageProcessingTask($config, $imageMapper, $fileService); $imageProcessingTask->setContext($this->context); $image = new OCP_Image();