From a9d73b77b40743fb2e408c4c3490c8bd0c15b434 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 4 Nov 2022 12:46:23 -0100 Subject: [PATCH] fix min_id Signed-off-by: Maxence Lange --- lib/Command/ExtendedBase.php | 10 ++- lib/Command/Timeline.php | 76 +++++++++---------- lib/Db/SocialLimitsQueryBuilder.php | 2 +- lib/Db/StreamRequest.php | 55 ++++++++++---- lib/Exceptions/UnknownTimelineException.php | 35 +++++++++ .../Version0003Date20200611000001.php | 1 - lib/Model/Client/Options/TimelineOptions.php | 27 ++++--- lib/Service/StreamService.php | 6 +- tests/psalm-baseline.xml | 5 -- 9 files changed, 139 insertions(+), 78 deletions(-) create mode 100644 lib/Exceptions/UnknownTimelineException.php diff --git a/lib/Command/ExtendedBase.php b/lib/Command/ExtendedBase.php index c7feb0a7..feca6ff9 100644 --- a/lib/Command/ExtendedBase.php +++ b/lib/Command/ExtendedBase.php @@ -2,7 +2,6 @@ declare(strict_types=1); - /** * Nextcloud - Social Support * @@ -30,7 +29,6 @@ declare(strict_types=1); namespace OCA\Social\Command; -use OCA\Social\Tools\Exceptions\CacheItemNotFoundException; use OC\Core\Command\Base; use OCA\Social\AP; use OCA\Social\Exceptions\ItemUnknownException; @@ -38,12 +36,14 @@ use OCA\Social\Exceptions\RedundancyLimitException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Model\ActivityPub\Actor\Person; use OCA\Social\Model\ActivityPub\Stream; +use OCA\Social\Tools\Exceptions\CacheItemNotFoundException; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Output\OutputInterface; class ExtendedBase extends Base { protected ?OutputInterface $output = null; protected bool $asJson = false; + protected int $crop = 0; protected function outputActor(Person $actor): void { if ($this->asJson) { @@ -65,9 +65,8 @@ class ExtendedBase extends Base { } $table = new Table($this->output); - $table->setHeaders(['Id', 'Source', 'Type', 'Author', 'Content']); + $table->setHeaders(['Nid', 'Id', 'Source', 'Type', 'Author', 'Content']); $table->render(); - $this->output->writeln(''); foreach ($streams as $item) { $objectId = $item->getObjectId(); @@ -95,8 +94,11 @@ class ExtendedBase extends Base { ->getAccount(); } + $content = ($this->crop) ? substr($content, 0, $this->crop) : $content; + $table->appendRow( [ + $item->getNid(), '' . $item->getId() . '', '' . $item->getActor() ->getAccount() . '', diff --git a/lib/Command/Timeline.php b/lib/Command/Timeline.php index 961cae3e..6adbb42a 100644 --- a/lib/Command/Timeline.php +++ b/lib/Command/Timeline.php @@ -33,10 +33,12 @@ namespace OCA\Social\Command; use Exception; use OCA\Social\Db\StreamRequest; +use OCA\Social\Exceptions\UnknownTimelineException; use OCA\Social\Model\ActivityPub\Actor\Person; +use OCA\Social\Model\ActivityPub\Stream; +use OCA\Social\Model\Client\Options\TimelineOptions; use OCA\Social\Service\AccountService; use OCA\Social\Service\ConfigService; -use OCA\Social\Service\MiscService; use OCP\IUserManager; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -51,16 +53,10 @@ use Symfony\Component\Console\Output\OutputInterface; */ class Timeline extends ExtendedBase { private IUserManager $userManager; - private StreamRequest $streamRequest; - private AccountService $accountService; - private ConfigService $configService; - private MiscService $miscService; - - private ?int $count = null; @@ -71,12 +67,12 @@ class Timeline extends ExtendedBase { * @param StreamRequest $streamRequest * @param AccountService $accountService * @param ConfigService $configService - * @param MiscService $miscService */ public function __construct( - IUserManager $userManager, StreamRequest $streamRequest, - AccountService $accountService, ConfigService $configService, - MiscService $miscService + IUserManager $userManager, + StreamRequest $streamRequest, + AccountService $accountService, + ConfigService $configService ) { parent::__construct(); @@ -84,7 +80,6 @@ class Timeline extends ExtendedBase { $this->streamRequest = $streamRequest; $this->accountService = $accountService; $this->configService = $configService; - $this->miscService = $miscService; } @@ -97,6 +92,9 @@ class Timeline extends ExtendedBase { ->addArgument('userId', InputArgument::REQUIRED, 'viewer') ->addArgument('timeline', InputArgument::REQUIRED, 'timeline') ->addOption('count', '', InputOption::VALUE_REQUIRED, 'number of elements', '5') + ->addOption('min_id', '', InputOption::VALUE_REQUIRED, 'min_id', 0) + ->addOption('max_id', '', InputOption::VALUE_REQUIRED, 'max_id', 0) + ->addOption('crop', '', InputOption::VALUE_REQUIRED, 'crop', 0) ->addOption('json', '', InputOption::VALUE_NONE, 'return JSON format') ->setDescription('Get stream by timeline and viewer'); } @@ -108,14 +106,12 @@ class Timeline extends ExtendedBase { * * @throws Exception */ - protected function execute(InputInterface $input, OutputInterface $output) { + protected function execute(InputInterface $input, OutputInterface $output): int { $output = new ConsoleOutput(); $this->output = $output->section(); $this->asJson = $input->getOption('json'); - $this->count = intval($input->getOption('count')); - - $timeline = $input->getArgument('timeline'); + $this->crop = intval($input->getOption('crop')); $userId = $input->getArgument('userId'); if ($this->userManager->get($userId) === null) { @@ -127,7 +123,24 @@ class Timeline extends ExtendedBase { if (!$this->asJson) { $this->outputActor($actor); } - $this->displayStream($actor, $timeline); + + $this->streamRequest->setViewer($actor); + + $options = new TimelineOptions(); + $options->setFormat(Stream::FORMAT_LOCAL); + $options->setLimit(intval($input->getOption('count'))) + ->setMinId(intval($input->getOption('min_id'))) + ->setMaxId(intval($input->getOption('max_id'))); + + try { + $options->setTimeline($input->getArgument('timeline')); + $this->outputStreams($this->streamRequest->getTimeline($options)); + } catch (UnknownTimelineException $e) { + echo $input->getArgument('timeline'); + $this->displayUnsupportedStream($options); + } + + return 0; } @@ -137,42 +150,27 @@ class Timeline extends ExtendedBase { * * @throws Exception */ - private function displayStream(Person $actor, string $timeline) { - $this->streamRequest->setViewer($actor); - switch ($timeline) { - case 'home': - $stream = $this->streamRequest->getTimelineHome_dep(0, $this->count); - $this->outputStreams($stream); - break; - + private function displayUnsupportedStream(TimelineOptions $options) { + switch ($options->getTimeline()) { case 'direct': - $stream = $this->streamRequest->getTimelineDirect(0, $this->count); + $stream = $this->streamRequest->getTimelineDirect(0, $options->getLimit()); $this->outputStreams($stream); break; case 'notifications': - $stream = $this->streamRequest->getTimelineNotifications(0, $this->count); + $stream = $this->streamRequest->getTimelineNotifications(0, $options->getLimit()); $this->outputStreams($stream); break; case 'liked': - $stream = $this->streamRequest->getTimelineLiked(0, $this->count); - $this->outputStreams($stream); - break; - - case 'local': - $stream = $this->streamRequest->getTimelineGlobal_dep(0, $this->count, true); - $this->outputStreams($stream); - break; - - case 'global': - $stream = $this->streamRequest->getTimelineGlobal_dep(0, $this->count, false); + $stream = $this->streamRequest->getTimelineLiked(0, $options->getLimit()); $this->outputStreams($stream); break; default: throw new Exception( - 'Unknown timeline. Try home, direct, notifications, liked, local, global.' + 'Unknown timeline. Try ' . implode(', ', TimelineOptions::$availableTimelines) + . ', direct, notifications, liked' ); } } diff --git a/lib/Db/SocialLimitsQueryBuilder.php b/lib/Db/SocialLimitsQueryBuilder.php index f0c1cec5..11673466 100644 --- a/lib/Db/SocialLimitsQueryBuilder.php +++ b/lib/Db/SocialLimitsQueryBuilder.php @@ -345,7 +345,7 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder { if ($options->getMinId() > 0) { $options->setInverted(true); - $this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getMaxId()))); + $this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getMinId()))); } $this->setMaxResults($options->getLimit()); diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php index b300328c..c814c15e 100644 --- a/lib/Db/StreamRequest.php +++ b/lib/Db/StreamRequest.php @@ -30,11 +30,8 @@ declare(strict_types=1); namespace OCA\Social\Db; -use OCA\Social\Tools\Exceptions\DateTimeException; -use OCA\Social\Tools\Model\Cache; use DateTime; use Exception; -use OCP\DB\Exception as DBException; use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Exceptions\StreamNotFoundException; use OCA\Social\Model\ActivityPub\ACore; @@ -45,6 +42,9 @@ use OCA\Social\Model\ActivityPub\Stream; use OCA\Social\Model\Client\Options\TimelineOptions; use OCA\Social\Service\ConfigService; use OCA\Social\Service\MiscService; +use OCA\Social\Tools\Exceptions\DateTimeException; +use OCA\Social\Tools\Model\Cache; +use OCP\DB\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IURLGenerator; @@ -56,6 +56,7 @@ use Psr\Log\LoggerInterface; * @package OCA\Social\Db */ class StreamRequest extends StreamRequestBuilder { + private const NID_LIMIT = 1000000; private StreamDestRequest $streamDestRequest; private StreamTagsRequest $streamTagsRequest; @@ -75,9 +76,11 @@ class StreamRequest extends StreamRequestBuilder { if ($stream->getType() === Note::TYPE) { /** @var Note $stream */ $qb->setValue('hashtags', $qb->createNamedParameter(json_encode($stream->getHashtags()))) - ->setValue('attachments', $qb->createNamedParameter( - json_encode($stream->getAttachments(), JSON_UNESCAPED_SLASHES) - )); + ->setValue( + 'attachments', $qb->createNamedParameter( + json_encode($stream->getAttachments(), JSON_UNESCAPED_SLASHES) + ) + ); } try { @@ -98,9 +101,11 @@ class StreamRequest extends StreamRequestBuilder { $qb = $this->getStreamUpdateSql(); $qb->set('details', $qb->createNamedParameter(json_encode($stream->getDetailsAll()))); - $qb->set('cc', $qb->createNamedParameter( - json_encode($stream->getCcArray(), JSON_UNESCAPED_SLASHES) - )); + $qb->set( + 'cc', $qb->createNamedParameter( + json_encode($stream->getCcArray(), JSON_UNESCAPED_SLASHES) + ) + ); $qb->limitToIdPrim($qb->prim($stream->getId())); $qb->executeStatement(); @@ -326,6 +331,23 @@ class StreamRequest extends StreamRequestBuilder { } + public function getTimeline(TimelineOptions $options): array { + switch (strtolower($options->getTimeline())) { + case 'home': + return $this->getTimelineHome($options); + case 'public': + $options->setLocal(false); + + return $this->getTimelinePublic($options); + case 'local': + $options->setLocal(true); + + return $this->getTimelinePublic($options); + } + + return []; + } + /** * Should returns: * * Own posts, @@ -441,7 +463,7 @@ class StreamRequest extends StreamRequestBuilder { $qb->selectDestFollowing('sd', ''); $qb->innerJoinSteamDest('recipient', 'id_prim', 'sd', 's'); - $accountIsViewer = ($qb->hasViewer()) ? ($qb->getViewer()->getId() === $actorId) : false; + $accountIsViewer = ($qb->hasViewer() && $qb->getViewer()->getId() === $actorId); $qb->limitToDest($accountIsViewer ? '' : ACore::CONTEXT_PUBLIC, 'recipient', '', 'sd'); $qb->linkToCacheActors('ca', 's.attributed_to_prim'); @@ -478,7 +500,7 @@ class StreamRequest extends StreamRequestBuilder { } /** - * Should returns: + * Should return: * * All local public/federated posts * * @param TimelineOptions $options @@ -490,7 +512,9 @@ class StreamRequest extends StreamRequestBuilder { $qb = $this->getStreamSelectSql($options->getFormat()); $qb->paginate($options); - $qb->limitToLocal($options->isLocal()); + if ($options->isLocal()) { + $qb->limitToLocal(true); + } $qb->limitToType(Note::TYPE); $qb->linkToCacheActors('ca', 's.attributed_to_prim'); @@ -674,8 +698,13 @@ class StreamRequest extends StreamRequestBuilder { ->getId(); } + if ($stream->getNid() === 0) { + $stream->setNid($stream->getPublishedTime() * self::NID_LIMIT + rand(1, self::NID_LIMIT)); + } + $qb = $this->getStreamInsertSql(); - $qb->setValue('id', $qb->createNamedParameter($stream->getId())) + $qb->setValue('nid', $qb->createNamedParameter($stream->getNid())) + ->setValue('id', $qb->createNamedParameter($stream->getId())) ->setValue('type', $qb->createNamedParameter($stream->getType())) ->setValue('subtype', $qb->createNamedParameter($stream->getSubType())) ->setValue('to', $qb->createNamedParameter($stream->getTo())) diff --git a/lib/Exceptions/UnknownTimelineException.php b/lib/Exceptions/UnknownTimelineException.php new file mode 100644 index 00000000..56644f44 --- /dev/null +++ b/lib/Exceptions/UnknownTimelineException.php @@ -0,0 +1,35 @@ + + * @copyright 2022, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Exceptions; + +use Exception; + +class UnknownTimelineException extends Exception { +} diff --git a/lib/Migration/Version0003Date20200611000001.php b/lib/Migration/Version0003Date20200611000001.php index 549ddbca..29edfa9a 100644 --- a/lib/Migration/Version0003Date20200611000001.php +++ b/lib/Migration/Version0003Date20200611000001.php @@ -525,7 +525,6 @@ class Version0003Date20200611000001 extends SimpleMigrationStep { $table->addColumn( 'nid', 'bigint', [ - 'autoincrement' => true, 'length' => 11, 'unsigned' => true, ] diff --git a/lib/Model/Client/Options/TimelineOptions.php b/lib/Model/Client/Options/TimelineOptions.php index 5bef3831..6c581269 100644 --- a/lib/Model/Client/Options/TimelineOptions.php +++ b/lib/Model/Client/Options/TimelineOptions.php @@ -31,8 +31,9 @@ declare(strict_types=1); namespace OCA\Social\Model\Client\Options; -use OCA\Social\Tools\Traits\TArrayTools; use JsonSerializable; +use OCA\Social\Exceptions\UnknownTimelineException; +use OCA\Social\Tools\Traits\TArrayTools; use OCP\IRequest; /** @@ -43,25 +44,22 @@ use OCP\IRequest; class TimelineOptions extends CoreOptions implements JsonSerializable { use TArrayTools; - private string $timeline = ''; - private bool $local = false; - private bool $remote = false; - private bool $onlyMedia = false; - private int $minId = 0; - private int $maxId = 0; - private int $sinceId = 0; - private int $limit = 20; - private bool $inverted = false; + public static array $availableTimelines = [ + 'home', + 'local', + 'public' + ]; + /** * TimelineOptions constructor. @@ -86,8 +84,16 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { * @param string $timeline * * @return TimelineOptions + * @throws UnknownTimelineException */ public function setTimeline(string $timeline): self { + $timeline = strtolower($timeline); + if (!in_array($timeline, self::$availableTimelines)) { + throw new UnknownTimelineException( + 'unknown timeline: ' . implode(', ', self::$availableTimelines) + ); + } + $this->timeline = $timeline; return $this; @@ -270,6 +276,7 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { public function jsonSerialize(): array { return [ + 'timeline' => $this->getTimeline(), 'local' => $this->isLocal(), 'remote' => $this->isRemote(), 'only_media' => $this->isOnlyMedia(), diff --git a/lib/Service/StreamService.php b/lib/Service/StreamService.php index 4466c397..6bcd4465 100644 --- a/lib/Service/StreamService.php +++ b/lib/Service/StreamService.php @@ -412,11 +412,7 @@ class StreamService { * @return Note[] */ public function getTimeline(TimelineOptions $options): array { - if ($options->getTimeline() === 'home') { - return $this->streamRequest->getTimelineHome($options); - } elseif ($options->getTimeline() === 'public') { - return $this->streamRequest->getTimelinePublic($options); - } + return $this->streamRequest->getTimeline($options); } /** diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index b9ae93a8..2c030301 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -126,11 +126,6 @@ $varr[0] !== null - - - Note[] - - $host === null