From 15e038396b304ad86a1b90e48c2f7a460e4f3920 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Fri, 6 Dec 2019 14:39:15 -0300 Subject: [PATCH 01/13] Don't ignore shared folders --- lib/BackgroundJob/Tasks/AddMissingImagesTask.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php index c5b83f02..86f75229 100644 --- a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php +++ b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php @@ -28,7 +28,6 @@ use OCP\Files\File; use OCP\Files\Folder; -use OCP\Files\IHomeStorage; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext; @@ -124,7 +123,7 @@ private function addMissingImagesForUser(string $userId, int $model): int { \OC_Util::setupFS($userId); $userFolder = $this->context->rootFolder->getUserFolder($userId); - return $this->parseUserFolder($model, $userFolder); + return $this->parseUserFolder($userId, $model, $userFolder); } /** @@ -134,14 +133,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); 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 @@ -164,13 +163,7 @@ private function parseUserFolder(int $model, Folder $folder): int { * @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); From 3f9681c639a1e7feb7696f01da1965060f943369 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Fri, 6 Dec 2019 14:42:46 -0300 Subject: [PATCH 02/13] Read remote files on temp file --- .../Tasks/ImageProcessingTask.php | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/BackgroundJob/Tasks/ImageProcessingTask.php b/lib/BackgroundJob/Tasks/ImageProcessingTask.php index 150941ed..b39d8d01 100644 --- a/lib/BackgroundJob/Tasks/ImageProcessingTask.php +++ b/lib/BackgroundJob/Tasks/ImageProcessingTask.php @@ -212,7 +212,8 @@ private function findFaces(\CnnFaceDetection $cfd, string $dataDir, Image $image } // todo: this concat is wrong with shared files. - $imagePath = $dataDir . $file[0]->getPath(); + $imagePath = $this->getLocalFile($file[0]); + $this->logInfo('Processing image ' . $imagePath); $imageProcessingContext = $this->prepareImage($imagePath); if ($imageProcessingContext->getSkipDetection() === true) { @@ -383,4 +384,30 @@ private function calculateMaxImageArea(): int { $maxImageArea = intval((0.75 * $allowedMemory) / 1024); // in pixels^2 return $maxImageArea; } + + /** + * Get a path to either the local file or temporary file + * + * @param File $file + * @param int $maxSize maximum size for temporary files + * @return string + */ + private function getLocalFile(File $file, int $maxSize = null): string { + $useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal(); + if ($useTempFile) { + $absPath = \OC::$server->getTempManager()->getTemporaryFile(); + + $content = $file->fopen('r'); + if ($maxSize) { + $content = stream_get_contents($content, $maxSize); + } + + file_put_contents($absPath, $content); + //$this->tmpFiles[] = $absPath; + return $absPath; + } else { + return $file->getStorage()->getLocalFile($file->getInternalPath()); + } + } + } \ No newline at end of file From fb0efff9ef69b75c9dc38207aaee968c6fbe77c6 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Fri, 6 Dec 2019 21:18:29 -0300 Subject: [PATCH 03/13] Clean unused code and also force remove invalid entries detected here --- lib/BackgroundJob/Tasks/ImageProcessingTask.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/BackgroundJob/Tasks/ImageProcessingTask.php b/lib/BackgroundJob/Tasks/ImageProcessingTask.php index b39d8d01..6fad4d93 100644 --- a/lib/BackgroundJob/Tasks/ImageProcessingTask.php +++ b/lib/BackgroundJob/Tasks/ImageProcessingTask.php @@ -148,7 +148,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()); @@ -164,7 +163,7 @@ public function execute(FaceRecognitionContext $context) { $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); } @@ -197,21 +196,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); + 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($this->userId, '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 = $this->getLocalFile($file[0]); $this->logInfo('Processing image ' . $imagePath); @@ -395,15 +395,14 @@ private function calculateMaxImageArea(): int { private function getLocalFile(File $file, int $maxSize = null): string { $useTempFile = $file->isEncrypted() || !$file->getStorage()->isLocal(); if ($useTempFile) { - $absPath = \OC::$server->getTempManager()->getTemporaryFile(); + $absPath = $this->tempManager->getTemporaryFile(); $content = $file->fopen('r'); if ($maxSize) { $content = stream_get_contents($content, $maxSize); } - file_put_contents($absPath, $content); - //$this->tmpFiles[] = $absPath; + return $absPath; } else { return $file->getStorage()->getLocalFile($file->getInternalPath()); From 606554f23c47905fe3240e6e050b30a2dac352ec Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Sat, 7 Dec 2019 18:36:05 -0300 Subject: [PATCH 04/13] Misc fixes.. --- lib/BackgroundJob/Tasks/ImageProcessingTask.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/BackgroundJob/Tasks/ImageProcessingTask.php b/lib/BackgroundJob/Tasks/ImageProcessingTask.php index 6fad4d93..8f33ca02 100644 --- a/lib/BackgroundJob/Tasks/ImageProcessingTask.php +++ b/lib/BackgroundJob/Tasks/ImageProcessingTask.php @@ -159,11 +159,11 @@ public function execute(FaceRecognitionContext $context) { foreach($images as $image) { yield; - $imageProcessingContext = null; $startMillis = round(microtime(true) * 1000); try { $imageProcessingContext = $this->findFaces($cfd, $image); + if (($imageProcessingContext !== null) && ($imageProcessingContext->getSkipDetection() === false)) { $this->populateDescriptors($fld, $fr, $imageProcessingContext); } @@ -207,7 +207,7 @@ private function findFaces(\CnnFaceDetection $cfd, Image $image) { 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($this->userId, 'facerecognition', StaleImagesRemovalTask::STALE_IMAGES_REMOVAL_NEEDED_KEY, 'true'); + $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; } @@ -271,7 +271,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 @@ -398,7 +398,7 @@ private function getLocalFile(File $file, int $maxSize = null): string { $absPath = $this->tempManager->getTemporaryFile(); $content = $file->fopen('r'); - if ($maxSize) { + if ($maxSize !== null) { $content = stream_get_contents($content, $maxSize); } file_put_contents($absPath, $content); From 2addb25fbb0f86f9089c950a4835be9fd697c773 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Sun, 8 Dec 2019 11:25:50 -0300 Subject: [PATCH 05/13] Implement a FileService where to handle common code and simplify it. --- .../Tasks/AddMissingImagesTask.php | 22 ++- .../Tasks/ImageProcessingTask.php | 56 +++---- .../Tasks/StaleImagesRemovalTask.php | 82 ++++------ lib/Service/FileService.php | 151 ++++++++++++++++++ lib/Watcher.php | 69 ++++---- 5 files changed, 254 insertions(+), 126 deletions(-) create mode 100644 lib/Service/FileService.php diff --git a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php index 86f75229..0610e42e 100644 --- a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php +++ b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php @@ -35,6 +35,7 @@ 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. @@ -50,14 +51,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; } /** @@ -119,8 +127,7 @@ 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($userId, $model, $userFolder); @@ -163,8 +170,15 @@ private function parseUserFolder(string $userId, int $model, Folder $folder): in * @return array List of all images and folders to continue recursive crawling */ private function getPicturesFromFolder(Folder $folder, $results = array()) { + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); + $nodes = $folder->getDirectoryListing(); foreach ($nodes as $node) { + if (!$this->fileService->isUserFile($node) && + ($this->fileService->isSharedFile($node) && $handleSharedFiles !== 'true')) { + $this->logDebug('Ignore ' . $node->getPath() . ' since is shared and is disabled'); + continue; + } if ($node instanceof Folder and !$node->nodeExists('.nomedia')) { $results = $this->getPicturesFromFolder($node, $results); } else if ($node instanceof File) { diff --git a/lib/BackgroundJob/Tasks/ImageProcessingTask.php b/lib/BackgroundJob/Tasks/ImageProcessingTask.php index 8f33ca02..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; } @@ -183,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(); } } @@ -201,9 +207,7 @@ public function execute(FaceRecognitionContext $context) { */ 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. @@ -212,7 +216,7 @@ private function findFaces(\CnnFaceDetection $cfd, Image $image) { return null; } - $imagePath = $this->getLocalFile($file[0]); + $imagePath = $this->fileService->getLocalFile($file); $this->logInfo('Processing image ' . $imagePath); $imageProcessingContext = $this->prepareImage($imagePath); @@ -249,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"); } @@ -262,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); } @@ -385,28 +391,4 @@ private function calculateMaxImageArea(): int { return $maxImageArea; } - /** - * Get a path to either the local file or temporary file - * - * @param File $file - * @param int $maxSize maximum size for temporary files - * @return string - */ - private 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()); - } - } - } \ No newline at end of file diff --git a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php index 3a08a039..20b68220 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); @@ -164,18 +174,23 @@ private function staleImagesRemovalForUser(string $userId, int $model) { count($allImages), $userId)); yield; + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); + // 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->isUnderNoMedia($file)) || + ($this->fileService->isSharedFile($file) && $handleSharedFiles !== 'true')) { $this->deleteImage($image, $userId); $imagesRemoved++; } @@ -197,47 +212,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..ac76b3f4 --- /dev/null +++ b/lib/Service/FileService.php @@ -0,0 +1,151 @@ + + * + * @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 OCP\Files\IRootFolder; +use OCP\Files\File; +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 { + + /** @var string|null */ + private $userId; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var ITempManager */ + private $tempManager; + + public function __construct($userId, + IRootFolder $rootFolder, + ITempManager $tempManager) + { + $this->userId = $userId; + $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. + * + * @param File $file File to search for + * @return bool True if file is located under .nomedia, false otherwise + */ + public function isUnderNoMedia(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)) { + if ($parentNode->nodeExists('.nomedia')) { + return true; + } + $parentNode = $parentNode->getParent(); + } + return false; + } + + /** + * 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); + } + + /** + * 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()); + } + } + + /** + * 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..42deec38 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -28,11 +28,12 @@ use OCP\Files\IHomeStorage; use OCP\Files\Node; use OCP\IConfig; -use OCP\IDBConnection; use OCP\ILogger; use OCP\IUserManager; +use OCP\User; use OCA\FaceRecognition\FaceManagementService; +use OCA\FaceRecognition\Service\FileService; use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask; use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask; @@ -52,9 +53,6 @@ class Watcher { /** @var ILogger Logger */ private $logger; - /** @var IDBConnection */ - private $connection; - /** @var IUserManager */ private $userManager; @@ -67,6 +65,9 @@ class Watcher { /** @var PersonMapper */ private $personMapper; + /** @var FileService */ + private $fileService; + /** @var FaceManagementService */ private $faceManagementService; @@ -75,29 +76,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; } @@ -109,9 +110,17 @@ public function __construct(IConfig $config, */ public function postWrite(Node $node) { $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - // todo: should we also care about this too: instanceOfStorage(ISharedStorage::class); - if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) { + if ($this->fileService->isUserFile($node)) { + $owner = $node->getOwner()->getUid(); + } + else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { + // If we are going to analyze the shared files, we must 'appropriate' it. + $owner = User::getUser(); + } + else { + // Nextcloud also sends the Hooks when create thumbnails for example. return; } @@ -119,8 +128,6 @@ public function postWrite(Node $node) { return; } - $owner = $node->getOwner()->getUid(); - $enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); if ($enabled !== 'true') { $this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); @@ -144,16 +151,10 @@ public function postWrite(Node $node) { 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->isUnderNoMedia($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"); @@ -194,9 +195,17 @@ public function postWrite(Node $node) { */ public function postDelete(Node $node) { $model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID)); + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - // todo: should we also care about this too: instanceOfStorage(ISharedStorage::class); - if ($node->getStorage()->instanceOfStorage(IHomeStorage::class) === false) { + if ($this->fileService->isUserFile($node)) { + $owner = $node->getOwner()->getUid(); + } + else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { + // If we are going to analyze the shared files, we must 'appropriate' it. + $owner = User::getUser(); + } + else { + // Nextcloud also sends the Hooks when create thumbnails for example. return; } @@ -204,8 +213,6 @@ public function postDelete(Node $node) { return; } - $owner = $node->getOwner()->getUid(); - $enabled = $this->config->getUserValue($owner, 'facerecognition', 'enabled', 'false'); if ($enabled !== 'true') { $this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); From 6540607cb6f67e01693abcd0c15f11e1fefc0aa9 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Thu, 12 Dec 2019 11:46:34 -0300 Subject: [PATCH 06/13] Add a custom way to disable analysis in folders. Issue #171 This is done as described in issue 'Per-directory settings support #89' But it's just a small part of it. Then you have to add a .facerecognition.json file with '{detection = off}' in the folder you want to ignore. It's complicated, but then we'll add a side panel to do it easily. Note that the .nomedia file will still be considered. If there is a nomedia file, these folders will be ignored directly from the analysis. --- .../Tasks/StaleImagesRemovalTask.php | 2 +- lib/Service/FileService.php | 15 +++++++++++++-- lib/Watcher.php | 16 +++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php index 20b68220..1aaca7f8 100644 --- a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php +++ b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php @@ -189,7 +189,7 @@ private function staleImagesRemovalForUser(string $userId, int $model) { // Delete image doesn't exist anymore in filesystem or it is under .nomedia if (($file === null) || - ($this->fileService->isUnderNoMedia($file)) || + ($this->fileService->isUnderNoDetection($file)) || ($this->fileService->isSharedFile($file) && $handleSharedFiles !== 'true')) { $this->deleteImage($image, $userId); $imagesRemoved++; diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index ac76b3f4..7a15459c 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -80,17 +80,28 @@ public function getFileById($fileId, $userId = null): Node { /** * 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, false otherwise + * @return bool True if file is located under .nomedia or .facerecognition.json that disabled + * analysis, false otherwise */ - public function isUnderNoMedia(Node $node): bool { + 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)) { if ($parentNode->nodeExists('.nomedia')) { return true; } + if ($parentNode->nodeExists('.facerecognition.json')) { + $file = $this->folder->get('.facerecognition.json'); + $localPath = $this->getLocalFile($file); + + $settings = json_decode(file_get_contents($localPath)); + + if ($settings['detection'] === 'off') + return true; + } $parentNode = $parentNode->getParent(); } return false; diff --git a/lib/Watcher.php b/lib/Watcher.php index 42deec38..6d519215 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -141,6 +141,13 @@ public function postWrite(Node $node) { return; } + if ($node->getName() === '.facerecognition.json') { + // 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; } @@ -151,7 +158,7 @@ public function postWrite(Node $node) { return; } - if ($this->fileService->isUnderNoMedia($node)) { + if ($this->fileService->isUnderNoDetection($node)) { $this->logger->debug( "Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file"); return; @@ -227,6 +234,13 @@ public function postDelete(Node $node) { return; } + if ($node->getName() === '.facerecognition.json') { + // 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; } From ffc80d333af9e950ad22f50d1d3a6e7e1cfc3eb6 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Fri, 13 Dec 2019 12:32:53 -0300 Subject: [PATCH 07/13] Use constants to files names and valitate json before use it --- .../Tasks/AddMissingImagesTask.php | 2 +- lib/Service/FileService.php | 45 ++++++++++++++----- lib/Watcher.php | 8 ++-- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php index 0610e42e..38a0e619 100644 --- a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php +++ b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php @@ -179,7 +179,7 @@ private function getPicturesFromFolder(Folder $folder, $results = array()) { $this->logDebug('Ignore ' . $node->getPath() . ' since is shared and is disabled'); continue; } - if ($node instanceof Folder and !$node->nodeExists('.nomedia')) { + if ($node instanceof Folder and $this->fileService->allowsChildDetection($node)) { $results = $this->getPicturesFromFolder($node, $results); } else if ($node instanceof File) { if (Requirements::isImageTypeSupported($node->getMimeType())) { diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 7a15459c..f983b7c1 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -27,6 +27,7 @@ use OCP\Files\IRootFolder; use OCP\Files\File; +use OCP\Files\Folder; use OCP\Files\Node; use OCP\ITempManager; @@ -37,6 +38,10 @@ class FileService { + const NOMEDIA_FILE = ".nomedia"; + + const FACERECOGNITION_SETTINGS_FILE = ".facerecognition.json"; + /** @var string|null */ private $userId; @@ -90,23 +95,41 @@ 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)) { - if ($parentNode->nodeExists('.nomedia')) { + $allowDetection = $this->allowsChildDetection($parentNode); + if (!$allowDetection) return true; - } - if ($parentNode->nodeExists('.facerecognition.json')) { - $file = $this->folder->get('.facerecognition.json'); - $localPath = $this->getLocalFile($file); - - $settings = json_decode(file_get_contents($localPath)); - - if ($settings['detection'] === 'off') - 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)); + 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. */ diff --git a/lib/Watcher.php b/lib/Watcher.php index 6d519215..95bf4adf 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -134,14 +134,14 @@ 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 ($node->getName() === '.facerecognition.json') { + 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'); @@ -226,7 +226,7 @@ public function postDelete(Node $node) { 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. @@ -234,7 +234,7 @@ public function postDelete(Node $node) { return; } - if ($node->getName() === '.facerecognition.json') { + 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'); From 075dcdbc47c480bfc37b313e4f6bd84a57f3498b Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Mon, 16 Dec 2019 19:40:13 -0300 Subject: [PATCH 08/13] Move getPicturesFromFolder to fileService and fix rule to skip shared files --- .../Tasks/AddMissingImagesTask.php | 32 +---------------- lib/Service/FileService.php | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php index 38a0e619..4710c46e 100644 --- a/lib/BackgroundJob/Tasks/AddMissingImagesTask.php +++ b/lib/BackgroundJob/Tasks/AddMissingImagesTask.php @@ -33,7 +33,6 @@ 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; @@ -142,7 +141,7 @@ private function addMissingImagesForUser(string $userId, int $model): 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()); @@ -161,33 +160,4 @@ private function parseUserFolder(string $userId, int $model, Folder $folder): in 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()) { - $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - - $nodes = $folder->getDirectoryListing(); - foreach ($nodes as $node) { - if (!$this->fileService->isUserFile($node) && - ($this->fileService->isSharedFile($node) && $handleSharedFiles !== 'true')) { - $this->logDebug('Ignore ' . $node->getPath() . ' since is shared and is disabled'); - continue; - } - if ($node instanceof Folder and $this->fileService->allowsChildDetection($node)) { - $results = $this->getPicturesFromFolder($node, $results); - } else if ($node instanceof File) { - if (Requirements::isImageTypeSupported($node->getMimeType())) { - $results[] = $node; - } - } - } - - return $results; - } } diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index f983b7c1..557b4686 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -25,6 +25,9 @@ namespace OCA\FaceRecognition\Service; +use OCA\FaceRecognition\Helper\Requirements; + +use OCP\IConfig; use OCP\Files\IRootFolder; use OCP\Files\File; use OCP\Files\Folder; @@ -45,6 +48,9 @@ class FileService { /** @var string|null */ private $userId; + /** @var IConfig Config */ + private $config; + /** @var IRootFolder */ private $rootFolder; @@ -52,10 +58,12 @@ class FileService { private $tempManager; public function __construct($userId, + IConfig $config, IRootFolder $rootFolder, ITempManager $tempManager) { $this->userId = $userId; + $this->config = $config; $this->rootFolder = $rootFolder; $this->tempManager = $tempManager; } @@ -168,6 +176,33 @@ public function getLocalFile(File $file, int $maxSize = null): string { } } + /** + * 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()) { + $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); + $nodes = $folder->getDirectoryListing(); + foreach ($nodes as $node) { + if (!$this->isUserFile($node) || ($this->isSharedFile($node) && $handleSharedFiles !== 'true')) { + 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 */ From a5ac5dbaf50f400bdfe5f185f95aa2767477015b Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Mon, 16 Dec 2019 20:06:20 -0300 Subject: [PATCH 09/13] Fix json_decode to use arrays --- lib/Service/FileService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 557b4686..9a466b3b 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -127,7 +127,7 @@ public function allowsChildDetection(Folder $folder): bool { $file = $folder->get(FileService::FACERECOGNITION_SETTINGS_FILE); $localPath = $this->getLocalFile($file); - $settings = json_decode(file_get_contents($localPath)); + $settings = json_decode(file_get_contents($localPath), true); if ($settings === null || !array_key_exists('detection', $settings)) return true; From e96b10dc6e4aa67402965fdf879a751e2007461c Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Mon, 16 Dec 2019 20:53:52 -0300 Subject: [PATCH 10/13] Add yet another function to validate if the file must be handled --- .../Tasks/StaleImagesRemovalTask.php | 7 ++----- lib/Service/FileService.php | 16 ++++++++++++++-- lib/Watcher.php | 5 ++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php index 1aaca7f8..ac263073 100644 --- a/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php +++ b/lib/BackgroundJob/Tasks/StaleImagesRemovalTask.php @@ -174,8 +174,6 @@ private function staleImagesRemovalForUser(string $userId, int $model) { count($allImages), $userId)); yield; - $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - // Now iterate and check remaining images $processed = 0; $imagesRemoved = 0; @@ -188,9 +186,8 @@ private function staleImagesRemovalForUser(string $userId, int $model) { } // Delete image doesn't exist anymore in filesystem or it is under .nomedia - if (($file === null) || - ($this->fileService->isUnderNoDetection($file)) || - ($this->fileService->isSharedFile($file) && $handleSharedFiles !== 'true')) { + if (($file === null) || (!$this->fileService->isAllowedNode($file)) || + ($this->fileService->isUnderNoDetection($file))) { $this->deleteImage($image, $userId); $imagesRemoved++; } diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 9a466b3b..4d2a875f 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -152,6 +152,19 @@ 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 * @@ -185,10 +198,9 @@ public function getLocalFile(File $file, int $maxSize = null): string { * @return array List of all images and folders to continue recursive crawling */ public function getPicturesFromFolder(Folder $folder, $results = array()) { - $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); $nodes = $folder->getDirectoryListing(); foreach ($nodes as $node) { - if (!$this->isUserFile($node) || ($this->isSharedFile($node) && $handleSharedFiles !== 'true')) { + if (!$this->isAllowedNode($node)) { continue; } if ($node instanceof Folder && $this->allowsChildDetection($node)) { diff --git a/lib/Watcher.php b/lib/Watcher.php index 95bf4adf..075c4816 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -30,7 +30,6 @@ use OCP\IConfig; use OCP\ILogger; use OCP\IUserManager; -use OCP\User; use OCA\FaceRecognition\FaceManagementService; use OCA\FaceRecognition\Service\FileService; @@ -117,7 +116,7 @@ public function postWrite(Node $node) { } else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { // If we are going to analyze the shared files, we must 'appropriate' it. - $owner = User::getUser(); + $owner = \OC::$server->getUserSession()->getUser()->getUID(); } else { // Nextcloud also sends the Hooks when create thumbnails for example. @@ -209,7 +208,7 @@ public function postDelete(Node $node) { } else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { // If we are going to analyze the shared files, we must 'appropriate' it. - $owner = User::getUser(); + $owner = \OC::$server->getUserSession()->getUser()->getUID(); } else { // Nextcloud also sends the Hooks when create thumbnails for example. From 711936e67813245a2ea380e07721a4e3d99cef29 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Mon, 16 Dec 2019 22:09:31 -0300 Subject: [PATCH 11/13] Fix Tests --- tests/Integration/AddMissingImagesTaskTest.php | 3 ++- tests/Integration/DisabledUserRemovalTaskTest.php | 3 ++- tests/Integration/ImageProcessingTaskTest.php | 4 ++-- tests/Integration/StaleImagesRemovalTaskTest.php | 3 ++- tests/unit/ResizeTest.php | 6 +++--- 5 files changed, 11 insertions(+), 8 deletions(-) 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..0d4dad96 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 diff --git a/tests/Integration/StaleImagesRemovalTaskTest.php b/tests/Integration/StaleImagesRemovalTaskTest.php index b12b91f5..b0902169 100644 --- a/tests/Integration/StaleImagesRemovalTaskTest.php +++ b/tests/Integration/StaleImagesRemovalTaskTest.php @@ -140,6 +140,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(); From cbb9c9b164db8a510944c13955d93f93b476f814 Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Mon, 16 Dec 2019 22:23:59 -0300 Subject: [PATCH 12/13] Fix Tests --- tests/Integration/ImageProcessingTaskTest.php | 3 ++- tests/Integration/StaleImagesRemovalTaskTest.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Integration/ImageProcessingTaskTest.php b/tests/Integration/ImageProcessingTaskTest.php index 0d4dad96..f6e13332 100644 --- a/tests/Integration/ImageProcessingTaskTest.php +++ b/tests/Integration/ImageProcessingTaskTest.php @@ -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 b0902169..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 $_) { From a42f2e46abf32fbbfa25f5e2e4bd9819cbb2c62b Mon Sep 17 00:00:00 2001 From: Matias De lellis Date: Tue, 17 Dec 2019 08:04:02 -0300 Subject: [PATCH 13/13] Simplifies hooks --- lib/Watcher.php | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/lib/Watcher.php b/lib/Watcher.php index 075c4816..01500b51 100644 --- a/lib/Watcher.php +++ b/lib/Watcher.php @@ -108,18 +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)); - $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - - if ($this->fileService->isUserFile($node)) { - $owner = $node->getOwner()->getUid(); - } - else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { - // If we are going to analyze the shared files, we must 'appropriate' it. - $owner = \OC::$server->getUserSession()->getUser()->getUID(); - } - else { - // Nextcloud also sends the Hooks when create thumbnails for example. + if (!$this->fileService->isAllowedNode($node)) { + // Nextcloud sends the Hooks when create thumbnails for example. return; } @@ -127,6 +117,13 @@ public function postWrite(Node $node) { return; } + $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') { $this->logger->debug('The user ' . $owner . ' not have the analysis enabled. Skipping'); @@ -151,12 +148,6 @@ public function postWrite(Node $node) { return; } - if (!$this->userManager->userExists($owner)) { - $this->logger->debug( - "Skipping inserting image " . $node->getName() . " because it seems that user " . $owner . " doesn't exist"); - return; - } - if ($this->fileService->isUnderNoDetection($node)) { $this->logger->debug( "Skipping inserting image " . $node->getName() . " because is inside an folder that contains a .nomedia file"); @@ -165,6 +156,8 @@ public function postWrite(Node $node) { $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()); @@ -200,18 +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)); - $handleSharedFiles = $this->config->getAppValue('facerecognition', 'handle-shared-files', 'false'); - - if ($this->fileService->isUserFile($node)) { - $owner = $node->getOwner()->getUid(); - } - else if ($handleSharedFiles === 'true' && $this->fileService->isSharedFile($node)) { - // If we are going to analyze the shared files, we must 'appropriate' it. - $owner = \OC::$server->getUserSession()->getUser()->getUID(); - } - else { - // Nextcloud also sends the Hooks when create thumbnails for example. + if (!$this->fileService->isAllowedNode($node)) { + // Nextcloud sends the Hooks when create thumbnails for example. return; } @@ -219,6 +202,7 @@ public function postDelete(Node $node) { return; } + $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'); @@ -246,6 +230,8 @@ public function postDelete(Node $node) { $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());