Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(FileListener): Listen to file movements across share boundaries #1126

Merged
merged 7 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/lint-php-cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install dependencies
run: composer i
run: |
composer i
composer i
marcelklehr marked this conversation as resolved.
Show resolved Hide resolved

- name: Lint
run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
297 changes: 176 additions & 121 deletions lib/Hooks/FileListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Cache\CacheEntryInsertedEvent;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
use OCP\Files\Events\Node\NodeCreatedEvent;
Expand All @@ -39,7 +38,7 @@
use Psr\Log\LoggerInterface;

/**
* @psalm-implements IEventListener<Event>
* @template-implements IEventListener<Event>
*/
class FileListener implements IEventListener {
private FaceDetectionMapper $faceDetectionMapper;
Expand All @@ -49,170 +48,189 @@ class FileListener implements IEventListener {
private ?bool $movingFromIgnoredTerritory;
private StorageService $storageService;
private IManager $shareManager;
private IUserMountCache $userMountCache;
private IRootFolder $rootFolder;
private array $nodeCache;
/** @var list<string> */
private array $sourceUserIds;

public function __construct(FaceDetectionMapper $faceDetectionMapper, LoggerInterface $logger, QueueService $queue, IgnoreService $ignoreService, StorageService $storageService, IManager $shareManager, IUserMountCache $userMountCache, IRootFolder $rootFolder) {
public function __construct(FaceDetectionMapper $faceDetectionMapper, LoggerInterface $logger, QueueService $queue, IgnoreService $ignoreService, StorageService $storageService, IManager $shareManager, IRootFolder $rootFolder) {
$this->faceDetectionMapper = $faceDetectionMapper;
$this->logger = $logger;
$this->queue = $queue;
$this->ignoreService = $ignoreService;
$this->movingFromIgnoredTerritory = null;
$this->storageService = $storageService;
$this->shareManager = $shareManager;
$this->userMountCache = $userMountCache;
$this->rootFolder = $rootFolder;
$this->nodeCache = [];
$this->sourceUserIds = [];
}

public function handle(Event $event): void {
if ($event instanceof ShareCreatedEvent) {
$share = $event->getShare();
$ownerId = $share->getShareOwner();
$node = $share->getNode();
try {
if ($event instanceof ShareCreatedEvent) {
$share = $event->getShare();
$ownerId = $share->getShareOwner();
$node = $share->getNode();

$accessList = $this->shareManager->getAccessList($node, true, true);
$userIds = array_map(fn (int|string $id) => (string)$id, array_keys($accessList['users']));
/** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $accessList */
$accessList = $this->shareManager->getAccessList($node, true, true);
$userIds = array_map(fn ($id) => strval($id), array_keys($accessList['users']));

if ($node->getType() === FileInfo::TYPE_FOLDER) {
$mount = $node->getMountPoint();
if ($mount->getNumericStorageId() === null) {
return;
}
$files = $this->storageService->getFilesInMount($mount->getNumericStorageId(), $node->getId(), [ClusteringFaceClassifier::MODEL_NAME], 0, 0);
foreach ($files as $fileInfo) {
if ($node->getType() === FileInfo::TYPE_FOLDER) {
$mount = $node->getMountPoint();
if ($mount->getNumericStorageId() === null) {
return;
}
$files = $this->storageService->getFilesInMount($mount->getNumericStorageId(), $node->getId(), [ClusteringFaceClassifier::MODEL_NAME], 0, 0);
foreach ($files as $fileInfo) {
foreach ($userIds as $userId) {
if ($userId === $ownerId) {
continue;
}
if (count($this->faceDetectionMapper->findByFileIdAndUser($node->getId(), $userId)) > 0) {
continue;
}
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($fileInfo['fileid'], $ownerId, $userId);
}
}
} else {
foreach ($userIds as $userId) {
if ($userId === $ownerId) {
continue;
}
if (count($this->faceDetectionMapper->findByFileIdAndUser($node->getId(), $userId)) > 0) {
continue;
}
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($fileInfo['fileid'], $ownerId, $userId);
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($node->getId(), $ownerId, $userId);
}
}
} else {
foreach ($userIds as $userId) {
if ($userId === $ownerId) {
continue;
}
if ($event instanceof ShareDeletedEvent) {
$share = $event->getShare();
$node = $share->getNode();

/** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $accessList */
$accessList = $this->shareManager->getAccessList($node, true, true);
$userIds = array_keys($accessList['users']);

if ($node->getType() === FileInfo::TYPE_FOLDER) {
$mount = $node->getMountPoint();
if ($mount->getNumericStorageId() === null) {
return;
}
if (count($this->faceDetectionMapper->findByFileIdAndUser($node->getId(), $userId)) > 0) {
continue;
$files = $this->storageService->getFilesInMount($mount->getNumericStorageId(), $node->getId(), [ClusteringFaceClassifier::MODEL_NAME], 0, 0);
foreach ($files as $fileInfo) {
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($fileInfo['fileid'], $userIds);
}
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($node->getId(), $ownerId, $userId);
} else {
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($node->getId(), $userIds);
}
}
}
if ($event instanceof ShareDeletedEvent) {
$share = $event->getShare();
$node = $share->getNode();

$accessList = $this->shareManager->getAccessList($node, true, true);
/**
* @var string[] $userIds
*/
$userIds = array_keys($accessList['users']);

if ($node->getType() === FileInfo::TYPE_FOLDER) {
$mount = $node->getMountPoint();
if ($mount->getNumericStorageId() === null) {
if ($event instanceof BeforeNodeRenamedEvent) {
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());
return;
}
$files = $this->storageService->getFilesInMount($mount->getNumericStorageId(), $node->getId(), [ClusteringFaceClassifier::MODEL_NAME], 0, 0);
foreach ($files as $fileInfo) {
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($fileInfo['fileid'], $userIds);
// We try to remember whether the source node is in ignored territory
// because after moving isIgnored doesn't work anymore :(
if ($this->isIgnored($event->getSource())) {
$this->movingFromIgnoredTerritory = true;
} else {
$this->movingFromIgnoredTerritory = false;
}
} else {
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($node->getId(), $userIds);
}
}
if ($event instanceof BeforeNodeRenamedEvent) {
if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true) &&
in_array($event->getTarget()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getSource());
return;
}
// We try to remember whether the source node is in ignored territory
// because after moving isIgnored doesn't work anymore :(
if ($this->isIgnored($event->getSource())) {
$this->movingFromIgnoredTerritory = true;
} else {
$this->movingFromIgnoredTerritory = false;
}
return;
}
if ($event instanceof NodeRenamedEvent) {
if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true) &&
in_array($event->getTarget()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getTarget());
$this->postInsert($event->getSource()->getParent());
$this->postDelete($event->getTarget()->getParent());
/** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $sourceAccessList */
$sourceAccessList = $this->shareManager->getAccessList($event->getSource(), true, true);
$this->sourceUserIds = array_map(fn ($id) => strval($id), array_keys($sourceAccessList['users']));
return;
}
if ($this->movingFromIgnoredTerritory) {
if ($event instanceof NodeRenamedEvent) {
if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true) &&
in_array($event->getTarget()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getTarget());
$this->postInsert($event->getSource()->getParent());
$this->postDelete($event->getTarget()->getParent());
return;
}

if (in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true) &&
!in_array($event->getTarget()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->postInsert($event->getSource()->getParent());
return;
}

if (!in_array($event->getSource()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true) &&
in_array($event->getTarget()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getTarget());
$this->postDelete($event->getTarget()->getParent());
return;
}

if ($this->movingFromIgnoredTerritory) {
if ($this->isIgnored($event->getTarget())) {
return;
}
$this->postInsert($event->getTarget());
return;
}
if ($this->isIgnored($event->getTarget())) {
$this->postDelete($event->getTarget());
return;
}
$this->postInsert($event->getTarget());
return;
}
if ($this->isIgnored($event->getTarget())) {
$this->postDelete($event->getTarget());
$this->postRename($event->getSource(), $event->getTarget());
return;
}
return;
}
if ($event instanceof BeforeNodeDeletedEvent) {
$this->postDelete($event->getNode(), false);
return;
}
if ($event instanceof NodeDeletedEvent) {
if (in_array($event->getNode()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getNode());
$this->postInsert($event->getNode()->getParent());
if ($event instanceof BeforeNodeDeletedEvent) {
$this->postDelete($event->getNode(), false);
return;
}
}
if ($event instanceof NodeCreatedEvent) {
if (in_array($event->getNode()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getNode());
$this->postDelete($event->getNode()->getParent());
return;
}
$this->postInsert($event->getNode(), false);
return;
}
if ($event instanceof CacheEntryInsertedEvent) {
$node = current($this->rootFolder->getById($event->getFileId()));
if ($node === false) {
return;
}
if ($node instanceof Folder) {
return;
}
if (in_array($node->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($node);
$this->postDelete($node->getParent());
return;
if ($event instanceof NodeDeletedEvent) {
if (in_array($event->getNode()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getNode());
$this->postInsert($event->getNode()->getParent());
return;
}
}
$this->postInsert($node);
}
if ($event instanceof NodeRemovedFromCache) {
$cacheEntry = $event->getStorage()->getCache()->get($event->getPath());
if ($cacheEntry === false) {
if ($event instanceof NodeCreatedEvent) {
if (in_array($event->getNode()->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($event->getNode());
$this->postDelete($event->getNode()->getParent());
return;
}
$this->postInsert($event->getNode(), false);
return;
}
$node = current($this->rootFolder->getById($cacheEntry->getId()));
if ($node === false) {
return;
if ($event instanceof CacheEntryInsertedEvent) {
$node = current($this->rootFolder->getById($event->getFileId()));
if ($node === false) {
return;
}
if ($node instanceof Folder) {
return;
}
if (in_array($node->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($node);
$this->postDelete($node->getParent());
return;
}
$this->postInsert($node);
}
if (in_array($node->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($node);
$this->postInsert($node->getParent());
return;
if ($event instanceof NodeRemovedFromCache) {
$cacheEntry = $event->getStorage()->getCache()->get($event->getPath());
if ($cacheEntry === false) {
return;
}
$node = current($this->rootFolder->getById($cacheEntry->getId()));
if ($node === false) {
return;
}
if (in_array($node->getName(), [...Constants::IGNORE_MARKERS_ALL, ...Constants::IGNORE_MARKERS_IMAGE, ...Constants::IGNORE_MARKERS_AUDIO, ...Constants::IGNORE_MARKERS_VIDEO], true)) {
$this->resetIgnoreCache($node);
$this->postInsert($node->getParent());
return;
}
$this->postDelete($node);
}
$this->postDelete($node);
} catch (\Throwable $e) {
$this->logger->error('Error in recognize file listener', ['exception' => $e]);
}
}

Expand Down Expand Up @@ -315,6 +333,43 @@ public function postInsert(Node $node, bool $recurse = true): void {
}
}

public function postRename(Node $source, Node $target): void {
/** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $targetAccessList */
$targetAccessList = $this->shareManager->getAccessList($target, true, true);
$targetUserIds = array_map(fn ($id) => strval($id), array_keys($targetAccessList['users']));

$usersToAdd = array_diff($targetUserIds, $this->sourceUserIds);
$existingUsers = array_diff($targetUserIds, $usersToAdd);
// *handwaving* I know this is a stretch but it's good enough
$ownerId = $existingUsers[0];

if ($target->getType() === FileInfo::TYPE_FOLDER) {
marcelklehr marked this conversation as resolved.
Show resolved Hide resolved
$mount = $target->getMountPoint();
$numericStorageId = $mount->getNumericStorageId();
if ($numericStorageId === null) {
return;
}
$files = $this->storageService->getFilesInMount($numericStorageId, $target->getId(), [ClusteringFaceClassifier::MODEL_NAME], 0, 0);
foreach ($files as $fileInfo) {
foreach ($usersToAdd as $userId) {
if (count($this->faceDetectionMapper->findByFileIdAndUser($target->getId(), $userId)) > 0) {
continue;
}
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($fileInfo['fileid'], $ownerId, $userId);
}
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($fileInfo['fileid'], $targetUserIds);
}
} else {
foreach ($usersToAdd as $userId) {
if (count($this->faceDetectionMapper->findByFileIdAndUser($target->getId(), $userId)) > 0) {
continue;
}
$this->faceDetectionMapper->copyDetectionsForFileFromUserToUser($target->getId(), $ownerId, $userId);
}
$this->faceDetectionMapper->removeDetectionsForFileFromUsersNotInList($source->getId(), $targetUserIds);
}
}

/**
* @param \OCP\Files\Node $node
* @return bool
Expand Down
Loading
Loading