fix(FileListener): Handle edge cases of moving folders between varying stages of ignorance

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Marcel Klehr
2024-10-11 10:56:18 +02:00
parent d34017fcf6
commit cd924c2757
2 changed files with 125 additions and 58 deletions

View File

@ -47,6 +47,7 @@ use Psr\Log\LoggerInterface;
*/ */
class FileListener implements IEventListener { class FileListener implements IEventListener {
private ?bool $movingFromIgnoredTerritory; private ?bool $movingFromIgnoredTerritory;
private ?array $movingDirFromIgnoredTerritory;
/** @var list<string> */ /** @var list<string> */
private array $sourceUserIds; private array $sourceUserIds;
private ?Node $source = null; private ?Node $source = null;
@ -64,6 +65,7 @@ class FileListener implements IEventListener {
private IJobList $jobList, private IJobList $jobList,
) { ) {
$this->movingFromIgnoredTerritory = null; $this->movingFromIgnoredTerritory = null;
$this->movingDirFromIgnoredTerritory = null;
$this->sourceUserIds = []; $this->sourceUserIds = [];
} }
@ -139,16 +141,22 @@ class FileListener implements IEventListener {
} }
} }
if ($event instanceof BeforeNodeRenamedEvent) { if ($event instanceof BeforeNodeRenamedEvent) {
$this->movingFromIgnoredTerritory = null;
$this->movingDirFromIgnoredTerritory = [];
if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) { if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getSource()); $this->resetIgnoreCache($event->getSource());
return; return;
} }
// We try to remember whether the source node is in ignored territory // We try to remember whether the source node is in ignored territory
// because after moving isIgnored doesn't work anymore :( // because after moving isIgnored doesn't work anymore :(
if ($this->isIgnored($event->getSource())) { if ($event->getSource()->getType() !== FileInfo::TYPE_FOLDER) {
$this->movingFromIgnoredTerritory = true; if ($this->isFileIgnored($event->getSource())) {
$this->movingFromIgnoredTerritory = true;
} else {
$this->movingFromIgnoredTerritory = false;
}
} else { } else {
$this->movingFromIgnoredTerritory = false; $this->movingDirFromIgnoredTerritory = $this->getDirIgnores($event->getSource());
} }
$this->sourceUserIds = $this->getUsersWithFileAccess($event->getSource()); $this->sourceUserIds = $this->getUsersWithFileAccess($event->getSource());
$this->source = $event->getSource(); $this->source = $event->getSource();
@ -175,17 +183,39 @@ class FileListener implements IEventListener {
$this->postDelete($event->getTarget()->getParent()); $this->postDelete($event->getTarget()->getParent());
return; return;
} }
if ($event->getTarget()->getType() !== FileInfo::TYPE_FOLDER) {
if ($this->movingFromIgnoredTerritory) { if ($this->movingFromIgnoredTerritory) {
if ($this->isIgnored($event->getTarget())) { if ($this->isFileIgnored($event->getTarget())) {
return;
}
$this->postInsert($event->getTarget());
return;
}
if ($this->isFileIgnored($event->getTarget())) {
$this->postDelete($event->getTarget());
return;
}
} else {
if ($this->movingDirFromIgnoredTerritory !== null && count($this->movingDirFromIgnoredTerritory) !== 0) {
$oldIgnores = $this->movingDirFromIgnoredTerritory;
$newIgnores = $this->getDirIgnores($event->getTarget());
$diff1 = array_diff($newIgnores, $oldIgnores);
$diff2 = array_diff($oldIgnores, $newIgnores);
if (count($diff1) !== 0 || count($diff2) !== 0) {
if (count($diff1) !== 0) {
$this->postDelete($event->getTarget(), true, $diff1);
}
if (count($diff2) !== 0) {
$this->postInsert($event->getTarget(), true, $diff2);
}
}
return;
}
$ignoredMimeTypes = $this->getDirIgnores($event->getTarget());
if (!empty($ignoredMimeTypes)) {
$this->postDelete($event->getTarget(), true, $ignoredMimeTypes);
return; return;
} }
$this->postInsert($event->getTarget());
return;
}
if ($this->isIgnored($event->getTarget())) {
$this->postDelete($event->getTarget());
return;
} }
$this->postRename($this->source ?? $event->getSource(), $event->getTarget()); $this->postRename($this->source ?? $event->getSource(), $event->getTarget());
return; return;
@ -249,7 +279,7 @@ class FileListener implements IEventListener {
} }
} }
public function postDelete(Node $node, bool $recurse = true): void { public function postDelete(Node $node, bool $recurse = true, ?array $mimeTypes = null): void {
if ($node->getType() === FileInfo::TYPE_FOLDER) { if ($node->getType() === FileInfo::TYPE_FOLDER) {
if (!$recurse) { if (!$recurse) {
return; return;
@ -257,7 +287,7 @@ class FileListener implements IEventListener {
try { try {
/** @var Folder $node */ /** @var Folder $node */
foreach ($node->getDirectoryListing() as $child) { foreach ($node->getDirectoryListing() as $child) {
$this->postDelete($child); $this->postDelete($child, true, $mimeTypes);
} }
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
$this->logger->debug($e->getMessage(), ['exception' => $e]); $this->logger->debug($e->getMessage(), ['exception' => $e]);
@ -265,6 +295,10 @@ class FileListener implements IEventListener {
return; return;
} }
if ($mimeTypes !== null && !in_array($node->getMimetype(), $mimeTypes)) {
return;
}
// Try Deleting possibly existing face detections // Try Deleting possibly existing face detections
try { try {
/** /**
@ -294,7 +328,7 @@ class FileListener implements IEventListener {
/** /**
* @throws \OCP\Files\InvalidPathException * @throws \OCP\Files\InvalidPathException
*/ */
public function postInsert(Node $node, bool $recurse = true): void { public function postInsert(Node $node, bool $recurse = true, ?array $mimeTypes = null): void {
if ($node->getType() === FileInfo::TYPE_FOLDER) { if ($node->getType() === FileInfo::TYPE_FOLDER) {
if (!$recurse) { if (!$recurse) {
return; return;
@ -312,6 +346,10 @@ class FileListener implements IEventListener {
return; return;
} }
if ($mimeTypes !== null && !in_array($node->getMimetype(), $mimeTypes)) {
return;
}
$queueFile = new QueueFile(); $queueFile = new QueueFile();
if ($node->getMountPoint()->getNumericStorageId() === null) { if ($node->getMountPoint()->getNumericStorageId() === null) {
return; return;
@ -319,7 +357,7 @@ class FileListener implements IEventListener {
$queueFile->setStorageId($node->getMountPoint()->getNumericStorageId()); $queueFile->setStorageId($node->getMountPoint()->getNumericStorageId());
$queueFile->setRootId($node->getMountPoint()->getStorageRootId()); $queueFile->setRootId($node->getMountPoint()->getStorageRootId());
if ($this->isIgnored($node)) { if ($this->isFileIgnored($node)) {
return; return;
} }
@ -404,7 +442,7 @@ class FileListener implements IEventListener {
* @throws \OCP\Files\InvalidPathException * @throws \OCP\Files\InvalidPathException
* @throws \OCP\Files\NotFoundException * @throws \OCP\Files\NotFoundException
*/ */
public function isIgnored(Node $node) : bool { public function isFileIgnored(Node $node) : bool {
$ignoreMarkers = []; $ignoreMarkers = [];
$mimeType = $node->getMimetype(); $mimeType = $node->getMimetype();
$storageId = $node->getMountPoint()->getNumericStorageId(); $storageId = $node->getMountPoint()->getNumericStorageId();
@ -422,6 +460,7 @@ class FileListener implements IEventListener {
if (in_array($mimeType, Constants::AUDIO_FORMATS)) { if (in_array($mimeType, Constants::AUDIO_FORMATS)) {
$ignoreMarkers = array_merge($ignoreMarkers, Constants::IGNORE_MARKERS_AUDIO); $ignoreMarkers = array_merge($ignoreMarkers, Constants::IGNORE_MARKERS_AUDIO);
} }
if (count($ignoreMarkers) === 0) { if (count($ignoreMarkers) === 0) {
return true; return true;
} }
@ -429,15 +468,43 @@ class FileListener implements IEventListener {
$ignoreMarkers = array_merge($ignoreMarkers, Constants::IGNORE_MARKERS_ALL); $ignoreMarkers = array_merge($ignoreMarkers, Constants::IGNORE_MARKERS_ALL);
$ignoredPaths = $this->ignoreService->getIgnoredDirectories($storageId, $ignoreMarkers); $ignoredPaths = $this->ignoreService->getIgnoredDirectories($storageId, $ignoreMarkers);
$relevantIgnorePaths = array_filter($ignoredPaths, static function (string $ignoredPath) use ($node) {
return stripos($node->getInternalPath(), $ignoredPath ? $ignoredPath . '/' : $ignoredPath) === 0;
});
if (count($relevantIgnorePaths) > 0) { foreach ($ignoredPaths as $ignoredPath) {
return true; if (stripos($node->getInternalPath(), $ignoredPath ? $ignoredPath . '/' : $ignoredPath) === 0) {
return true;
}
}
return false;
}
/**
* @param \OCP\Files\Node $node
* @return array
* @throws Exception
*/
public function getDirIgnores(Node $node) : array {
$storageId = $node->getMountPoint()->getNumericStorageId();
if ($storageId === null) {
return [];
} }
return false; $ignoredMimeTypes = [];
foreach ([
[Constants::IGNORE_MARKERS_IMAGE, Constants::IMAGE_FORMATS],
[Constants::IGNORE_MARKERS_VIDEO, Constants::VIDEO_FORMATS],
[Constants::IGNORE_MARKERS_AUDIO, Constants::AUDIO_FORMATS],
[Constants::IGNORE_MARKERS_ALL, array_merge(Constants::IMAGE_FORMATS, Constants::VIDEO_FORMATS, Constants::AUDIO_FORMATS)],
] as $iteration) {
[$ignoreMarkers, $mimeTypes] = $iteration;
$ignoredPaths = $this->ignoreService->getIgnoredDirectories($storageId, $ignoreMarkers);
foreach ($ignoredPaths as $ignoredPath) {
if (stripos($node->getInternalPath(), $ignoredPath ? $ignoredPath . '/' : $ignoredPath) === 0) {
$ignoredMimeTypes = array_unique(array_merge($ignoredMimeTypes, $mimeTypes));
}
}
}
return $ignoredMimeTypes;
} }
private function resetIgnoreCache(Node $node) : void { private function resetIgnoreCache(Node $node) : void {

View File

@ -184,58 +184,58 @@ class ClassifierTest extends TestCase {
self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving it into ignored territory'); self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving it into ignored territory');
} }
/** /**
* @dataProvider ignoreImageFilesProvider * @dataProvider ignoreImageFilesProvider
* @return void * @return void
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
* @throws \OCP\Files\InvalidPathException * @throws \OCP\Files\InvalidPathException
* @throws \OCP\Files\NotFoundException * @throws \OCP\Files\NotFoundException
* @throws \OCP\Files\NotPermittedException * @throws \OCP\Files\NotPermittedException
*/ */
public function testFileListenerWithFolder(string $ignoreFileName) : void { public function testFileListenerWithFolder(string $ignoreFileName) : void {
$this->config->setAppValueString('imagenet.enabled', 'true'); $this->config->setAppValueString('imagenet.enabled', 'true');
$this->queue->clearQueue(ImagenetClassifier::MODEL_NAME); $this->queue->clearQueue(ImagenetClassifier::MODEL_NAME);
$folder = $this->userFolder->newFolder('/folder/'); $folder = $this->userFolder->newFolder('/folder/');
$this->testFile = $folder->newFile('/alpine.jpg', file_get_contents(__DIR__.'/res/alpine.JPG')); $this->testFile = $folder->newFile('/alpine.jpg', file_get_contents(__DIR__.'/res/alpine.JPG'));
$ignoreFolder = $this->userFolder->newFolder('/test/ignore/'); $ignoreFolder = $this->userFolder->newFolder('/test/ignore/');
$ignoredFolder = $ignoreFolder->newFolder('/folder/'); $ignoredFolder = $ignoreFolder->newFolder('/folder/');
$ignoreFile = $this->userFolder->newFile('/test/' . $ignoreFileName, ''); $ignoreFile = $this->userFolder->newFile('/test/' . $ignoreFileName, '');
$this->ignoredFile = $ignoredFolder->newFile('/alpine-2.jpg', file_get_contents(__DIR__.'/res/alpine.JPG')); $this->ignoredFile = $ignoredFolder->newFile('/alpine-2.jpg', file_get_contents(__DIR__.'/res/alpine.JPG'));
$storageId = $this->testFile->getMountPoint()->getNumericStorageId(); $storageId = $this->testFile->getMountPoint()->getNumericStorageId();
$rootId = $this->testFile->getMountPoint()->getStorageRootId(); $rootId = $this->testFile->getMountPoint()->getStorageRootId();
self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue'); self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue');
$folder->delete(); $folder->delete();
self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue'); self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue');
$ignoreFile->delete(); $ignoreFile->delete();
self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after deleting ignore file'); self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after deleting ignore file');
$ignoreFile = $this->userFolder->newFile('/test/' . $ignoreFileName, ''); $ignoreFile = $this->userFolder->newFile('/test/' . $ignoreFileName, '');
self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after creating ignore file'); self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after creating ignore file');
$ignoredFolder->move($this->userFolder->getPath() . '/folder2'); $ignoredFolder->move($this->userFolder->getPath() . '/folder2');
self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after moving it out of ignored territory'); self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after moving it out of ignored territory');
$ignoreFile->move($this->userFolder->getPath() . '/' . $ignoreFileName); $ignoreFile->move($this->userFolder->getPath() . '/' . $ignoreFileName);
self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving ignore file'); self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving ignore file');
$ignoreFile->move($this->userFolder->getPath() . '/test/' . $ignoreFileName); $ignoreFile->move($this->userFolder->getPath() . '/test/' . $ignoreFileName);
self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after moving ignore file'); self::assertCount(1, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'one element should have been added to imagenet queue after moving ignore file');
$ignoredFolder->move($this->userFolder->getPath() . '/test/ignore/folder'); $ignoredFolder->move($this->userFolder->getPath() . '/test/ignore/folder');
self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving it into ignored territory'); self::assertCount(0, $this->queue->getFromQueue(ImagenetClassifier::MODEL_NAME, $storageId, $rootId, 100), 'entry should have been removed from imagenet queue after moving it into ignored territory');
} }
/** /**
* @dataProvider ignoreImageFilesProvider * @dataProvider ignoreImageFilesProvider