diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index c7b93085..243955c1 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -12,6 +12,7 @@ use OCA\DAV\Events\SabrePluginAddEvent; use OCA\Files\Event\LoadSidebar; use OCA\Files_DownloadLimit\Capabilities; +use OCA\Files_DownloadLimit\Listener\BeforeNodeReadListener; use OCA\Files_DownloadLimit\Listener\BeforeTemplateRenderedListener; use OCA\Files_DownloadLimit\Listener\LoadSidebarListener; use OCA\Files_DownloadLimit\Listener\SabrePluginAddListener; @@ -22,6 +23,7 @@ use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Files\Events\Node\BeforeNodeReadEvent; class Application extends App implements IBootstrap { public const APP_ID = 'files_downloadlimit'; @@ -36,6 +38,8 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(ShareLinkAccessedEvent::class, ShareLinkAccessedListener::class); + $context->registerEventListener(BeforeNodeReadEvent::class, BeforeNodeReadListener::class); + $context->registerCapability(Capabilities::class); } diff --git a/lib/Listener/BeforeNodeReadListener.php b/lib/Listener/BeforeNodeReadListener.php new file mode 100644 index 00000000..fdac0491 --- /dev/null +++ b/lib/Listener/BeforeNodeReadListener.php @@ -0,0 +1,165 @@ + + */ +class BeforeNodeReadListener implements IEventListener { + private ICache $cache; + public function __construct( + private IManager $manager, + private LimitMapper $mapper, + private LoggerInterface $logger, + private IRequest $request, + private ISession $session, + ICacheFactory $cacheFactory, + ) { + $this->cache = $cacheFactory->createDistributed('files_downloadlimit_event'); + } + + public function handle(Event $event): void { + if ($event instanceof BeforeZipCreatedEvent) { + $this->handleBeforeZipCreatedEvent($event); + } elseif ($event instanceof BeforeNodeReadEvent) { + $this->handleBeforeNodeReadEvent($event); + } + } + + public function handleBeforeZipCreatedEvent(BeforeZipCreatedEvent $event): void { + $files = $event->getFiles(); + if (count($files) !== 0) { + /* No need to do anything, count will be triggered for each file in the zip by the BeforeNodeReadEvent */ + return; + } + + $node = $event->getFolder(); + if (!($node instanceof Folder)) { + return; + } + + try { + $storage = $node->getStorage(); + } catch (NotFoundException) { + return; + } + + if (!$storage->instanceOfStorage(ISharedStorage::class)) { + return; + } + + /** @var ISharedStorage $storage */ + $share = $storage->getShare(); + + if (!in_array($share->getShareType(), [IShare::TYPE_EMAIL, IShare::TYPE_LINK])) { + return; + } + + /* Cache that that folder download activity was published */ + $this->cache->set($this->request->getId(), $node->getPath(), 3600); + + $this->singleFileDownloaded($share); + } + + public function handleBeforeNodeReadEvent(BeforeNodeReadEvent $event): void { + $node = $event->getNode(); + if (!($node instanceof File)) { + return; + } + + try { + $storage = $node->getStorage(); + } catch (NotFoundException) { + return; + } + + if (!$storage->instanceOfStorage(ISharedStorage::class)) { + return; + } + + /** @var ISharedStorage $storage */ + $share = $storage->getShare(); + + if (!in_array($share->getShareType(), [IShare::TYPE_EMAIL, IShare::TYPE_LINK])) { + return; + } + + $path = $this->cache->get($this->request->getId()); + if (is_string($path) && str_starts_with($node->getPath(), $path)) { + /* An activity was published for a containing folder already */ + return; + } + + /* Avoid publishing several activities for one video playing */ + $cacheKey = $node->getId() . $node->getPath() . $this->session->getId(); + if (($this->request->getHeader('range') !== '') && ($this->cache->get($cacheKey) === 'true')) { + /* This is a range request and an activity for the same file was published in the same session */ + return; + } + $this->cache->set($cacheKey, 'true', 3600); + + + $this->singleFileDownloaded($share); + } + + protected function singleFileDownloaded(IShare $share): void { + + $token = $share->getToken(); + if ($token === null) { + return; + } + // Make sure we have a valid limit + try { + $shareLimit = $this->mapper->get($token); + $limit = $shareLimit->getLimit(); + + // Increment this download event + $downloads = $shareLimit->getDownloads() + 1; + + // If we reached the maximum allowed download count + if ($downloads >= $limit) { + // Delete share + $this->manager->deleteShare($share); + // Delete limit + $this->mapper->delete($shareLimit); + return; + } + + // Else, we just update the current download count + $shareLimit->setDownloads($downloads); + $this->mapper->update($shareLimit); + } catch (DoesNotExistException $e) { + // No limit is set, ignore + } catch (\Exception $e) { + $this->logger->error('Error while handling share link accessed event: ' . $e->getMessage()); + } + + + } + +}