diff options
author | Konstantinos Sideris <sideris.konstantin@gmail.com> | 2018-06-09 16:03:14 +0300 |
---|---|---|
committer | Konstantinos Sideris <sideris.konstantin@gmail.com> | 2018-06-09 16:03:14 +0300 |
commit | b89257a34b2a98b737f4ae544f7e436b9000b240 (patch) | |
tree | 81d7f355721541afbd91dc9a085abbb4666f3565 /src | |
parent | 1366b01790631ba1134a4dfd03d939b4b40360b8 (diff) |
Migrate to mtxclient for the http calls
Diffstat (limited to 'src')
-rw-r--r-- | src/AvatarProvider.cc | 50 | ||||
-rw-r--r-- | src/Cache.cc | 112 | ||||
-rw-r--r-- | src/ChatPage.cc | 884 | ||||
-rw-r--r-- | src/CommunitiesList.cc | 61 | ||||
-rw-r--r-- | src/Logging.cpp | 50 | ||||
-rw-r--r-- | src/LoginPage.cc | 69 | ||||
-rw-r--r-- | src/MainWindow.cc | 54 | ||||
-rw-r--r-- | src/MatrixClient.cc | 1371 | ||||
-rw-r--r-- | src/RegisterPage.cc | 130 | ||||
-rw-r--r-- | src/RoomList.cc | 54 | ||||
-rw-r--r-- | src/TextInputWidget.cc | 2 | ||||
-rw-r--r-- | src/dialogs/ReCaptcha.cpp | 15 | ||||
-rw-r--r-- | src/dialogs/RoomSettings.cpp | 80 | ||||
-rw-r--r-- | src/main.cc | 46 | ||||
-rw-r--r-- | src/timeline/TimelineItem.cc | 20 | ||||
-rw-r--r-- | src/timeline/TimelineView.cc | 217 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.cc | 35 | ||||
-rw-r--r-- | src/timeline/widgets/AudioItem.cc | 32 | ||||
-rw-r--r-- | src/timeline/widgets/FileItem.cc | 44 | ||||
-rw-r--r-- | src/timeline/widgets/ImageItem.cc | 140 | ||||
-rw-r--r-- | src/timeline/widgets/VideoItem.cc | 16 |
21 files changed, 1400 insertions, 2082 deletions
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 im |