Skip to content

Commit

Permalink
Merge pull request #1337 from nextcloud/artonge/feat/listen_to_more_e…
Browse files Browse the repository at this point in the history
…vent_for_albums

Listen to more events for albums
  • Loading branch information
artonge authored May 9, 2023
2 parents f20476a + 70e719b commit bf88722
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 124 deletions.
33 changes: 33 additions & 0 deletions lib/Album/AlbumMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ public function removeFile(int $albumId, int $fileId): void {
$query->executeStatement();
}

/**
* Remove all files added by a user from an album.
*/
public function removeFilesForUser(int $albumId, string $userId) {
// Remove all photos by this user from the album:
$query = $this->connection->getQueryBuilder();
Expand All @@ -289,6 +292,36 @@ public function removeFilesForUser(int $albumId, string $userId) {
->executeStatement();
}

/**
* Remove a given file from any albums in which it was added by a given user.
*/
public function removeFileWithOwner(int $fileId, string $ownerId): void {
// Get concerned albums before deleting them.
$query = $this->connection->getQueryBuilder();
$albumsRows = $query->select('album_id')
->from("photos_albums_files")
->where($query->expr()->eq("owner_id", $query->createNamedParameter($ownerId)))
->andWhere($query->expr()->eq("file_id", $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
->executeQuery()
->fetchAll();

// Remove any occurrence of fileId when owner is ownerId.
$query = $this->connection->getQueryBuilder();
$query->delete("photos_albums_files")
->where($query->expr()->eq("owner_id", $query->createNamedParameter($ownerId)))
->andWhere($query->expr()->eq("file_id", $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
->executeStatement();

// Update last_added_photo for concerned albums.
foreach ($albumsRows as $row) {
$query = $this->connection->getQueryBuilder();
$query->update("photos_albums")
->set('last_added_photo', $query->createNamedParameter($this->getLastAdded($row['album_id']), IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('album_id', $query->createNamedParameter($row['album_id'], IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
}

private function getLastAdded(int $albumId): int {
$query = $this->connection->getQueryBuilder();
$query->select("file_id")
Expand Down
18 changes: 9 additions & 9 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@
use OCA\DAV\Events\SabrePluginAuthInitEvent;
use OCA\Photos\Listener\SabrePluginAuthInitListener;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Photos\Listener\NodeDeletedListener;
use OCA\Photos\Listener\TagListener;
use OCA\Photos\Listener\GroupUserRemovedListener;
use OCA\Photos\Listener\GroupDeletedListener;
use OCA\Photos\Listener\PlaceManagerEventListener;
use OCA\Photos\Listener\AlbumsManagementEventListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand All @@ -42,6 +40,8 @@
use OCP\Group\Events\UserRemovedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\User\Events\UserDeletedEvent;

class Application extends App implements IBootstrap {
public const APP_ID = 'photos';
Expand Down Expand Up @@ -75,15 +75,15 @@ public function register(IRegistrationContext $context): void {
/** Register $principalBackend for the DAV collection */
$context->registerServiceAlias('principalBackend', Principal::class);

$context->registerEventListener(NodeDeletedEvent::class, NodeDeletedListener::class);

$context->registerEventListener(UserRemovedEvent::class, GroupUserRemovedListener::class);

$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);

// Priority of -1 to be triggered after event listeners populating metadata.
$context->registerEventListener(NodeWrittenEvent::class, PlaceManagerEventListener::class, -1);

$context->registerEventListener(NodeDeletedEvent::class, AlbumsManagementEventListener::class);
$context->registerEventListener(UserRemovedEvent::class, AlbumsManagementEventListener::class);
$context->registerEventListener(GroupDeletedEvent::class, AlbumsManagementEventListener::class);
$context->registerEventListener(UserDeletedEvent::class, AlbumsManagementEventListener::class);
$context->registerEventListener(ShareDeletedEvent::class, AlbumsManagementEventListener::class);

$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);

$context->registerEventListener(MapperEvent::EVENT_ASSIGN, TagListener::class);
Expand Down
108 changes: 108 additions & 0 deletions lib/Listener/AlbumsManagementEventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace OCA\Photos\Listener;

use OCA\Photos\Album\AlbumMapper;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\User\Events\UserDeletedEvent;
use Psr\Log\LoggerInterface;

class AlbumsManagementEventListener implements IEventListener {
private AlbumMapper $albumMapper;
private LoggerInterface $logger;

public function __construct(
AlbumMapper $albumMapper,
LoggerInterface $logger,
) {
$this->albumMapper = $albumMapper;
$this->logger = $logger;
}

public function handle(Event $event): void {
if ($event instanceof NodeDeletedEvent) {
try {
// Remove node from all albums containing it.
$albums = $this->albumMapper->getForFile($event->getNode()->getId());

foreach ($albums as $album) {
$this->albumMapper->removeFile($album->getId(), $event->getNode()->getId());
}
} catch(\Throwable $ex) {
// If an error occur, return silently as we don't want to block the rest of the deletion process.
// It happened already during migrations when the albums table is not yet created, but a folder is deleted by the theming app.
$this->logger->error($ex->getMessage(), ['exception' => $ex]);
}
} elseif ($event instanceof UserDeletedEvent) {
// Delete all user's albums.
$albums = $this->albumMapper->getForUser($event->getUser()->getUID());
foreach ($albums as $album) {
$this->albumMapper->delete($album->getId());
}
} elseif ($event instanceof ShareDeletedEvent) {
$receiverId = $event->getShare()->getSharedWith();
$this->forEachSubNode(
$event->getShare()->getNode(),
// Remove node from any album when the owner is $receiverId.
fn ($node) => $this->albumMapper->removeFileWithOwner($node->getId(), $receiverId),
);
} elseif ($event instanceof UserRemovedEvent) {
// Get all shared albums for this group:
$albums_group = $this->albumMapper->getSharedAlbumsForCollaborator($event->getGroup()->getGID(), AlbumMapper::TYPE_GROUP);
// Get all albums shared with this specific user:
$albums_user = $this->albumMapper->getSharedAlbumsForCollaborator($event->getUser()->getUID(), AlbumMapper::TYPE_USER);
// Get all group-shared albums that are not directly shared with the removed user in addition
$albums = array_udiff($albums_group, $albums_user, fn ($a, $b) => ($a->getId() - $b->getId()));

// Remove their photos from theses albums:
foreach ($albums as $album) {
$this->albumMapper->removeFilesForUser($album->getId(), $event->getUser()->getUID());
}
} elseif ($event instanceof GroupDeletedEvent) {
// Get all shared albums for this group:
$albums_group = $this->albumMapper->getSharedAlbumsForCollaborator($event->getGroup()->getGID(), AlbumMapper::TYPE_GROUP);

// Get all users of this group:
$users = $event->getGroup()->getUsers();

foreach ($users as $user) {
// Get all albums shared with this specific user:
$albums_user = $this->albumMapper->getSharedAlbumsForCollaborator($user->getUID(), AlbumMapper::TYPE_USER);

// Get all group-shared albums that are not directly shared with the removed user in addition
$albums = array_udiff($albums_group, $albums_user, fn ($a, $b) => ($a->getId() - $b->getId()));

// Remove their photos from theses albums:
foreach ($albums as $album) {
$this->albumMapper->removeFilesForUser($album->getId(), $user->getUID());
}
}

foreach ($albums_group as $album) {
$this->albumMapper->deleteGroupFromAlbumCollaboratorsList($event->getGroup()->getGID(), $album->getId());
}
}
}

private function forEachSubNode(Node $node, callable $callback): void {
if ($node instanceof Folder) {
foreach ($node->getDirectoryListing() as $subNode) {
$this->forEachSubNode($subNode, $callback);
}
} elseif ($node instanceof File) {
if (!str_starts_with($node->getMimeType(), 'image')) {
return;
}

$callback($node);
}
}
}
47 changes: 0 additions & 47 deletions lib/Listener/GroupDeletedListener.php

This file was deleted.

34 changes: 0 additions & 34 deletions lib/Listener/GroupUserRemovedListener.php

This file was deleted.

34 changes: 0 additions & 34 deletions lib/Listener/NodeDeletedListener.php

This file was deleted.

0 comments on commit bf88722

Please sign in to comment.