diff --git a/appinfo/info.xml b/appinfo/info.xml index 787d334655e..8af180d8da9 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m ]]> - 19.0.0-dev.2 + 19.0.0-dev.3 agpl Daniel Calviño Sánchez diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 87847d0768d..034d0392ae4 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -84,7 +84,8 @@ use OCA\Talk\Events\SystemMessagesMultipleSentEvent; use OCA\Talk\Events\UserJoinedRoomEvent; use OCA\Talk\Federation\CloudFederationProviderTalk; -use OCA\Talk\Federation\Listener as FederationListener; +use OCA\Talk\Federation\Proxy\TalkV1\Notifier\MessageSentListener as TalkV1MessageSentListener; +use OCA\Talk\Federation\Proxy\TalkV1\Notifier\RoomModifiedListener as TalkV1RoomModifiedListener; use OCA\Talk\Files\Listener as FilesListener; use OCA\Talk\Files\TemplateLoader as FilesTemplateLoader; use OCA\Talk\Flow\RegisterOperationsListener; @@ -278,7 +279,10 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(TranscriptionFailedEvent::class, RecordingListener::class); // Federation listeners - $context->registerEventListener(RoomModifiedEvent::class, FederationListener::class); + $context->registerEventListener(RoomModifiedEvent::class, TalkV1RoomModifiedListener::class); + $context->registerEventListener(ChatMessageSentEvent::class, TalkV1MessageSentListener::class); + $context->registerEventListener(SystemMessageSentEvent::class, TalkV1MessageSentListener::class); + $context->registerEventListener(SystemMessagesMultipleSentEvent::class, TalkV1MessageSentListener::class); // Signaling listeners (External) $context->registerEventListener(AttendeesAddedEvent::class, SignalingListener::class); diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php index b85207f2f73..f9f1446115b 100644 --- a/lib/Chat/Parser/SystemMessage.php +++ b/lib/Chat/Parser/SystemMessage.php @@ -958,7 +958,7 @@ protected function getGuestName(Room $room, string $actorType, string $actorId): } } - protected function parseMissedCall(Room $room, array $parameters, string $currentActorId): array { + protected function parseMissedCall(Room $room, array $parameters, ?string $currentActorId): array { if ($parameters['users'][0] !== $currentActorId) { return [ $this->l->t('You missed a call from {user}'), diff --git a/lib/Federation/BackendNotifier.php b/lib/Federation/BackendNotifier.php index 1fad3fbd10e..487722c90cd 100644 --- a/lib/Federation/BackendNotifier.php +++ b/lib/Federation/BackendNotifier.php @@ -275,6 +275,36 @@ public function sendRoomModifiedUpdate( $this->sendUpdateToRemote($remote, $notification); } + /** + * Send information to remote participants that a message was posted + * Sent from Host server to Remote participant server + */ + public function sendMessageUpdate( + string $remoteServer, + int $localAttendeeId, + #[SensitiveParameter] + string $accessToken, + string $localToken, + array $messageData, + ): void { + $remote = $this->prepareRemoteUrl($remoteServer); + + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + FederationManager::NOTIFICATION_MESSAGE_POSTED, + FederationManager::TALK_ROOM_RESOURCE, + (string) $localAttendeeId, + [ + 'remoteServerUrl' => $this->getServerRemoteUrl(), + 'sharedSecret' => $accessToken, + 'remoteToken' => $localToken, + 'messageData' => $messageData, + ], + ); + + $this->sendUpdateToRemote($remote, $notification); + } + /** * @param string $remote * @param array{notificationType: string, resourceType: string, providerId: string, notification: array} $data diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index 2d68aa1f666..525784e2b35 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -38,6 +38,8 @@ use OCA\Talk\Model\AttendeeMapper; use OCA\Talk\Model\Invitation; use OCA\Talk\Model\InvitationMapper; +use OCA\Talk\Model\ProxyCacheMessages; +use OCA\Talk\Model\ProxyCacheMessagesMapper; use OCA\Talk\Participant; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; @@ -55,6 +57,8 @@ use OCP\Federation\ICloudFederationShare; use OCP\Federation\ICloudIdManager; use OCP\HintException; +use OCP\ICache; +use OCP\ICacheFactory; use OCP\ISession; use OCP\IUser; use OCP\IUserManager; @@ -64,6 +68,7 @@ use SensitiveParameter; class CloudFederationProviderTalk implements ICloudFederationProvider { + protected ?ICache $proxyCacheMessages; public function __construct( private ICloudIdManager $cloudIdManager, @@ -81,7 +86,10 @@ public function __construct( private ISession $session, private IEventDispatcher $dispatcher, private LoggerInterface $logger, + private ProxyCacheMessagesMapper $proxyCacheMessagesMapper, + ICacheFactory $cacheFactory, ) { + $this->proxyCacheMessages = $cacheFactory->isAvailable() ? $cacheFactory->createDistributed('talk/pcm/') : null; } /** @@ -185,6 +193,8 @@ public function notificationReceived($notificationType, $providerId, array $noti return $this->shareUnshared((int) $providerId, $notification); case FederationManager::NOTIFICATION_ROOM_MODIFIED: return $this->roomModified((int) $providerId, $notification); + case FederationManager::NOTIFICATION_MESSAGE_POSTED: + return $this->messagePosted((int) $providerId, $notification); } throw new BadRequestException([$notificationType]); @@ -297,6 +307,53 @@ private function roomModified(int $remoteAttendeeId, array $notification): array return []; } + /** + * @param int $remoteAttendeeId + * @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, messageData: array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string}} $notification + * @return array + * @throws ActionNotSupportedException + * @throws AuthenticationFailedException + * @throws ShareNotFound + */ + private function messagePosted(int $remoteAttendeeId, array $notification): array { + $invite = $this->getByRemoteAttendeeAndValidate($notification['remoteServerUrl'], $remoteAttendeeId, $notification['sharedSecret']); + try { + $room = $this->manager->getRoomById($invite->getLocalRoomId()); + } catch (RoomNotFoundException) { + throw new ShareNotFound(); + } + + // Sanity check to make sure the room is a remote room + if (!$room->isFederatedRemoteRoom()) { + throw new ShareNotFound(); + } + + $message = new ProxyCacheMessages(); + $message->setLocalToken($room->getToken()); + $message->setRemoteServerUrl($notification['remoteServerUrl']); + $message->setRemoteToken($notification['remoteToken']); + $message->setRemoteMessageId($notification['messageData']['remoteMessageId']); + $message->setActorType($notification['messageData']['actorType']); + $message->setActorId($notification['messageData']['actorId']); + $message->setActorDisplayName($notification['messageData']['actorDisplayName']); + $message->setMessageType($notification['messageData']['messageType']); + $message->setSystemMessage($notification['messageData']['systemMessage']); + $message->setExpirationDateTime(new \DateTimeImmutable($notification['messageData']['expirationDatetime'])); + $message->setMessage($notification['messageData']['message']); + $message->setMessageParameters($notification['messageData']['messageParameter']); + $this->proxyCacheMessagesMapper->insert($message); + + if ($this->proxyCacheMessages instanceof ICache) { + $cacheKey = sha1(json_encode([$notification['remoteServerUrl'], $notification['remoteToken']])); + $cacheData = $this->proxyCacheMessages->get($cacheKey); + if ($cacheData === null || $cacheData < $notification['messageData']['remoteMessageId']) { + $this->proxyCacheMessages->set($cacheKey, $notification['messageData']['remoteMessageId'], 300); + } + } + + return []; + } + /** * @throws AuthenticationFailedException * @throws ActionNotSupportedException diff --git a/lib/Federation/FederationManager.php b/lib/Federation/FederationManager.php index 7ed8970f807..6b2b7ab8846 100644 --- a/lib/Federation/FederationManager.php +++ b/lib/Federation/FederationManager.php @@ -57,6 +57,7 @@ class FederationManager { public const NOTIFICATION_SHARE_DECLINED = 'SHARE_DECLINED'; public const NOTIFICATION_SHARE_UNSHARED = 'SHARE_UNSHARED'; public const NOTIFICATION_ROOM_MODIFIED = 'ROOM_MODIFIED'; + public const NOTIFICATION_MESSAGE_POSTED = 'MESSAGE_POSTED'; public const TOKEN_LENGTH = 64; public function __construct( diff --git a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php index 3484a4c4842..8e9e16ad31f 100644 --- a/lib/Federation/Proxy/TalkV1/Controller/ChatController.php +++ b/lib/Federation/Proxy/TalkV1/Controller/ChatController.php @@ -34,16 +34,22 @@ use OCA\Talk\Room; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; +use OCP\ICache; +use OCP\ICacheFactory; /** * @psalm-import-type TalkChatMentionSuggestion from ResponseDefinitions * @psalm-import-type TalkChatMessageWithParent from ResponseDefinitions */ class ChatController { + protected ?ICache $proxyCacheMessages; + public function __construct( protected ProxyRequest $proxy, protected UserConverter $userConverter, + ICacheFactory $cacheFactory, ) { + $this->proxyCacheMessages = $cacheFactory->isAvailable() ? $cacheFactory->createDistributed('talk/pcm/') : null; } /** @@ -126,11 +132,23 @@ public function receiveMessages( int $includeLastKnown, int $noStatusUpdate, int $markNotificationsAsRead): DataResponse { - - // FIXME - // Poor-mans timeout, should later on cancel/trigger earlier, - // when we received a OCM message notifying us about a chat message - sleep(max(0, $timeout - 5)); + $cacheKey = sha1(json_encode([$room->getRemoteServer(), $room->getRemoteToken()])); + + if ($lookIntoFuture) { + if ($this->proxyCacheMessages instanceof ICache) { + for ($i = 0; $i <= $timeout; $i++) { + $cacheData = (int) $this->proxyCacheMessages->get($cacheKey); + if ($lastKnownMessageId !== $cacheData) { + break; + } + sleep(1); + } + } else { + // Poor-mans timeout, should later on cancel/trigger earlier, + // by checking the PCM database table + sleep(max(0, $timeout - 5)); + } + } $proxy = $this->proxy->get( $participant->getAttendee()->getInvitedCloudId(), @@ -159,6 +177,12 @@ public function receiveMessages( } if ($proxy->getHeader('X-Chat-Last-Given')) { $headers['X-Chat-Last-Given'] = (string) (int) $proxy->getHeader('X-Chat-Last-Given'); + if ($this->proxyCacheMessages instanceof ICache) { + $cacheData = $this->proxyCacheMessages->get($cacheKey); + if ($cacheData === null || $cacheData < $headers['X-Chat-Last-Given']) { + $this->proxyCacheMessages->set($cacheKey, (int) $headers['X-Chat-Last-Given'], 300); + } + } } /** @var TalkChatMessageWithParent[] $data */ diff --git a/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php new file mode 100644 index 00000000000..3c7dc3a854f --- /dev/null +++ b/lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php @@ -0,0 +1,112 @@ + + * + * @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\Talk\Federation\Proxy\TalkV1\Notifier; + +use OCA\Talk\Chat\ChatManager; +use OCA\Talk\Chat\MessageParser; +use OCA\Talk\Events\ASystemMessageSentEvent; +use OCA\Talk\Events\ChatMessageSentEvent; +use OCA\Talk\Events\SystemMessageSentEvent; +use OCA\Talk\Events\SystemMessagesMultipleSentEvent; +use OCA\Talk\Federation\BackendNotifier; +use OCA\Talk\Model\Attendee; +use OCA\Talk\Service\ParticipantService; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Federation\ICloudIdManager; +use OCP\L10N\IFactory; + +/** + * @template-implements IEventListener + */ +class MessageSentListener implements IEventListener { + public function __construct( + protected BackendNotifier $backendNotifier, + protected ParticipantService $participantService, + protected ICloudIdManager $cloudIdManager, + protected MessageParser $messageParser, + protected IFactory $l10nFactory, + ) { + } + + public function handle(Event $event): void { + if (!$event instanceof ChatMessageSentEvent + && !$event instanceof SystemMessageSentEvent + && !$event instanceof SystemMessagesMultipleSentEvent) { + return; + } + + if ($event instanceof ASystemMessageSentEvent && $event->shouldSkipLastActivityUpdate()) { + return; + } + + // FIXME once we store/cache the info skip this if the room has no federation participant + // if (!$event->getRoom()->hasFederatedParticipants()) { + // return; + // } + + // Try to have as neutral as possible messages + $l = $this->l10nFactory->get('spreed', 'en', 'en'); + $chatMessage = $this->messageParser->createMessage($event->getRoom(), null, $event->getComment(), $l); + $this->messageParser->parseMessage($chatMessage); + + if (!$chatMessage->getVisibility()) { + return; + } + + $expireDate = $event->getComment()->getExpireDate(); + + $messageData = [ + 'remoteMessageId' => (int) $event->getComment()->getId(), + 'actorType' => $chatMessage->getActorType(), + 'actorId' => $chatMessage->getActorId(), + 'actorDisplayName' => $chatMessage->getActorDisplayName(), + 'messageType' => $chatMessage->getMessageType(), + 'systemMessage' => $chatMessage->getMessageType() === ChatManager::VERB_SYSTEM ? $chatMessage->getMessageRaw() : '', + 'expirationDatetime' => $expireDate ? $expireDate->format(\DateTime::ATOM) : '', + 'message' => $chatMessage->getMessage(), + 'messageParameter' => json_encode($chatMessage->getMessageParameters()), + ]; + + $notifiedServers = []; + $participants = $this->participantService->getParticipantsByActorType($event->getRoom(), Attendee::ACTOR_FEDERATED_USERS); + foreach ($participants as $participant) { + $cloudId = $this->cloudIdManager->resolveCloudId($participant->getAttendee()->getActorId()); + + if (isset($notifiedServers[$cloudId->getRemote()])) { + continue; + } + $notifiedServers[$cloudId->getRemote()] = true; + + $this->backendNotifier->sendMessageUpdate( + $cloudId->getRemote(), + $participant->getAttendee()->getId(), + $participant->getAttendee()->getAccessToken(), + $event->getRoom()->getToken(), + $messageData, + ); + } + } +} diff --git a/lib/Federation/Listener.php b/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php similarity index 93% rename from lib/Federation/Listener.php rename to lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php index f5f29a86f74..2047cca5e57 100644 --- a/lib/Federation/Listener.php +++ b/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php @@ -21,10 +21,11 @@ * */ -namespace OCA\Talk\Federation; +namespace OCA\Talk\Federation\Proxy\TalkV1\Notifier; use OCA\Talk\Events\ARoomModifiedEvent; use OCA\Talk\Events\RoomModifiedEvent; +use OCA\Talk\Federation\BackendNotifier; use OCA\Talk\Model\Attendee; use OCA\Talk\Service\ParticipantService; use OCP\EventDispatcher\Event; @@ -34,7 +35,7 @@ /** * @template-implements IEventListener */ -class Listener implements IEventListener { +class RoomModifiedListener implements IEventListener { public function __construct( protected BackendNotifier $backendNotifier, protected ParticipantService $participantService, diff --git a/lib/Migration/Version19000Date20240227084313.php b/lib/Migration/Version19000Date20240227084313.php new file mode 100644 index 00000000000..5af30e4df9c --- /dev/null +++ b/lib/Migration/Version19000Date20240227084313.php @@ -0,0 +1,107 @@ + + * + * @author Joas Schilling + * + * @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\Talk\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * A temporary message cache for TalkV1 proxying to serve "last message" and help with notifications + */ +class Version19000Date20240227084313 extends SimpleMigrationStep { + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->createTable('talk_proxy_messages'); + $table->addColumn('id', Types::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + ]); + $table->addColumn('local_token', Types::STRING, [ + 'notnull' => true, + 'length' => 32, + ]); + $table->addColumn('remote_server_url', Types::STRING, [ + 'notnull' => true, + 'length' => 512, + ]); + $table->addColumn('remote_token', Types::STRING, [ + 'notnull' => true, + 'length' => 32, + ]); + $table->addColumn('remote_message_id', Types::BIGINT, [ + 'notnull' => true, + 'unsigned' => true, + ]); + $table->addColumn('actor_type', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('actor_id', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('actor_display_name', Types::STRING, [ + 'notnull' => false, + 'length' => 255, + ]); + $table->addColumn('message_type', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('system_message', Types::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + $table->addColumn('expiration_datetime', Types::DATETIME, [ + 'notnull' => false, + ]); + $table->addColumn('message', Types::TEXT, [ + 'notnull' => false, + ]); + $table->addColumn('message_parameters', Types::TEXT, [ + 'notnull' => false, + ]); + + $table->setPrimaryKey(['id']); + + $table->addUniqueIndex(['remote_server_url', 'remote_token', 'remote_message_id'], 'talk_pcm_remote'); + $table->addIndex(['local_token'], 'talk_pmc_local'); + + return $schema; + } +} diff --git a/lib/Model/ProxyCacheMessages.php b/lib/Model/ProxyCacheMessages.php new file mode 100644 index 00000000000..0d7cf7db78d --- /dev/null +++ b/lib/Model/ProxyCacheMessages.php @@ -0,0 +1,86 @@ + + * + * @author Joas Schilling + * + * @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\Talk\Model; + +use OCP\AppFramework\Db\Entity; + +/** + * @method void setLocalToken(string $localToken) + * @method string getLocalToken() + * @method void setRemoteServerUrl(string $remoteServerUrl) + * @method string getRemoteServerUrl() + * @method void setRemoteToken(string $remoteToken) + * @method string getRemoteToken() + * @method void setRemoteMessageId(int $remoteMessageId) + * @method int getRemoteMessageId() + * @method void setActorType(string $actorType) + * @method string getActorType() + * @method void setActorId(string $actorId) + * @method string getActorId() + * @method void setActorDisplayName(string $actorDisplayName) + * @method string getActorDisplayName() + * @method void setMessageType(string $messageType) + * @method string getMessageType() + * @method void setSystemMessage(?string $systemMessage) + * @method string|null getSystemMessage() + * @method void setExpirationDateTime(?\DateTimeImmutable $expirationDateTime) + * @method \DateTimeImmutable|null getExpirationDateTime() + * @method void setMessage(?string $message) + * @method string|null getMessage() + * @method void setMessageParameters(?string $messageParameters) + * @method string|null getMessageParameters() + */ +class ProxyCacheMessages extends Entity { + + protected string $localToken = ''; + protected string $remoteServerUrl = ''; + protected string $remoteToken = ''; + protected int $remoteMessageId = 0; + protected string $actorType = ''; + protected string $actorId = ''; + protected ?string $actorDisplayName = null; + protected ?string $messageType = null; + protected ?string $systemMessage = null; + protected ?\DateTimeImmutable $expirationDatetime = null; + protected ?string $message = null; + protected ?string $messageParameters = null; + + public function __construct() { + $this->addType('localToken', 'string'); + $this->addType('remoteServerUrl', 'string'); + $this->addType('remoteToken', 'string'); + $this->addType('remoteMessageId', 'int'); + $this->addType('actorType', 'string'); + $this->addType('actorId', 'string'); + $this->addType('actorDisplayName', 'string'); + $this->addType('messageType', 'string'); + $this->addType('systemMessage', 'string'); + $this->addType('expirationDatetime', 'datetime'); + $this->addType('message', 'string'); + $this->addType('messageParameters', 'string'); + } +} diff --git a/lib/Model/ProxyCacheMessagesMapper.php b/lib/Model/ProxyCacheMessagesMapper.php new file mode 100644 index 00000000000..7d6855fa143 --- /dev/null +++ b/lib/Model/ProxyCacheMessagesMapper.php @@ -0,0 +1,62 @@ + + * + * @author Joas Schilling + * + * @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\Talk\Model; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\QBMapper; +use OCP\AppFramework\Db\TTransactional; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +/** + * @method ProxyCacheMessages mapRowToEntity(array $row) + * @method ProxyCacheMessages findEntity(IQueryBuilder $query) + * @method ProxyCacheMessages[] findEntities(IQueryBuilder $query) + * @template-extends QBMapper + */ +class ProxyCacheMessagesMapper extends QBMapper { + use TTransactional; + + public function __construct( + IDBConnection $db, + ) { + parent::__construct($db, 'talk_proxy_messages', ProxyCacheMessages::class); + } + + /** + * @throws DoesNotExistException + */ + public function findByRemote(string $remoteServerUrl, string $remoteToken, int $remoteMessageId): ProxyCacheMessages { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from($this->getTableName()) + ->where($query->expr()->eq('remote_server_url', $query->createNamedParameter($remoteServerUrl, IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('remote_token', $query->createNamedParameter($remoteToken, IQueryBuilder::PARAM_STR))) + ->andWhere($query->expr()->eq('remote_message_id', $query->createNamedParameter($remoteMessageId, IQueryBuilder::PARAM_INT))); + + return $this->findEntity($query); + } +} diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index 0e5ed382e06..bffb0d976e1 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -362,7 +362,7 @@ public function formatRoomV4( } $lastMessage = $room->getLastMessage(); - if ($lastMessage instanceof IComment) { + if ($room->getRemoteServer() === '' && $lastMessage instanceof IComment) { $roomData['lastMessage'] = $this->formatLastMessage( $responseFormat, $room, diff --git a/tests/integration/run.sh b/tests/integration/run.sh index 82c19bd06d8..d17538127cc 100755 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -33,7 +33,7 @@ PORT_FED=8180 export PORT_FED echo "" > phpserver_fed.log -php -S localhost:${PORT_FED} -t ${ROOT_DIR} &> phpserver_fed.log & +PHP_CLI_SERVER_WORKERS=3 php -S localhost:${PORT_FED} -t ${ROOT_DIR} &> phpserver_fed.log & PHPPID2=$! echo -e "Running on process ID: \033[1;35m$PHPPID2\033[0m" diff --git a/tests/php/Federation/FederationTest.php b/tests/php/Federation/FederationTest.php index 897fee6c2bf..f320ad682b5 100644 --- a/tests/php/Federation/FederationTest.php +++ b/tests/php/Federation/FederationTest.php @@ -33,6 +33,7 @@ use OCA\Talk\Model\AttendeeMapper; use OCA\Talk\Model\Invitation; use OCA\Talk\Model\InvitationMapper; +use OCA\Talk\Model\ProxyCacheMessagesMapper; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\RoomService; @@ -45,6 +46,7 @@ use OCP\Federation\ICloudFederationProviderManager; use OCP\Federation\ICloudFederationShare; use OCP\Federation\ICloudIdManager; +use OCP\ICacheFactory; use OCP\ISession; use OCP\IURLGenerator; use OCP\IUser; @@ -91,6 +93,9 @@ class FederationTest extends TestCase { /** @var AttendeeMapper|MockObject */ protected $attendeeMapper; + protected ProxyCacheMessagesMapper|MockObject $proxyCacheMessageMapper; + protected ICacheFactory|MockObject $cacheFactory; + public function setUp(): void { parent::setUp(); @@ -105,6 +110,8 @@ public function setUp(): void { $this->appManager = $this->createMock(IAppManager::class); $this->logger = $this->createMock(LoggerInterface::class); $this->url = $this->createMock(IURLGenerator::class); + $this->proxyCacheMessageMapper = $this->createMock(ProxyCacheMessagesMapper::class); + $this->cacheFactory = $this->createMock(ICacheFactory::class); $this->backendNotifier = new BackendNotifier( $this->cloudFederationFactory, @@ -137,7 +144,9 @@ public function setUp(): void { $this->createMock(Manager::class), $this->createMock(ISession::class), $this->createMock(IEventDispatcher::class), - $this->logger + $this->logger, + $this->proxyCacheMessageMapper, + $this->cacheFactory, ); }