aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2017-06-13 17:30:13 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-07-27 14:35:28 +0900
commitbaee19241daffd50e0b32559cda64d5b6ede09a2 (patch)
tree2aa9e41f64d0dddeff3b5fdf8cca1f433cf0a5d7 /lib
parent284b751ee424985341812a32721227112160a905 (diff)
downloadlibquotient-baee19241daffd50e0b32559cda64d5b6ede09a2.tar.gz
libquotient-baee19241daffd50e0b32559cda64d5b6ede09a2.zip
Initial support for local echo
The Room class has gained a new internal container, unsyncedEvents, storing locally-created Event objects that are about to be sent or are sent but not yet synced. These objects are supposed to be complete enough to be displayed by clients in a usual way; access to them is provided by Room::pendingEvents() accessor. A set of pendingEvent* signals has been added to notify clients about changes in this container (adding, removal, status update). Yet unsent events don't have Event::id() at all; sent but yet unsynced ones have Event::id() but have almost nothing else except the content for now (probably a sender and an (at least local) timestamp are worth adding). Also: SendEventJob is removed in favor of GTAD-generated SendMessageJob.
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp15
-rw-r--r--lib/connection.h7
-rw-r--r--lib/events/roomevent.cpp5
-rw-r--r--lib/events/roomevent.h1
-rw-r--r--lib/jobs/sendeventjob.cpp45
-rw-r--r--lib/jobs/sendeventjob.h57
-rw-r--r--lib/room.cpp107
-rw-r--r--lib/room.h11
8 files changed, 127 insertions, 121 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 8007cea1..d3a53cf4 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -30,7 +30,7 @@
#include "csapi/account-data.h"
#include "csapi/joining.h"
#include "csapi/to_device.h"
-#include "jobs/sendeventjob.h"
+#include "csapi/room_send.h"
#include "jobs/syncjob.h"
#include "jobs/mediathumbnailjob.h"
#include "jobs/downloadfilejob.h"
@@ -405,11 +405,6 @@ void Connection::stopSync()
}
}
-void Connection::postMessage(Room* room, const QString& type, const QString& message) const
-{
- callApi<SendEventJob>(room->id(), type, message);
-}
-
PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const
{
return callApi<PostReceiptJob>(room->id(), "m.read", event->id());
@@ -645,6 +640,14 @@ SendToDeviceJob* Connection::sendToDevices(const QString& eventType,
eventType, generateTxnId(), json);
}
+SendMessageJob* Connection::sendMessage(const QString& roomId, const RoomEvent& event) const
+{
+ const auto txnId = event.transactionId().isEmpty()
+ ? generateTxnId() : event.transactionId();
+ return callApi<SendMessageJob>(roomId, event.matrixType(),
+ txnId, event.contentJson());
+}
+
QUrl Connection::homeserver() const
{
return d->data->baseUrl();
diff --git a/lib/connection.h b/lib/connection.h
index 7adab883..48ca2232 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -47,6 +47,7 @@ namespace QMatrixClient
class GetContentJob;
class DownloadFileJob;
class SendToDeviceJob;
+ class SendMessageJob;
/** Create a single-shot connection that triggers on the signal and
* then self-disconnects
@@ -421,11 +422,11 @@ namespace QMatrixClient
SendToDeviceJob* sendToDevices(const QString& eventType,
const UsersToDevicesToEvents& eventsMap) const;
+ SendMessageJob* sendMessage(const QString& roomId,
+ const RoomEvent& event) const;
+
// Old API that will be abolished any time soon. DO NOT USE.
- /** @deprecated Use callApi<PostMessageJob>() or Room::postMessage() instead */
- virtual void postMessage(Room* room, const QString& type,
- const QString& message) const;
/** @deprecated Use callApi<PostReceiptJob>() or Room::postReceipt() instead */
virtual PostReceiptJob* postReceipt(Room* room,
RoomEvent* event) const;
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index 3d09af8a..75850772 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -75,6 +75,11 @@ QString RoomEvent::redactionReason() const
return isRedacted() ? _redactedBecause->reason() : QString{};
}
+QString RoomEvent::stateKey() const
+{
+ return fullJson()["state_key"_ls].toString();
+}
+
void RoomEvent::addId(const QString& newId)
{
Q_ASSERT(id().isEmpty()); Q_ASSERT(!newId.isEmpty());
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index d2bc6edc..fcbb33e5 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -57,6 +57,7 @@ namespace QMatrixClient {
}
QString redactionReason() const;
const QString& transactionId() const { return _txnId; }
+ QString stateKey() const;
/**
* Sets the transaction id for locally created events. This should be
diff --git a/lib/jobs/sendeventjob.cpp b/lib/jobs/sendeventjob.cpp
deleted file mode 100644
index e5852c65..00000000
--- a/lib/jobs/sendeventjob.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/******************************************************************************
- * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "sendeventjob.h"
-
-#include "events/roommessageevent.h"
-
-using namespace QMatrixClient;
-
-SendEventJob::SendEventJob(const QString& roomId, const QString& type,
- const QString& plainText)
- : SendEventJob(roomId, RoomMessageEvent(plainText, type))
-{ }
-
-void SendEventJob::beforeStart(const ConnectionData* connData)
-{
- BaseJob::beforeStart(connData);
- setApiEndpoint(apiEndpoint() + connData->generateTxnId());
-}
-
-BaseJob::Status SendEventJob::parseJson(const QJsonDocument& data)
-{
- _eventId = data.object().value("event_id"_ls).toString();
- if (!_eventId.isEmpty())
- return Success;
-
- qCDebug(JOBS) << data;
- return { UserDefinedError, "No event_id in the JSON response" };
-}
-
diff --git a/lib/jobs/sendeventjob.h b/lib/jobs/sendeventjob.h
deleted file mode 100644
index af81ae26..00000000
--- a/lib/jobs/sendeventjob.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/******************************************************************************
- * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#pragma once
-
-#include "basejob.h"
-
-#include "connectiondata.h"
-
-namespace QMatrixClient
-{
- class SendEventJob: public BaseJob
- {
- public:
- /** Constructs a job that sends an arbitrary room event */
- template <typename EvT>
- SendEventJob(const QString& roomId, const EvT& event)
- : BaseJob(HttpVerb::Put, QStringLiteral("SendEventJob"),
- QStringLiteral("_matrix/client/r0/rooms/%1/send/%2/")
- .arg(roomId, EvT::matrixTypeId()), // See also beforeStart()
- Query(),
- Data(event.contentJson()))
- { }
-
- /**
- * Constructs a plain text message job (for compatibility with
- * the old PostMessageJob API).
- */
- SendEventJob(const QString& roomId, const QString& type,
- const QString& plainText);
-
- QString eventId() const { return _eventId; }
-
- protected:
- Status parseJson(const QJsonDocument& data) override;
-
- private:
- QString _eventId;
-
- void beforeStart(const ConnectionData* connData) override;
- };
-} // namespace QMatrixClient
diff --git a/lib/room.cpp b/lib/room.cpp
index a8007f20..ca29eca5 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -27,13 +27,13 @@
#include "csapi/account-data.h"
#include "csapi/message_pagination.h"
#include "csapi/room_state.h"
+#include "csapi/room_send.h"
#include "events/simplestateevents.h"
#include "events/roomavatarevent.h"
#include "events/roommemberevent.h"
#include "events/typingevent.h"
#include "events/receiptevent.h"
#include "events/redactionevent.h"
-#include "jobs/sendeventjob.h"
#include "jobs/mediathumbnailjob.h"
#include "jobs/downloadfilejob.h"
#include "jobs/postreadmarkersjob.h"
@@ -88,6 +88,7 @@ class Room::Private
Connection* connection;
Timeline timeline;
+ RoomEvents unsyncedEvents;
QHash<QString, TimelineItem::index_t> eventsIndex;
QString id;
QStringList aliases;
@@ -199,9 +200,20 @@ class Room::Private
void markMessagesAsRead(rev_iter_t upToMarker);
+ void sendEvent(RoomEventPtr&& event);
+
+ template <typename EventT, typename... ArgTs>
+ void sendEvent(ArgTs&&... eventArgs)
+ {
+ sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...));
+ }
+
+ void deleteLocalEcho(const RoomEventPtr& remoteEcho);
+
template <typename EvT>
auto requestSetState(const QString& stateKey, const EvT& event)
{
+ // TODO: Queue up state events sending (see #133).
return connection->callApi<SetRoomStateWithKeyJob>(
id, EvT::matrixTypeId(), stateKey, event.contentJson());
}
@@ -274,6 +286,11 @@ const Room::Timeline& Room::messageEvents() const
return d->timeline;
}
+const RoomEvents& Room::pendingEvents() const
+{
+ return d->unsyncedEvents;
+}
+
QString Room::name() const
{
return d->name;
@@ -1073,32 +1090,64 @@ void Room::updateData(SyncRoomData&& data)
}
}
+void Room::Private::sendEvent(RoomEventPtr&& event)
+{
+ auto* pEvent = rawPtr(event);
+ emit q->pendingEventAboutToAdd();
+ unsyncedEvents.emplace_back(move(event));
+ emit q->pendingEventAdded();
+
+ if (pEvent->transactionId().isEmpty())
+ pEvent->setTransactionId(connection->generateTxnId());
+ // TODO: Enqueue the job rather than immediately trigger it
+ auto call = connection->sendMessage(id, *pEvent);
+ Room::connect(call, &BaseJob::success, q, [this,call,pEvent]
+ {
+ const auto comparator =
+ [pEvent] (const auto& eptr) { return rawPtr(eptr) == pEvent; };
+
+ // Find an event by the pointer saved in the lambda
+ auto it = std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(),
+ comparator);
+ if (it == unsyncedEvents.end())
+ return; // The event is already synced, nothing to do
+
+ pEvent->addId(call->eventId());
+ emit q->pendingEventChanged(it - unsyncedEvents.begin());
+ });
+}
+
void Room::postMessage(const QString& type, const QString& plainText)
{
- postMessage(RoomMessageEvent { plainText, type });
+ d->sendEvent<RoomMessageEvent>(plainText, type);
}
void Room::postMessage(const QString& plainText, MessageEventType type)
{
- postMessage(RoomMessageEvent { plainText, type });
+ d->sendEvent<RoomMessageEvent>(plainText, type);
}
void Room::postHtmlMessage(const QString& plainText, const QString& htmlText,
MessageEventType type)
{
- postMessage(RoomMessageEvent { plainText, type,
- new EventContent::TextContent(htmlText, QStringLiteral("text/html")) });
-
+ d->sendEvent<RoomMessageEvent>(plainText, type,
+ new EventContent::TextContent(htmlText, QStringLiteral("text/html")));
}
-void Room::postMessage(const RoomMessageEvent& event)
+void Room::postMessage(RoomEvent* event)
{
if (usesEncryption())
{
qCCritical(MAIN) << "Room" << displayName()
<< "enforces encryption; sending encrypted messages is not supported yet";
}
- connection()->callApi<SendEventJob>(id(), event);
+ d->sendEvent(RoomEventPtr(event));
+}
+
+void Room::postMessage(const QString& matrixType,
+ const QJsonObject& eventContent)
+{
+ d->sendEvent(loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent)));
}
void Room::setName(const QString& newName)
@@ -1116,6 +1165,41 @@ void Room::setTopic(const QString& newTopic)
d->requestSetState(RoomTopicEvent(newTopic));
}
+void Room::Private::deleteLocalEcho(const RoomEventPtr& remoteEcho)
+{
+ if (remoteEcho->senderId() == connection->userId())
+ {
+ auto localEchoIt =
+ std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(),
+ [&remoteEcho] (const RoomEventPtr& le)
+ {
+ if (le->type() != remoteEcho->type())
+ return false;
+
+ if (!le->id().isEmpty())
+ return le->id() == remoteEcho->id();
+ if (!le->transactionId().isEmpty())
+ return le->transactionId() ==
+ remoteEcho->transactionId();
+
+ // This one is not reliable (there can be two unsynced
+ // events with the same type, sender and state key) but
+ // it's the best we have for state events.
+ if (le->isStateEvent())
+ return le->stateKey() == remoteEcho->stateKey();
+
+ // Empty id and no state key, hmm... (shrug)
+ return le->contentJson() == remoteEcho->contentJson();
+ });
+ if (localEchoIt != unsyncedEvents.end())
+ {
+ emit q->pendingEventAboutToRemove(localEchoIt - unsyncedEvents.begin());
+ unsyncedEvents.erase(localEchoIt);
+ emit q->pendingEventRemoved();
+ }
+ }
+}
+
void Room::getPreviousContent(int limit)
{
d->getPreviousContent(limit);
@@ -1399,7 +1483,12 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
#endif
if (!normalEvents.empty())
+ {
+ for (const auto& e: normalEvents)
+ deleteLocalEcho(e);
+
emit q->aboutToAddNewMessages(normalEvents);
+ }
const auto insertedSize = moveEventsToTimeline(normalEvents, Newer);
const auto from = timeline.cend() - insertedSize;
if (insertedSize > 0)
@@ -1648,7 +1737,7 @@ void Room::processAccountDataEvent(EventPtr&& event)
// efficient; maaybe do it another day
if (!currentData || currentData->contentJson() != event->contentJson())
{
- currentData = std::move(event);
+ currentData = move(event);
qCDebug(MAIN) << "Updated account data of type"
<< currentData->matrixType();
emit accountDataChanged(currentData->matrixType());
diff --git a/lib/room.h b/lib/room.h
index e7f260dd..01a7389c 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -216,6 +216,7 @@ namespace QMatrixClient
Q_INVOKABLE QString roomMembername(const QString& userId) const;
const Timeline& messageEvents() const;
+ const RoomEvents& pendingEvents() const;
/**
* A convenience method returning the read marker to the before-oldest
* message
@@ -350,7 +351,10 @@ namespace QMatrixClient
MessageEventType type = MessageEventType::Text);
void postHtmlMessage(const QString& plainText, const QString& htmlText,
MessageEventType type = MessageEventType::Text);
- void postMessage(const RoomMessageEvent& event);
+ /** Post a pre-created room message event; takes ownership of the event */
+ void postMessage(RoomEvent* event);
+ void postMessage(const QString& matrixType,
+ const QJsonObject& eventContent);
/** @deprecated If you have a custom event type, construct the event
* and pass it as a whole to postMessage() */
void postMessage(const QString& type, const QString& plainText);
@@ -384,6 +388,11 @@ namespace QMatrixClient
void aboutToAddHistoricalMessages(RoomEventsRange events);
void aboutToAddNewMessages(RoomEventsRange events);
void addedMessages();
+ void pendingEventAboutToAdd();
+ void pendingEventAdded();
+ void pendingEventAboutToRemove(int pendingEventIndex);
+ void pendingEventRemoved();
+ void pendingEventChanged(int pendingEventIndex);
/**
* @brief The room name, the canonical alias or other aliases changed