Migrate to mtxclient for the http calls
This commit is contained in:
parent
1366b01790
commit
b89257a34b
@ -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
2
deps/CMakeLists.txt
vendored
@ -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)
|
||||||
|
1
deps/cmake/Olm.cmake
vendored
1
deps/cmake/Olm.cmake
vendored
@ -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
|
||||||
)
|
)
|
||||||
|
2
deps/cmake/SpdLog.cmake
vendored
2
deps/cmake/SpdLog.cmake
vendored
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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_;
|
||||||
|
@ -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
18
include/Logging.hpp
Normal 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();
|
||||||
|
}
|
@ -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_;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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 ¬ifications);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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_;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(); }
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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_;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
112
src/Cache.cc
112
src/Cache.cc
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
890
src/ChatPage.cc
890
src/ChatPage.cc
File diff suppressed because it is too large
Load Diff
@ -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
50
src/Logging.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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_ =
|
||||||
|
1375
src/MatrixClient.cc
1375
src/MatrixClient.cc
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
46
src/main.cc
46
src/main.cc
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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(); });
|
||||||
|
@ -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()));
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user