Migrate to mtxclient for the http calls

This commit is contained in:
Konstantinos Sideris 2018-06-09 16:03:14 +03:00
parent 1366b01790
commit b89257a34b
44 changed files with 1624 additions and 2430 deletions

View File

@ -53,7 +53,6 @@ include(LMDB)
# Discover Qt dependencies. # Discover Qt dependencies.
# #
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5Concurrent REQUIRED) find_package(Qt5Concurrent REQUIRED)
find_package(Qt5Svg REQUIRED) find_package(Qt5Svg REQUIRED)
@ -181,6 +180,7 @@ set(SRC_FILES
src/Community.cc src/Community.cc
src/InviteeItem.cc src/InviteeItem.cc
src/LoginPage.cc src/LoginPage.cc
src/Logging.cpp
src/MainWindow.cc src/MainWindow.cc
src/MatrixClient.cc src/MatrixClient.cc
src/QuickSwitcher.cc src/QuickSwitcher.cc
@ -287,7 +287,6 @@ qt5_wrap_cpp(MOC_HEADERS
include/LoginPage.h include/LoginPage.h
include/MainWindow.h include/MainWindow.h
include/InviteeItem.h include/InviteeItem.h
include/MatrixClient.h
include/QuickSwitcher.h include/QuickSwitcher.h
include/RegisterPage.h include/RegisterPage.h
include/RoomInfoListItem.h include/RoomInfoListItem.h
@ -314,7 +313,6 @@ set(COMMON_LIBS
MatrixStructs::MatrixStructs MatrixStructs::MatrixStructs
MatrixClient::MatrixClient MatrixClient::MatrixClient
Qt5::Widgets Qt5::Widgets
Qt5::Network
Qt5::Svg Qt5::Svg
Qt5::Concurrent) Qt5::Concurrent)

2
deps/CMakeLists.txt vendored
View File

@ -40,7 +40,7 @@ set(MATRIX_STRUCTS_URL https://github.com/mujx/matrix-structs)
set(MATRIX_STRUCTS_TAG eeb7373729a1618e2b3838407863342b88b8a0de) set(MATRIX_STRUCTS_TAG eeb7373729a1618e2b3838407863342b88b8a0de)
set(MTXCLIENT_URL https://github.com/mujx/mtxclient) set(MTXCLIENT_URL https://github.com/mujx/mtxclient)
set(MTXCLIENT_TAG 219d2a8887376122e76ba0f64c0cc9935f62f308) set(MTXCLIENT_TAG 57f56d1fe73989dbe041a7ac0a28bf2e3286bf98)
set(OLM_URL https://git.matrix.org/git/olm.git) set(OLM_URL https://git.matrix.org/git/olm.git)
set(OLM_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae) set(OLM_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae)

View File

@ -15,6 +15,7 @@ ExternalProject_Add(
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND ${MAKE_CMD} static BUILD_COMMAND ${MAKE_CMD} static
INSTALL_COMMAND INSTALL_COMMAND
mkdir -p ${DEPS_INSTALL_DIR}/lib &&
cp -R ${DEPS_BUILD_DIR}/olm/include ${DEPS_INSTALL_DIR} && cp -R ${DEPS_BUILD_DIR}/olm/include ${DEPS_INSTALL_DIR} &&
cp ${DEPS_BUILD_DIR}/olm/build/libolm.a ${DEPS_INSTALL_DIR}/lib cp ${DEPS_BUILD_DIR}/olm/build/libolm.a ${DEPS_INSTALL_DIR}/lib
) )

View File

@ -8,6 +8,8 @@ ExternalProject_Add(
SOURCE_DIR ${DEPS_BUILD_DIR}/spdlog SOURCE_DIR ${DEPS_BUILD_DIR}/spdlog
CONFIGURE_COMMAND ${CMAKE_COMMAND} CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DSPDLOG_BUILD_EXAMPLES=0
-DSPDLOG_BUILD_TESTING=0
-DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release
${DEPS_BUILD_DIR}/spdlog ${DEPS_BUILD_DIR}/spdlog
) )

View File

@ -20,15 +20,17 @@
#include <QImage> #include <QImage>
#include <functional> #include <functional>
class AvatarProvider : public QObject class AvatarProxy : public QObject
{ {
Q_OBJECT Q_OBJECT
public: signals:
//! The callback is called with the downloaded avatar for the given user void avatarDownloaded(const QByteArray &data);
//! or the avatar is downloaded first and then saved for re-use.
static void resolve(const QString &room_id,
const QString &userId,
QObject *receiver,
std::function<void(QImage)> callback);
}; };
using AvatarCallback = std::function<void(QImage)>;
namespace AvatarProvider {
void
resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb);
}

View File

@ -192,7 +192,7 @@ public:
void saveState(const mtx::responses::Sync &res); void saveState(const mtx::responses::Sync &res);
bool isInitialized() const; bool isInitialized() const;
QString nextBatchToken() const; std::string nextBatchToken() const;
void deleteData(); void deleteData();
@ -237,6 +237,7 @@ public:
{ {
return image(QString::fromStdString(url)); return image(QString::fromStdString(url));
} }
void saveImage(const std::string &url, const std::string &data);
void saveImage(const QString &url, const QByteArray &data); void saveImage(const QString &url, const QByteArray &data);
RoomInfo singleRoomInfo(const std::string &room_id); RoomInfo singleRoomInfo(const std::string &room_id);

View File

@ -17,6 +17,8 @@
#pragma once #pragma once
#include <atomic>
#include <QFrame> #include <QFrame>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMap> #include <QMap>
@ -50,9 +52,6 @@ constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000; constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000; constexpr int TYPING_REFRESH_TIMEOUT = 10000;
Q_DECLARE_METATYPE(mtx::responses::Rooms)
Q_DECLARE_METATYPE(std::vector<std::string>)
class ChatPage : public QWidget class ChatPage : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -71,7 +70,37 @@ public:
QSharedPointer<UserSettings> userSettings() { return userSettings_; } QSharedPointer<UserSettings> userSettings() { return userSettings_; }
void deleteConfigs(); void deleteConfigs();
public slots:
void leaveRoom(const QString &room_id);
signals: signals:
void connectionLost();
void connectionRestored();
void notificationsRetrieved(const mtx::responses::Notifications &);
void uploadFailed(const QString &msg);
void imageUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void fileUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void audioUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void videoUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
qint64 dsize);
void contentLoaded(); void contentLoaded();
void closing(); void closing();
void changeWindowTitle(const QString &msg); void changeWindowTitle(const QString &msg);
@ -82,30 +111,44 @@ signals:
void showOverlayProgressBar(); void showOverlayProgressBar();
void startConsesusTimer(); void startConsesusTimer();
void removeTimelineEvent(const QString &room_id, const QString &event_id);
void ownProfileOk();
void setUserDisplayName(const QString &name);
void setUserAvatar(const QImage &avatar);
void loggedOut();
void trySyncCb();
void tryInitialSyncCb();
void leftRoom(const QString &room_id);
void initializeRoomList(QMap<QString, RoomInfo>); void initializeRoomList(QMap<QString, RoomInfo>);
void initializeViews(const mtx::responses::Rooms &rooms); void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews(const std::vector<std::string> &rooms); void initializeEmptyViews(const std::vector<std::string> &rooms);
void syncUI(const mtx::responses::Rooms &rooms); void syncUI(const mtx::responses::Rooms &rooms);
void continueSync(const QString &next_batch);
void syncRoomlist(const std::map<QString, RoomInfo> &updates); void syncRoomlist(const std::map<QString, RoomInfo> &updates);
void syncTopBar(const std::map<QString, RoomInfo> &updates); void syncTopBar(const std::map<QString, RoomInfo> &updates);
void dropToLoginPageCb(const QString &msg);
private slots: private slots:
void showUnreadMessageNotification(int count); void showUnreadMessageNotification(int count);
void updateTopBarAvatar(const QString &roomid, const QPixmap &img); void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name);
void updateOwnCommunitiesInfo(const QList<QString> &own_communities); void updateOwnCommunitiesInfo(const QList<QString> &own_communities);
void initialSyncCompleted(const mtx::responses::Sync &response);
void syncCompleted(const mtx::responses::Sync &response);
void changeTopRoomInfo(const QString &room_id); void changeTopRoomInfo(const QString &room_id);
void logout(); void logout();
void removeRoom(const QString &room_id); void removeRoom(const QString &room_id);
//! Handles initial sync failures. void dropToLoginPage(const QString &msg);
void retryInitialSync(int status_code = -1);
void joinRoom(const QString &room);
void createRoom(const mtx::requests::CreateRoom &req);
void sendTypingNotifications();
private: private:
static ChatPage *instance_; static ChatPage *instance_;
void tryInitialSync();
void trySync();
//! Check if the given room is currently open. //! Check if the given room is currently open.
bool isRoomActive(const QString &room_id) bool isRoomActive(const QString &room_id)
{ {
@ -161,8 +204,8 @@ private:
// Safety net if consensus is not possible or too slow. // Safety net if consensus is not possible or too slow.
QTimer *showContentTimer_; QTimer *showContentTimer_;
QTimer *consensusTimer_; QTimer *consensusTimer_;
QTimer *syncTimeoutTimer_; QTimer connectivityTimer_;
QTimer *initialSyncTimer_; std::atomic_bool isConnected_;
QString current_room_; QString current_room_;
QString current_community_; QString current_community_;

View File

@ -23,12 +23,14 @@ public:
signals: signals:
void communityChanged(const QString &id); void communityChanged(const QString &id);
void avatarRetrieved(const QString &id, const QPixmap &img);
public slots: public slots:
void updateCommunityAvatar(const QString &id, const QPixmap &img); void updateCommunityAvatar(const QString &id, const QPixmap &img);
void highlightSelectedCommunity(const QString &id); void highlightSelectedCommunity(const QString &id);
private: private:
void fetchCommunityAvatar(const QString &id, const QString &avatarUrl);
void addGlobalItem() { addCommunity(QSharedPointer<Community>(new Community), "world"); } void addGlobalItem() { addCommunity(QSharedPointer<Community>(new Community), "world"); }
//! Check whether or not a community id is currently managed. //! Check whether or not a community id is currently managed.

18
include/Logging.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include <spdlog/spdlog.h>
namespace log {
void
init(const std::string &file);
std::shared_ptr<spdlog::logger>
main();
std::shared_ptr<spdlog::logger>
net();
std::shared_ptr<spdlog::logger>
db();
}

View File

@ -28,6 +28,12 @@ class OverlayModal;
class RaisedButton; class RaisedButton;
class TextField; class TextField;
namespace mtx {
namespace responses {
struct Login;
}
}
class LoginPage : public QWidget class LoginPage : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -42,12 +48,19 @@ signals:
void loggingIn(); void loggingIn();
void errorOccurred(); void errorOccurred();
//! Used to trigger the corresponding slot outside of the main thread.
void versionErrorCb(const QString &err);
void loginErrorCb(const QString &err);
void versionOkCb();
void loginOk(const mtx::responses::Login &res);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
public slots: public slots:
// Displays errors produced during the login. // Displays errors produced during the login.
void loginError(QString msg) { error_label_->setText(msg); } void loginError(const QString &msg) { error_label_->setText(msg); }
private slots: private slots:
// Callback for the back button. // Callback for the back button.
@ -63,13 +76,25 @@ private slots:
void onServerAddressEntered(); void onServerAddressEntered();
// Callback for errors produced during server probing // Callback for errors produced during server probing
void versionError(QString error_message); void versionError(const QString &error_message);
// Callback for successful server probing // Callback for successful server probing
void versionSuccess(); void versionOk();
private: private:
bool isMatrixIdValid(); bool isMatrixIdValid();
void checkHomeserverVersion();
std::string initialDeviceName()
{
#if defined(Q_OS_MAC)
return "nheko on macOS";
#elif defined(Q_OS_LINUX)
return "nheko on Linux";
#elif defined(Q_OS_WIN)
return "nheko on Windows";
#else
return "nheko";
#endif
}
QVBoxLayout *top_layout_; QVBoxLayout *top_layout_;

View File

@ -59,6 +59,7 @@ class MainWindow : public QMainWindow
public: public:
explicit MainWindow(QWidget *parent = 0); explicit MainWindow(QWidget *parent = 0);
~MainWindow();
static MainWindow *instance() { return instance_; }; static MainWindow *instance() { return instance_; };
void saveCurrentWindowSize(); void saveCurrentWindowSize();
@ -96,7 +97,7 @@ private slots:
void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); } void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); }
//! Show the chat page and start communicating with the given access token. //! Show the chat page and start communicating with the given access token.
void showChatPage(QString user_id, QString home_server, QString token); void showChatPage();
void showOverlayProgressBar(); void showOverlayProgressBar();
void removeOverlayProgressBar(); void removeOverlayProgressBar();

View File

@ -1,287 +1,25 @@
/*
* 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/>.
*/
#pragma once #pragma once
#include <QFileInfo> #include <QMetaType>
#include <QJsonDocument>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <memory>
#include <mtx.hpp>
#include <mtx/errors.hpp>
class DownloadMediaProxy : public QObject #include <mtx/responses.hpp>
{ #include <mtxclient/http/client.hpp>
Q_OBJECT
signals:
void imageDownloaded(const QPixmap &data);
void fileDownloaded(const QByteArray &data);
void avatarDownloaded(const QImage &img);
};
class StateEventProxy : public QObject
{
Q_OBJECT
signals:
void stateEventSent();
void stateEventError(const QString &msg);
};
Q_DECLARE_METATYPE(mtx::responses::Login)
Q_DECLARE_METATYPE(mtx::responses::Messages)
Q_DECLARE_METATYPE(mtx::responses::Notifications)
Q_DECLARE_METATYPE(mtx::responses::Rooms)
Q_DECLARE_METATYPE(mtx::responses::Sync) Q_DECLARE_METATYPE(mtx::responses::Sync)
Q_DECLARE_METATYPE(std::string)
/* Q_DECLARE_METATYPE(std::vector<std::string>);
* MatrixClient provides the high level API to communicate with
* a Matrix homeserver. All the responses are returned through signals.
*/
class MatrixClient : public QNetworkAccessManager
{
Q_OBJECT
public:
MatrixClient(QObject *parent = 0);
// Client API.
void initialSync() noexcept;
void sync() noexcept;
template<class EventBody, mtx::events::EventType EventT>
std::shared_ptr<StateEventProxy> sendStateEvent(const EventBody &body,
const QString &roomId,
const QString &stateKey = "");
void sendRoomMessage(mtx::events::MessageType ty,
int txnId,
const QString &roomid,
const QString &msg,
const QString &mime,
uint64_t media_size,
const QString &url = "") noexcept;
void login(const QString &username, const QString &password) noexcept;
void registerUser(const QString &username,
const QString &password,
const QString &server,
const QString &session = "") noexcept;
void versions() noexcept;
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
//! Download user's avatar.
QSharedPointer<DownloadMediaProxy> fetchUserAvatar(const QUrl &avatarUrl);
void fetchCommunityAvatar(const QString &communityId, const QUrl &avatarUrl);
void fetchCommunityProfile(const QString &communityId);
void fetchCommunityRooms(const QString &communityId);
QSharedPointer<DownloadMediaProxy> downloadImage(const QUrl &url);
QSharedPointer<DownloadMediaProxy> downloadFile(const QUrl &url);
void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept;
void uploadImage(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data);
void uploadFile(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data);
void uploadAudio(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data);
void uploadVideo(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data);
void uploadFilter(const QString &filter) noexcept;
void joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
void removeTypingNotification(const QString &roomid);
void readEvent(const QString &room_id, const QString &event_id);
void redactEvent(const QString &room_id, const QString &event_id);
void inviteUser(const QString &room_id, const QString &user);
void createRoom(const mtx::requests::CreateRoom &request);
void getNotifications() noexcept;
QUrl getHomeServer() { return server_; };
int transactionId() { return txn_id_; };
int incrementTransactionId() { return ++txn_id_; };
void reset() noexcept;
public slots:
void getOwnProfile() noexcept;
void getOwnCommunities() noexcept;
void logout() noexcept;
void setServer(const QString &server)
{
server_ = QUrl(QString("%1://%2").arg(serverProtocol_).arg(server));
};
void setAccessToken(const QString &token) { token_ = token; };
void setNextBatchToken(const QString &next_batch) { next_batch_ = next_batch; };
signals:
void loginError(const QString &error);
void registerError(const QString &error);
void registrationFlow(const QString &user,
const QString &pass,
const QString &server,
const QString &session);
void versionError(const QString &error);
void loggedOut();
void invitedUser(const QString &room_id, const QString &user);
void roomCreated(const QString &room_id);
void loginSuccess(const QString &userid, const QString &homeserver, const QString &token);
void registerSuccess(const QString &userid,
const QString &homeserver,
const QString &token);
void versionSuccess();
void uploadFailed(int statusCode, const QString &msg);
void imageUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t size);
void fileUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t size);
void audioUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t size);
void videoUploaded(const QString &roomid,
const QString &filename,
const QString &url,
const QString &mime,
uint64_t size);
void roomAvatarRetrieved(const QString &roomid,
const QPixmap &img,
const QString &url,
const QByteArray &data);
void userAvatarRetrieved(const QString &userId, const QImage &img);
void communityAvatarRetrieved(const QString &communityId, const QPixmap &img);
void communityProfileRetrieved(const QString &communityId, const QJsonObject &profile);
void communityRoomsRetrieved(const QString &communityId, const QJsonObject &rooms);
// Returned profile data for the user's account.
void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name);
void getOwnCommunitiesResponse(const QList<QString> &own_communities);
void initialSyncCompleted(const mtx::responses::Sync &response);
void initialSyncFailed(int status_code = -1);
void syncCompleted(const mtx::responses::Sync &response);
void syncFailed(const QString &msg);
void joinFailed(const QString &msg);
void messageSent(const QString &event_id, const QString &roomid, int txn_id);
void messageSendFailed(const QString &roomid, int txn_id);
void emoteSent(const QString &event_id, const QString &roomid, int txn_id);
void messagesRetrieved(const QString &room_id, const mtx::responses::Messages &msgs);
void joinedRoom(const QString &room_id);
void leftRoom(const QString &room_id);
void roomCreationFailed(const QString &msg);
void redactionFailed(const QString &error);
void redactionCompleted(const QString &room_id, const QString &event_id);
void invalidToken();
void syncError(const QString &error);
void notificationsRetrieved(const mtx::responses::Notifications &notifications);
private:
QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
QJsonObject getUploadReply(QNetworkReply *reply);
void setupAuth(QNetworkRequest &req)
{
req.setRawHeader("Authorization", QString("Bearer %1").arg(token_).toLocal8Bit());
}
// Client API prefix.
QString clientApiUrl_;
// Media API prefix.
QString mediaApiUrl_;
// The Matrix server used for communication.
QUrl server_;
// The access token used for authentication.
QString token_;
// Increasing transaction ID.
int txn_id_;
//! Token to be used for the next sync.
QString next_batch_;
//! http or https (default).
QString serverProtocol_;
//! Filter to be send as filter-param for (initial) /sync requests.
QString filter_;
};
namespace http { namespace http {
//! Initialize the http module namespace v2 {
void mtx::http::Client *
init();
//! Retrieve the client instance.
MatrixClient *
client(); client();
} }
template<class EventBody, mtx::events::EventType EventT> //! Initialize the http module
std::shared_ptr<StateEventProxy> void
MatrixClient::sendStateEvent(const EventBody &body, const QString &roomId, const QString &stateKey) init();
{
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/state/%2/%3")
.arg(roomId)
.arg(QString::fromStdString(to_string(EventT)))
.arg(stateKey));
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
setupAuth(request);
auto proxy = std::shared_ptr<StateEventProxy>(new StateEventProxy,
[](StateEventProxy *p) { p->deleteLater(); });
auto serializedBody = nlohmann::json(body).dump();
auto reply = put(request, QByteArray(serializedBody.data(), serializedBody.size()));
connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
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 proxy->stateEventError(QString::fromStdString(res.error));
} catch (const std::exception &e) {
emit proxy->stateEventError(QString::fromStdString(e.what()));
}
return;
}
try {
mtx::responses::EventId res = nlohmann::json::parse(data);
emit proxy->stateEventSent();
} catch (const std::exception &e) {
emit proxy->stateEventError(QString::fromStdString(e.what()));
}
});
return proxy;
} }

View File

@ -44,6 +44,11 @@ signals:
void backButtonClicked(); void backButtonClicked();
void errorOccurred(); void errorOccurred();
void registering(); void registering();
void registerOk();
void registerErrorCb(const QString &msg);
void registrationFlow(const std::string &user,
const std::string &pass,
const std::string &session);
private slots: private slots:
void onBackButtonClicked(); void onBackButtonClicked();

View File

@ -60,6 +60,8 @@ signals:
void acceptInvite(const QString &room_id); void acceptInvite(const QString &room_id);
void declineInvite(const QString &room_id); void declineInvite(const QString &room_id);
void roomAvatarChanged(const QString &room_id, const QPixmap &img); void roomAvatarChanged(const QString &room_id, const QPixmap &img);
void joinRoom(const QString &room_id);
void updateRoomAvatarCb(const QString &room_id, const QPixmap &img);
public slots: public slots:
void updateRoomAvatar(const QString &roomid, const QPixmap &img); void updateRoomAvatar(const QString &roomid, const QPixmap &img);

View File

@ -129,6 +129,16 @@ public:
QColor borderColor() const { return borderColor_; } QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; } void setBorderColor(QColor &color) { borderColor_ = color; }
void disableInput()
{
input_->setEnabled(false);
input_->setPlaceholderText(tr("Connection lost. Nheko is trying to re-connect..."));
}
void enableInput()
{
input_->setEnabled(true);
input_->setPlaceholderText(tr("Write a message..."));
}
public slots: public slots:
void openFileSelection(); void openFileSelection();

View File

@ -12,7 +12,7 @@ class ReCaptcha : public QWidget
Q_OBJECT Q_OBJECT
public: public:
ReCaptcha(const QString &server, const QString &session, QWidget *parent = nullptr); ReCaptcha(const QString &session, QWidget *parent = nullptr);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;

View File

@ -30,6 +30,9 @@ public:
signals: signals:
void nameChanged(const QString &roomName); void nameChanged(const QString &roomName);
void nameEventSentCb(const QString &newName);
void topicEventSentCb();
void stateEventErrorCb(const QString &msg);
private: private:
QString roomId_; QString roomId_;

View File

@ -197,12 +197,24 @@ public:
void sendReadReceipt() const void sendReadReceipt() const
{ {
if (!event_id_.isEmpty()) if (!event_id_.isEmpty())
http::client()->readEvent(room_id_, event_id_); http::v2::client()->read_event(
room_id_.toStdString(),
event_id_.toStdString(),
[this](mtx::http::RequestErr err) {
if (err) {
qWarning() << QString("failed to read_event (%1, %2)")
.arg(room_id_, event_id_);
}
});
} }
//! Add a user avatar for this event. //! Add a user avatar for this event.
void addAvatar(); void addAvatar();
signals:
void eventRedacted(const QString &event_id);
void redactionFailed(const QString &msg);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override;

View File

@ -18,7 +18,6 @@
#pragma once #pragma once
#include <QApplication> #include <QApplication>
#include <QDebug>
#include <QLayout> #include <QLayout>
#include <QList> #include <QList>
#include <QQueue> #include <QQueue>
@ -42,31 +41,13 @@ struct DescInfo;
struct PendingMessage struct PendingMessage
{ {
mtx::events::MessageType ty; mtx::events::MessageType ty;
int txn_id; std::string txn_id;
QString body; QString body;
QString filename; QString filename;
QString mime; QString mime;
uint64_t media_size; uint64_t media_size;
QString event_id; QString event_id;
TimelineItem *widget; TimelineItem *widget;
PendingMessage(mtx::events::MessageType ty,
int txn_id,
QString body,
QString filename,
QString mime,
uint64_t media_size,
QString event_id,
TimelineItem *widget)
: ty(ty)
, txn_id(txn_id)
, body(body)
, filename(filename)
, mime(mime)
, media_size(media_size)
, event_id(event_id)
, widget(widget)
{}
}; };
// In which place new TimelineItems should be inserted. // In which place new TimelineItems should be inserted.
@ -129,7 +110,7 @@ public:
const QString &filename, const QString &filename,
const QString &mime, const QString &mime,
uint64_t size); uint64_t size);
void updatePendingMessage(int txn_id, QString event_id); void updatePendingMessage(const std::string &txn_id, const QString &event_id);
void scrollDown(); void scrollDown();
QLabel *createDateSeparator(QDateTime datetime); QLabel *createDateSeparator(QDateTime datetime);
@ -142,18 +123,21 @@ public slots:
void fetchHistory(); void fetchHistory();
// Add old events at the top of the timeline. // Add old events at the top of the timeline.
void addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs); void addBackwardsEvents(const mtx::responses::Messages &msgs);
// Whether or not the initial batch has been loaded. // Whether or not the initial batch has been loaded.
bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; } bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }
void handleFailedMessage(int txnid); void handleFailedMessage(const std::string &txn_id);
private slots: private slots:
void sendNextPendingMessage(); void sendNextPendingMessage();
signals: signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info); void updateLastTimelineMessage(const QString &user, const DescInfo &info);
void messagesRetrieved(const mtx::responses::Messages &res);
void messageFailed(const std::string &txn_id);
void messageSent(const std::string &txn_id, const QString &event_id);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
@ -165,6 +149,13 @@ private:
QWidget *relativeWidget(TimelineItem *item, int dt) const; QWidget *relativeWidget(TimelineItem *item, int dt) const;
//! Callback for all message sending.
void sendRoomMessageHandler(const std::string &txn_id,
const mtx::responses::EventId &res,
mtx::http::RequestErr err);
//! Call the /messages endpoint to fill the timeline.
void getMessages();
//! HACK: Fixing layout flickering when adding to the bottom //! HACK: Fixing layout flickering when adding to the bottom
//! of the timeline. //! of the timeline.
void pushTimelineItem(TimelineItem *item) void pushTimelineItem(TimelineItem *item)
@ -230,8 +221,10 @@ private:
uint64_t origin_server_ts, uint64_t origin_server_ts,
TimelineDirection direction); TimelineDirection direction);
bool isPendingMessage(const QString &txnid, const QString &sender, const QString &userid); bool isPendingMessage(const std::string &txn_id,
void removePendingMessage(const QString &txnid); const QString &sender,
const QString &userid);
void removePendingMessage(const std::string &txn_id);
bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); } bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); }
@ -320,9 +313,15 @@ TimelineView::addUserMessage(const QString &url,
// Keep track of the sender and the timestamp of the current message. // Keep track of the sender and the timestamp of the current message.
saveLastMessageInfo(local_user_, QDateTime::currentDateTime()); saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
int txn_id = http::client()->incrementTransactionId(); PendingMessage message;
message.ty = MsgType;
message.txn_id = mtx::client::utils::random_token();
message.body = url;
message.filename = trimmed;
message.mime = mime;
message.media_size = size;
message.widget = view_item;
PendingMessage message(MsgType, txn_id, url, trimmed, mime, size, "", view_item);
handleNewUserMessage(message); handleNewUserMessage(message);
} }
@ -351,10 +350,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
const auto event_id = QString::fromStdString(event.event_id); const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender); const auto sender = QString::fromStdString(event.sender);
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id); const auto txn_id = event.unsigned_data.transaction_id;
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) || if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) { isDuplicate(event_id)) {
removePendingMessage(txnid); removePendingMessage(txn_id);
return nullptr; return nullptr;
} }
@ -376,10 +375,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
const auto event_id = QString::fromStdString(event.event_id); const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender); const auto sender = QString::fromStdString(event.sender);
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id); const auto txn_id = event.unsigned_data.transaction_id;
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) || if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) { isDuplicate(event_id)) {
removePendingMessage(txnid); removePendingMessage(txn_id);
return nullptr; return nullptr;
} }

View File

@ -56,6 +56,8 @@ signals:
void updateRoomsLastMessage(const QString &user, const DescInfo &info); void updateRoomsLastMessage(const QString &user, const DescInfo &info);
public slots: public slots:
void removeTimelineEvent(const QString &room_id, const QString &event_id);
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void queueTextMessage(const QString &msg); void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
@ -80,10 +82,6 @@ public slots:
const QString &mime, const QString &mime,
uint64_t dsize); uint64_t dsize);
private slots:
void messageSent(const QString &eventid, const QString &roomid, int txnid);
void messageSendFailed(const QString &roomid, int txnid);
private: private:
//! Check if the given room id is managed by a TimelineView. //! Check if the given room id is managed by a TimelineView.
bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); } bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); }

View File

@ -69,9 +69,14 @@ protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
signals:
void fileDownloadedCb(const QByteArray &data);
private slots:
void fileDownloaded(const QByteArray &data);
private: private:
void init(); void init();
void fileDownloaded(const QByteArray &data);
enum class AudioState enum class AudioState
{ {

View File

@ -52,15 +52,20 @@ public:
QColor iconColor() const { return iconColor_; } QColor iconColor() const { return iconColor_; }
QColor backgroundColor() const { return backgroundColor_; } QColor backgroundColor() const { return backgroundColor_; }
signals:
void fileDownloadedCb(const QByteArray &data);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
private slots:
void fileDownloaded(const QByteArray &data);
private: private:
void openUrl(); void openUrl();
void init(); void init();
void fileDownloaded(const QByteArray &data);
QUrl url_; QUrl url_;
QString text_; QString text_;

View File

@ -40,13 +40,17 @@ public:
uint64_t size, uint64_t size,
QWidget *parent = nullptr); QWidget *parent = nullptr);
void setImage(const QPixmap &image);
QSize sizeHint() const override; QSize sizeHint() const override;
public slots: public slots:
//! Show a save as dialog for the image. //! Show a save as dialog for the image.
void saveAs(); void saveAs();
void setImage(const QPixmap &image);
void saveImage(const QString &filename, const QByteArray &data);
signals:
void imageDownloaded(const QPixmap &img);
void imageSaved(const QString &filename, const QByteArray &data);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
@ -57,7 +61,9 @@ protected:
bool isInteractive_ = true; bool isInteractive_ = true;
private: private:
void init();
void openUrl(); void openUrl();
void downloadMedia(const QUrl &url);
int max_width_ = 500; int max_width_ = 500;
int max_height_ = 300; int max_height_ = 300;

View File

@ -16,17 +16,17 @@
*/ */
#include <QBuffer> #include <QBuffer>
#include <QtConcurrent> #include <memory>
#include "AvatarProvider.h" #include "AvatarProvider.h"
#include "Cache.h" #include "Cache.h"
#include "Logging.hpp"
#include "MatrixClient.h" #include "MatrixClient.h"
namespace AvatarProvider {
void void
AvatarProvider::resolve(const QString &room_id, resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback)
const QString &user_id,
QObject *receiver,
std::function<void(QImage)> callback)
{ {
const auto key = QString("%1 %2").arg(room_id).arg(user_id); const auto key = QString("%1 %2").arg(room_id).arg(user_id);
const auto avatarUrl = Cache::avatarUrl(room_id, user_id); const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
@ -43,24 +43,30 @@ AvatarProvider::resolve(const QString &room_id,
return; 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()) mtx::http::ThumbOpts opts;
return; opts.mxc_url = avatarUrl.toStdString();
connect(proxy.data(), http::v2::client()->get_thumbnail(
&DownloadMediaProxy::avatarDownloaded, opts,
receiver, [opts, proxy = std::move(proxy)](const std::string &res, mtx::http::RequestErr err) {
[user_id, proxy, callback, avatarUrl](const QImage &img) { if (err) {
proxy->deleteLater(); log::net()->warn("failed to download avatar: {} - ({} {})",
QtConcurrent::run([img, avatarUrl]() { opts.mxc_url,
QByteArray data; mtx::errors::to_string(err->matrix_error.errcode),
QBuffer buffer(&data); err->matrix_error.error);
buffer.open(QIODevice::WriteOnly); return;
img.save(&buffer, "PNG"); }
cache::client()->saveImage(avatarUrl, data); cache::client()->saveImage(opts.mxc_url, res);
});
callback(img); auto data = QByteArray(res.data(), res.size());
}); emit proxy->avatarDownloaded(data);
});
}
} }

View File

@ -19,7 +19,6 @@
#include <stdexcept> #include <stdexcept>
#include <QByteArray> #include <QByteArray>
#include <QDebug>
#include <QFile> #include <QFile>
#include <QHash> #include <QHash>
#include <QStandardPaths> #include <QStandardPaths>
@ -27,6 +26,7 @@
#include <variant.hpp> #include <variant.hpp>
#include "Cache.h" #include "Cache.h"
#include "Logging.hpp"
#include "Utils.h" #include "Utils.h"
//! Should be changed when a breaking change occurs in the cache format. //! Should be changed when a breaking change occurs in the cache format.
@ -62,6 +62,14 @@ namespace cache {
void void
init(const QString &user_id) 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_) if (!instance_)
instance_ = std::make_unique<Cache>(user_id); instance_ = std::make_unique<Cache>(user_id);
} }
@ -88,7 +96,7 @@ Cache::Cache(const QString &userId, QObject *parent)
void void
Cache::setup() Cache::setup()
{ {
qDebug() << "Setting up cache"; log::db()->debug("setting up cache");
auto statePath = QString("%1/%2/state") auto statePath = QString("%1/%2/state")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
@ -105,7 +113,7 @@ Cache::setup()
env_.set_max_dbs(1024UL); env_.set_max_dbs(1024UL);
if (isInitial) { if (isInitial) {
qDebug() << "First time initializing LMDB"; log::db()->info("initializing LMDB");
if (!QDir().mkpath(statePath)) { if (!QDir().mkpath(statePath)) {
throw std::runtime_error( throw std::runtime_error(
@ -121,7 +129,7 @@ Cache::setup()
std::string(e.what())); 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); QDir stateDir(statePath);
@ -142,29 +150,34 @@ Cache::setup()
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE); readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE); notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
txn.commit(); txn.commit();
qRegisterMetaType<RoomInfo>();
} }
void 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 { try {
auto txn = lmdb::txn::begin(env_); auto txn = lmdb::txn::begin(env_);
lmdb::dbi_put(txn, lmdb::dbi_put(txn,
mediaDb_, mediaDb_,
lmdb::val(key.data(), key.size()), lmdb::val(url.data(), url.size()),
lmdb::val(image.data(), image.size())); lmdb::val(img_data.data(), img_data.size()));
txn.commit(); txn.commit();
} catch (const lmdb::error &e) { } 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 QByteArray
Cache::image(lmdb::txn &txn, const std::string &url) const 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()); return QByteArray(image.data(), image.size());
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
qCritical() << "image:" << e.what() << QString::fromStdString(url); log::db()->critical("image: {}, {}", e.what(), url);
} }
return QByteArray(); return QByteArray();
@ -208,7 +221,7 @@ Cache::image(const QString &url) const
return QByteArray(image.data(), image.size()); return QByteArray(image.data(), image.size());
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
qCritical() << "image:" << e.what() << url; log::db()->critical("image: {} {}", e.what(), url.toStdString());
} }
return QByteArray(); return QByteArray();
@ -271,7 +284,7 @@ Cache::isInitialized() const
return res; return res;
} }
QString std::string
Cache::nextBatchToken() const Cache::nextBatchToken() const
{ {
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
@ -281,13 +294,13 @@ Cache::nextBatchToken() const
txn.commit(); txn.commit();
return QString::fromUtf8(token.data(), token.size()); return std::string(token.data(), token.size());
} }
void void
Cache::deleteData() Cache::deleteData()
{ {
qInfo() << "Deleting cache data"; log::db()->info("deleting data");
if (!cacheDirectory_.isEmpty()) if (!cacheDirectory_.isEmpty())
QDir(cacheDirectory_).removeRecursively(); QDir(cacheDirectory_).removeRecursively();
@ -309,8 +322,9 @@ Cache::isFormatValid()
std::string stored_version(current_version.data(), current_version.size()); std::string stored_version(current_version.data(), current_version.size());
if (stored_version != CURRENT_CACHE_FORMAT_VERSION) { if (stored_version != CURRENT_CACHE_FORMAT_VERSION) {
qWarning() << "Stored format version" << QString::fromStdString(stored_version); log::db()->warn("breaking changes in the cache format. stored: {}, current: {}",
qWarning() << "There are breaking changes in the cache format."; stored_version,
CURRENT_CACHE_FORMAT_VERSION);
return false; return false;
} }
@ -360,7 +374,7 @@ Cache::readReceipts(const QString &event_id, const QString &room_id)
} }
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
qCritical() << "readReceipts:" << e.what(); log::db()->critical("readReceipts: {}", e.what());
} }
return receipts; 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())); lmdb::val(merged_receipts.data(), merged_receipts.size()));
} catch (const lmdb::error &e) { } 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; return tmp;
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() log::db()->warn("failed to parse room info: room_id ({}), {}",
<< "failed to parse room info:" << QString::fromStdString(room_id) room_id,
<< QString::fromStdString(std::string(data.data(), data.size())); 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)); room_info.emplace(QString::fromStdString(room), std::move(tmp));
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() log::db()->warn("failed to parse room info: room_id ({}), {}",
<< "failed to parse room info:" << QString::fromStdString(room) room,
<< QString::fromStdString(std::string(data.data(), data.size())); std::string(data.data(), data.size()));
} }
} else { } else {
// Check if the room is an invite. // 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), room_info.emplace(QString::fromStdString(room),
std::move(tmp)); std::move(tmp));
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << "failed to parse room info for invite:" log::db()->warn(
<< QString::fromStdString(room) "failed to parse room info for invite: room_id ({}), {}",
<< QString::fromStdString( room,
std::string(data.data(), data.size())); std::string(data.data(), data.size()));
} }
} }
} }
@ -703,7 +717,7 @@ Cache::getRoomAvatarUrl(lmdb::txn &txn,
return QString::fromStdString(msg.content.url); return QString::fromStdString(msg.content.url);
} catch (const json::exception &e) { } 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(); cursor.close();
return QString::fromStdString(m.avatar_url); return QString::fromStdString(m.avatar_url);
} catch (const json::exception &e) { } 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()) if (!msg.content.name.empty())
return QString::fromStdString(msg.content.name); return QString::fromStdString(msg.content.name);
} catch (const json::exception &e) { } 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()) if (!msg.content.alias.empty())
return QString::fromStdString(msg.content.alias); return QString::fromStdString(msg.content.alias);
} catch (const json::exception &e) { } 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 { try {
members.emplace(user_id, json::parse(member_data)); members.emplace(user_id, json::parse(member_data));
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << QString::fromStdString(e.what()); log::db()->warn("failed to parse member info: {}", e.what());
} }
ii++; ii++;
@ -828,7 +843,7 @@ Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
json::parse(std::string(event.data(), event.size())); json::parse(std::string(event.data(), event.size()));
return msg.content.join_rule; return msg.content.join_rule;
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << e.what(); log::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
} }
} }
return JoinRule::Knock; return JoinRule::Knock;
@ -850,7 +865,7 @@ Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
json::parse(std::string(event.data(), event.size())); json::parse(std::string(event.data(), event.size()));
return msg.content.guest_access == AccessState::CanJoin; return msg.content.guest_access == AccessState::CanJoin;
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << e.what(); log::db()->warn("failed to parse m.room.guest_access event: {}", e.what());
} }
} }
return false; return false;
@ -874,7 +889,7 @@ Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
if (!msg.content.topic.empty()) if (!msg.content.topic.empty())
return QString::fromStdString(msg.content.topic); return QString::fromStdString(msg.content.topic);
} catch (const json::exception &e) { } 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())); json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.name); return QString::fromStdString(msg.content.name);
} catch (const json::exception &e) { } 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); return QString::fromStdString(tmp.name);
} catch (const json::exception &e) { } 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())); json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.url); return QString::fromStdString(msg.content.url);
} catch (const json::exception &e) { } 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); return QString::fromStdString(tmp.avatar_url);
} catch (const json::exception &e) { } 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())); json::parse(std::string(event.data(), event.size()));
return QString::fromStdString(msg.content.topic); return QString::fromStdString(msg.content.topic);
} catch (const json::exception &e) { } 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(); return QImage();
} }
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << "failed to parse room info" << e.what() log::db()->warn("failed to parse room info: {}, {}",
<< QString::fromStdString(std::string(response.data(), response.size())); e.what(),
std::string(response.data(), response.size()));
} }
if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) { if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) {
@ -1054,7 +1070,7 @@ void
Cache::populateMembers() Cache::populateMembers()
{ {
auto rooms = joinedRooms(); auto rooms = joinedRooms();
qDebug() << "loading" << rooms.size() << "rooms"; log::db()->info("loading {} rooms", rooms.size());
auto txn = lmdb::txn::begin(env_); 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), QString::fromStdString(tmp.name),
QImage::fromData(image(txn, tmp.avatar_url))}); QImage::fromData(image(txn, tmp.avatar_url))});
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << e.what(); log::db()->warn("{}", e.what());
} }
currentIndex += 1; currentIndex += 1;
@ -1253,7 +1269,7 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
std::min(min_event_level, std::min(min_event_level,
(uint16_t)msg.content.state_level(to_string(ty))); (uint16_t)msg.content.state_level(to_string(ty)));
} catch (const json::exception &e) { } catch (const json::exception &e) {
qWarning() << "hasEnoughPowerLevel: " << e.what(); log::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
#include "Cache.h"
#include "CommunitiesList.h" #include "CommunitiesList.h"
#include "Logging.hpp"
#include "MatrixClient.h" #include "MatrixClient.h"
#include <QLabel> #include <QLabel>
@ -38,17 +40,14 @@ CommunitiesList::CommunitiesList(QWidget *parent)
scrollArea_->setWidget(scrollAreaContents_); scrollArea_->setWidget(scrollAreaContents_);
topLayout_->addWidget(scrollArea_); topLayout_->addWidget(scrollArea_);
connect(http::client(), // connect(http::client(),
&MatrixClient::communityProfileRetrieved, // &MatrixClient::communityProfileRetrieved,
this, // this,
[](QString communityId, QJsonObject profile) { // [this](QString communityId, QJsonObject profile) {
http::client()->fetchCommunityAvatar( // fetchCommunityAvatar(communityId, profile["avatar_url"].toString());
communityId, QUrl(profile["avatar_url"].toString())); // });
}); connect(
connect(http::client(), this, &CommunitiesList::avatarRetrieved, this, &CommunitiesList::updateCommunityAvatar);
SIGNAL(communityAvatarRetrieved(const QString &, const QPixmap &)),
this,
SLOT(updateCommunityAvatar(const QString &, const QPixmap &)));
} }
void void
@ -61,8 +60,8 @@ CommunitiesList::setCommunities(const std::map<QString, QSharedPointer<Community
for (const auto &community : communities) { for (const auto &community : communities) {
addCommunity(community.second, community.first); addCommunity(community.second, community.first);
http::client()->fetchCommunityProfile(community.first); // http::client()->fetchCommunityProfile(community.first);
http::client()->fetchCommunityRooms(community.first); // http::client()->fetchCommunityRooms(community.first);
} }
communities_["world"]->setPressedState(true); communities_["world"]->setPressedState(true);
@ -77,7 +76,7 @@ CommunitiesList::addCommunity(QSharedPointer<Community> community, const QString
communities_.emplace(community_id, QSharedPointer<CommunitiesListItem>(list_item)); 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); 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);
});
}

50
src/Logging.cpp Normal file
View File

@ -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;
}
}

View File

@ -137,16 +137,16 @@ LoginPage::LoginPage(QWidget *parent)
setLayout(top_layout_); 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(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(password_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(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(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())); connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
} }
@ -180,17 +180,47 @@ LoginPage::onMatrixIdEntered()
inferredServerAddress_ = homeServer; inferredServerAddress_ = homeServer;
serverInput_->setText(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 void
LoginPage::onServerAddressEntered() LoginPage::onServerAddressEntered()
{ {
error_label_->setText(""); error_label_->setText("");
http::client()->setServer(serverInput_->text()); http::v2::client()->set_server(serverInput_->text().toStdString());
http::client()->versions(); checkHomeserverVersion();
serverLayout_->removeWidget(errorIcon_); serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide(); errorIcon_->hide();
@ -199,11 +229,8 @@ LoginPage::onServerAddressEntered()
} }
void 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); error_label_->setText(error);
serverInput_->show(); serverInput_->show();
@ -215,7 +242,7 @@ LoginPage::versionError(QString error)
} }
void void
LoginPage::versionSuccess() LoginPage::versionOk()
{ {
serverLayout_->removeWidget(spinner_); serverLayout_->removeWidget(spinner_);
matrixidLayout_->removeWidget(spinner_); matrixidLayout_->removeWidget(spinner_);
@ -241,8 +268,20 @@ LoginPage::onLoginButtonClicked()
if (password_input_->text().isEmpty()) if (password_input_->text().isEmpty())
return loginError(tr("Empty password")); return loginError(tr("Empty password"));
http::client()->setServer(serverInput_->text()); http::v2::client()->set_server(serverInput_->text().toStdString());
http::client()->login(QString::fromStdString(user.localpart()), password_input_->text()); 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(); emit loggingIn();
} }

View File

@ -17,7 +17,6 @@
#include <QApplication> #include <QApplication>
#include <QLayout> #include <QLayout>
#include <QNetworkReply>
#include <QSettings> #include <QSettings>
#include <QShortcut> #include <QShortcut>
@ -26,6 +25,7 @@
#include "ChatPage.h" #include "ChatPage.h"
#include "Config.h" #include "Config.h"
#include "LoadingIndicator.h" #include "LoadingIndicator.h"
#include "Logging.hpp"
#include "LoginPage.h" #include "LoginPage.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
@ -46,6 +46,15 @@
MainWindow *MainWindow::instance_ = nullptr; 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) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, progressModal_{nullptr} , progressModal_{nullptr}
@ -54,9 +63,6 @@ MainWindow::MainWindow(QWidget *parent)
setWindowTitle("nheko"); setWindowTitle("nheko");
setObjectName("MainWindow"); setObjectName("MainWindow");
// Initialize the http client.
http::init();
restoreWindowSize(); restoreWindowSize();
QFont font("Open Sans"); QFont font("Open Sans");
@ -124,21 +130,13 @@ MainWindow::MainWindow(QWidget *parent)
connect( connect(
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
connect(http::client(), connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
SIGNAL(loginSuccess(QString, QString, QString)), http::v2::client()->set_user(res.user_id);
this, showChatPage();
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(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); 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 home_server = settings.value("auth/home_server").toString();
QString user_id = settings.value("auth/user_id").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 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; QSettings settings;
settings.setValue("auth/access_token", token); settings.setValue("auth/access_token", token);
settings.setValue("auth/home_server", homeserver); settings.setValue("auth/home_server", homeserver);
@ -317,7 +331,7 @@ MainWindow::openLeaveRoomDialog(const QString &room_id)
leaveRoomModal_->hide(); leaveRoomModal_->hide();
if (leaving) if (leaving)
http::client()->leaveRoom(roomToLeave); chat_page_->leaveRoom(roomToLeave);
}); });
leaveRoomModal_ = leaveRoomModal_ =

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
#include "Config.h" #include "Config.h"
#include "FlatButton.h" #include "FlatButton.h"
#include "Logging.hpp"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "RaisedButton.h" #include "RaisedButton.h"
@ -125,35 +126,53 @@ RegisterPage::RegisterPage(QWidget *parent)
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_confirmation_, 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(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(http::client(), connect(this, &RegisterPage::registerErrorCb, this, &RegisterPage::registerError);
SIGNAL(registerError(const QString &)), connect(
this, this,
SLOT(registerError(const QString &))); &RegisterPage::registrationFlow,
connect(http::client(), this,
&MatrixClient::registrationFlow, [this](const std::string &user, const std::string &pass, const std::string &session) {
this, emit errorOccurred();
[this](const QString &user,
const QString &pass,
const QString &server,
const QString &session) {
emit errorOccurred();
if (!captchaDialog_) { if (!captchaDialog_) {
captchaDialog_ = captchaDialog_ = std::make_shared<dialogs::ReCaptcha>(
std::make_shared<dialogs::ReCaptcha>(server, session, this); QString::fromStdString(session), this);
connect(captchaDialog_.get(), connect(
&dialogs::ReCaptcha::closing, captchaDialog_.get(),
this, &dialogs::ReCaptcha::closing,
[this, user, pass, server, session]() { this,
captchaDialog_->close(); [this, user, pass, session]() {
emit registering(); captchaDialog_->close();
http::client()->registerUser( emit registering();
user, pass, server, session);
});
}
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_); setLayout(top_layout_);
} }
@ -185,11 +204,56 @@ RegisterPage::onRegisterButtonClicked()
} else if (!server_input_->hasAcceptableInput()) { } else if (!server_input_->hasAcceptableInput()) {
registerError(tr("Invalid server name")); registerError(tr("Invalid server name"));
} else { } else {
QString username = username_input_->text(); auto username = username_input_->text().toStdString();
QString password = password_input_->text(); auto password = password_input_->text().toStdString();
QString server = server_input_->text(); 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(); emit registering();
} }
} }

View File

@ -16,11 +16,11 @@
*/ */
#include <QBuffer> #include <QBuffer>
#include <QDebug>
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
#include "Cache.h" #include "Cache.h"
#include "Logging.hpp"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "OverlayModal.h" #include "OverlayModal.h"
@ -55,18 +55,7 @@ RoomList::RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent)
scrollArea_->setWidget(scrollAreaContents_); scrollArea_->setWidget(scrollAreaContents_);
topLayout_->addWidget(scrollArea_); topLayout_->addWidget(scrollArea_);
connect(http::client(), connect(this, &RoomList::updateRoomAvatarCb, this, &RoomList::updateRoomAvatar);
&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);
});
} }
void void
@ -101,7 +90,28 @@ RoomList::updateAvatar(const QString &room_id, const QString &url)
savedImgData = cache::client()->image(url); savedImgData = cache::client()->image(url);
if (savedImgData.isEmpty()) { 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 { } else {
QPixmap img; QPixmap img;
img.loadFromData(savedImgData); img.loadFromData(savedImgData);
@ -131,7 +141,8 @@ void
RoomList::updateUnreadMessageCount(const QString &roomid, int count) RoomList::updateUnreadMessageCount(const QString &roomid, int count)
{ {
if (!roomExists(roomid)) { if (!roomExists(roomid)) {
qWarning() << "UpdateUnreadMessageCount: Unknown roomid"; log::main()->warn("updateUnreadMessageCount: unknown room_id {}",
roomid.toStdString());
return; return;
} }
@ -156,7 +167,7 @@ RoomList::calculateUnreadMessageCount()
void void
RoomList::initialize(const QMap<QString, RoomInfo> &info) RoomList::initialize(const QMap<QString, RoomInfo> &info)
{ {
qDebug() << "initialize room list"; log::main()->info("initialize room list");
rooms_.clear(); rooms_.clear();
@ -209,7 +220,7 @@ RoomList::highlightSelectedRoom(const QString &room_id)
emit roomChanged(room_id); emit roomChanged(room_id);
if (!roomExists(room_id)) { if (!roomExists(room_id)) {
qDebug() << "RoomList: clicked unknown roomid"; log::main()->warn("roomlist: clicked unknown room_id");
return; return;
} }
@ -232,7 +243,8 @@ void
RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img)
{ {
if (!roomExists(roomid)) { if (!roomExists(roomid)) {
qWarning() << "Avatar update on non existent room" << roomid; log::main()->warn("avatar update on non-existent room_id: {}",
roomid.toStdString());
return; return;
} }
@ -246,7 +258,9 @@ void
RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info) RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
{ {
if (!roomExists(roomid)) { 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; return;
} }
@ -314,7 +328,7 @@ RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias)
joinRoomModal_->hide(); joinRoomModal_->hide();
if (isJoining) if (isJoining)
http::client()->joinRoom(roomAlias); emit joinRoom(roomAlias);
} }
void void

View File

@ -71,8 +71,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
this, this,
&FilteredTextEdit::uploadData); &FilteredTextEdit::uploadData);
qRegisterMetaType<SearchResult>();
qRegisterMetaType<QVector<SearchResult>>();
connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults); connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults);
connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) { connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) {
popup_.hide(); popup_.hide();

View File

@ -6,6 +6,7 @@
#include "Config.h" #include "Config.h"
#include "FlatButton.h" #include "FlatButton.h"
#include "MatrixClient.h"
#include "RaisedButton.h" #include "RaisedButton.h"
#include "Theme.h" #include "Theme.h"
@ -13,7 +14,7 @@
using namespace dialogs; using namespace dialogs;
ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *parent) ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
setAutoFillBackground(true); setAutoFillBackground(true);
@ -51,12 +52,12 @@ ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *par
layout->addWidget(label); layout->addWidget(label);
layout->addLayout(buttonLayout); layout->addLayout(buttonLayout);
connect(openCaptchaBtn_, &QPushButton::clicked, [server, session]() { connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
const auto url = const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
QString( "fallback/web?session=%3")
"https://%1/_matrix/client/r0/auth/m.login.recaptcha/fallback/web?session=%2") .arg(QString::fromStdString(http::v2::client()->server()))
.arg(server) .arg(http::v2::client()->port())
.arg(session); .arg(session);
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
}); });

View File

@ -67,6 +67,20 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
labelLayout->addWidget(errorField_); labelLayout->addWidget(errorField_);
layout->addLayout(labelLayout); 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]() { connect(applyBtn_, &QPushButton::clicked, [this]() {
// Check if the values are changed from the originals. // Check if the values are changed from the originals.
auto newName = nameInput_->text().trimmed(); auto newName = nameInput_->text().trimmed();
@ -85,53 +99,37 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
state::Name body; state::Name body;
body.name = newName.toStdString(); body.name = newName.toStdString();
auto proxy = http::v2::client()->send_state_event<state::Name, EventType::RoomName>(
http::client()->sendStateEvent<state::Name, EventType::RoomName>(body, roomId_.toStdString(),
roomId_); body,
connect(proxy.get(), [this, newName](const mtx::responses::EventId &,
&StateEventProxy::stateEventSent, mtx::http::RequestErr err) {
this, if (err) {
[this, proxy, newName]() { emit stateEventErrorCb(
Q_UNUSED(proxy); QString::fromStdString(err->matrix_error.error));
errorField_->hide(); return;
emit nameChanged(newName); }
close();
});
connect(proxy.get(), emit nameEventSentCb(newName);
&StateEventProxy::stateEventError, });
this,
[this, proxy, newName](const QString &msg) {
Q_UNUSED(proxy);
errorField_->setText(msg);
errorField_->show();
});
} }
if (newTopic != initialTopic_ && !newTopic.isEmpty()) { if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
state::Topic body; state::Topic body;
body.topic = newTopic.toStdString(); body.topic = newTopic.toStdString();
auto proxy = http::v2::client()->send_state_event<state::Topic, EventType::RoomTopic>(
http::client()->sendStateEvent<state::Topic, EventType::RoomTopic>( roomId_.toStdString(),
body, roomId_); body,
connect(proxy.get(), [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
&StateEventProxy::stateEventSent, if (err) {
this, emit stateEventErrorCb(
[this, proxy, newTopic]() { QString::fromStdString(err->matrix_error.error));
Q_UNUSED(proxy); return;
errorField_->hide(); }
close();
});
connect(proxy.get(), emit topicEventSentCb();
&StateEventProxy::stateEventError, });
this,
[this, proxy, newTopic](const QString &msg) {
Q_UNUSED(proxy);
errorField_->setText(msg);
errorField_->show();
});
} }
}); });
connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close); connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);

View File

@ -22,15 +22,17 @@
#include <QLabel> #include <QLabel>
#include <QLayout> #include <QLayout>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QNetworkProxy>
#include <QPalette> #include <QPalette>
#include <QPoint> #include <QPoint>
#include <QPushButton> #include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QStandardPaths>
#include <QTranslator> #include <QTranslator>
#include "Config.h" #include "Config.h"
#include "Logging.hpp"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h"
#include "RaisedButton.h" #include "RaisedButton.h"
#include "RunGuard.h" #include "RunGuard.h"
#include "version.hpp" #include "version.hpp"
@ -46,32 +48,6 @@ screenCenter(int width, int height)
return QPoint(x, y); 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 int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
@ -133,7 +109,17 @@ main(int argc, char *argv[])
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
app.setWindowIcon(QIcon(":/logos/nheko.png")); 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; QSettings settings;
@ -154,8 +140,6 @@ main(int argc, char *argv[])
appTranslator.load("nheko_" + lang, ":/translations"); appTranslator.load("nheko_" + lang, ":/translations");
app.installTranslator(&appTranslator); app.installTranslator(&appTranslator);
setupProxy();
MainWindow w; MainWindow w;
// Move the MainWindow to the center // Move the MainWindow to the center
@ -167,5 +151,7 @@ main(int argc, char *argv[])
QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize); QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize);
log::main()->info("starting nheko {}", nheko::version);
return app.exec(); return app.exec();
} }

View File

@ -62,9 +62,27 @@ TimelineItem::init()
ChatPage::instance()->showReadReceipts(event_id_); 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]() { connect(redactMsg_, &QAction::triggered, this, [this]() {
if (!event_id_.isEmpty()) 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(); }); connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); });

View File

@ -23,6 +23,7 @@
#include "ChatPage.h" #include "ChatPage.h"
#include "Config.h" #include "Config.h"
#include "FloatingButton.h" #include "FloatingButton.h"
#include "Logging.hpp"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
@ -100,7 +101,7 @@ TimelineView::TimelineView(const QString &room_id, QWidget *parent)
, room_id_{room_id} , room_id_{room_id}
{ {
init(); init();
http::client()->messages(room_id_, ""); getMessages();
} }
void void
@ -140,7 +141,7 @@ TimelineView::fetchHistory()
return; return;
isPaginationInProgress_ = true; isPaginationInProgress_ = true;
http::client()->messages(room_id_, prev_batch_token_); getMessages();
paginationTimer_->start(5000); paginationTimer_->start(5000);
return; return;
@ -189,18 +190,13 @@ TimelineView::sliderMoved(int position)
isPaginationInProgress_ = true; isPaginationInProgress_ = true;
// FIXME: Maybe move this to TimelineViewManager to remove the getMessages();
// extra calls?
http::client()->messages(room_id_, prev_batch_token_);
} }
} }
void 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. // We've reached the start of the timline and there're no more messages.
if ((msgs.end == msgs.start) && msgs.chunk.size() == 0) { if ((msgs.end == msgs.start) && msgs.chunk.size() == 0) {
isTimelineFinished = true; isTimelineFinished = true;
@ -427,10 +423,10 @@ TimelineView::init()
paginationTimer_ = new QTimer(this); paginationTimer_ = new QTimer(this);
connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory); connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory);
connect(http::client(), connect(this, &TimelineView::messagesRetrieved, this, &TimelineView::addBackwardsEvents);
&MatrixClient::messagesRetrieved,
this, connect(this, &TimelineView::messageFailed, this, &TimelineView::handleFailedMessage);
&TimelineView::addBackwardsEvents); connect(this, &TimelineView::messageSent, this, &TimelineView::updatePendingMessage);
connect(scroll_area_->verticalScrollBar(), connect(scroll_area_->verticalScrollBar(),
SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)),
@ -442,6 +438,27 @@ TimelineView::init()
SLOT(sliderRangeChanged(int, int))); SLOT(sliderRangeChanged(int, int)));
} }
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 void
TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction) TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction)
{ {
@ -513,7 +530,7 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
} }
void void
TimelineView::updatePendingMessage(int txn_id, QString event_id) TimelineView::updatePendingMessage(const std::string &txn_id, const QString &event_id)
{ {
if (!pending_msgs_.isEmpty() && if (!pending_msgs_.isEmpty() &&
pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet 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()); saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
int txn_id = http::client()->incrementTransactionId(); PendingMessage message;
PendingMessage message(ty, txn_id, body, "", "", -1, "", view_item); message.ty = ty;
message.txn_id = mtx::client::utils::random_token();
message.body = body;
message.widget = view_item;
handleNewUserMessage(message); handleNewUserMessage(message);
} }
@ -567,19 +587,119 @@ TimelineView::sendNextPendingMessage()
if (pending_msgs_.size() == 0) if (pending_msgs_.size() == 0)
return; return;
using namespace mtx::events;
PendingMessage &m = pending_msgs_.head(); PendingMessage &m = pending_msgs_.head();
switch (m.ty) { switch (m.ty) {
case mtx::events::MessageType::Audio: case mtx::events::MessageType::Audio: {
case mtx::events::MessageType::Image: msg::Audio audio;
case mtx::events::MessageType::Video: audio.info.mimetype = m.mime.toStdString();
case mtx::events::MessageType::File: audio.info.size = m.media_size;
// FIXME: Improve the API audio.body = m.filename.toStdString();
http::client()->sendRoomMessage( audio.url = m.body.toStdString();
m.ty, m.txn_id, room_id_, m.filename, m.mime, m.media_size, m.body);
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; 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: default:
http::client()->sendRoomMessage( log::main()->warn("cannot send unknown message type: {}", m.body.toStdString());
m.ty, m.txn_id, room_id_, m.body, m.mime, m.media_size);
break; break;
} }
} }
@ -593,7 +713,7 @@ TimelineView::notifyForLastEvent()
if (lastTimelineItem) if (lastTimelineItem)
emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage()); emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
else else
qWarning() << "Cast to TimelineView failed" << room_id_; log::main()->warn("cast to TimelineView failed: {}", room_id_.toStdString());
} }
void void
@ -606,29 +726,27 @@ TimelineView::notifyForLastEvent(const TimelineEvent &event)
} }
bool bool
TimelineView::isPendingMessage(const QString &txnid, TimelineView::isPendingMessage(const std::string &txn_id,
const QString &sender, const QString &sender,
const QString &local_userid) const QString &local_userid)
{ {
if (sender != local_userid) if (sender != local_userid)
return false; return false;
auto match_txnid = [txnid](const auto &msg) -> bool { auto match_txnid = [txn_id](const auto &msg) -> bool { return msg.txn_id == txn_id; };
return QString::number(msg.txn_id) == txnid;
};
return std::any_of(pending_msgs_.cbegin(), pending_msgs_.cend(), match_txnid) || 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); std::any_of(pending_sent_msgs_.cbegin(), pending_sent_msgs_.cend(), match_txnid);
} }
void void
TimelineView::removePendingMessage(const QString &txnid) TimelineView::removePendingMessage(const std::string &txn_id)
{ {
if (txnid.isEmpty()) if (txn_id.empty())
return; return;
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) { 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); int index = std::distance(pending_sent_msgs_.begin(), it);
pending_sent_msgs_.removeAt(index); 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) { 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); int index = std::distance(pending_msgs_.begin(), it);
pending_msgs_.removeAt(index); pending_msgs_.removeAt(index);
return; return;
@ -648,9 +766,9 @@ TimelineView::removePendingMessage(const QString &txnid)
} }
void 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. // Note: We do this even if the message has already been echoed.
QTimer::singleShot(2000, this, SLOT(sendNextPendingMessage())); QTimer::singleShot(2000, this, SLOT(sendNextPendingMessage()));
} }
@ -673,7 +791,16 @@ TimelineView::readLastEvent() const
const auto eventId = getLastEventId(); const auto eventId = getLastEventId();
if (!eventId.isEmpty()) 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 QString
@ -743,7 +870,8 @@ void
TimelineView::removeEvent(const QString &event_id) TimelineView::removeEvent(const QString &event_id)
{ {
if (!eventIds_.contains(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; return;
} }
@ -860,3 +988,16 @@ TimelineView::isDateDifference(const QDateTime &first, const QDateTime &second)
return diffInSeconds > fifteenMins; 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()));
}

View File

@ -35,42 +35,15 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
: QStackedWidget(parent) : QStackedWidget(parent)
{ {
setStyleSheet("border: none;"); 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 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. auto view = views_[room_id];
QSettings settings;
settings.setValue("client/transaction_id", txn_id + 1);
auto view = views_[roomid]; if (view)
view->updatePendingMessage(txn_id, event_id); view->removeEvent(event_id);
}
void
TimelineViewManager::messageSendFailed(const QString &roomid, int txn_id)
{
auto view = views_[roomid];
view->handleFailedMessage(txn_id);
} }
void void

View File

@ -50,21 +50,12 @@ AudioItem::init()
playIcon_.addFile(":/icons/icons/ui/play-sign.png"); playIcon_.addFile(":/icons/icons/ui/play-sign.png");
pauseIcon_.addFile(":/icons/icons/ui/pause-symbol.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_ = new QMediaPlayer;
player_->setMedia(QUrl(url_)); player_->setMedia(QUrl(url_));
player_->setVolume(100); player_->setVolume(100);
player_->setNotifyInterval(1000); player_->setNotifyInterval(1000);
connect(this, &AudioItem::fileDownloadedCb, this, &AudioItem::fileDownloaded);
connect(player_, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state) { connect(player_, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state) {
if (state == QMediaPlayer::StoppedState) { if (state == QMediaPlayer::StoppedState) {
state_ = AudioState::Play; state_ = AudioState::Play;
@ -129,14 +120,19 @@ AudioItem::mousePressEvent(QMouseEvent *event)
if (filenameToSave_.isEmpty()) if (filenameToSave_.isEmpty())
return; return;
auto proxy = http::client()->downloadFile(url_); http::v2::client()->download(
connect(proxy.data(), url_.toString().toStdString(),
&DownloadMediaProxy::fileDownloaded, [this](const std::string &data,
this, const std::string &,
[proxy, this](const QByteArray &data) { const std::string &,
proxy->deleteLater(); mtx::http::RequestErr err) {
fileDownloaded(data); if (err) {
}); qWarning() << "failed to retrieve m.audio content:" << url_;
return;
}
emit fileDownloadedCb(QByteArray(data.data(), data.size()));
});
} }
} }

View File

@ -49,17 +49,9 @@ FileItem::init()
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png"); 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); setFixedHeight(Height);
connect(this, &FileItem::fileDownloadedCb, this, &FileItem::fileDownloaded);
} }
FileItem::FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event, QWidget *parent) FileItem::FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event, QWidget *parent)
@ -89,8 +81,15 @@ FileItem::openUrl()
if (url_.toString().isEmpty()) if (url_.toString().isEmpty())
return; return;
if (!QDesktopServices::openUrl(url_)) auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
qWarning() << "Could not open url" << url_.toString(); 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 QSize
@ -115,14 +114,19 @@ FileItem::mousePressEvent(QMouseEvent *event)
if (filenameToSave_.isEmpty()) if (filenameToSave_.isEmpty())
return; return;
auto proxy = http::client()->downloadFile(url_); http::v2::client()->download(
connect(proxy.data(), url_.toString().toStdString(),
&DownloadMediaProxy::fileDownloaded, [this](const std::string &data,
this, const std::string &,
[proxy, this](const QByteArray &data) { const std::string &,
proxy->deleteLater(); mtx::http::RequestErr err) {
fileDownloaded(data); if (err) {
}); qWarning() << "failed to retrieve m.file content:" << url_;
return;
}
emit fileDownloadedCb(QByteArray(data.data(), data.size()));
});
} else { } else {
openUrl(); openUrl();
} }

View File

@ -30,37 +30,62 @@
#include "dialogs/ImageOverlay.h" #include "dialogs/ImageOverlay.h"
#include "timeline/widgets/ImageItem.h" #include "timeline/widgets/ImageItem.h"
ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent) void
: QWidget(parent) ImageItem::downloadMedia(const QUrl &url)
, event_{event} {
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;
}
QPixmap img;
img.loadFromData(QByteArray(data.data(), data.size()));
emit imageDownloaded(img);
});
}
void
ImageItem::saveImage(const QString &filename, const QByteArray &data)
{
try {
QFile file(filename);
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); setMouseTracking(true);
setCursor(Qt::PointingHandCursor); setCursor(Qt::PointingHandCursor);
setAttribute(Qt::WA_Hover, true); setAttribute(Qt::WA_Hover, true);
connect(this, &ImageItem::imageDownloaded, this, &ImageItem::setImage);
connect(this, &ImageItem::imageSaved, this, &ImageItem::saveImage);
downloadMedia(url_);
}
ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent)
: QWidget(parent)
, event_{event}
{
url_ = QString::fromStdString(event.content.url); url_ = QString::fromStdString(event.content.url);
text_ = QString::fromStdString(event.content.body); text_ = QString::fromStdString(event.content.body);
QList<QString> url_parts = url_.toString().split("mxc://"); init();
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,
[this, proxy](const QPixmap &img) {
proxy->deleteLater();
setImage(img);
});
} }
ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent) 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} , text_{filename}
{ {
Q_UNUSED(size); Q_UNUSED(size);
init();
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);
});
} }
void void
@ -102,8 +103,15 @@ ImageItem::openUrl()
if (url_.toString().isEmpty()) if (url_.toString().isEmpty())
return; return;
if (!QDesktopServices::openUrl(url_)) auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
qWarning() << "Could not open url" << url_.toString(); 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 QSize
@ -231,23 +239,17 @@ ImageItem::saveAs()
if (filename.isEmpty()) if (filename.isEmpty())
return; return;
auto proxy = http::client()->downloadFile(url_); http::v2::client()->download(
connect(proxy.data(), url_.toString().toStdString(),
&DownloadMediaProxy::fileDownloaded, [this, filename](const std::string &data,
this, const std::string &,
[proxy, filename](const QByteArray &data) { const std::string &,
proxy->deleteLater(); mtx::http::RequestErr err) {
if (err) {
qWarning() << "failed to retrieve image:" << url_;
return;
}
try { emit imageSaved(filename, QByteArray(data.data(), data.size()));
QFile file(filename); });
if (!file.open(QIODevice::WriteOnly))
return;
file.write(data);
file.close();
} catch (const std::exception &ex) {
qDebug() << "Error while saving file to:" << ex.what();
}
});
} }

View File

@ -27,15 +27,15 @@
void void
VideoItem::init() VideoItem::init()
{ {
QList<QString> url_parts = url_.toString().split("mxc://"); // QList<QString> url_parts = url_.toString().split("mxc://");
if (url_parts.size() != 2) { // if (url_parts.size() != 2) {
qDebug() << "Invalid format for image" << url_.toString(); // qDebug() << "Invalid format for image" << url_.toString();
return; // return;
} // }
QString media_params = url_parts[1]; // QString media_params = url_parts[1];
url_ = QString("%1/_matrix/media/r0/download/%2") // url_ = QString("%1/_matrix/media/r0/download/%2")
.arg(http::client()->getHomeServer().toString(), media_params); // .arg(http::client()->getHomeServer().toString(), media_params);
} }
VideoItem::VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event, QWidget *parent) VideoItem::VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event, QWidget *parent)