diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-07-27 18:34:28 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-07-27 18:34:28 +0900 |
commit | 84aa055bba602635599def37915b96ccf3f63484 (patch) | |
tree | 59857c792f913871ab70385fb7e9f037fa58973a | |
parent | 284b751ee424985341812a32721227112160a905 (diff) | |
parent | c7e4d01c479452aad4616ee2d5a285f4fe0565aa (diff) | |
download | libquotient-84aa055bba602635599def37915b96ccf3f63484.tar.gz libquotient-84aa055bba602635599def37915b96ccf3f63484.zip |
Merge branch 'kitsune-local-echo'
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/qmc-example.cpp | 56 | ||||
-rw-r--r-- | lib/connection.cpp | 15 | ||||
-rw-r--r-- | lib/connection.h | 7 | ||||
-rw-r--r-- | lib/events/roomevent.cpp | 5 | ||||
-rw-r--r-- | lib/events/roomevent.h | 1 | ||||
-rw-r--r-- | lib/jobs/sendeventjob.cpp | 45 | ||||
-rw-r--r-- | lib/jobs/sendeventjob.h | 57 | ||||
-rw-r--r-- | lib/room.cpp | 209 | ||||
-rw-r--r-- | lib/room.h | 23 | ||||
-rw-r--r-- | lib/util.h | 18 | ||||
-rw-r--r-- | libqmatrixclient.pri | 2 |
12 files changed, 241 insertions, 198 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a377ec4d..7be054e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,6 @@ set(libqmatrixclient_SRCS lib/events/directchatevent.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp - lib/jobs/sendeventjob.cpp lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 4de42623..8670b263 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -2,7 +2,7 @@ #include "connection.h" #include "room.h" #include "user.h" -#include "jobs/sendeventjob.h" +#include "csapi/room_send.h" #include "csapi/joining.h" #include "csapi/leaving.h" @@ -26,9 +26,10 @@ class QMCTest : public QObject void setup(const QString& testRoomName); void onNewRoom(Room* r); void startTests(); + void sendMessage(); void addAndRemoveTag(); void sendAndRedact(); - void checkRedactionOutcome(QString evtIdToRedact, + void checkRedactionOutcome(const QString& evtIdToRedact, RoomEventsRange events); void markDirectChat(); void checkDirectChatOutcome( @@ -87,9 +88,8 @@ void QMCTest::setup(const QString& testRoomName) c->sync(10000); else if (targetRoom) { - auto j = c->callApi<SendEventJob>(targetRoom->id(), - RoomMessageEvent(origin % ": All tests finished")); - connect(j, &BaseJob::finished, this, &QMCTest::leave); + targetRoom->postMessage(origin % ": All tests finished"); + connect(targetRoom, &Room::pendingEventMerged, this, &QMCTest::leave); } else finalize(); @@ -142,11 +142,32 @@ void QMCTest::onNewRoom(Room* r) void QMCTest::startTests() { cout << "Starting tests" << endl; + sendMessage(); addAndRemoveTag(); sendAndRedact(); markDirectChat(); } +void QMCTest::sendMessage() +{ + running.push_back("Message sending"); + cout << "Sending a message" << endl; + auto txnId = targetRoom->postMessage("Hello, " % origin % " is here"); + auto& pending = targetRoom->pendingEvents(); + if (pending.empty()) + { + QMC_CHECK("Message sending", false); + return; + } + auto it = std::find_if(pending.begin(), pending.end(), + [&txnId] (const RoomEventPtr& e) { + return e->transactionId() == txnId; + }); + QMC_CHECK("Message sending", it != pending.end()); + // TODO: Wait when it actually gets sent; check that it obtained an id + // Independently, check when it shows up in the timeline. +} + void QMCTest::addAndRemoveTag() { running.push_back("Tagging test"); @@ -176,19 +197,22 @@ void QMCTest::sendAndRedact() { running.push_back("Redaction"); cout << "Sending a message to redact" << endl; - auto* job = targetRoom->connection()->callApi<SendEventJob>(targetRoom->id(), - RoomMessageEvent(origin % ": Message to redact")); - connect(job, &BaseJob::success, targetRoom, [job,this] { - cout << "Message to redact has been succesfully sent, redacting" << endl; - targetRoom->redactEvent(job->eventId(), origin); - // Make sure to save the event id because the job is about to end. - connect(targetRoom, &Room::aboutToAddNewMessages, this, - std::bind(&QMCTest::checkRedactionOutcome, - this, job->eventId(), _1)); - }); + if (auto* job = targetRoom->connection()->sendMessage(targetRoom->id(), + RoomMessageEvent(origin % ": message to redact"))) + { + connect(job, &BaseJob::success, targetRoom, [job,this] { + cout << "Redacting the message" << endl; + targetRoom->redactEvent(job->eventId(), origin); + // Make sure to save the event id because the job is about to end. + connect(targetRoom, &Room::aboutToAddNewMessages, this, + std::bind(&QMCTest::checkRedactionOutcome, + this, job->eventId(), _1)); + }); + } else + QMC_CHECK("Redaction", false); } -void QMCTest::checkRedactionOutcome(QString evtIdToRedact, +void QMCTest::checkRedactionOutcome(const QString& evtIdToRedact, RoomEventsRange events) { static bool checkSucceeded = false; 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..4b349f44 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; @@ -174,8 +175,7 @@ class Room::Private void addNewMessageEvents(RoomEvents&& events); void addHistoricalMessageEvents(RoomEvents&& events); - /** - * @brief Move events into the timeline + /** Move events into the timeline * * Insert events into the timeline, either new or historical. * Pointers in the original container become empty, the ownership @@ -184,11 +184,11 @@ class Room::Private * @param placement - position and direction of insertion: Older for * historical messages, Newer for new ones */ - Timeline::size_type moveEventsToTimeline(RoomEventsRange events, - EventsPlacement placement); + Timeline::difference_type moveEventsToTimeline(RoomEventsRange events, + EventsPlacement placement); /** - * Removes events from the passed container that are already in the timeline + * Remove events from the passed container that are already in the timeline */ void dropDuplicateEvents(RoomEvents& events) const; @@ -199,9 +199,18 @@ class Room::Private void markMessagesAsRead(rev_iter_t upToMarker); + QString sendEvent(RoomEventPtr&& event); + + template <typename EventT, typename... ArgTs> + QString sendEvent(ArgTs&&... eventArgs) + { + return sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...)); + } + 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 +283,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; @@ -937,7 +951,7 @@ inline auto makeErrorStr(const Event& e, QByteArray msg) return msg.append("; event dump follows:\n").append(e.originalJson()); } -Room::Timeline::size_type Room::Private::moveEventsToTimeline( +Room::Timeline::difference_type Room::Private::moveEventsToTimeline( RoomEventsRange events, EventsPlacement placement) { // Historical messages arrive in newest-to-oldest order, so the process for @@ -956,18 +970,33 @@ Room::Timeline::size_type Room::Private::moveEventsToTimeline( Q_ASSERT_X(!eventsIndex.contains(eId), __FUNCTION__, makeErrorStr(*e, "Event is already in the timeline; " "incoming events were not properly deduplicated")); - if (auto* redEvt = eventCast<const RedactionEvent>(e)) - processRedaction(redEvt); if (placement == Older) + { + // No need to process redaction events here: historical redacted + // events already come redacted. +#ifndef KEEP_REDACTIONS_IN_TIMELINE + if (is<RedactionEvent>(*e)) + continue; +#endif timeline.emplace_front(move(e), --index); + } else + { + if (auto* redEvt = eventCast<const RedactionEvent>(e)) + { + processRedaction(redEvt); +#ifndef KEEP_REDACTIONS_IN_TIMELINE + continue; +#endif + } timeline.emplace_back(move(e), ++index); + } eventsIndex.insert(eId, index); Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId); } - // Pointers in "events" are empty now, but events.size() didn't change - Q_ASSERT(int(events.size()) == (index - baseIndex) * int(placement)); - return events.size(); + const auto insertedSize = (index - baseIndex) * int(placement); + Q_ASSERT(insertedSize >= 0); + return insertedSize; } QString Room::roomMembername(const User* u) const @@ -1073,32 +1102,65 @@ void Room::updateData(SyncRoomData&& data) } } -void Room::postMessage(const QString& type, const QString& plainText) +QString Room::Private::sendEvent(RoomEventPtr&& event) { - postMessage(RoomMessageEvent { plainText, type }); + 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()); + }); + return pEvent->transactionId(); } -void Room::postMessage(const QString& plainText, MessageEventType type) +QString Room::postMessage(const QString& type, const QString& plainText) { - postMessage(RoomMessageEvent { plainText, type }); + return d->sendEvent<RoomMessageEvent>(plainText, type); } -void Room::postHtmlMessage(const QString& plainText, const QString& htmlText, - MessageEventType type) +QString Room::postMessage(const QString& plainText, MessageEventType type) { - postMessage(RoomMessageEvent { plainText, type, - new EventContent::TextContent(htmlText, QStringLiteral("text/html")) }); + return d->sendEvent<RoomMessageEvent>(plainText, type); +} +QString Room::postHtmlMessage(const QString& plainText, const QString& htmlText, + MessageEventType type) +{ + return d->sendEvent<RoomMessageEvent>(plainText, type, + new EventContent::TextContent(htmlText, QStringLiteral("text/html"))); } -void Room::postMessage(const RoomMessageEvent& event) +QString Room::postMessage(RoomEvent* event) { if (usesEncryption()) { qCCritical(MAIN) << "Room" << displayName() << "enforces encryption; sending encrypted messages is not supported yet"; } - connection()->callApi<SendEventJob>(id(), event); + return d->sendEvent(RoomEventPtr(event)); +} + +QString Room::postMessage(const QString& matrixType, + const QJsonObject& eventContent) +{ + return d->sendEvent(loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent))); } void Room::setName(const QString& newName) @@ -1116,6 +1178,26 @@ void Room::setTopic(const QString& newTopic) d->requestSetState(RoomTopicEvent(newTopic)); } +bool isEchoEvent(const RoomEventPtr& le, const RoomEventPtr& re) +{ + if (le->type() != re->type()) + return false; + + if (!le->id().isEmpty()) + return le->id() == re->id(); + if (!le->transactionId().isEmpty()) + return le->transactionId() == re->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() == re->stateKey(); + + // Empty id and no state key, hmm... (shrug) + return le->contentJson() == re->contentJson(); +} + void Room::getPreviousContent(int limit) { d->getPreviousContent(limit); @@ -1387,39 +1469,49 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) auto timelineSize = timeline.size(); dropDuplicateEvents(events); -#ifndef KEEP_REDACTIONS_IN_TIMELINE - // We want to process redactions in the order of arrival (covering the - // case of one redaction superseding another one), hence stable partition. - const auto normalsBegin = - stable_partition(events.begin(), events.end(), isRedaction); - RoomEventsRange redactions { events.begin(), normalsBegin }, - normalEvents { normalsBegin, events.end() }; -#else - RoomEventsRange normalEvents { events }; -#endif + if (events.empty()) + return; - if (!normalEvents.empty()) - emit q->aboutToAddNewMessages(normalEvents); - const auto insertedSize = moveEventsToTimeline(normalEvents, Newer); - const auto from = timeline.cend() - insertedSize; - if (insertedSize > 0) + auto totalInserted = 0; + for (auto it = events.begin(); it != events.end();) { - qCDebug(MAIN) - << "Room" << displayname << "received" << insertedSize - << "new events; the last event is now" << timeline.back(); - q->onAddNewTimelineEvents(from); - } -#ifndef KEEP_REDACTIONS_IN_TIMELINE - for (const auto& r: redactions) - { - Q_ASSERT(isRedaction(r)); - processRedaction(eventCast<RedactionEvent>(r)); + auto nextPendingPair = findFirstOf(it, events.end(), + unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent); + auto nextPending = nextPendingPair.first; + + if (it != nextPending) + { + RoomEventsRange eventsSpan { it, nextPending }; + emit q->aboutToAddNewMessages(eventsSpan); + if (auto insertedSize = moveEventsToTimeline(eventsSpan, Newer)) + { + totalInserted += insertedSize; + q->onAddNewTimelineEvents(timeline.cend() - insertedSize); + } + emit q->addedMessages(); + } + if (nextPending == events.end()) + break; + + it = nextPending + 1; + emit q->pendingEventAboutToMerge(nextPending->get(), + nextPendingPair.second - unsyncedEvents.begin()); + unsyncedEvents.erase(nextPendingPair.second); + if (auto insertedSize = moveEventsToTimeline({nextPending, it}, Newer)) + { + totalInserted += insertedSize; + q->onAddNewTimelineEvents(timeline.cend() - insertedSize); + } + emit q->pendingEventMerged(); } -#endif - if (insertedSize > 0) + + if (totalInserted > 0) { - emit q->addedMessages(); + qCDebug(MAIN) + << "Room" << displayname << "received" << totalInserted + << "new events; the last event is now" << timeline.back(); + const auto from = timeline.cend() - totalInserted; // The first event in the just-added batch (referred to by `from`) // defines whose read marker can possibly be promoted any further over // the same author's events newly arrived. Others will need explicit @@ -1437,7 +1529,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events) updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); } - Q_ASSERT(timeline.size() == timelineSize + insertedSize); + Q_ASSERT(timeline.size() == timelineSize + totalInserted); } void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) @@ -1445,18 +1537,11 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) const auto timelineSize = timeline.size(); dropDuplicateEvents(events); -#ifndef KEEP_REDACTIONS_IN_TIMELINE - const auto redactionsBegin = - remove_if(events.begin(), events.end(), isRedaction); - RoomEventsRange normalEvents { events.begin(), redactionsBegin }; -#else - RoomEventsRange normalEvents { events }; -#endif - if (normalEvents.empty()) + if (events.empty()) return; - emit q->aboutToAddHistoricalMessages(normalEvents); - const auto insertedSize = moveEventsToTimeline(normalEvents, Older); + emit q->aboutToAddHistoricalMessages(events); + const auto insertedSize = moveEventsToTimeline(events, Older); const auto from = timeline.crend() - insertedSize; qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize @@ -1648,7 +1733,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()); @@ -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 @@ -346,14 +347,18 @@ namespace QMatrixClient void setJoinState( JoinState state ); public slots: - void postMessage(const QString& plainText, - MessageEventType type = MessageEventType::Text); - void postHtmlMessage(const QString& plainText, const QString& htmlText, - MessageEventType type = MessageEventType::Text); - void postMessage(const RoomMessageEvent& event); + QString postMessage(const QString& plainText, + MessageEventType type = MessageEventType::Text); + QString postHtmlMessage( + const QString& plainText, const QString& htmlText, + MessageEventType type = MessageEventType::Text); + /** Post a pre-created room message event; takes ownership of the event */ + QString postMessage(RoomEvent* event); + QString 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); + QString postMessage(const QString& type, const QString& plainText); void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); void setTopic(const QString& newTopic); @@ -384,6 +389,12 @@ namespace QMatrixClient void aboutToAddHistoricalMessages(RoomEventsRange events); void aboutToAddNewMessages(RoomEventsRange events); void addedMessages(); + void pendingEventAboutToAdd(); + void pendingEventAdded(); + void pendingEventAboutToMerge(RoomEvent* serverEvent, + int pendingEventIndex); + void pendingEventMerged(); + void pendingEventChanged(int pendingEventIndex); /** * @brief The room name, the canonical alias or other aliases changed @@ -191,6 +191,24 @@ namespace QMatrixClient iterator to; }; + /** A replica of std::find_first_of that returns a pair of iterators + * + * Convenient for cases when you need to know which particular "first of" + * [sFirst, sLast) has been found in [first, last). + */ + template<typename InputIt, typename ForwardIt, typename Pred> + inline std::pair<InputIt, ForwardIt> findFirstOf( + InputIt first, InputIt last, ForwardIt sFirst, ForwardIt sLast, + Pred pred) + { + for (; first != last; ++first) + for (auto it = sFirst; it != sLast; ++it) + if (pred(*first, *it)) + return std::make_pair(first, it); + + return std::make_pair(last, sLast); + } + /** A guard pointer that disconnects an interested object upon destruction * It's almost QPointer<> except that you have to initialise it with one * more additional parameter - a pointer to a QObject that will be diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index d3858de2..2f6a701a 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -33,7 +33,6 @@ HEADERS += \ $$SRCPATH/events/eventloader.h \ $$SRCPATH/jobs/requestdata.h \ $$SRCPATH/jobs/basejob.h \ - $$SRCPATH/jobs/sendeventjob.h \ $$SRCPATH/jobs/syncjob.h \ $$SRCPATH/jobs/mediathumbnailjob.h \ $$SRCPATH/jobs/downloadfilejob.h \ @@ -65,7 +64,6 @@ SOURCES += \ $$SRCPATH/events/directchatevent.cpp \ $$SRCPATH/jobs/requestdata.cpp \ $$SRCPATH/jobs/basejob.cpp \ - $$SRCPATH/jobs/sendeventjob.cpp \ $$SRCPATH/jobs/syncjob.cpp \ $$SRCPATH/jobs/mediathumbnailjob.cpp \ $$SRCPATH/jobs/downloadfilejob.cpp \ |