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,
);
}