diff --git a/src/AvatarProvider.cc b/src/AvatarProvider.cc
index 49e52a82..ad095023 100644
--- a/src/AvatarProvider.cc
+++ b/src/AvatarProvider.cc
@@ -16,17 +16,17 @@
*/
#include <QBuffer>
-#include <QtConcurrent>
+#include <memory>
#include "AvatarProvider.h"
#include "Cache.h"
+#include "Logging.hpp"
#include "MatrixClient.h"
+namespace AvatarProvider {
+
void
-AvatarProvider::resolve(const QString &room_id,
- const QString &user_id,
- QObject *receiver,
- std::function<void(QImage)> callback)
+resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback)
{
const auto key = QString("%1 %2").arg(room_id).arg(user_id);
const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
@@ -43,24 +43,30 @@ AvatarProvider::resolve(const QString &room_id,
return;
}
- auto proxy = http::client()->fetchUserAvatar(avatarUrl);
+ auto proxy = std::make_shared<AvatarProxy>();
+ QObject::connect(proxy.get(),
+ &AvatarProxy::avatarDownloaded,
+ receiver,
+ [callback](const QByteArray &data) { callback(QImage::fromData(data)); });
- if (proxy.isNull())
- return;
+ mtx::http::ThumbOpts opts;
+ opts.mxc_url = avatarUrl.toStdString();
- connect(proxy.data(),
- &DownloadMediaProxy::avatarDownloaded,
- receiver,
- [user_id, proxy, callback, avatarUrl](const QImage &img) {
- proxy->deleteLater();
- QtConcurrent::run([img, avatarUrl]() {
- QByteArray data;
- QBuffer buffer(&data);
- buffer.open(QIODevice::WriteOnly);
- img.save(&buffer, "PNG");
+ http::v2::client()->get_thumbnail(
+ opts,
+ [opts, proxy = std::move(proxy)](const std::string &res, mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn("failed to download avatar: {} - ({} {})",
+ opts.mxc_url,
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
- cache::client()->saveImage(avatarUrl, data);
- });
- callback(img);
- });
+ cache::client()->saveImage(opts.mxc_url, res);
+
+ auto data = QByteArray(res.data(), res.size());
+ emit proxy->avatarDownloaded(data);
+ });
+}
}
diff --git a/src/Cache.cc b/src/Cache.cc
index c055ab05..2a555425 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -19,7 +19,6 @@
#include <stdexcept>
#include <QByteArray>
-#include <QDebug>
#include <QFile>
#include <QHash>
#include <QStandardPaths>
@@ -27,6 +26,7 @@
#include <variant.hpp>
#include "Cache.h"
+#include "Logging.hpp"
#include "Utils.h"
//! Should be changed when a breaking change occurs in the cache format.
@@ -62,6 +62,14 @@ namespace cache {
void
init(const QString &user_id)
{
+ qRegisterMetaType<SearchResult>();
+ qRegisterMetaType<QVector<SearchResult>>();
+ qRegisterMetaType<RoomMember>();
+ qRegisterMetaType<RoomSearchResult>();
+ qRegisterMetaType<RoomInfo>();
+ qRegisterMetaType<QMap<QString, RoomInfo>>();
+ qRegisterMetaType<std::map<QString, RoomInfo>>();
+
if (!instance_)
instance_ = std::make_unique<Cache>(user_id);
}
@@ -88,7 +96,7 @@ Cache::Cache(const QString &userId, QObject *parent)
void
Cache::setup()
{
- qDebug() << "Setting up cache";
+ log::db()->debug("setting up cache");
auto statePath = QString("%1/%2/state")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
@@ -105,7 +113,7 @@ Cache::setup()
env_.set_max_dbs(1024UL);
if (isInitial) {
- qDebug() << "First time initializing LMDB";
+ log::db()->info("initializing LMDB");
if (!QDir().mkpath(statePath)) {
throw std::runtime_error(
@@ -121,7 +129,7 @@ Cache::setup()
std::string(e.what()));
}
- qWarning() << "Resetting cache due to LMDB version mismatch:" << e.what();
+ log::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what());
QDir stateDir(statePath);
@@ -142,29 +150,34 @@ Cache::setup()
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
txn.commit();
-
- qRegisterMetaType<RoomInfo>();
}
void
-Cache::saveImage(const QString &url, const QByteArray &image)
+Cache::saveImage(const std::string &url, const std::string &img_data)
{
- auto key = url.toUtf8();
+ if (url.empty() || img_data.empty())
+ return;
try {
auto txn = lmdb::txn::begin(env_);
lmdb::dbi_put(txn,
mediaDb_,
- lmdb::val(key.data(), key.size()),
- lmdb::val(image.data(), image.size()));
+ lmdb::val(url.data(), url.size()),
+ lmdb::val(img_data.data(), img_data.size()));
txn.commit();
} catch (const lmdb::error &e) {
- qCritical() << "saveImage:" << e.what();
+ log::db()->critical("saveImage: {}", e.what());
}
}
+void
+Cache::saveImage(const QString &url, const QByteArray &image)
+{
+ saveImage(url.toStdString(), std::string(image.constData(), image.length()));
+}
+
QByteArray
Cache::image(lmdb::txn &txn, const std::string &url) const
{
@@ -180,7 +193,7 @@ Cache::image(lmdb::txn &txn, const std::string &url) const
return QByteArray(image.data(), image.size());
} catch (const lmdb::error &e) {
- qCritical() << "image:" << e.what() << QString::fromStdString(url);
+ log::db()->critical("image: {}, {}", e.what(), url);
}
return QByteArray();
@@ -208,7 +221,7 @@ Cache::image(const QString &url) const
return QByteArray(image.data(), image.size());
} catch (const lmdb::error &e) {
- qCritical() << "image:" << e.what() << url;
+ log::db()->critical("image: {} {}", e.what(), url.toStdString());
}
return QByteArray();
@@ -271,7 +284,7 @@ Cache::isInitialized() const
return res;
}
-QString
+std::string
Cache::nextBatchToken() const
{
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
@@ -281,13 +294,13 @@ Cache::nextBatchToken() const
txn.commit();
- return QString::fromUtf8(token.data(), token.size());
+ return std::string(token.data(), token.size());
}
void
Cache::deleteData()
{
- qInfo() << "Deleting cache data";
+ log::db()->info("deleting data");
if (!cacheDirectory_.isEmpty())
QDir(cacheDirectory_).removeRecursively();
@@ -309,8 +322,9 @@ Cache::isFormatValid()
std::string stored_version(current_version.data(), current_version.size());
if (stored_version != CURRENT_CACHE_FORMAT_VERSION) {
- qWarning() << "Stored format version" << QString::fromStdString(stored_version);
- qWarning() << "There are breaking changes in the cache format.";
+ log::db()->warn("breaking changes in the cache format. stored: {}, current: {}",
+ stored_version,
+ CURRENT_CACHE_FORMAT_VERSION);
return false;
}
@@ -360,7 +374,7 @@ Cache::readReceipts(const QString &event_id, const QString &room_id)
}
} catch (const lmdb::error &e) {
- qCritical() << "readReceipts:" << e.what();
+ log::db()->critical("readReceipts: {}", e.what());
}
return receipts;
@@ -410,7 +424,7 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
lmdb::val(merged_receipts.data(), merged_receipts.size()));
} catch (const lmdb::error &e) {
- qCritical() << "updateReadReceipts:" << e.what();
+ log::db()->critical("updateReadReceipts: {}", e.what());
}
}
}
@@ -568,9 +582,9 @@ Cache::singleRoomInfo(const std::string &room_id)
return tmp;
} catch (const json::exception &e) {
- qWarning()
- << "failed to parse room info:" << QString::fromStdString(room_id)
- << QString::fromStdString(std::string(data.data(), data.size()));
+ log::db()->warn("failed to parse room info: room_id ({}), {}",
+ room_id,
+ std::string(data.data(), data.size()));
}
}
@@ -600,9 +614,9 @@ Cache::getRoomInfo(const std::vector<std::string> &rooms)
room_info.emplace(QString::fromStdString(room), std::move(tmp));
} catch (const json::exception &e) {
- qWarning()
- << "failed to parse room info:" << QString::fromStdString(room)
- << QString::fromStdString(std::string(data.data(), data.size()));
+ log::db()->warn("failed to parse room info: room_id ({}), {}",
+ room,
+ std::string(data.data(), data.size()));
}
} else {
// Check if the room is an invite.
@@ -615,10 +629,10 @@ Cache::getRoomInfo(const std::vector<std::string> &rooms)
room_info.emplace(QString::fromStdString(room),
std::move(tmp));
} catch (const json::exception &e) {
- qWarning() << "failed to parse room info for invite:"
- << QString::fromStdString(room)
- << QString::fromStdString(
- std::string(data.data(), data.size()));
+ log::db()->warn(
+ "failed to parse room info for invite: room_id ({}), {}",
+ room,
+ std::string(data.data(), data.size()));
}
}
}
@@ -703,7 +717,7 @@ Cache::getRoomAvatarUrl(lmdb::txn &txn,
return QString::fromStdString(msg.content.url);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.avatar event: {}", e.what());
}
}
@@ -726,7 +740,7 @@ Cache::getRoomAvatarUrl(lmdb::txn &txn,
cursor.close();
return QString::fromStdString(m.avatar_url);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse member info: {}", e.what());
}
}
@@ -753,7 +767,7 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
if (!msg.content.name.empty())
return QString::fromStdString(msg.content.name);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.name event: {}", e.what());
}
}
@@ -768,7 +782,8 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
if (!msg.content.alias.empty())
return QString::fromStdString(msg.content.alias);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.canonical_alias event: {}",
+ e.what());
}
}
@@ -784,7 +799,7 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
try {
members.emplace(user_id, json::parse(member_data));
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse member info: {}", e.what());
}
ii++;
@@ -828,7 +843,7 @@ Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
json::parse(std::string(event.data(), event.size()));
return msg.content.join_rule;
} catch (const json::exception &e) {
- qWarning() << e.what();
+ log::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
}
}
return JoinRule::Knock;
@@ -850,7 +865,7 @@ Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
json::parse(std::string(event.data(), event.size()));
return msg.content.guest_access == AccessState::CanJoin;
} catch (const json::exception &e) {
- qWarning() << e.what();
+ log::db()->warn("failed to parse m.room.guest_access event: {}", e.what());
}
}
return false;
@@ -874,7 +889,7 @@ Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
if (!msg.content.topic.empty())
return QString::fromStdString(msg.content.topic);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.topic event: {}", e.what());
}
}
@@ -897,7 +912,7 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.name);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.name event: {}", e.what());
}
}
@@ -914,7 +929,7 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
return QString::fromStdString(tmp.name);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse member info: {}", e.what());
}
}
@@ -939,7 +954,7 @@ Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &me
json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.url);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.avatar event: {}", e.what());
}
}
@@ -956,7 +971,7 @@ Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &me
return QString::fromStdString(tmp.avatar_url);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse member info: {}", e.what());
}
}
@@ -981,7 +996,7 @@ Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.topic);
} catch (const json::exception &e) {
- qWarning() << QString::fromStdString(e.what());
+ log::db()->warn("failed to parse m.room.topic event: {}", e.what());
}
}
@@ -1017,8 +1032,9 @@ Cache::getRoomAvatar(const std::string &room_id)
return QImage();
}
} catch (const json::exception &e) {
- qWarning() << "failed to parse room info" << e.what()
- << QString::fromStdString(std::string(response.data(), response.size()));
+ log::db()->warn("failed to parse room info: {}, {}",
+ e.what(),
+ std::string(response.data(), response.size()));
}
if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) {
@@ -1054,7 +1070,7 @@ void
Cache::populateMembers()
{
auto rooms = joinedRooms();
- qDebug() << "loading" << rooms.size() << "rooms";
+ log::db()->info("loading {} rooms", rooms.size());
auto txn = lmdb::txn::begin(env_);
@@ -1182,7 +1198,7 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
QString::fromStdString(tmp.name),
QImage::fromData(image(txn, tmp.avatar_url))});
} catch (const json::exception &e) {
- qWarning() << e.what();
+ log::db()->warn("{}", e.what());
}
currentIndex += 1;
@@ -1253,7 +1269,7 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
std::min(min_event_level,
(uint16_t)msg.content.state_level(to_string(ty)));
} catch (const json::exception &e) {
- qWarning() << "hasEnoughPowerLevel: " << e.what();
+ log::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
}
}
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 9ae860fb..64ce69d6 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -16,13 +16,13 @@
*/
#include <QApplication>
-#include <QDebug>
#include <QSettings>
#include <QtConcurrent>
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
+#include "Logging.hpp"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "OverlayModal.h"
@@ -43,13 +43,12 @@
#include "dialogs/ReadReceipts.h"
#include "timeline/TimelineViewManager.h"
-constexpr int SYNC_RETRY_TIMEOUT = 40 * 1000;
-constexpr int INITIAL_SYNC_RETRY_TIMEOUT = 240 * 1000;
-
-ChatPage *ChatPage::instance_ = nullptr;
+ChatPage *ChatPage::instance_ = nullptr;
+constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent)
+ , isConnected_(true)
, userSettings_{userSettings}
{
setObjectName("chatPage");
@@ -78,13 +77,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
sidebarActions_ = new SideBarActions(this);
connect(
sidebarActions_, &SideBarActions::showSettings, this, &ChatPage::showUserSettingsPage);
- connect(
- sidebarActions_, &SideBarActions::joinRoom, http::client(), &MatrixClient::joinRoom);
- connect(
- sidebarActions_, &SideBarActions::createRoom, http::client(), &MatrixClient::createRoom);
+ connect(sidebarActions_, &SideBarActions::joinRoom, this, &ChatPage::joinRoom);
+ connect(sidebarActions_, &SideBarActions::createRoom, this, &ChatPage::createRoom);
user_info_widget_ = new UserInfoWidget(sideBar_);
room_list_ = new RoomList(userSettings_, sideBar_);
+ connect(room_list_, &RoomList::joinRoom, this, &ChatPage::joinRoom);
sideBarLayout_->addWidget(user_info_widget_);
sideBarLayout_->addWidget(room_list_);
@@ -107,6 +105,11 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
contentLayout_->addWidget(top_bar_);
contentLayout_->addWidget(view_manager_);
+ connect(this,
+ &ChatPage::removeTimelineEvent,
+ view_manager_,
+ &TimelineViewManager::removeTimelineEvent);
+
// Splitter
splitter->addWidget(sideBar_);
splitter->addWidget(content_);
@@ -120,16 +123,81 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
typingRefresher_ = new QTimer(this);
typingRefresher_->setInterval(TYPING_REFRESH_TIMEOUT);
+ connect(this, &ChatPage::connectionLost, this, [this]() {
+ log::net()->info("connectivity lost");
+ isConnected_ = false;
+ http::v2::client()->shutdown();
+ text_input_->disableInput();
+ });
+ connect(this, &ChatPage::connectionRestored, this, [this]() {
+ log::net()->info("trying to re-connect");
+ text_input_->enableInput();
+ isConnected_ = true;
+
+ // Drop all pending connections.
+ http::v2::client()->shutdown();
+ trySync();
+ });
+
+ connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
+ connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
+ if (http::v2::client()->access_token().empty()) {
+ connectivityTimer_.stop();
+ return;
+ }
+
+ http::v2::client()->versions(
+ [this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
+ if (err) {
+ emit connectionLost();
+ return;
+ }
+
+ if (!isConnected_)
+ emit connectionRestored();
+ });
+ });
+
+ connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
connect(user_info_widget_, &UserInfoWidget::logout, this, [this]() {
- http::client()->logout();
+ http::v2::client()->logout([this](const mtx::responses::Logout &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ // TODO: handle special errors
+ emit contentLoaded();
+ log::net()->warn("failed to logout: {} - {}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
+
+ emit loggedOut();
+ });
+
emit showOverlayProgressBar();
});
- connect(http::client(), &MatrixClient::loggedOut, this, &ChatPage::logout);
connect(top_bar_, &TopRoomBar::inviteUsers, this, [this](QStringList users) {
+ const auto room_id = current_room_.toStdString();
+
for (int ii = 0; ii < users.size(); ++ii) {
- QTimer::singleShot(ii * 1000, this, [this, ii, users]() {
- http::client()->inviteUser(current_room_, users.at(ii));
+ QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() {
+ const auto user = users.at(ii);
+
+ http::v2::client()->invite_user(
+ room_id,
+ user.toStdString(),
+ [this, user](const mtx::responses::RoomInvite &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit showNotification(
+ QString("Failed to invite user: %1").arg(user));
+ return;
+ }
+
+ emit showNotification(
+ QString("Invited user: %1").arg(user));
+ });
});
}
});
@@ -155,36 +223,30 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect(room_list_, &RoomList::acceptInvite, this, [this](const QString &room_id) {
view_manager_->addRoom(room_id);
- http::client()->joinRoom(room_id);
+ joinRoom(room_id);
room_list_->removeRoom(room_id, currentRoom() == room_id);
});
connect(room_list_, &RoomList::declineInvite, this, [this](const QString &room_id) {
- http::client()->leaveRoom(room_id);
+ leaveRoom(room_id);
room_list_->removeRoom(room_id, currentRoom() == room_id);
});
- connect(text_input_, &TextInputWidget::startedTyping, this, [this]() {
- if (!userSettings_->isTypingNotificationsEnabled())
- return;
-
- typingRefresher_->start();
- http::client()->sendTypingNotification(current_room_);
- });
-
+ connect(
+ text_input_, &TextInputWidget::startedTyping, this, &ChatPage::sendTypingNotifications);
+ connect(typingRefresher_, &QTimer::timeout, this, &ChatPage::sendTypingNotifications);
connect(text_input_, &TextInputWidget::stoppedTyping, this, [this]() {
if (!userSettings_->isTypingNotificationsEnabled())
return;
typingRefresher_->stop();
- http::client()->removeTypingNotification(current_room_);
- });
-
- connect(typingRefresher_, &QTimer::timeout, this, [this]() {
- if (!userSettings_->isTypingNotificationsEnabled())
- return;
-
- http::client()->sendTypingNotification(current_room_);
+ http::v2::client()->stop_typing(
+ current_room_.toStdString(), [](mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn("failed to stop typing notifications: {}",
+ err->matrix_error.error);
+ }
+ });
});
connect(view_manager_,
@@ -207,142 +269,242 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
view_manager_,
SLOT(queueEmoteMessage(const QString &)));
- connect(text_input_,
- &TextInputWidget::sendJoinRoomRequest,
- http::client(),
- &MatrixClient::joinRoom);
+ connect(text_input_, &TextInputWidget::sendJoinRoomRequest, this, &ChatPage::joinRoom);
connect(text_input_,
&TextInputWidget::uploadImage,
this,
- [this](QSharedPointer<QIODevice> data, const QString &fn) {
- http::client()->uploadImage(current_room_, fn, data);
+ [this](QSharedPointer<QIODevice> dev, const QString &fn) {
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForData(dev.data());
+
+ if (!dev->open(QIODevice::ReadOnly)) {
+ emit uploadFailed(
+ QString("Error while reading media: %1").arg(dev->errorString()));
+ return;
+ }
+
+ auto bin = dev->readAll();
+ auto payload = std::string(bin.data(), bin.size());
+
+ http::v2::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fn).fileName().toStdString(),
+ [this,
+ room_id = current_room_,
+ filename = fn,
+ mime = mime.name(),
+ size = payload.size()](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit uploadFailed(
+ tr("Failed to upload image. Please try again."));
+ log::net()->warn("failed to upload image: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit imageUploaded(room_id,
+ filename,
+ QString::fromStdString(res.content_uri),
+ mime,
+ size);
+ });
});
connect(text_input_,
&TextInputWidget::uploadFile,
this,
- [this](QSharedPointer<QIODevice> data, const QString &fn) {
- http::client()->uploadFile(current_room_, fn, data);
+ [this](QSharedPointer<QIODevice> dev, const QString &fn) {
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForData(dev.data());
+
+ if (!dev->open(QIODevice::ReadOnly)) {
+ emit uploadFailed(
+ QString("Error while reading media: %1").arg(dev->errorString()));
+ return;
+ }
+
+ auto bin = dev->readAll();
+ auto payload = std::string(bin.data(), bin.size());
+
+ http::v2::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fn).fileName().toStdString(),
+ [this,
+ room_id = current_room_,
+ filename = fn,
+ mime = mime.name(),
+ size = payload.size()](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit uploadFailed(
+ tr("Failed to upload file. Please try again."));
+ log::net()->warn("failed to upload file: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit fileUploaded(room_id,
+ filename,
+ QString::fromStdString(res.content_uri),
+ mime,
+ size);
+ });
});
connect(text_input_,
&TextInputWidget::uploadAudio,
this,
- [this](QSharedPointer<QIODevice> data, const QString &fn) {
- http::client()->uploadAudio(current_room_, fn, data);
+ [this](QSharedPointer<QIODevice> dev, const QString &fn) {
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForData(dev.data());
+
+ if (!dev->open(QIODevice::ReadOnly)) {
+ emit uploadFailed(
+ QString("Error while reading media: %1").arg(dev->errorString()));
+ return;
+ }
+
+ auto bin = dev->readAll();
+ auto payload = std::string(bin.data(), bin.size());
+
+ http::v2::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fn).fileName().toStdString(),
+ [this,
+ room_id = current_room_,
+ filename = fn,
+ mime = mime.name(),
+ size = payload.size()](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit uploadFailed(
+ tr("Failed to upload audio. Please try again."));
+ log::net()->warn("failed to upload audio: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit audioUploaded(room_id,
+ filename,
+ QString::fromStdString(res.content_uri),
+ mime,
+ size);
+ });
});
connect(text_input_,
&TextInputWidget::uploadVideo,
this,
- [this](QSharedPointer<QIODevice> data, const QString &fn) {
- http::client()->uploadVideo(current_room_, fn, data);
+ [this](QSharedPointer<QIODevice> dev, const QString &fn) {
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForData(dev.data());
+
+ if (!dev->open(QIODevice::ReadOnly)) {
+ emit uploadFailed(
+ QString("Error while reading media: %1").arg(dev->errorString()));
+ return;
+ }
+
+ auto bin = dev->readAll();
+ auto payload = std::string(bin.data(), bin.size());
+
+ http::v2::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fn).fileName().toStdString(),
+ [this,
+ room_id = current_room_,
+ filename = fn,
+ mime = mime.name(),
+ size = payload.size()](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit uploadFailed(
+ tr("Failed to upload video. Please try again."));
+ log::net()->warn("failed to upload video: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit videoUploaded(room_id,
+ filename,
+ QString::fromStdString(res.content_uri),
+ mime,
+ size);
+ });
});
- connect(
- http::client(), &MatrixClient::roomCreationFailed, this, &ChatPage::showNotification);
- connect(http::client(), &MatrixClient::joinFailed, this, &ChatPage::showNotification);
- connect(http::client(), &MatrixClient::uploadFailed, this, [this](int, const QString &msg) {
+ connect(this, &ChatPage::uploadFailed, this, [this](const QString &msg) {
text_input_->hideUploadSpinner();
emit showNotification(msg);
});
- connect(
- http::client(),
- &MatrixClient::imageUploaded,
- this,
- [this](QString roomid, QString filename, QString url, QString mime, uint64_t dsize) {
- text_input_->hideUploadSpinner();
- view_manager_->queueImageMessage(roomid, filename, url, mime, dsize);
- });
- connect(
- http::client(),
- &MatrixClient::fileUploaded,
- this,
- [this](QString roomid, QString filename, QString url, QString mime, uint64_t dsize) {
- text_input_->hideUploadSpinner();
- view_manager_->queueFileMessage(roomid, filename, url, mime, dsize);
- });
- connect(
- http::client(),
- &MatrixClient::audioUploaded,
- this,
- [this](QString roomid, QString filename, QString url, QString mime, uint64_t dsize) {
- text_input_->hideUploadSpinner();
- view_manager_->queueAudioMessage(roomid, filename, url, mime, dsize);
- });
- connect(
- http::client(),
- &MatrixClient::videoUploaded,
- this,
- [this](QString roomid, QString filename, QString url, QString mime, uint64_t dsize) {
- text_input_->hideUploadSpinner();
- view_manager_->queueVideoMessage(roomid, filename, url, mime, dsize);
- });
-
- connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
-
- connect(http::client(),
- &MatrixClient::initialSyncCompleted,
- this,
- &ChatPage::initialSyncCompleted);
- connect(
- http::client(), &MatrixClient::initialSyncFailed, this, &ChatPage::retryInitialSync);
- connect(http::client(), &MatrixClient::syncCompleted, this, &ChatPage::syncCompleted);
- connect(http::client(),
- &MatrixClient::getOwnProfileResponse,
+ connect(this,
+ &ChatPage::imageUploaded,
this,
- &ChatPage::updateOwnProfileInfo);
- connect(http::client(),
- SIGNAL(getOwnCommunitiesResponse(QList<QString>)),
+ [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
+ text_input_->hideUploadSpinner();
+ view_manager_->queueImageMessage(roomid, filename, url, mime, dsize);
+ });
+ connect(this,
+ &ChatPage::fileUploaded,
this,
- SLOT(updateOwnCommunitiesInfo(QList<QString>)));
- connect(http::client(),
- &MatrixClient::communityProfileRetrieved,
+ [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
+ text_input_->hideUploadSpinner();
+ view_manager_->queueFileMessage(roomid, filename, url, mime, dsize);
+ });
+ connect(this,
+ &ChatPage::audioUploaded,
this,
- [this](QString communityId, QJsonObject profile) {
- communities_[communityId]->parseProfile(profile);
+ [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
+ text_input_->hideUploadSpinner();
+ view_manager_->queueAudioMessage(roomid, filename, url, mime, dsize);
});
- connect(http::client(),
- &MatrixClient::communityRoomsRetrieved,
+ connect(this,
+ &ChatPage::videoUploaded,
this,
- [this](QString communityId, QJsonObject rooms) {
- communities_[communityId]->parseRooms(rooms);
-
- if (communityId == current_community_) {
- if (communityId == "world") {
- room_list_->setFilterRooms(false);
- } else {
- room_list_->setRoomFilter(
- communities_[communityId]->getRoomList());
- }
- }
+ [this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
+ text_input_->hideUploadSpinner();
+ view_manager_->queueVideoMessage(roomid, filename, url, mime, dsize);
});
- connect(http::client(), &MatrixClient::joinedRoom, this, [this](const QString &room_id) {
- emit showNotification("You joined the room.");
+ connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
- // We remove any invites with the same room_id.
- try {
- cache::client()->removeInvite(room_id.toStdString());
- } catch (const lmdb::error &e) {
- emit showNotification(QString("Failed to remove invite: %1")
- .arg(QString::fromStdString(e.what())));
- }
- });
- connect(http::client(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
- connect(http::client(), &MatrixClient::invitedUser, this, [this](QString, QString user) {
- emit showNotification(QString("Invited user %1").arg(user));
- });
- connect(http::client(), &MatrixClient::roomCreated, this, [this](QString room_id) {
- emit showNotification(QString("Room %1 created").arg(room_id));
- });
- connect(http::client(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
- emit showNotification(QString("Message redaction failed: %1").arg(error));
- });
- connect(http::client(),
- &MatrixClient::notificationsRetrieved,
- this,
- &ChatPage::sendDesktopNotifications);
+ // connect(http::client(),
+ // SIGNAL(getOwnCommunitiesResponse(QList<QString>)),
+ // this,
+ // SLOT(updateOwnCommunitiesInfo(QList<QString>)));
+ // connect(http::client(),
+ // &MatrixClient::communityProfileRetrieved,
+ // this,
+ // [this](QString communityId, QJsonObject profile) {
+ // communities_[communityId]->parseProfile(profile);
+ // });
+ // connect(http::client(),
+ // &MatrixClient::communityRoomsRetrieved,
+ // this,
+ // [this](QString communityId, QJsonObject rooms) {
+ // communities_[communityId]->parseRooms(rooms);
+
+ // if (communityId == current_community_) {
+ // if (communityId == "world") {
+ // room_list_->setFilterRooms(false);
+ // } else {
+ // room_list_->setRoomFilter(
+ // communities_[communityId]->getRoomList());
+ // }
+ // }
+ // });
+
+ connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
+ connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendDesktopNotifications);
showContentTimer_ = new QTimer(this);
showContentTimer_->setSingleShot(true);
@@ -361,20 +523,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
}
});
- initialSyncTimer_ = new QTimer(this);
- connect(initialSyncTimer_, &QTimer::timeout, this, [this]() { retryInitialSync(); });
-
- syncTimeoutTimer_ = new QTimer(this);
- connect(syncTimeoutTimer_, &QTimer::timeout, this, [this]() {
- if (http::client()->getHomeServer().isEmpty()) {
- syncTimeoutTimer_->stop();
- return;
- }
-
- qDebug() << "Sync took too long. Retrying...";
- http::client()->sync();
- });
-
connect(communitiesList_,
&CommunitiesList::communityChanged,
this,
@@ -394,12 +542,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
this,
&ChatPage::setGroupViewState);
- connect(this, &ChatPage::continueSync, this, [this](const QString &next_batch) {
- syncTimeoutTimer_->start(SYNC_RETRY_TIMEOUT);
- http::client()->setNextBatchToken(next_batch);
- http::client()->sync();
- });
-
connect(this, &ChatPage::startConsesusTimer, this, [this]() {
consensusTimer_->start(CONSENSUS_TIMEOUT);
showContentTimer_->start(SHOW_CONTENT_TIMEOUT);
@@ -418,7 +560,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
try {
room_list_->cleanupInvites(cache::client()->invites());
} catch (const lmdb::error &e) {
- qWarning() << "failed to retrieve invites" << e.what();
+ log::db()->error("failed to retrieve invites: {}", e.what());
}
view_manager_->initialize(rooms);
@@ -437,7 +579,20 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
}
if (hasNotifications)
- http::client()->getNotifications();
+ http::v2::client()->notifications(
+ 5,
+ [this](const mtx::responses::Notifications &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn(
+ "failed to retrieve notifications: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit notificationsRetrieved(std::move(res));
+ });
});
connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
connect(
@@ -446,12 +601,21 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
changeTopRoomInfo(currentRoom());
});
- instance_ = this;
+ // Callbacks to update the user info (top left corner of the page).
+ connect(this, &ChatPage::setUserAvatar, user_info_widget_, &UserInfoWidget::setAvatar);
+ connect(this, &ChatPage::setUserDisplayName, this, [this](const QString &name) {
+ QSettings settings;
+ auto userid = settings.value("auth/user_id").toString();
+ user_info_widget_->setUserId(userid);
+ user_info_widget_->setDisplayName(name);
+ });
+
+ connect(this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync);
+ connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync);
- qRegisterMetaType<std::map<QString, RoomInfo>>();
- qRegisterMetaType<QMap<QString, RoomInfo>>();
- qRegisterMetaType<mtx::responses::Rooms>();
- qRegisterMetaType<std::vector<std::string>>();
+ connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
+
+ instance_ = this;
}
void
@@ -462,6 +626,19 @@ ChatPage::logout()
resetUI();
emit closing();
+ connectivityTimer_.stop();
+}
+
+void
+ChatPage::dropToLoginPage(const QString &msg)
+{
+ deleteConfigs();
+ resetUI();
+
+ http::v2::client()->shutdown();
+ connectivityTimer_.stop();
+
+ emit showLoginPage(msg);
}
void
@@ -490,17 +667,66 @@ ChatPage::deleteConfigs()
settings.endGroup();
cache::client()->deleteData();
-
- http::client()->reset();
+ http::v2::client()->clear();
}
void
ChatPage::bootstrap(QString userid, QString homeserver, QString token)
{
- http::client()->setServer(homeserver);
- http::client()->setAccessToken(token);
- http::client()->getOwnProfile();
- http::client()->getOwnCommunities();
+ using namespace mtx::identifiers;
+
+ try {
+ http::v2::client()->set_user(parse<User>(userid.toStdString()));
+ } catch (const std::invalid_argument &e) {
+ log::main()->critical("bootstrapped with invalid user_id: {}",
+ userid.toStdString());
+ }
+
+ http::v2::client()->set_server(homeserver.toStdString());
+ http::v2::client()->set_access_token(token.toStdString());
+ http::v2::client()->get_profile(
+ userid.toStdString(),
+ [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn("failed to retrieve own profile info");
+ return;
+ }
+
+ emit setUserDisplayName(QString::fromStdString(res.display_name));
+
+ if (cache::client()) {
+ auto data = cache::client()->image(res.avatar_url);
+ if (!data.isNull()) {
+ emit setUserAvatar(QImage::fromData(data));
+ return;
+ }
+ }
+
+ if (res.avatar_url.empty())
+ return;
+
+ http::v2::client()->download(
+ res.avatar_url,
+ [this, res](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn(
+ "failed to download user avatar: {} - {}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
+
+ if (cache::client())
+ cache::client()->saveImage(res.avatar_url, data);
+
+ emit setUserAvatar(
+ QImage::fromData(QByteArray(data.data(), data.size())));
+ });
+ });
+ // TODO http::client()->getOwnCommunities();
cache::init(userid);
@@ -518,62 +744,12 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
return;
}
} catch (const lmdb::error &e) {
- qCritical() << "Cache failure" << e.what();
+ log::db()->critical("failure during boot: {}", e.what());
cache::client()->deleteData();
- qInfo() << "Falling back to initial sync ...";
+ log::net()->info("falling back to initial sync");
}
- http::client()->initialSync();
-
- initialSyncTimer_->start(INITIAL_SYNC_RETRY_TIMEOUT);
-}
-
-void
-ChatPage::syncCompleted(const mtx::responses::Sync &response)
-{
- syncTimeoutTimer_->stop();
-
- QtConcurrent::run([this, res = std::move(response)]() {
- try {
- cache::client()->saveState(res);
- emit syncUI(res.rooms);
-
- auto updates = cache::client()->roomUpdates(res);
-
- emit syncTopBar(updates);
- emit syncRoomlist(updates);
-
- } catch (const lmdb::error &e) {
- std::cout << "save cache error:" << e.what() << '\n';
- // TODO: retry sync.
- return;
- }
-
- emit continueSync(cache::client()->nextBatchToken());
- });
-}
-
-void
-ChatPage::initialSyncCompleted(const mtx::responses::Sync &response)
-{
- initialSyncTimer_->stop();
-
- qDebug() << "initial sync completed";
-
- QtConcurrent::run([this, res = std::move(response)]() {
- try {
- cache::client()->saveState(res);
- emit initializeViews(std::move(res.rooms));
- emit initializeRoomList(cache::client()->roomInfo());
- } catch (const lmdb::error &e) {
- qWarning() << "cache error:" << QString::fromStdString(e.what());
- emit retryInitialSync();
- return;
- }
-
- emit continueSync(cache::client()->nextBatchToken());
- emit contentLoaded();
- });
+ tryInitialSync();
}
void
@@ -586,41 +762,6 @@ ChatPage::updateTopBarAvatar(const QString &roomid, const QPixmap &img)
}
void
-ChatPage::updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name)
-{
- QSettings settings;
- auto userid = settings.value("auth/user_id").toString();
-
- user_info_widget_->setUserId(userid);
- user_info_widget_->setDisplayName(display_name);
-
- if (!avatar_url.isValid())
- return;
-
- if (cache::client()) {
- auto data = cache::client()->image(avatar_url.toString());
- if (!data.isNull()) {
- user_info_widget_->setAvatar(QImage::fromData(data));
- return;
- }
- }
-
- auto proxy = http::client()->fetchUserAvatar(avatar_url);
-
- if (proxy.isNull())
- return;
-
- proxy->setParent(this);
- connect(proxy.data(),
- &DownloadMediaProxy::avatarDownloaded,
- this,
- [this, proxy](const QImage &img) {
- proxy->deleteLater();
- user_info_widget_->setAvatar(img);
- });
-}
-
-void
ChatPage::updateOwnCommunitiesInfo(const QList<QString> &own_communities)
{
for (int i = 0; i < own_communities.size(); i++) {
@@ -636,7 +777,7 @@ void
ChatPage::changeTopRoomInfo(const QString &room_id)
{
if (room_id.isEmpty()) {
- qWarning() << "can't switch to empty room_id";
+ log::main()->warn("cannot switch to empty room_id");
return;
}
@@ -660,7 +801,7 @@ ChatPage::changeTopRoomInfo(const QString &room_id)
top_bar_->updateRoomAvatar(img);
} catch (const lmdb::error &e) {
- qWarning() << "failed to change top bar room info" << e.what();
+ log::main()->error("failed to change top bar room info: {}", e.what());
}
current_room_ = room_id;
@@ -681,7 +822,7 @@ ChatPage::showUnreadMessageNotification(int count)
void
ChatPage::loadStateFromCache()
{
- qDebug() << "restoring state from cache";
+ log::db()->info("restoring state from cache");
QtConcurrent::run([this]() {
try {
@@ -696,7 +837,7 @@ ChatPage::loadStateFromCache()
}
// Start receiving events.
- emit continueSync(cache::client()->nextBatchToken());
+ emit trySyncCb();
// Check periodically if the timelines have been loaded.
emit startConsesusTimer();
@@ -740,7 +881,7 @@ ChatPage::removeRoom(const QString &room_id)
cache::client()->removeRoom(room_id);
cache::client()->removeInvite(room_id.toStdString());
} catch (const lmdb::error &e) {
- qCritical() << "The cache couldn't be updated: " << e.what();
+ log::db()->critical("failure while removing room: {}", e.what());
// TODO: Notify the user.
}
@@ -824,33 +965,6 @@ ChatPage::setGroupViewState(bool isEnabled)
}
void
-ChatPage::retryInitialSync(int status_code)
-{
- initialSyncTimer_->stop();
-
- if (http::client()->getHomeServer().isEmpty()) {
- deleteConfigs();
- resetUI();
- emit showLoginPage("Sync error. Please try again.");
- return;
- }
-
- // Retry on Bad-Gateway & Gateway-Timeout errors
- if (status_code == -1 || status_code == 504 || status_code == 502 || status_code == 524) {
- qWarning() << "retrying initial sync";
-
- http::client()->initialSync();
- initialSyncTimer_->start(INITIAL_SYNC_RETRY_TIMEOUT);
- } else {
- // Drop into the login screen.
- deleteConfigs();
- resetUI();
-
- emit showLoginPage(QString("Sync error %1. Please try again.").arg(status_code));
- }
-}
-
-void
ChatPage::updateRoomNotificationCount(const QString &room_id, uint16_t notification_count)
{
room_list_->updateUnreadMessageCount(room_id, notification_count);
@@ -886,7 +1000,197 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
utils::event_body(item.event));
}
} catch (const lmdb::error &e) {
- qWarning() << e.what();
+ log::db()->warn("error while sending desktop notification: {}", e.what());
}
}
}
+
+void
+ChatPage::tryInitialSync()
+{
+ mtx::http::SyncOpts opts;
+ opts.timeout = 0;
+
+ log::net()->info("trying initial sync");
+
+ http::v2::client()->sync(
+ opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
+ if (err) {
+ const auto error = QString::fromStdString(err->matrix_error.error);
+ const auto msg = tr("Please try to login again: %1").arg(error);
+ const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
+ const int status_code = static_cast<int>(err->status_code);
+
+ log::net()->error("sync error: {} {}", status_code, err_code);
+
+ switch (status_code) {
+ case 502:
+ case 504:
+ case 524: {
+ emit tryInitialSyncCb();
+ return;
+ }
+ default: {
+ emit dropToLoginPageCb(msg);
+ return;
+ }
+ }
+ }
+
+ log::net()->info("initial sync completed");
+
+ try {
+ cache::client()->saveState(res);
+ emit initializeViews(std::move(res.rooms));
+ emit initializeRoomList(cache::client()->roomInfo());
+ } catch (const lmdb::error &e) {
+ log::db()->error("{}", e.what());
+ emit tryInitialSyncCb();
+ return;
+ }
+
+ emit trySyncCb();
+ emit contentLoaded();
+ });
+}
+
+void
+ChatPage::trySync()
+{
+ mtx::http::SyncOpts opts;
+
+ if (!connectivityTimer_.isActive())
+ connectivityTimer_.start();
+
+ try {
+ opts.since = cache::client()->nextBatchToken();
+ } catch (const lmdb::error &e) {
+ log::db()->error("failed to retrieve next batch token: {}", e.what());
+ return;
+ }
+
+ http::v2::client()->sync(
+ opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
+ if (err) {
+ const auto error = QString::fromStdString(err->matrix_error.error);
+ const auto msg = tr("Please try to login again: %1").arg(error);
+ const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
+ const int status_code = static_cast<int>(err->status_code);
+
+ log::net()->error("sync error: {} {}", status_code, err_code);
+
+ switch (status_code) {
+ case 502:
+ case 504:
+ case 524: {
+ emit trySync();
+ return;
+ }
+ case 401:
+ case 403: {
+ // We are logged out.
+ if (http::v2::client()->access_token().empty())
+ return;
+
+ emit dropToLoginPageCb(msg);
+ return;
+ }
+ default: {
+ emit trySync();
+ return;
+ }
+ }
+ }
+
+ log::net()->debug("sync completed: {}", res.next_batch);
+
+ // TODO: fine grained error handling
+ try {
+ cache::client()->saveState(res);
+ emit syncUI(res.rooms);
+
+ auto updates = cache::client()->roomUpdates(res);
+
+ emit syncTopBar(updates);
+ emit syncRoomlist(updates);
+ } catch (const lmdb::error &e) {
+ log::db()->error("saving sync response: {}", e.what());
+ }
+
+ emit trySyncCb();
+ });
+}
+
+void
+ChatPage::joinRoom(const QString &room)
+{
+ const auto room_id = room.toStdString();
+
+ http::v2::client()->join_room(
+ room_id, [this, room_id](const nlohmann::json &, mtx::http::RequestErr err) {
+ if (err) {
+ emit showNotification(
+ QString("Failed to join room: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ emit showNotification("You joined the room");
+
+ // We remove any invites with the same room_id.
+ try {
+ cache::client()->removeInvite(room_id);
+ } catch (const lmdb::error &e) {
+ emit showNotification(
+ QString("Failed to remove invite: %1").arg(e.what()));
+ }
+ });
+}
+
+void
+ChatPage::createRoom(const mtx::requests::CreateRoom &req)
+{
+ http::v2::client()->create_room(
+ req, [this](const mtx::responses::CreateRoom &res, mtx::http::RequestErr err) {
+ if (err) {
+ emit showNotification(
+ tr("Room creation failed: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ emit showNotification(QString("Room %1 created")
+ .arg(QString::fromStdString(res.room_id.to_string())));
+ });
+}
+
+void
+ChatPage::leaveRoom(const QString &room_id)
+{
+ http::v2::client()->leave_room(
+ room_id.toStdString(), [this, room_id](const json &, mtx::http::RequestErr err) {
+ if (err) {
+ emit showNotification(
+ tr("Failed to leave room: %1")
+ .arg(QString::fromStdString(err->matrix_error.error)));
+ return;
+ }
+
+ emit leftRoom(room_id);
+ });
+}
+
+void
+ChatPage::sendTypingNotifications()
+{
+ if (!userSettings_->isTypingNotificationsEnabled())
+ return;
+
+ http::v2::client()->start_typing(
+ current_room_.toStdString(), 10'000, [](mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn("failed to send typing notification: {}",
+ err->matrix_error.error);
+ }
+ });
+}
diff --git a/src/CommunitiesList.cc b/src/CommunitiesList.cc
index 0d7f5aab..8ccd5e9d 100644
--- a/src/CommunitiesList.cc
+++ b/src/CommunitiesList.cc
@@ -1,4 +1,6 @@
+#include "Cache.h"
#include "CommunitiesList.h"
+#include "Logging.hpp"
#include "MatrixClient.h"
#include <QLabel>
@@ -38,17 +40,14 @@ CommunitiesList::CommunitiesList(QWidget *parent)
scrollArea_->setWidget(scrollAreaContents_);
topLayout_->addWidget(scrollArea_);
- connect(http::client(),
- &MatrixClient::communityProfileRetrieved,
- this,
- [](QString communityId, QJsonObject profile) {
- http::client()->fetchCommunityAvatar(
- communityId, QUrl(profile["avatar_url"].toString()));
- });
- connect(http::client(),
- SIGNAL(communityAvatarRetrieved(const QString &, const QPixmap &)),
- this,
- SLOT(updateCommunityAvatar(const QString &, const QPixmap &)));
+ // connect(http::client(),
+ // &MatrixClient::communityProfileRetrieved,
+ // this,
+ // [this](QString communityId, QJsonObject profile) {
+ // fetchCommunityAvatar(communityId, profile["avatar_url"].toString());
+ // });
+ connect(
+ this, &CommunitiesList::avatarRetrieved, this, &CommunitiesList::updateCommunityAvatar);
}
void
@@ -61,8 +60,8 @@ CommunitiesList::setCommunities(const std::map<QString, QSharedPointer<Community
for (const auto &community : communities) {
addCommunity(community.second, community.first);
- http::client()->fetchCommunityProfile(community.first);
- http::client()->fetchCommunityRooms(community.first);
+ // http::client()->fetchCommunityProfile(community.first);
+ // http::client()->fetchCommunityRooms(community.first);
}
communities_["world"]->setPressedState(true);
@@ -77,7 +76,7 @@ CommunitiesList::addCommunity(QSharedPointer<Community> community, const QString
communities_.emplace(community_id, QSharedPointer<CommunitiesListItem>(list_item));
- http::client()->fetchCommunityAvatar(community_id, community->getAvatar());
+ fetchCommunityAvatar(community_id, community->getAvatar().toString());
contentsLayout_->insertWidget(contentsLayout_->count() - 1, list_item);
@@ -117,3 +116,37 @@ CommunitiesList::highlightSelectedCommunity(const QString &community_id)
}
}
}
+
+void
+CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl)
+{
+ auto savedImgData = cache::client()->image(avatarUrl);
+ if (!savedImgData.isNull()) {
+ QPixmap pix;
+ pix.loadFromData(savedImgData);
+ emit avatarRetrieved(id, pix);
+ return;
+ }
+
+ mtx::http::ThumbOpts opts;
+ opts.mxc_url = avatarUrl.toStdString();
+ http::v2::client()->get_thumbnail(
+ opts, [this, opts, id](const std::string &res, mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn("failed to download avatar: {} - ({} {})",
+ opts.mxc_url,
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
+
+ cache::client()->saveImage(opts.mxc_url, res);
+
+ auto data = QByteArray(res.data(), res.size());
+
+ QPixmap pix;
+ pix.loadFromData(data);
+
+ emit avatarRetrieved(id, pix);
+ });
+}
diff --git a/src/Logging.cpp b/src/Logging.cpp
new file mode 100644
index 00000000..c6c1c502
--- /dev/null
+++ b/src/Logging.cpp
@@ -0,0 +1,50 @@
+#include "Logging.hpp"
+
+#include <iostream>
+#include <spdlog/sinks/file_sinks.h>
+
+namespace {
+std::shared_ptr<spdlog::logger> db_logger = nullptr;
+std::shared_ptr<spdlog::logger> net_logger = nullptr;
+std::shared_ptr<spdlog::logger> main_logger = nullptr;
+
+constexpr auto MAX_FILE_SIZE = 1024 * 1024 * 6;
+constexpr auto MAX_LOG_FILES = 3;
+}
+
+namespace log {
+void
+init(const std::string &file_path)
+{
+ auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
+ file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
+
+ auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
+
+ std::vector<spdlog::sink_ptr> sinks;
+ sinks.push_back(file_sink);
+ sinks.push_back(console_sink);
+
+ net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
+ main_logger = std::make_shared<spdlog::logger>("main", std::begin(sinks), std::end(sinks));
+ db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
+}
+
+std::shared_ptr<spdlog::logger>
+main()
+{
+ return main_logger;
+}
+
+std::shared_ptr<spdlog::logger>
+net()
+{
+ return net_logger;
+}
+
+std::shared_ptr<spdlog::logger>
+db()
+{
+ return db_logger;
+}
+}
diff --git a/src/LoginPage.cc b/src/LoginPage.cc
index c7f9b042..d695a759 100644
--- a/src/LoginPage.cc
+++ b/src/LoginPage.cc
@@ -137,16 +137,16 @@ LoginPage::LoginPage(QWidget *parent)
setLayout(top_layout_);
+ connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk);
+ connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError);
+ connect(this, &LoginPage::loginErrorCb, this, &LoginPage::loginError);
+
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(http::client(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString)));
- connect(http::client(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccurred()));
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
- connect(http::client(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString)));
- connect(http::client(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess()));
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
}
@@ -180,17 +180,47 @@ LoginPage::onMatrixIdEntered()
inferredServerAddress_ = homeServer;
serverInput_->setText(homeServer);
- http::client()->setServer(homeServer);
- http::client()->versions();
+
+ http::v2::client()->set_server(user.hostname());
+ checkHomeserverVersion();
}
}
void
+LoginPage::checkHomeserverVersion()
+{
+ http::v2::client()->versions(
+ [this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
+ if (err) {
+ using namespace boost::beast::http;
+
+ if (err->status_code == status::not_found) {
+ emit versionErrorCb(tr("The required endpoints were not found. "
+ "Possibly not a Matrix server."));
+ return;
+ }
+
+ if (!err->parse_error.empty()) {
+ emit versionErrorCb(tr("Received malformed response. Make sure "
+ "the homeserver domain is valid."));
+ return;
+ }
+
+ emit versionErrorCb(tr(
+ "An unknown error occured. Make sure the homeserver domain is valid."));
+ return;
+ }
+
+ emit versionOkCb();
+ });
+}
+
+void
LoginPage::onServerAddressEntered()
{
error_label_->setText("");
- http::client()->setServer(serverInput_->text());
- http::client()->versions();
+ http::v2::client()->set_server(serverInput_->text().toStdString());
+ checkHomeserverVersion();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
@@ -199,11 +229,8 @@ LoginPage::onServerAddressEntered()
}
void
-LoginPage::versionError(QString error)
+LoginPage::versionError(const QString &error)
{
- QUrl currentServer = http::client()->getHomeServer();
- QString mxidAddress = matrixid_input_->text().split(":").at(1);
-
error_label_->setText(error);
serverInput_->show();
@@ -215,7 +242,7 @@ LoginPage::versionError(QString error)
}
void
-LoginPage::versionSuccess()
+LoginPage::versionOk()
{
serverLayout_->removeWidget(spinner_);
matrixidLayout_->removeWidget(spinner_);
@@ -241,8 +268,20 @@ LoginPage::onLoginButtonClicked()
if (password_input_->text().isEmpty())
return loginError(tr("Empty password"));
- http::client()->setServer(serverInput_->text());
- http::client()->login(QString::fromStdString(user.localpart()), password_input_->text());
+ http::v2::client()->set_server(serverInput_->text().toStdString());
+ http::v2::client()->login(
+ user.localpart(),
+ password_input_->text().toStdString(),
+ initialDeviceName(),
+ [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
+ if (err) {
+ emit loginError(QString::fromStdString(err->matrix_error.error));
+ emit errorOccurred();
+ return;
+ }
+
+ emit loginOk(res);
+ });
emit loggingIn();
}
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index c46cbff1..9ba8b28e 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -17,7 +17,6 @@
#include <QApplication>
#include <QLayout>
-#include <QNetworkReply>
#include <QSettings>
#include <QShortcut>
@@ -26,6 +25,7 @@
#include "ChatPage.h"
#include "Config.h"
#include "LoadingIndicator.h"
+#include "Logging.hpp"
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
@@ -46,6 +46,15 @@
MainWindow *MainWindow::instance_ = nullptr;
+MainWindow::~MainWindow()
+{
+ if (http::v2::client() != nullptr) {
+ http::v2::client()->shutdown();
+ // TODO: find out why waiting for the threads to join is slow.
+ http::v2::client()->close();
+ }
+}
+
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, progressModal_{nullptr}
@@ -54,9 +63,6 @@ MainWindow::MainWindow(QWidget *parent)
setWindowTitle("nheko");
setObjectName("MainWindow");
- // Initialize the http client.
- http::init();
-
restoreWindowSize();
QFont font("Open Sans");
@@ -124,21 +130,13 @@ MainWindow::MainWindow(QWidget *parent)
connect(
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
- connect(http::client(),
- SIGNAL(loginSuccess(QString, QString, QString)),
- this,
- SLOT(showChatPage(QString, QString, QString)));
-
- connect(http::client(),
- SIGNAL(registerSuccess(QString, QString, QString)),
- this,
- SLOT(showChatPage(QString, QString, QString)));
- connect(http::client(), &MatrixClient::invalidToken, this, [this]() {
- chat_page_->deleteConfigs();
- showLoginPage();
- login_page_->loginError("Invalid token detected. Please try to login again.");
+ connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
+ http::v2::client()->set_user(res.user_id);
+ showChatPage();
});
+ connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
+
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
@@ -157,7 +155,18 @@ MainWindow::MainWindow(QWidget *parent)
QString home_server = settings.value("auth/home_server").toString();
QString user_id = settings.value("auth/user_id").toString();
- showChatPage(user_id, home_server, token);
+ http::v2::client()->set_access_token(token.toStdString());
+ http::v2::client()->set_server(home_server.toStdString());
+
+ try {
+ using namespace mtx::identifiers;
+ http::v2::client()->set_user(parse<User>(user_id.toStdString()));
+ } catch (const std::invalid_argument &e) {
+ log::main()->critical("bootstrapped with invalid user_id: {}",
+ user_id.toStdString());
+ }
+
+ showChatPage();
}
}
@@ -216,8 +225,13 @@ MainWindow::removeOverlayProgressBar()
}
void
-MainWindow::showChatPage(QString userid, QString homeserver, QString token)
+MainWindow::showChatPage()
{
+ auto userid = QString::fromStdString(http::v2::client()->user_id().to_string());
+ auto homeserver = QString::fromStdString(http::v2::client()->server() + ":" +
+ std::to_string(http::v2::client()->port()));
+ auto token = QString::fromStdString(http::v2::client()->access_token());
+
QSettings settings;
settings.setValue("auth/access_token", token);
settings.setValue("auth/home_server", homeserver);
@@ -317,7 +331,7 @@ MainWindow::openLeaveRoomDialog(const QString &room_id)
leaveRoomModal_->hide();
if (leaving)
- http::client()->leaveRoom(roomToLeave);
+ chat_page_->leaveRoom(roomToLeave);
});
leaveRoomModal_ =
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index c4eaf347..0eb4658a 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -1,1371 +1,32 @@
-/*
- * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <QDebug>
-#include <QFile>
-#include <QImageReader>
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QMimeDatabase>
-#include <QNetworkReply>
-#include <QNetworkRequest>
-#include <QPixmap>
-#include <QProcessEnvironment>
-#include <QSettings>
-#include <QUrlQuery>
-#include <QtConcurrent>
-#include <mtx/errors.hpp>
-
#include "MatrixClient.h"
-#include <mtxclient/http/client.hpp>
+
+#include <memory>
namespace {
-std::unique_ptr<MatrixClient> instance_ = nullptr;
+auto v2_client_ = std::make_shared<mtx::http::Client>("matrix.org");
}
namespace http {
+namespace v2 {
-std::shared_ptr<mtx::http::Client> client_ = nullptr;
-
-void
-init()
-{
- if (!instance_)
- instance_ = std::make_unique<MatrixClient>();
-}
-
-MatrixClient *
+mtx::http::Client *
client()
{
- return instance_.get();
-}
-}
-
-MatrixClient::MatrixClient(QObject *parent)
- : QNetworkAccessManager(parent)
- , clientApiUrl_{"/_matrix/client/r0"}
- , mediaApiUrl_{"/_matrix/media/r0"}
- , serverProtocol_{"https"}
-{
- qRegisterMetaType<mtx::responses::Sync>();
-
- QSettings settings;
- txn_id_ = settings.value("client/transaction_id", 1).toInt();
-
- auto env = QProcessEnvironment::systemEnvironment();
-
- auto allowInsecureConnections = env.value("NHEKO_ALLOW_INSECURE_CONNECTIONS", "0");
-
- if (allowInsecureConnections == "1") {
- qWarning() << "Insecure connections are allowed: SSL errors will be ignored";
- connect(
- this,
- &QNetworkAccessManager::sslErrors,
- this,
- [](QNetworkReply *reply, const QList<QSslError> &) { reply->ignoreSslErrors(); });
- }
-
- QJsonObject default_filter{
- {
- "room",
- QJsonObject{
- {"include_leave", true},
- {
- "account_data",
- QJsonObject{
- {"not_types", QJsonArray{"*"}},
- },
- },
- },
- },
- {
- "account_data",
- QJsonObject{
- {"not_types", QJsonArray{"*"}},
- },
- },
- {
- "presence",
- QJsonObject{
- {"not_types", QJsonArray{"*"}},
- },
- },
- };
-
- filter_ = settings
- .value("client/sync_filter",
- QJsonDocument(default_filter).toJson(QJsonDocument::Compact))
- .toString();
-
- connect(this,
- &QNetworkAccessManager::networkAccessibleChanged,
- this,
- [this](NetworkAccessibility status) {
- if (status != NetworkAccessibility::Accessible)
- setNetworkAccessible(NetworkAccessibility::Accessible);
- });
-}
-
-void
-MatrixClient::reset() noexcept
-{
- next_batch_.clear();
- server_.clear();
- token_.clear();
-
- txn_id_ = 0;
-}
-
-void
-MatrixClient::login(const QString &username, const QString &password) noexcept
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/login");
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- mtx::requests::Login login;
- login.user = username.toStdString();
- login.password = password.toStdString();
- login.initial_device_display_name = "nheko";
-
-#if defined(Q_OS_MAC)
- login.initial_device_display_name = "nheko on Mac OS";
-#elif defined(Q_OS_LINUX)
- login.initial_device_display_name = "nheko on Linux";
-#elif defined(Q_OS_WIN)
- login.initial_device_display_name = "nheko on Windows";
-#endif
-
- json j = login;
-
- auto data = QByteArray::fromStdString(j.dump());
- auto reply = post(request, data);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status_code =
- reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status_code == 403) {
- emit loginError(tr("Wrong username or password"));
- return;
- }
-
- if (status_code == 404) {
- emit loginError(tr("Login endpoint was not found on the server"));
- return;
- }
-
- if (status_code >= 400) {
- qWarning() << "Login error: " << reply->errorString();
- emit loginError(tr("An unknown error occured. Please try again."));
- return;
- }
-
- if (reply->error()) {
- emit loginError(reply->errorString());
- return;
- }
-
- try {
- mtx::responses::Login login =
- nlohmann::json::parse(reply->readAll().data());
-
- auto hostname = server_.host();
-
- if (server_.port() > 0)
- hostname = QString("%1:%2").arg(server_.host()).arg(server_.port());
-
- emit loginSuccess(QString::fromStdString(login.user_id.to_string()),
- hostname,
- QString::fromStdString(login.access_token));
- } catch (std::exception &e) {
- qWarning() << "Malformed JSON response" << e.what();
- emit loginError(tr("Malformed response. Possibly not a Matrix server"));
- }
- });
-}
-void
-MatrixClient::logout() noexcept
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/logout");
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- QJsonObject body{};
- auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status != 200) {
- qWarning() << "Logout error: " << reply->errorString();
- return;
- }
-
- emit loggedOut();
- });
-}
-
-void
-MatrixClient::registerUser(const QString &user,
- const QString &pass,
- const QString &server,
- const QString &session) noexcept
-{
- setServer(server);
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/register");
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- QJsonObject body{{"username", user}, {"password", pass}};
-
- // We trying to register using the response from the recaptcha.
- if (!session.isEmpty())
- body = QJsonObject{
- {"username", user},
- {"password", pass},
- {"auth", QJsonObject{{"type", "m.login.recaptcha"}, {"session", session}}}};
-
- auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, user, pass, server]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- auto data = reply->readAll();
-
- // Try to parse a regular register response.
- try {
- mtx::responses::Register res = nlohmann::json::parse(data);
- emit registerSuccess(QString::fromStdString(res.user_id.to_string()),
- QString::fromStdString(res.user_id.hostname()),
- QString::fromStdString(res.access_token));
- } catch (const std::exception &e) {
- qWarning() << "Register" << e.what();
- }
-
- // Check if the server requires a registration flow.
- try {
- mtx::responses::RegistrationFlows res = nlohmann::json::parse(data);
- emit registrationFlow(
- user, pass, server, QString::fromStdString(res.session));
- return;
- } catch (const std::exception &) {
- }
-
- // We encountered an unknown error.
- if (status == 0 || status >= 400) {
- try {
- mtx::errors::Error res = nlohmann::json::parse(data);
- emit registerError(QString::fromStdString(res.error));
- return;
- } catch (const std::exception &) {
- }
-
- emit registerError(reply->errorString());
- }
- });
-}
-
-void
-MatrixClient::sync() noexcept
-{
- // the filter is not uploaded yet (so it is a json with { at the beginning)
- // ignore for now that the filter might be uploaded multiple times as we expect
- // servers to do deduplication
- if (filter_.startsWith("{")) {
- uploadFilter(filter_);
- }
-
- QUrlQuery query;
- query.addQueryItem("set_presence", "online");
- query.addQueryItem("filter", filter_);
- query.addQueryItem("timeout", "30000");
-
- if (next_batch_.isEmpty()) {
- qDebug() << "Sync requires a valid next_batch token. Initial sync should "
- "be performed.";
- return;
- }
-
- query.addQueryItem("since", next_batch_);
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/sync");
- endpoint.setQuery(query);
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- auto reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- auto data = reply->readAll();
-
- if (status == 0 || status >= 400) {
- try {
- mtx::errors::Error res = nlohmann::json::parse(data);
-
- if (res.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN) {
- emit invalidToken();
- return;
- }
-
- emit syncError(QString::fromStdString(res.error));
-
- return;
- } catch (const nlohmann::json::exception &e) {
- qWarning() << e.what();
- }
- }
-
- try {
- emit syncCompleted(nlohmann::json::parse(std::move(data)));
- } catch (std::exception &e) {
- qWarning() << "Sync error: " << e.what();
- }
- });
-}
-
-void
-MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
- int txnId,
- const QString &roomid,
- const QString &msg,
- const QString &mime,
- uint64_t media_size,
- const QString &url) noexcept
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ +
- QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txnId));
-
- QJsonObject body;
- QJsonObject info = {{"size", static_cast<qint64>(media_size)}, {"mimetype", mime}};
-
- switch (ty) {
- case mtx::events::MessageType::Text:
- body = {{"msgtype", "m.text"}, {"body", msg}};
- break;
- case mtx::events::MessageType::Emote:
- body = {{"msgtype", "m.emote"}, {"body", msg}};
- break;
- case mtx::events::MessageType::Image:
- body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}, {"info", info}};
- break;
- case mtx::events::MessageType::File:
- body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}, {"info", info}};
- break;
- case mtx::events::MessageType::Audio:
- body = {{"msgtype", "m.audio"}, {"body", msg}, {"url", url}, {"info", info}};
- break;
- case mtx::events::MessageType::Video:
- body = {{"msgtype", "m.video"}, {"body", msg}, {"url", url}, {"info", info}};
- break;
- default:
- qDebug() << "SendRoomMessage: Unknown message type for" << msg;
- return;
- }
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, txnId]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- emit messageSendFailed(roomid, txnId);
- return;
- }
-
- auto data = reply->readAll();
-
- if (data.isEmpty()) {
- emit messageSendFailed(roomid, txnId);
- return;
- }
-
- auto json = QJsonDocument::fromJson(data);
-
- if (!json.isObject()) {
- qDebug() << "Send message response is not a JSON object";
- emit messageSendFailed(roomid, txnId);
- return;
- }
-
- auto object = json.object();
-
- if (!object.contains("event_id")) {
- qDebug() << "SendTextMessage: missing event_id from response";
- emit messageSendFailed(roomid, txnId);
- return;
- }
-
- emit messageSent(object.value("event_id").toString(), roomid, txnId);
- });
-}
-
-void
-MatrixClient::initialSync() noexcept
-{
- QUrlQuery query;
- query.addQueryItem("timeout", "0");
- query.addQueryItem("filter", filter_);
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/sync");
- endpoint.setQuery(query);
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- auto reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qDebug() << "Error code received" << status;
- emit initialSyncFailed(status);
- return;
- }
-
- QtConcurrent::run([data = reply->readAll(), this]() {
- try {
- emit initialSyncCompleted(nlohmann::json::parse(std::move(data)));
- } catch (std::exception &e) {
- qWarning() << "Initial sync error:" << e.what();
- emit initialSyncFailed();
- }
- });
- });
-}
-
-void
-MatrixClient::versions() noexcept
-{
- QUrl endpoint(server_);
- endpoint.setPath("/_matrix/client/versions");
-
- QNetworkRequest request(endpoint);
-
- auto reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status_code =
- reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (reply->error()) {
- emit versionError(reply->errorString());
- return;
- }
-
- if (status_code == 404) {
- emit versionError("Versions endpoint was not found on the server. Possibly "
- "not a Matrix server");
- return;
- }
-
- if (status_code >= 400) {
- emit versionError("An unknown error occured. Please try again.");
- return;
- }
-
- try {
- mtx::responses::Versions versions =
- nlohmann::json::parse(reply->readAll().data());
-
- emit versionSuccess();
- } catch (std::exception &e) {
- emit versionError("Malformed response. Possibly not a Matrix server");
- }
- });
-}
-
-void
-MatrixClient::getOwnProfile() noexcept
-{
- // FIXME: Remove settings from the matrix client. The class should store the
- // user's matrix ID.
- QSettings settings;
- auto userid = settings.value("auth/user_id", "").toString();
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/profile/" + userid);
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- QNetworkReply *reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- try {
- mtx::responses::Profile profile =
- nlohmann::json::parse(reply->readAll().data());
-
- emit getOwnProfileResponse(QUrl(QString::fromStdString(profile.avatar_url)),
- QString::fromStdString(profile.display_name));
- } catch (std::exception &e) {
- qWarning() << "Profile:" << e.what();
- }
- });
-}
-
-void
-MatrixClient::getOwnCommunities() noexcept
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/joined_groups");
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- QNetworkReply *reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto data = reply->readAll();
- auto json = QJsonDocument::fromJson(data).object();
-
- if (!json.contains("groups")) {
- qWarning() << "failed to parse own communities. 'groups' key not found";
- return;
- }
-
- QList<QString> response;
- for (auto group : json["groups"].toArray())
- response.append(group.toString());
-
- emit getOwnCommunitiesResponse(response);
- });
-}
-
-void
-MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url)
-{
- QList<QString> url_parts = avatar_url.toString().split("mxc://");
-
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for room avatar " << avatar_url.toString();
- return;
- }
-
- QUrlQuery query;
- query.addQueryItem("width", "512");
- query.addQueryItem("height", "512");
- query.addQueryItem("method", "crop");
-
- QString media_url =
- QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
-
- QUrl endpoint(media_url);
- endpoint.setQuery(query);
-
- QNetworkRequest avatar_request(endpoint);
-
- QNetworkReply *reply = get(avatar_request);
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, avatar_url]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto img = reply->readAll();
-
- if (img.size() == 0)
- return;
-
- QPixmap pixmap;
- pixmap.loadFromData(img);
-
- emit roomAvatarRetrieved(roomid, pixmap, avatar_url.toString(), img);
- });
-}
-
-void
-MatrixClient::fetchCommunityAvatar(const QString &communityId, const QUrl &avatar_url)
-{
- if (avatar_url.isEmpty())
- return;
-
- QList<QString> url_parts = avatar_url.toString().split("mxc://");
-
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for community avatar " << avatar_url.toString();
- return;
- }
-
- QUrlQuery query;
- query.addQueryItem("width", "512");
- query.addQueryItem("height", "512");
- query.addQueryItem("method", "crop");
-
- QString media_url =
- QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
-
- QUrl endpoint(media_url);
- endpoint.setQuery(query);
-
- QNetworkRequest avatar_request(endpoint);
-
- QNetworkReply *reply = get(avatar_request);
- connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto img = reply->readAll();
-
- if (img.size() == 0)
- return;
-
- QPixmap pixmap;
- pixmap.loadFromData(img);
-
- emit communityAvatarRetrieved(communityId, pixmap);
- });
-}
-
-void
-MatrixClient::fetchCommunityProfile(const QString &communityId)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/profile");
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- QNetworkReply *reply = get(request);
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto data = reply->readAll();
- const auto json = QJsonDocument::fromJson(data).object();
-
- emit communityProfileRetrieved(communityId, json);
- });
-}
-
-void
-MatrixClient::fetchCommunityRooms(const QString &communityId)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/rooms");
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- QNetworkReply *reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto data = reply->readAll();
- const auto json = QJsonDocument::fromJson(data).object();
-
- emit communityRoomsRetrieved(communityId, json);
- });
-}
-
-QSharedPointer<DownloadMediaProxy>
-MatrixClient::fetchUserAvatar(const QUrl &avatarUrl)
-{
- QList<QString> url_parts = avatarUrl.toString().split("mxc://");
-
- if (url_parts.size() != 2)
- return QSharedPointer<DownloadMediaProxy>();
-
- QUrlQuery query;
- query.addQueryItem("width", "128");
- query.addQueryItem("height", "128");
- query.addQueryItem("method", "crop");
-
- QString media_url =
- QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
-
- QUrl endpoint(media_url);
- endpoint.setQuery(query);
-
- QNetworkRequest avatar_request(endpoint);
-
- auto reply = get(avatar_request);
- auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
- [](auto proxy) { proxy->deleteLater(); });
- connect(reply, &QNetworkReply::finished, this, [reply, proxy, avatarUrl]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString() << avatarUrl;
- return;
- }
-
- auto data = reply->readAll();
-
- if (data.size() == 0) {
- qWarning() << "received avatar with no data:" << avatarUrl;
- return;
- }
-
- QImage img;
- img.loadFromData(data);
-
- emit proxy->avatarDownloaded(img);
- });
-
- return proxy;
-}
-
-QSharedPointer<DownloadMediaProxy>
-MatrixClient::downloadImage(const QUrl &url)
-{
- QNetworkRequest image_request(url);
-
- auto reply = get(image_request);
- auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
- [](auto proxy) { proxy->deleteLater(); });
- connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- auto img = reply->readAll();
-
- if (img.size() == 0)
- return;
-
- QPixmap pixmap;
- pixmap.loadFromData(img);
-
- emit proxy->imageDownloaded(pixmap);
- });
-
- return proxy;
-}
-
-QSharedPointer<DownloadMediaProxy>
-MatrixClient::downloadFile(const QUrl &url)
-{
- QNetworkRequest fileRequest(url);
-
- auto reply = get(fileRequest);
- auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
- [](auto proxy) { proxy->deleteLater(); });
- connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- // TODO: Handle error
- qWarning() << reply->errorString();
- return;
- }
-
- auto data = reply->readAll();
-
- if (data.size() == 0)
- return;
-
- emit proxy->fileDownloaded(data);
- });
-
- return proxy;
-}
-
-void
-MatrixClient::messages(const QString &roomid, const QString &from_token, int limit) noexcept
-{
- QUrlQuery query;
- query.addQueryItem("from", from_token);
- query.addQueryItem("dir", "b");
- query.addQueryItem("limit", QString::number(limit));
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/messages").arg(roomid));
- endpoint.setQuery(query);
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- auto reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- try {
- mtx::responses::Messages messages =
- nlohmann::json::parse(reply->readAll().data());
-
- emit messagesRetrieved(roomid, messages);
- } catch (std::exception &e) {
- qWarning() << "Room messages from" << roomid << e.what();
- return;
- }
- });
-}
-
-void
-MatrixClient::uploadImage(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data)
-{
- auto reply = makeUploadRequest(data);
-
- if (reply == nullptr)
- return;
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
- auto json = getUploadReply(reply);
- if (json.isEmpty())
- return;
-
- auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
- auto size =
- reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
-
- emit imageUploaded(
- roomid, filename, json.value("content_uri").toString(), mime, size);
- });
-}
-
-void
-MatrixClient::uploadFile(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data)
-{
- auto reply = makeUploadRequest(data);
-
- if (reply == nullptr)
- return;
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
- auto json = getUploadReply(reply);
- if (json.isEmpty())
- return;
-
- auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
- auto size =
- reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
-
- emit fileUploaded(
- roomid, filename, json.value("content_uri").toString(), mime, size);
- });
-}
-
-void
-MatrixClient::uploadAudio(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data)
-{
- auto reply = makeUploadRequest(data);
-
- if (reply == nullptr)
- return;
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
- auto json = getUploadReply(reply);
- if (json.isEmpty())
- return;
-
- auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
- auto size =
- reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
-
- emit audioUploaded(
- roomid, filename, json.value("content_uri").toString(), mime, size);
- });
-}
-
-void
-MatrixClient::uploadVideo(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data)
-{
- auto reply = makeUploadRequest(data);
-
- if (reply == nullptr)
- return;
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
- auto json = getUploadReply(reply);
- if (json.isEmpty())
- return;
-
- auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
- auto size =
- reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
-
- emit videoUploaded(
- roomid, filename, json.value("content_uri").toString(), mime, size);
- });
-}
-
-void
-MatrixClient::uploadFilter(const QString &filter) noexcept
-{
- // validate that filter is a Json-String
- QJsonDocument doc = QJsonDocument::fromJson(filter.toUtf8());
- if (doc.isNull() || !doc.isObject()) {
- qWarning() << "Input which should be uploaded as filter is no JsonObject";
- return;
- }
-
- QSettings settings;
- auto userid = settings.value("auth/user_id", "").toString();
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/user/%1/filter").arg(userid));
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- auto reply = post(request, doc.toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString() << "42";
- return;
- }
-
- auto data = reply->readAll();
- auto response = QJsonDocument::fromJson(data);
- auto filter_id = response.object()["filter_id"].toString();
-
- qDebug() << "Filter with ID" << filter_id << "created.";
- QSettings settings;
- settings.setValue("client/sync_filter", filter_id);
- settings.sync();
-
- // set the filter_ var so following syncs will use it
- filter_ = filter_id;
- });
-}
-
-void
-MatrixClient::joinRoom(const QString &roomIdOrAlias)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/join/%1").arg(roomIdOrAlias));
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- auto reply = post(request, "{}");
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- auto data = reply->readAll();
- auto response = QJsonDocument::fromJson(data);
- auto json = response.object();
-
- if (json.contains("error"))
- emit joinFailed(json["error"].toString());
- else
- qDebug() << reply->errorString();
-
- return;
- }
-
- auto data = reply->readAll();
- auto response = QJsonDocument::fromJson(data);
- auto room_id = response.object()["room_id"].toString();
-
- emit joinedRoom(room_id);
- });
-}
-
-void
-MatrixClient::leaveRoom(const QString &roomId)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/leave").arg(roomId));
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- auto reply = post(request, "{}");
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomId]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
-
- emit leftRoom(roomId);
- });
+ return v2_client_.get();
}
-void
-MatrixClient::inviteUser(const QString &roomId, const QString &user)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/invite").arg(roomId));
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- QJsonObject body{{"user_id", user}};
- auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomId, user]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- // TODO: Handle failure.
- qWarning() << reply->errorString();
- return;
- }
-
- emit invitedUser(roomId, user);
- });
-}
+} // namespace v2
void
-MatrixClient::createRoom(const mtx::requests::CreateRoom &create_room_request)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/createRoom"));
-
- QNetworkRequest request(endpoint);
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- nlohmann::json body = create_room_request;
- auto reply = post(request, QString::fromStdString(body.dump()).toUtf8());
-
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- auto data = reply->readAll();
- auto response = QJsonDocument::fromJson(data);
- auto json = response.object();
-
- if (json.contains("error"))
- emit roomCreationFailed(json["error"].toString());
- else
- qDebug() << reply->errorString();
-
- return;
- }
-
- auto data = reply->readAll();
- auto response = QJsonDocument::fromJson(data);
- auto room_id = response.object()["room_id"].toString();
-
- emit roomCreated(room_id);
- });
-}
-
-void
-MatrixClient::sendTypingNotification(const QString &roomid, int timeoutInMillis)
-{
- QSettings settings;
- QString user_id = settings.value("auth/user_id").toString();
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
-
- QString msgType("");
- QJsonObject body;
-
- body = {{"typing", true}, {"timeout", timeoutInMillis}};
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-}
-
-void
-MatrixClient::removeTypingNotification(const QString &roomid)
-{
- QSettings settings;
- QString user_id = settings.value("auth/user_id").toString();
-
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
-
- QString msgType("");
- QJsonObject body;
-
- body = {{"typing", false}};
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-}
-
-void
-MatrixClient::readEvent(const QString &room_id, const QString &event_id)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/read_markers").arg(room_id));
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- QJsonObject body({{"m.fully_read", event_id}, {"m.read", event_id}});
- auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [reply]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- qWarning() << reply->errorString();
- return;
- }
- });
-}
-
-QNetworkReply *
-MatrixClient::makeUploadRequest(QSharedPointer<QIODevice> iodev)
-{
- QUrl endpoint(server_);
- endpoint.setPath(mediaApiUrl_ + "/upload");
-
- if (!iodev->open(QIODevice::ReadOnly)) {
- qWarning() << "Error while reading device:" << iodev->errorString();
- return nullptr;
- }
-
- QMimeDatabase db;
- QMimeType mime = db.mimeTypeForData(iodev.data());
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
- setupAuth(request);
-
- auto reply = post(request, iodev.data());
-
- return reply;
-}
-
-QJsonObject
-MatrixClient::getUploadReply(QNetworkReply *reply)
-{
- QJsonObject object;
-
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (status == 0 || status >= 400) {
- emit uploadFailed(status,
- QString("Media upload failed - %1").arg(reply->errorString()));
- return object;
- }
-
- auto res_data = reply->readAll();
-
- if (res_data.isEmpty()) {
- emit uploadFailed(status, "Media upload failed - Empty response");
- return object;
- }
-
- auto json = QJsonDocument::fromJson(res_data);
-
- if (!json.isObject()) {
- emit uploadFailed(status, "Media upload failed - Invalid response");
- return object;
- }
-
- object = json.object();
- if (!object.contains("content_uri")) {
- emit uploadFailed(status, "Media upload failed - Missing 'content_uri'");
- return QJsonObject{};
- }
-
- return object;
-}
-
-void
-MatrixClient::redactEvent(const QString &room_id, const QString &event_id)
+init()
{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/redact/%2/%3")
- .arg(room_id)
- .arg(event_id)
- .arg(incrementTransactionId()));
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- // TODO: no reason specified
- QJsonObject body{};
- auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
-
- connect(reply, &QNetworkReply::finished, this, [reply, this, room_id, event_id]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- auto data = reply->readAll();
-
- if (status == 0 || status >= 400) {
- try {
- mtx::errors::Error res = nlohmann::json::parse(data);
- emit redactionFailed(QString::fromStdString(res.error));
- return;
- } catch (const std::exception &) {
- }
- }
-
- try {
- mtx::responses::EventId res = nlohmann::json::parse(data);
- emit redactionCompleted(room_id, event_id);
- } catch (const std::exception &e) {
- emit redactionFailed(QString::fromStdString(e.what()));
- }
- });
+ qRegisterMetaType<mtx::responses::Login>();
+ qRegisterMetaType<mtx::responses::Messages>();
+ qRegisterMetaType<mtx::responses::Notifications>();
+ qRegisterMetaType<mtx::responses::Rooms>();
+ qRegisterMetaType<mtx::responses::Sync>();
+ qRegisterMetaType<std::string>();
+ qRegisterMetaType<std::vector<std::string>>();
}
-void
-MatrixClient::getNotifications() noexcept
-{
- QUrlQuery query;
- query.addQueryItem("limit", "5");
-
- QUrl endpoint(server_);
- endpoint.setQuery(query);
- endpoint.setPath(clientApiUrl_ + "/notifications");
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- setupAuth(request);
-
- auto reply = get(request);
- connect(reply, &QNetworkReply::finished, this, [reply, this]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- auto data = reply->readAll();
-
- if (status == 0 || status >= 400) {
- try {
- mtx::errors::Error res = nlohmann::json::parse(data);
- std::cout << nlohmann::json::parse(data).dump(2) << '\n';
- // TODO: Response with an error signal
- return;
- } catch (const std::exception &) {
- }
- }
-
- try {
- emit notificationsRetrieved(nlohmann::json::parse(data));
- } catch (const std::exception &e) {
- qWarning() << "failed to parse /notifications response" << e.what();
- }
- });
-}
+} // namespace http
diff --git a/src/RegisterPage.cc b/src/RegisterPage.cc
index 7d80b727..a5960b83 100644
--- a/src/RegisterPage.cc
+++ b/src/RegisterPage.cc
@@ -20,6 +20,7 @@
#include "Config.h"
#include "FlatButton.h"
+#include "Logging.hpp"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "RaisedButton.h"
@@ -125,35 +126,53 @@ RegisterPage::RegisterPage(QWidget *parent)
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
- connect(http::client(),
- SIGNAL(registerError(const QString &)),
- this,
- SLOT(registerError(const QString &)));
- connect(http::client(),
- &MatrixClient::registrationFlow,
- this,
- [this](const QString &user,
- const QString &pass,
- const QString &server,
- const QString &session) {
- emit errorOccurred();
+ connect(this, &RegisterPage::registerErrorCb, this, &RegisterPage::registerError);
+ connect(
+ this,
+ &RegisterPage::registrationFlow,
+ this,
+ [this](const std::string &user, const std::string &pass, const std::string &session) {
+ emit errorOccurred();
- if (!captchaDialog_) {
- captchaDialog_ =
- std::make_shared<dialogs::ReCaptcha>(server, session, this);
- connect(captchaDialog_.get(),
- &dialogs::ReCaptcha::closing,
- this,
- [this, user, pass, server, session]() {
- captchaDialog_->close();
- emit registering();
- http::client()->registerUser(
- user, pass, server, session);
- });
- }
+ if (!captchaDialog_) {
+ captchaDialog_ = std::make_shared<dialogs::ReCaptcha>(
+ QString::fromStdString(session), this);
+ connect(
+ captchaDialog_.get(),
+ &dialogs::ReCaptcha::closing,
+ this,
+ [this, user, pass, session]() {
+ captchaDialog_->close();
+ emit registering();
- QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
- });
+ http::v2::client()->flow_response(
+ user,
+ pass,
+ session,
+ "m.login.recaptcha",
+ [this](const mtx::responses::Register &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn(
+ "failed to retrieve registration flows: {}",
+ err->matrix_error.error);
+ emit errorOccurred();
+ emit registerErrorCb(QString::fromStdString(
+ err->matrix_error.error));
+ return;
+ }
+
+ http::v2::client()->set_user(res.user_id);
+ http::v2::client()->set_access_token(
+ res.access_token);
+
+ emit registerOk();
+ });
+ });
+ }
+
+ QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
+ });
setLayout(top_layout_);
}
@@ -185,11 +204,56 @@ RegisterPage::onRegisterButtonClicked()
} else if (!server_input_->hasAcceptableInput()) {
registerError(tr("Invalid server name"));
} else {
- QString username = username_input_->text();
- QString password = password_input_->text();
- QString server = server_input_->text();
+ auto username = username_input_->text().toStdString();
+ auto password = password_input_->text().toStdString();
+ auto server = server_input_->text().toStdString();
+
+ http::v2::client()->set_server(server);
+ http::v2::client()->registration(
+ username,
+ password,
+ [this, username, password](const mtx::responses::Register &res,
+ mtx::http::RequestErr err) {
+ if (!err) {
+ http::v2::client()->set_user(res.user_id);
+ http::v2::client()->set_access_token(res.access_token);
+
+ emit registerOk();
+ return;
+ }
+
+ // The server requires registration flows.
+ if (err->status_code == boost::beast::http::status::unauthorized) {
+ http::v2::client()->flow_register(
+ username,
+ password,
+ [this, username, password](
+ const mtx::responses::RegistrationFlows &res,
+ mtx::http::RequestErr err) {
+ if (res.session.empty() && err) {
+ log::net()->warn(
+ "failed to retrieve registration flows: ({}) "
+ "{}",
+ static_cast<int>(err->status_code),
+ err->matrix_error.error);
+ emit errorOccurred();
+ emit registerErrorCb(QString::fromStdString(
+ err->matrix_error.error));
+ return;
+ }
+
+ emit registrationFlow(username, password, res.session);
+ });
+ return;
+ }
+
+ log::net()->warn("failed to register: status_code ({})",
+ static_cast<int>(err->status_code));
+
+ emit registerErrorCb(QString::fromStdString(err->matrix_error.error));
+ emit errorOccurred();
+ });
- http::client()->registerUser(username, password, server);
emit registering();
}
}
diff --git a/src/RoomList.cc b/src/RoomList.cc
index e7c5ef30..d3ed2e66 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -16,11 +16,11 @@
*/
#include <QBuffer>
-#include <QDebug>
#include <QObject>
#include <QTimer>
#include "Cache.h"
+#include "Logging.hpp"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "OverlayModal.h"
@@ -55,18 +55,7 @@ RoomList::RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent)
scrollArea_->setWidget(scrollAreaContents_);
topLayout_->addWidget(scrollArea_);
- connect(http::client(),
- &MatrixClient::roomAvatarRetrieved,
- this,
- [this](const QString &room_id,
- const QPixmap &img,
- const QString &url,
- const QByteArray &data) {
- if (cache::client())
- cache::client()->saveImage(url, data);
-
- updateRoomAvatar(room_id, img);
- });
+ connect(this, &RoomList::updateRoomAvatarCb, this, &RoomList::updateRoomAvatar);
}
void
@@ -101,7 +90,28 @@ RoomList::updateAvatar(const QString &room_id, const QString &url)
savedImgData = cache::client()->image(url);
if (savedImgData.isEmpty()) {
- http::client()->fetchRoomAvatar(room_id, url);
+ mtx::http::ThumbOpts opts;
+ opts.mxc_url = url.toStdString();
+ http::v2::client()->get_thumbnail(
+ opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn(
+ "failed to download thumbnail: {}, {} - {}",
+ opts.mxc_url,
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
+
+ if (cache::client())
+ cache::client()->saveImage(opts.mxc_url, res);
+
+ auto data = QByteArray(res.data(), res.size());
+ QPixmap pixmap;
+ pixmap.loadFromData(data);
+
+ emit updateRoomAvatarCb(room_id, pixmap);
+ });
} else {
QPixmap img;
img.loadFromData(savedImgData);
@@ -131,7 +141,8 @@ void
RoomList::updateUnreadMessageCount(const QString &roomid, int count)
{
if (!roomExists(roomid)) {
- qWarning() << "UpdateUnreadMessageCount: Unknown roomid";
+ log::main()->warn("updateUnreadMessageCount: unknown room_id {}",
+ roomid.toStdString());
return;
}
@@ -156,7 +167,7 @@ RoomList::calculateUnreadMessageCount()
void
RoomList::initialize(const QMap<QString, RoomInfo> &info)
{
- qDebug() << "initialize room list";
+ log::main()->info("initialize room list");
rooms_.clear();
@@ -209,7 +220,7 @@ RoomList::highlightSelectedRoom(const QString &room_id)
emit roomChanged(room_id);
if (!roomExists(room_id)) {
- qDebug() << "RoomList: clicked unknown roomid";
+ log::main()->warn("roomlist: clicked unknown room_id");
return;
}
@@ -232,7 +243,8 @@ void
RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img)
{
if (!roomExists(roomid)) {
- qWarning() << "Avatar update on non existent room" << roomid;
+ log::main()->warn("avatar update on non-existent room_id: {}",
+ roomid.toStdString());
return;
}
@@ -246,7 +258,9 @@ void
RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
{
if (!roomExists(roomid)) {
- qWarning() << "Description update on non existent room" << roomid << info.body;
+ log::main()->warn("description update on non-existent room_id: {}, {}",
+ roomid.toStdString(),
+ info.body.toStdString());
return;
}
@@ -314,7 +328,7 @@ RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias)
joinRoomModal_->hide();
if (isJoining)
- http::client()->joinRoom(roomAlias);
+ emit joinRoom(roomAlias);
}
void
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index f3753971..acb33fa7 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -71,8 +71,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
this,
&FilteredTextEdit::uploadData);
- qRegisterMetaType<SearchResult>();
- qRegisterMetaType<QVector<SearchResult>>();
connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults);
connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) {
popup_.hide();
diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp
index ba487cea..6b1143b5 100644
--- a/src/dialogs/ReCaptcha.cpp
+++ b/src/dialogs/ReCaptcha.cpp
@@ -6,6 +6,7 @@
#include "Config.h"
#include "FlatButton.h"
+#include "MatrixClient.h"
#include "RaisedButton.h"
#include "Theme.h"
@@ -13,7 +14,7 @@
using namespace dialogs;
-ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *parent)
+ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
: QWidget(parent)
{
setAutoFillBackground(true);
@@ -51,12 +52,12 @@ ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *par
layout->addWidget(label);
layout->addLayout(buttonLayout);
- connect(openCaptchaBtn_, &QPushButton::clicked, [server, session]() {
- const auto url =
- QString(
- "https://%1/_matrix/client/r0/auth/m.login.recaptcha/fallback/web?session=%2")
- .arg(server)
- .arg(session);
+ connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
+ const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
+ "fallback/web?session=%3")
+ .arg(QString::fromStdString(http::v2::client()->server()))
+ .arg(http::v2::client()->port())
+ .arg(session);
QDesktopServices::openUrl(url);
});
diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp
index 4d2f304b..2396fc19 100644
--- a/src/dialogs/RoomSettings.cpp
+++ b/src/dialogs/RoomSettings.cpp
@@ -67,6 +67,20 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
labelLayout->addWidget(errorField_);
layout->addLayout(labelLayout);
+ connect(this, &EditModal::stateEventErrorCb, this, [this](const QString &msg) {
+ errorField_->setText(msg);
+ errorField_->show();
+ });
+ connect(this, &EditModal::nameEventSentCb, this, [this](const QString &newName) {
+ errorField_->hide();
+ emit nameChanged(newName);
+ close();
+ });
+ connect(this, &EditModal::topicEventSentCb, this, [this]() {
+ errorField_->hide();
+ close();
+ });
+
connect(applyBtn_, &QPushButton::clicked, [this]() {
// Check if the values are changed from the originals.
auto newName = nameInput_->text().trimmed();
@@ -85,53 +99,37 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
state::Name body;
body.name = newName.toStdString();
- auto proxy =
- http::client()->sendStateEvent<state::Name, EventType::RoomName>(body,
- roomId_);
- connect(proxy.get(),
- &StateEventProxy::stateEventSent,
- this,
- [this, proxy, newName]() {
- Q_UNUSED(proxy);
- errorField_->hide();
- emit nameChanged(newName);
- close();
- });
+ http::v2::client()->send_state_event<state::Name, EventType::RoomName>(
+ roomId_.toStdString(),
+ body,
+ [this, newName](const mtx::responses::EventId &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit stateEventErrorCb(
+ QString::fromStdString(err->matrix_error.error));
+ return;
+ }
- connect(proxy.get(),
- &StateEventProxy::stateEventError,
- this,
- [this, proxy, newName](const QString &msg) {
- Q_UNUSED(proxy);
- errorField_->setText(msg);
- errorField_->show();
- });
+ emit nameEventSentCb(newName);
+ });
}
if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
state::Topic body;
body.topic = newTopic.toStdString();
- auto proxy =
- http::client()->sendStateEvent<state::Topic, EventType::RoomTopic>(
- body, roomId_);
- connect(proxy.get(),
- &StateEventProxy::stateEventSent,
- this,
- [this, proxy, newTopic]() {
- Q_UNUSED(proxy);
- errorField_->hide();
- close();
- });
+ http::v2::client()->send_state_event<state::Topic, EventType::RoomTopic>(
+ roomId_.toStdString(),
+ body,
+ [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit stateEventErrorCb(
+ QString::fromStdString(err->matrix_error.error));
+ return;
+ }
- connect(proxy.get(),
- &StateEventProxy::stateEventError,
- this,
- [this, proxy, newTopic](const QString &msg) {
- Q_UNUSED(proxy);
- errorField_->setText(msg);
- errorField_->show();
- });
+ emit topicEventSentCb();
+ });
}
});
connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
diff --git a/src/main.cc b/src/main.cc
index bd3a212c..1df8d0c9 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -22,15 +22,17 @@
#include <QLabel>
#include <QLayout>
#include <QLibraryInfo>
-#include <QNetworkProxy>
#include <QPalette>
#include <QPoint>
#include <QPushButton>
#include <QSettings>
+#include <QStandardPaths>
#include <QTranslator>
#include "Config.h"
+#include "Logging.hpp"
#include "MainWindow.h"
+#include "MatrixClient.h"
#include "RaisedButton.h"
#include "RunGuard.h"
#include "version.hpp"
@@ -46,32 +48,6 @@ screenCenter(int width, int height)
return QPoint(x, y);
}
-void
-setupProxy()
-{
- QSettings settings;
-
- /**
- To set up a SOCKS proxy:
- [user]
- proxy\socks\host=<>
- proxy\socks\port=<>
- proxy\socks\user=<>
- proxy\socks\password=<>
- **/
- if (settings.contains("user/proxy/socks/host")) {
- QNetworkProxy proxy;
- proxy.setType(QNetworkProxy::Socks5Proxy);
- proxy.setHostName(settings.value("user/proxy/socks/host").toString());
- proxy.setPort(settings.value("user/proxy/socks/port").toInt());
- if (settings.contains("user/proxy/socks/user"))
- proxy.setUser(settings.value("user/proxy/socks/user").toString());
- if (settings.contains("user/proxy/socks/password"))
- proxy.setPassword(settings.value("user/proxy/socks/password").toString());
- QNetworkProxy::setApplicationProxy(proxy);
- }
-}
-
int
main(int argc, char *argv[])
{
@@ -133,7 +109,17 @@ main(int argc, char *argv[])
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
app.setWindowIcon(QIcon(":/logos/nheko.png"));
- qSetMessagePattern("%{time process}: [%{type}] - %{message}");
+
+ http::init();
+
+ try {
+ log::init(QString("%1/nheko.log")
+ .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
+ .toStdString());
+ } catch (const spdlog::spdlog_ex &ex) {
+ std::cout << "Log initialization failed: " << ex.what() << std::endl;
+ std::exit(1);
+ }
QSettings settings;
@@ -154,8 +140,6 @@ main(int argc, char *argv[])
appTranslator.load("nheko_" + lang, ":/translations");
app.installTranslator(&appTranslator);
- setupProxy();
-
MainWindow w;
// Move the MainWindow to the center
@@ -167,5 +151,7 @@ main(int argc, char *argv[])
QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize);
+ log::main()->info("starting nheko {}", nheko::version);
+
return app.exec();
}
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 250373e4..83a0aaed 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cc
@@ -62,9 +62,27 @@ TimelineItem::init()
ChatPage::instance()->showReadReceipts(event_id_);
});
+ connect(this, &TimelineItem::eventRedacted, this, [this](const QString &event_id) {
+ emit ChatPage::instance()->removeTimelineEvent(room_id_, event_id);
+ });
+ connect(this, &TimelineItem::redactionFailed, this, [](const QString &msg) {
+ emit ChatPage::instance()->showNotification(msg);
+ });
connect(redactMsg_, &QAction::triggered, this, [this]() {
if (!event_id_.isEmpty())
- http::client()->redactEvent(room_id_, event_id_);
+ http::v2::client()->redact_event(
+ room_id_.toStdString(),
+ event_id_.toStdString(),
+ [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+ if (err) {
+ emit redactionFailed(tr("Message redaction failed: %1")
+ .arg(QString::fromStdString(
+ err->matrix_error.error)));
+ return;
+ }
+
+ emit eventRedacted(event_id_);
+ });
});
connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); });
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 71058d74..5ef390a9 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -23,6 +23,7 @@
#include "ChatPage.h"
#include "Config.h"
#include "FloatingButton.h"
+#include "Logging.hpp"
#include "UserSettingsPage.h"
#include "Utils.h"
@@ -100,7 +101,7 @@ TimelineView::TimelineView(const QString &room_id, QWidget *parent)
, room_id_{room_id}
{
init();
- http::client()->messages(room_id_, "");
+ getMessages();
}
void
@@ -140,7 +141,7 @@ TimelineView::fetchHistory()
return;
isPaginationInProgress_ = true;
- http::client()->messages(room_id_, prev_batch_token_);
+ getMessages();
paginationTimer_->start(5000);
return;
@@ -189,18 +190,13 @@ TimelineView::sliderMoved(int position)
isPaginationInProgress_ = true;
- // FIXME: Maybe move this to TimelineViewManager to remove the
- // extra calls?
- http::client()->messages(room_id_, prev_batch_token_);
+ getMessages();
}
}
void
-TimelineView::addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs)
+TimelineView::addBackwardsEvents(const mtx::responses::Messages &msgs)
{
- if (room_id_ != room_id)
- return;
-
// We've reached the start of the timline and there're no more messages.
if ((msgs.end == msgs.start) && msgs.chunk.size() == 0) {
isTimelineFinished = true;
@@ -427,10 +423,10 @@ TimelineView::init()
paginationTimer_ = new QTimer(this);
connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory);
- connect(http::client(),
- &MatrixClient::messagesRetrieved,
- this,
- &TimelineView::addBackwardsEvents);
+ connect(this, &TimelineView::messagesRetrieved, this, &TimelineView::addBackwardsEvents);
+
+ connect(this, &TimelineView::messageFailed, this, &TimelineView::handleFailedMessage);
+ connect(this, &TimelineView::messageSent, this, &TimelineView::updatePendingMessage);
connect(scroll_area_->verticalScrollBar(),
SIGNAL(valueChanged(int)),
@@ -443,6 +439,27 @@ TimelineView::init()
}
void
+TimelineView::getMessages()
+{
+ mtx::http::MessagesOpts opts;
+ opts.room_id = room_id_.toStdString();
+ opts.from = prev_batch_token_.toStdString();
+
+ http::v2::client()->messages(
+ opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->error("failed to call /messages ({}): {} - {}",
+ opts.room_id,
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error);
+ return;
+ }
+
+ emit messagesRetrieved(std::move(res));
+ });
+}
+
+void
TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction)
{
if (direction == TimelineDirection::Bottom)
@@ -513,7 +530,7 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
}
void
-TimelineView::updatePendingMessage(int txn_id, QString event_id)
+TimelineView::updatePendingMessage(const std::string &txn_id, const QString &event_id)
{
if (!pending_msgs_.isEmpty() &&
pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet
@@ -548,8 +565,11 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
- int txn_id = http::client()->incrementTransactionId();
- PendingMessage message(ty, txn_id, body, "", "", -1, "", view_item);
+ PendingMessage message;
+ message.ty = ty;
+ message.txn_id = mtx::client::utils::random_token();
+ message.body = body;
+ message.widget = view_item;
handleNewUserMessage(message);
}
@@ -567,19 +587,119 @@ TimelineView::sendNextPendingMessage()
if (pending_msgs_.size() == 0)
return;
+ using namespace mtx::events;
+
PendingMessage &m = pending_msgs_.head();
switch (m.ty) {
- case mtx::events::MessageType::Audio:
- case mtx::events::MessageType::Image:
- case mtx::events::MessageType::Video:
- case mtx::events::MessageType::File:
- // FIXME: Improve the API
- http::client()->sendRoomMessage(
- m.ty, m.txn_id, room_id_, m.filename, m.mime, m.media_size, m.body);
+ case mtx::events::MessageType::Audio: {
+ msg::Audio audio;
+ audio.info.mimetype = m.mime.toStdString();
+ audio.info.size = m.media_size;
+ audio.body = m.filename.toStdString();
+ audio.url = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::Audio, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ audio,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
+
+ break;
+ }
+ case mtx::events::MessageType::Image: {
+ msg::Image image;
+ image.info.mimetype = m.mime.toStdString();
+ image.info.size = m.media_size;
+ image.body = m.filename.toStdString();
+ image.url = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::Image, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ image,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
+
+ break;
+ }
+ case mtx::events::MessageType::Video: {
+ msg::Video video;
+ video.info.mimetype = m.mime.toStdString();
+ video.info.size = m.media_size;
+ video.body = m.filename.toStdString();
+ video.url = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::Video, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ video,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
+
+ break;
+ }
+ case mtx::events::MessageType::File: {
+ msg::File file;
+ file.info.mimetype = m.mime.toStdString();
+ file.info.size = m.media_size;
+ file.body = m.filename.toStdString();
+ file.url = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::File, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ file,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
+
+ break;
+ }
+ case mtx::events::MessageType::Text: {
+ msg::Text text;
+ text.body = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::Text, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ text,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
+
+ break;
+ }
+ case mtx::events::MessageType::Emote: {
+ msg::Emote emote;
+ emote.body = m.body.toStdString();
+
+ http::v2::client()->send_room_message<msg::Emote, EventType::RoomMessage>(
+ room_id_.toStdString(),
+ m.txn_id,
+ emote,
+ std::bind(&TimelineView::sendRoomMessageHandler,
+ this,
+ m.txn_id,
+ std::placeholders::_1,
+ std::placeholders::_2));
break;
+ }
default:
- http::client()->sendRoomMessage(
- m.ty, m.txn_id, room_id_, m.body, m.mime, m.media_size);
+ log::main()->warn("cannot send unknown message type: {}", m.body.toStdString());
break;
}
}
@@ -593,7 +713,7 @@ TimelineView::notifyForLastEvent()
if (lastTimelineItem)
emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
else
- qWarning() << "Cast to TimelineView failed" << room_id_;
+ log::main()->warn("cast to TimelineView failed: {}", room_id_.toStdString());
}
void
@@ -606,29 +726,27 @@ TimelineView::notifyForLastEvent(const TimelineEvent &event)
}
bool
-TimelineView::isPendingMessage(const QString &txnid,
+TimelineView::isPendingMessage(const std::string &txn_id,
const QString &sender,
const QString &local_userid)
{
if (sender != local_userid)
return false;
- auto match_txnid = [txnid](const auto &msg) -> bool {
- return QString::number(msg.txn_id) == txnid;
- };
+ auto match_txnid = [txn_id](const auto &msg) -> bool { return msg.txn_id == txn_id; };
return std::any_of(pending_msgs_.cbegin(), pending_msgs_.cend(), match_txnid) ||
std::any_of(pending_sent_msgs_.cbegin(), pending_sent_msgs_.cend(), match_txnid);
}
void
-TimelineView::removePendingMessage(const QString &txnid)
+TimelineView::removePendingMessage(const std::string &txn_id)
{
- if (txnid.isEmpty())
+ if (txn_id.empty())
return;
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) {
- if (QString::number(it->txn_id) == txnid) {
+ if (it->txn_id == txn_id) {
int index = std::distance(pending_sent_msgs_.begin(), it);
pending_sent_msgs_.removeAt(index);
@@ -639,7 +757,7 @@ TimelineView::removePendingMessage(const QString &txnid)
}
}
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
- if (QString::number(it->txn_id) == txnid) {
+ if (it->txn_id == txn_id) {
int index = std::distance(pending_msgs_.begin(), it);
pending_msgs_.removeAt(index);
return;
@@ -648,9 +766,9 @@ TimelineView::removePendingMessage(const QString &txnid)
}
void
-TimelineView::handleFailedMessage(int txnid)
+TimelineView::handleFailedMessage(const std::string &txn_id)
{
- Q_UNUSED(txnid);
+ Q_UNUSED(txn_id);
// Note: We do this even if the message has already been echoed.
QTimer::singleShot(2000, this, SLOT(sendNextPendingMessage()));
}
@@ -673,7 +791,16 @@ TimelineView::readLastEvent() const
const auto eventId = getLastEventId();
if (!eventId.isEmpty())
- http::client()->readEvent(room_id_, eventId);
+ http::v2::client()->read_event(room_id_.toStdString(),
+ eventId.toStdString(),
+ [this, eventId](mtx::http::RequestErr err) {
+ if (err) {
+ log::net()->warn(
+ "failed to read event ({}, {})",
+ room_id_.toStdString(),
+ eventId.toStdString());
+ }
+ });
}
QString
@@ -743,7 +870,8 @@ void
TimelineView::removeEvent(const QString &event_id)
{
if (!eventIds_.contains(event_id)) {
- qWarning() << "unknown event_id couldn't be removed:" << event_id;
+ log::main()->warn("cannot remove widget with unknown event_id: {}",
+ event_id.toStdString());
return;
}
@@ -860,3 +988,16 @@ TimelineView::isDateDifference(const QDateTime &first, const QDateTime &second)
return diffInSeconds > fifteenMins;
}
+
+void
+TimelineView::sendRoomMessageHandler(const std::string &txn_id,
+ const mtx::responses::EventId &res,
+ mtx::http::RequestErr err)
+{
+ if (err) {
+ emit messageFailed(txn_id);
+ return;
+ }
+
+ emit messageSent(txn_id, QString::fromStdString(res.event_id.to_string()));
+}
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index b7ce53ae..9026463d 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cc
@@ -35,42 +35,15 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
: QStackedWidget(parent)
{
setStyleSheet("border: none;");
-
- connect(
- http::client(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent);
-
- connect(http::client(),
- &MatrixClient::messageSendFailed,
- this,
- &TimelineViewManager::messageSendFailed);
-
- connect(http::client(),
- &MatrixClient::redactionCompleted,
- this,
- [this](const QString &room_id, const QString &event_id) {
- auto view = views_[room_id];
-
- if (view)
- view->removeEvent(event_id);
- });
}
void
-TimelineViewManager::messageSent(const QString &event_id, const QString &roomid, int txn_id)
+TimelineViewManager::removeTimelineEvent(const QString &room_id, const QString &event_id)
{
- // We save the latest valid transaction ID for later use.
- QSettings settings;
- settings.setValue("client/transaction_id", txn_id + 1);
+ auto view = views_[room_id];
- auto view = views_[roomid];
- view->updatePendingMessage(txn_id, event_id);
-}
-
-void
-TimelineViewManager::messageSendFailed(const QString &roomid, int txn_id)
-{
- auto view = views_[roomid];
- view->handleFailedMessage(txn_id);
+ if (view)
+ view->removeEvent(event_id);
}
void
diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc
index 65ca401b..1ad47747 100644
--- a/src/timeline/widgets/AudioItem.cc
+++ b/src/timeline/widgets/AudioItem.cc
@@ -50,21 +50,12 @@ AudioItem::init()
playIcon_.addFile(":/icons/icons/ui/play-sign.png");
pauseIcon_.addFile(":/icons/icons/ui/pause-symbol.png");
- QList<QString> url_parts = url_.toString().split("mxc://");
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
- }
-
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2")
- .arg(http::client()->getHomeServer().toString(), media_params);
-
player_ = new QMediaPlayer;
player_->setMedia(QUrl(url_));
player_->setVolume(100);
player_->setNotifyInterval(1000);
+ connect(this, &AudioItem::fileDownloadedCb, this, &AudioItem::fileDownloaded);
connect(player_, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state) {
if (state == QMediaPlayer::StoppedState) {
state_ = AudioState::Play;
@@ -129,14 +120,19 @@ AudioItem::mousePressEvent(QMouseEvent *event)
if (filenameToSave_.isEmpty())
return;
- auto proxy = http::client()->downloadFile(url_);
- connect(proxy.data(),
- &DownloadMediaProxy::fileDownloaded,
- this,
- [proxy, this](const QByteArray &data) {
- proxy->deleteLater();
- fileDownloaded(data);
- });
+ http::v2::client()->download(
+ url_.toString().toStdString(),
+ [this](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ qWarning() << "failed to retrieve m.audio content:" << url_;
+ return;
+ }
+
+ emit fileDownloadedCb(QByteArray(data.data(), data.size()));
+ });
}
}
diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc
index f3906a04..43689243 100644
--- a/src/timeline/widgets/FileItem.cc
+++ b/src/timeline/widgets/FileItem.cc
@@ -49,17 +49,9 @@ FileItem::init()
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png");
- QList<QString> url_parts = url_.toString().split("mxc://");
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
- }
-
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2")
- .arg(http::client()->getHomeServer().toString(), media_params);
-
setFixedHeight(Height);
+
+ connect(this, &FileItem::fileDownloadedCb, this, &FileItem::fileDownloaded);
}
FileItem::FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event, QWidget *parent)
@@ -89,8 +81,15 @@ FileItem::openUrl()
if (url_.toString().isEmpty())
return;
- if (!QDesktopServices::openUrl(url_))
- qWarning() << "Could not open url" << url_.toString();
+ auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
+ auto urlToOpen = QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
+ .arg(QString::fromStdString(http::v2::client()->server()))
+ .arg(http::v2::client()->port())
+ .arg(QString::fromStdString(mxc_parts.server))
+ .arg(QString::fromStdString(mxc_parts.media_id));
+
+ if (!QDesktopServices::openUrl(urlToOpen))
+ qWarning() << "Could not open url" << urlToOpen;
}
QSize
@@ -115,14 +114,19 @@ FileItem::mousePressEvent(QMouseEvent *event)
if (filenameToSave_.isEmpty())
return;
- auto proxy = http::client()->downloadFile(url_);
- connect(proxy.data(),
- &DownloadMediaProxy::fileDownloaded,
- this,
- [proxy, this](const QByteArray &data) {
- proxy->deleteLater();
- fileDownloaded(data);
- });
+ http::v2::client()->download(
+ url_.toString().toStdString(),
+ [this](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ qWarning() << "failed to retrieve m.file content:" << url_;
+ return;
+ }
+
+ emit fileDownloadedCb(QByteArray(data.data(), data.size()));
+ });
} else {
openUrl();
}
diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc
index 66cd31ab..6aa010a4 100644
--- a/src/timeline/widgets/ImageItem.cc
+++ b/src/timeline/widgets/ImageItem.cc
@@ -30,37 +30,62 @@
#include "dialogs/ImageOverlay.h"
#include "timeline/widgets/ImageItem.h"
-ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent)
- : QWidget(parent)
- , event_{event}
+void
+ImageItem::downloadMedia(const QUrl &url)
{
- setMouseTracking(true);
- setCursor(Qt::PointingHandCursor);
- setAttribute(Qt::WA_Hover, true);
+ http::v2::client()->download(url.toString().toStdString(),
+ [this, url](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ qWarning()
+ << "failed to retrieve image:" << url;
+ return;
+ }
- url_ = QString::fromStdString(event.content.url);
- text_ = QString::fromStdString(event.content.body);
+ QPixmap img;
+ img.loadFromData(QByteArray(data.data(), data.size()));
+ emit imageDownloaded(img);
+ });
+}
- QList<QString> url_parts = url_.toString().split("mxc://");
+void
+ImageItem::saveImage(const QString &filename, const QByteArray &data)
+{
+ try {
+ QFile file(filename);
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
+ if (!file.open(QIODevice::WriteOnly))
+ return;
+
+ file.write(data);
+ file.close();
+ } catch (const std::exception &ex) {
+ qDebug() << "Error while saving file to:" << ex.what();
}
+}
+
+void
+ImageItem::init()
+{
+ setMouseTracking(true);
+ setCursor(Qt::PointingHandCursor);
+ setAttribute(Qt::WA_Hover, true);
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2")
- .arg(http::client()->getHomeServer().toString(), media_params);
+ connect(this, &ImageItem::imageDownloaded, this, &ImageItem::setImage);
+ connect(this, &ImageItem::imageSaved, this, &ImageItem::saveImage);
+ downloadMedia(url_);
+}
- auto proxy = http::client()->downloadImage(url_);
+ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent)
+ : QWidget(parent)
+ , event_{event}
+{
+ url_ = QString::fromStdString(event.content.url);
+ text_ = QString::fromStdString(event.content.body);
- connect(proxy.data(),
- &DownloadMediaProxy::imageDownloaded,
- this,
- [this, proxy](const QPixmap &img) {
- proxy->deleteLater();
- setImage(img);
- });
+ init();
}
ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
@@ -69,31 +94,7 @@ ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size,
, text_{filename}
{
Q_UNUSED(size);
-
- setMouseTracking(true);
- setCursor(Qt::PointingHandCursor);
- setAttribute(Qt::WA_Hover, true);
-
- QList<QString> url_parts = url_.toString().split("mxc://");
-
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
- }
-
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2")
- .arg(http::client()->getHomeServer().toString(), media_params);
-
- auto proxy = http::client()->downloadImage(url_);
-
- connect(proxy.data(),
- &DownloadMediaProxy::imageDownloaded,
- this,
- [proxy, this](const QPixmap &img) {
- proxy->deleteLater();
- setImage(img);
- });
+ init();
}
void
@@ -102,8 +103,15 @@ ImageItem::openUrl()
if (url_.toString().isEmpty())
return;
- if (!QDesktopServices::openUrl(url_))
- qWarning() << "Could not open url" << url_.toString();
+ auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
+ auto urlToOpen = QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
+ .arg(QString::fromStdString(http::v2::client()->server()))
+ .arg(http::v2::client()->port())
+ .arg(QString::fromStdString(mxc_parts.server))
+ .arg(QString::fromStdString(mxc_parts.media_id));
+
+ if (!QDesktopServices::openUrl(urlToOpen))
+ qWarning() << "Could not open url" << urlToOpen;
}
QSize
@@ -231,23 +239,17 @@ ImageItem::saveAs()
if (filename.isEmpty())
return;
- auto proxy = http::client()->downloadFile(url_);
- connect(proxy.data(),
- &DownloadMediaProxy::fileDownloaded,
- this,
- [proxy, filename](const QByteArray &data) {
- proxy->deleteLater();
-
- try {
- QFile file(filename);
-
- if (!file.open(QIODevice::WriteOnly))
- return;
+ http::v2::client()->download(
+ url_.toString().toStdString(),
+ [this, filename](const std::string &data,
+ const std::string &,
+ const std::string &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ qWarning() << "failed to retrieve image:" << url_;
+ return;
+ }
- file.write(data);
- file.close();
- } catch (const std::exception &ex) {
- qDebug() << "Error while saving file to:" << ex.what();
- }
- });
+ emit imageSaved(filename, QByteArray(data.data(), data.size()));
+ });
}
diff --git a/src/timeline/widgets/VideoItem.cc b/src/timeline/widgets/VideoItem.cc
index f5bcfd6e..c1f68847 100644
--- a/src/timeline/widgets/VideoItem.cc
+++ b/src/timeline/widgets/VideoItem.cc
@@ -27,15 +27,15 @@
void
VideoItem::init()
{
- QList<QString> url_parts = url_.toString().split("mxc://");
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
- }
+ // QList<QString> url_parts = url_.toString().split("mxc://");
+ // if (url_parts.size() != 2) {
+ // qDebug() << "Invalid format for image" << url_.toString();
+ // return;
+ // }
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2")
- .arg(http::client()->getHomeServer().toString(), media_params);
+ // QString media_params = url_parts[1];
+ // url_ = QString("%1/_matrix/media/r0/download/%2")
+ // .arg(http::client()->getHomeServer().toString(), media_params);
}
VideoItem::VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event, QWidget *parent)
|