diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 9cae4608..6b0057a4 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -332,16 +332,18 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
connect(
this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents);
connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) {
- pending.remove(txn_id);
+ pending.removeOne(txn_id);
failed.insert(txn_id);
int idx = idToIndex(txn_id);
if (idx < 0) {
nhlog::ui()->warn("Failed index out of range");
return;
}
+ isProcessingPending = false;
emit dataChanged(index(idx, 0), index(idx, 0));
});
connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) {
+ pending.removeOne(txn_id);
int idx = idToIndex(txn_id);
if (idx < 0) {
nhlog::ui()->warn("Sent index out of range");
@@ -365,11 +367,19 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
// ask to be notified for read receipts
cache::client()->addPendingReceipt(room_id_, event_id);
+ isProcessingPending = false;
emit dataChanged(index(idx, 0), index(idx, 0));
+
+ if (pending.size() > 0)
+ emit nextPendingMessage();
});
connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) {
emit ChatPage::instance()->showNotification(msg);
});
+
+ connect(
+ this, &TimelineModel::nextPendingMessage, this, &TimelineModel::processOnePendingMessage);
+ connect(this, &TimelineModel::newMessageToSend, this, &TimelineModel::addPendingMessage);
}
QHash<int, QByteArray>
@@ -1035,6 +1045,7 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
} catch (const lmdb::error &e) {
nhlog::db()->critical(
"failed to save megolm outbound session: {}", e.what());
+ emit messageFailed(QString::fromStdString(txn_id));
}
});
@@ -1044,13 +1055,14 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
http::client()->query_keys(
req,
- [keeper = std::move(keeper), megolm_payload, this](
+ [keeper = std::move(keeper), megolm_payload, txn_id, this](
const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to query device keys: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
// TODO: Mark the event as failed. Communicate with the UI.
+ emit messageFailed(QString::fromStdString(txn_id));
return;
}
@@ -1150,9 +1162,11 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
} catch (const lmdb::error &e) {
nhlog::db()->critical(
"failed to open outbound megolm session ({}): {}", room_id, e.what());
+ emit messageFailed(QString::fromStdString(txn_id));
} catch (const mtx::crypto::olm_exception &e) {
nhlog::crypto()->critical(
"failed to open outbound megolm session ({}): {}", room_id, e.what());
+ emit messageFailed(QString::fromStdString(txn_id));
}
}
@@ -1241,3 +1255,82 @@ TimelineModel::handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
(void)keeper;
});
}
+
+struct SendMessageVisitor
+{
+ SendMessageVisitor(const QString &txn_id, TimelineModel *model)
+ : txn_id_qstr_(txn_id)
+ , model_(model)
+ {}
+
+ template<typename T>
+ void operator()(const mtx::events::Event<T> &)
+ {}
+
+ template<typename T,
+ std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
+ void operator()(const mtx::events::RoomEvent<T> &msg)
+
+ {
+ if (cache::client()->isRoomEncrypted(model_->room_id_.toStdString())) {
+ model_->sendEncryptedMessage(txn_id_qstr_.toStdString(),
+ nlohmann::json(msg.content));
+ } else {
+ QString txn_id_qstr = txn_id_qstr_;
+ TimelineModel *model = model_;
+ http::client()->send_room_message<T, mtx::events::EventType::RoomMessage>(
+ model->room_id_.toStdString(),
+ txn_id_qstr.toStdString(),
+ msg.content,
+ [txn_id_qstr, model](const mtx::responses::EventId &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ const int status_code =
+ static_cast<int>(err->status_code);
+ nhlog::net()->warn("[{}] failed to send message: {} {}",
+ txn_id_qstr.toStdString(),
+ err->matrix_error.error,
+ status_code);
+ emit model->messageFailed(txn_id_qstr);
+ }
+ emit model->messageSent(
+ txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
+ });
+ }
+ }
+
+ QString txn_id_qstr_;
+ TimelineModel *model_;
+};
+
+void
+TimelineModel::processOnePendingMessage()
+{
+ if (isProcessingPending || pending.isEmpty())
+ return;
+
+ isProcessingPending = true;
+
+ QString txn_id_qstr = pending.first();
+
+ boost::apply_visitor(SendMessageVisitor{txn_id_qstr, this}, events.value(txn_id_qstr));
+}
+
+void
+TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
+{
+ internalAddEvents({event});
+
+ QString txn_id_qstr =
+ boost::apply_visitor([](const auto &e) -> QString { return eventId(e); }, event);
+ beginInsertRows(QModelIndex(),
+ static_cast<int>(this->eventOrder.size()),
+ static_cast<int>(this->eventOrder.size()));
+ pending.push_back(txn_id_qstr);
+ this->eventOrder.insert(this->eventOrder.end(), txn_id_qstr);
+ endInsertRows();
+ updateLastMessage();
+
+ if (!isProcessingPending)
+ emit nextPendingMessage();
+}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 31e41315..e7842b99 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -173,6 +173,8 @@ public slots:
private slots:
// Add old events at the top of the timeline.
void addBackwardsEvents(const mtx::responses::Messages &msgs);
+ void processOnePendingMessage();
+ void addPendingMessage(mtx::events::collections::TimelineEvents event);
signals:
void oldMessagesRetrieved(const mtx::responses::Messages &res);
@@ -181,6 +183,8 @@ signals:
void currentIndexChanged(int index);
void redactionFailed(QString id);
void eventRedacted(QString id);
+ void nextPendingMessage();
+ void newMessageToSend(mtx::events::collections::TimelineEvents event);
private:
DecryptionResult decryptEvent(
@@ -198,7 +202,8 @@ private:
void readEvent(const std::string &id);
QHash<QString, mtx::events::collections::TimelineEvents> events;
- QSet<QString> pending, failed, read;
+ QSet<QString> failed, read;
+ QList<QString> pending;
std::vector<QString> eventOrder;
QString room_id_;
@@ -206,11 +211,14 @@ private:
bool isInitialSync = true;
bool paginationInProgress = false;
+ bool isProcessingPending = false;
QHash<QString, QColor> userColors;
QString currentId;
TimelineViewManager *manager_;
+
+ friend struct SendMessageVisitor;
};
template<class T>
@@ -224,35 +232,6 @@ TimelineModel::sendMessage(const T &msg)
msgCopy.event_id = txn_id;
msgCopy.sender = http::client()->user_id().to_string();
msgCopy.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- internalAddEvents({msgCopy});
-
- QString txn_id_qstr = QString::fromStdString(txn_id);
- beginInsertRows(QModelIndex(),
- static_cast<int>(this->eventOrder.size()),
- static_cast<int>(this->eventOrder.size()));
- pending.insert(txn_id_qstr);
- this->eventOrder.insert(this->eventOrder.end(), txn_id_qstr);
- endInsertRows();
- updateLastMessage();
- if (cache::client()->isRoomEncrypted(room_id_.toStdString()))
- sendEncryptedMessage(txn_id, nlohmann::json(msg));
- else
- http::client()->send_room_message<T, mtx::events::EventType::RoomMessage>(
- room_id_.toStdString(),
- txn_id,
- msg,
- [this, txn_id, txn_id_qstr](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code = static_cast<int>(err->status_code);
- nhlog::net()->warn("[{}] failed to send message: {} {}",
- txn_id,
- err->matrix_error.error,
- status_code);
- emit messageFailed(txn_id_qstr);
- }
- emit messageSent(txn_id_qstr,
- QString::fromStdString(res.event_id.to_string()));
- });
+ emit newMessageToSend(msgCopy);
}
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index d733ad90..06c42a39 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -97,6 +97,9 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
addRoom(QString::fromStdString(it->first));
models.value(QString::fromStdString(it->first))->addEvents(it->second.timeline);
}
+
+ this->isInitialSync_ = false;
+ emit initialSyncChanged(false);
}
void
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 691c8ddb..0bc58e68 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -12,10 +12,6 @@
#include "TimelineModel.h"
#include "Utils.h"
-// temporary for stubs
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-
class MxcImageProvider;
class ColorImageProvider;
@@ -25,6 +21,8 @@ class TimelineViewManager : public QObject
Q_PROPERTY(
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
+ Q_PROPERTY(
+ bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
public:
TimelineViewManager(QWidget *parent = 0);
@@ -36,6 +34,7 @@ public:
void clearAll() { models.clear(); }
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
+ Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
void openImageOverlay(QString mxcUrl,
QString originalFilename,
QString mimeType,
@@ -66,6 +65,7 @@ signals:
void clearRoomMessageCount(QString roomid);
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
void activeTimelineChanged(TimelineModel *timeline);
+ void initialSyncChanged(bool isInitialSync);
void mediaCached(QString mxcUrl, QString cacheUrl);
public slots:
@@ -107,11 +107,11 @@ private:
QQuickWidget *view;
#endif
QWidget *container;
- TimelineModel *timeline_ = nullptr;
+
MxcImageProvider *imgProvider;
ColorImageProvider *colorImgProvider;
QHash<QString, QSharedPointer<TimelineModel>> models;
+ TimelineModel *timeline_ = nullptr;
+ bool isInitialSync_ = true;
};
-
-#pragma GCC diagnostic pop
|