From 023c61b88f3bfdc3829606a17fa3bf9deac600fc Mon Sep 17 00:00:00 2001 From: Sean Molenaar Date: Tue, 19 Jan 2021 12:30:48 +0100 Subject: Mappers: Implement item purging Signed-off-by: Sean Molenaar --- CHANGELOG.md | 1 + lib/Command/Updater/AfterUpdate.php | 19 ++++++++-- lib/Db/ItemMapperV2.php | 57 +++++++++++++++++++++++----- lib/Service/ItemServiceV2.php | 21 ++++++----- tests/Unit/Command/AfterUpdateTest.php | 68 ++++++++++++++++++++++++++++++++-- 5 files changed, 141 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c24f8f1a..2b25b3751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1 ### Fixed - Fetch feed after creation (#1058) +- Implement missing item purger (#1063) - Update FeedIO Response call and add tests - Improve Psalm tests and dependency definition diff --git a/lib/Command/Updater/AfterUpdate.php b/lib/Command/Updater/AfterUpdate.php index bedc8c9ff..f9e5671f0 100644 --- a/lib/Command/Updater/AfterUpdate.php +++ b/lib/Command/Updater/AfterUpdate.php @@ -15,6 +15,7 @@ use OCA\News\Service\ItemServiceV2; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class AfterUpdate extends Command @@ -42,14 +43,26 @@ class AfterUpdate extends Command { $this->setName('news:updater:after-update') ->setDescription('removes old read articles which are not starred') - ->addArgument('purge_count', InputArgument::OPTIONAL, 'The amount of items to purge'); + ->addArgument('purge-count', InputArgument::OPTIONAL, 'The amount of items to purge') + ->addOption('purge-unread', null, InputOption::VALUE_NONE, 'If unread items should be purged'); } protected function execute(InputInterface $input, OutputInterface $output): int { - $count = (int) $input->getArgument('purge_count'); + $count = $input->getArgument('purge-count'); + $removeUnread = $input->getOption('purge-unread'); - $output->writeln($this->itemService->purgeOverThreshold($count)); + if ($count !== null) { + $count = intval($count); + } + + $result = $this->itemService->purgeOverThreshold($count, $removeUnread); + if ($result === null) { + $output->writeln('No cleanup needed', $output::VERBOSITY_VERBOSE); + return 0; + } + + $output->writeln('Removed ' . $result . ' item(s)', $output::VERBOSITY_VERBOSE); return 0; } diff --git a/lib/Db/ItemMapperV2.php b/lib/Db/ItemMapperV2.php index 0c3191540..256621815 100644 --- a/lib/Db/ItemMapperV2.php +++ b/lib/Db/ItemMapperV2.php @@ -12,6 +12,7 @@ namespace OCA\News\Db; +use Doctrine\DBAL\FetchMode; use OCA\News\Utility\Time; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; @@ -138,20 +139,58 @@ class ItemMapperV2 extends NewsMapperV2 /** * Delete items from feed that are over the max item threshold * - * TODO: Implement + * @param int $threshold Deletion threshold + * @param bool $removeUnread If unread articles should be removed * - * @param int $threshold Deletion threshold + * @return int|null Removed items + * + * @throws \Doctrine\DBAL\Exception|\OCP\DB\Exception */ - public function deleteOverThreshold(int $threshold) + public function deleteOverThreshold(int $threshold, bool $removeUnread = false): ?int { - $builder = $this->db->getQueryBuilder(); + $feedQb = $this->db->getQueryBuilder(); + $feedQb->addSelect('feed_id', $feedQb->func()->count('*', 'itemCount'), 'feeds.articles_per_update') + ->from($this->tableName, 'items') + ->innerJoin('items', FeedMapperV2::TABLE_NAME, 'feeds', 'items.feed_id = feeds.id') + ->groupBy('feed_id'); + + $feeds = $this->db->executeQuery($feedQb->getSQL()) + ->fetchAll(FetchMode::ASSOCIATIVE); + + if ($feeds === []) { + return null; + } + + $rangeQuery = $this->db->getQueryBuilder(); + $rangeQuery->select('id') + ->from($this->tableName) + ->where('feed_id = :feedId') + ->andWhere('starred = 0') + ->orderBy('updated_date', 'DESC'); + + if ($removeUnread === false) { + $rangeQuery->andWhere('unread = 0'); + } + + $total_items = []; + foreach ($feeds as $feed) { + if ($feed['itemCount'] < $threshold) { + continue; + } + + $rangeQuery->setFirstResult(max($threshold, $feed['articles_per_update'])); + + $items = $this->db->executeQuery($rangeQuery->getSQL(), ['feedId' => $feed['feed_id']]) + ->fetchAll(FetchMode::COLUMN); + + $total_items = array_merge($total_items, $items); + } - $query = $builder->addSelect('COUNT(*)') - ->from($this->tableName) - ->groupBy('feed_id') - ->where(''); + $deleteQb = $this->db->getQueryBuilder(); + $deleteQb->delete($this->tableName) + ->where('id IN (?)'); - return $this->db->executeQuery($query->getSQL()); + return $this->db->executeUpdate($deleteQb->getSQL(), [$total_items], [IQueryBuilder::PARAM_INT_ARRAY]); } /** diff --git a/lib/Service/ItemServiceV2.php b/lib/Service/ItemServiceV2.php index 54cefa197..f13b249b8 100644 --- a/lib/Service/ItemServiceV2.php +++ b/lib/Service/ItemServiceV2.php @@ -113,22 +113,25 @@ class ItemServiceV2 extends Service return $this->mapper->findAllForFeed($feedId); } - - - public function purgeOverThreshold(int $threshold = null) + /** + * @param int|null $threshold + * @param bool $removeUnread + * + * @return int|null Amount of deleted items or null if not applicable + */ + public function purgeOverThreshold(int $threshold = null, bool $removeUnread = false): ?int { - - $threshold = (int) $threshold ?? $this->config->getAppValue( + $threshold = (int) ($threshold ?? $this->config->getAppValue( Application::NAME, 'autoPurgeCount', Application::DEFAULT_SETTINGS['autoPurgeCount'] - ); + )); - if ($threshold === 0) { - return ''; + if ($threshold <= 0) { + return null; } - return $this->mapper->deleteOverThreshold($threshold); + return $this->mapper->deleteOverThreshold($threshold, $removeUnread); } /** diff --git a/tests/Unit/Command/AfterUpdateTest.php b/tests/Unit/Command/AfterUpdateTest.php index fbc327ecc..81fd175cf 100644 --- a/tests/Unit/Command/AfterUpdateTest.php +++ b/tests/Unit/Command/AfterUpdateTest.php @@ -62,17 +62,77 @@ class AfterUpdateTest extends TestCase { $this->consoleInput->expects($this->once()) ->method('getArgument') - ->with('purge_count') + ->with('purge-count') ->willReturn('1'); + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('purge-unread') + ->willReturn(false); + + $this->service->expects($this->exactly(1)) + ->method('purgeOverThreshold') + ->with('1', false) + ->willReturn(1); + + $this->consoleOutput->expects($this->exactly(1)) + ->method('writeln') + ->with('Removed 1 item(s)'); + + $result = $this->command->run($this->consoleInput, $this->consoleOutput); + $this->assertSame(0, $result); + } + + /** + * Test a valid call will work + */ + public function testValidEmpty() + { + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('purge-count') + ->willReturn('1'); + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('purge-unread') + ->willReturn(false); + + + $this->service->expects($this->exactly(1)) + ->method('purgeOverThreshold') + ->with('1', false) + ->willReturn(null); + + $this->consoleOutput->expects($this->exactly(1)) + ->method('writeln') + ->with('No cleanup needed'); + + $result = $this->command->run($this->consoleInput, $this->consoleOutput); + $this->assertSame(0, $result); + } + + /** + * Test a valid call will work + */ + public function testValidEmptyUnread() + { + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('purge-count') + ->willReturn('1'); + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('purge-unread') + ->willReturn(true); + $this->service->expects($this->exactly(1)) ->method('purgeOverThreshold') - ->with('1') - ->willReturn('test'); + ->with('1', true) + ->willReturn(null); $this->consoleOutput->expects($this->exactly(1)) ->method('writeln') - ->with('test'); + ->with('No cleanup needed'); $result = $this->command->run($this->consoleInput, $this->consoleOutput); $this->assertSame(0, $result); -- cgit v1.2.3