mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 09:30:47 +03:00
Migrate to mtxclient for the http calls
This commit is contained in:
parent
1366b01790
commit
b89257a34b
44 changed files with 1624 additions and 2430 deletions
|
@ -53,7 +53,6 @@ include(LMDB)
|
|||
# Discover Qt dependencies.
|
||||
#
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5LinguistTools REQUIRED)
|
||||
find_package(Qt5Concurrent REQUIRED)
|
||||
find_package(Qt5Svg REQUIRED)
|
||||
|
@ -181,6 +180,7 @@ set(SRC_FILES
|
|||
src/Community.cc
|
||||
src/InviteeItem.cc
|
||||
src/LoginPage.cc
|
||||
src/Logging.cpp
|
||||
src/MainWindow.cc
|
||||
src/MatrixClient.cc
|
||||
src/QuickSwitcher.cc
|
||||
|
@ -287,7 +287,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
include/LoginPage.h
|
||||
include/MainWindow.h
|
||||
include/InviteeItem.h
|
||||
include/MatrixClient.h
|
||||
include/QuickSwitcher.h
|
||||
include/RegisterPage.h
|
||||
include/RoomInfoListItem.h
|
||||
|
@ -314,7 +313,6 @@ set(COMMON_LIBS
|
|||
MatrixStructs::MatrixStructs
|
||||
MatrixClient::MatrixClient
|
||||
Qt5::Widgets
|
||||
Qt5::Network
|
||||
Qt5::Svg
|
||||
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(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_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae)
|
||||
|
|
1
deps/cmake/Olm.cmake
vendored
1
deps/cmake/Olm.cmake
vendored
|
@ -15,6 +15,7 @@ ExternalProject_Add(
|
|||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ${MAKE_CMD} static
|
||||
INSTALL_COMMAND
|
||||
mkdir -p ${DEPS_INSTALL_DIR}/lib &&
|
||||
cp -R ${DEPS_BUILD_DIR}/olm/include ${DEPS_INSTALL_DIR} &&
|
||||
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
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
|
||||
-DSPDLOG_BUILD_EXAMPLES=0
|
||||
-DSPDLOG_BUILD_TESTING=0
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
${DEPS_BUILD_DIR}/spdlog
|
||||
)
|
||||
|
|
|
@ -20,15 +20,17 @@
|
|||
#include <QImage>
|
||||
#include <functional>
|
||||
|
||||
class AvatarProvider : public QObject
|
||||
class AvatarProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//! The callback is called with the downloaded avatar for the given user
|
||||
//! 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);
|
||||
signals:
|
||||
void avatarDownloaded(const QByteArray &data);
|
||||
};
|
||||
|
||||
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);
|
||||
bool isInitialized() const;
|
||||
|
||||
QString nextBatchToken() const;
|
||||
std::string nextBatchToken() const;
|
||||
|
||||
void deleteData();
|
||||
|
||||
|
@ -237,6 +237,7 @@ public:
|
|||
{
|
||||
return image(QString::fromStdString(url));
|
||||
}
|
||||
void saveImage(const std::string &url, const std::string &data);
|
||||
void saveImage(const QString &url, const QByteArray &data);
|
||||
|
||||
RoomInfo singleRoomInfo(const std::string &room_id);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMap>
|
||||
|
@ -50,9 +52,6 @@ constexpr int CONSENSUS_TIMEOUT = 1000;
|
|||
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
|
||||
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
|
||||
|
||||
Q_DECLARE_METATYPE(mtx::responses::Rooms)
|
||||
Q_DECLARE_METATYPE(std::vector<std::string>)
|
||||
|
||||
class ChatPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -71,7 +70,37 @@ public:
|
|||
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
|
||||
void deleteConfigs();
|
||||
|
||||
public slots:
|
||||
void leaveRoom(const QString &room_id);
|
||||
|
||||
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 closing();
|
||||
void changeWindowTitle(const QString &msg);
|
||||
|
@ -82,30 +111,44 @@ signals:
|
|||
void showOverlayProgressBar();
|
||||
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 initializeViews(const mtx::responses::Rooms &rooms);
|
||||
void initializeEmptyViews(const std::vector<std::string> &rooms);
|
||||
void syncUI(const mtx::responses::Rooms &rooms);
|
||||
void continueSync(const QString &next_batch);
|
||||
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
|
||||
void syncTopBar(const std::map<QString, RoomInfo> &updates);
|
||||
void dropToLoginPageCb(const QString &msg);
|
||||
|
||||
private slots:
|
||||
void showUnreadMessageNotification(int count);
|
||||
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 initialSyncCompleted(const mtx::responses::Sync &response);
|
||||
void syncCompleted(const mtx::responses::Sync &response);
|
||||
void changeTopRoomInfo(const QString &room_id);
|
||||
void logout();
|
||||
void removeRoom(const QString &room_id);
|
||||
//! Handles initial sync failures.
|
||||
void retryInitialSync(int status_code = -1);
|
||||
void dropToLoginPage(const QString &msg);
|
||||
|
||||
void joinRoom(const QString &room);
|
||||
void createRoom(const mtx::requests::CreateRoom &req);
|
||||
void sendTypingNotifications();
|
||||
|
||||
private:
|
||||
static ChatPage *instance_;
|
||||
|
||||
void tryInitialSync();
|
||||
void trySync();
|
||||
|
||||
//! Check if the given room is currently open.
|
||||
bool isRoomActive(const QString &room_id)
|
||||
{
|
||||
|
@ -161,8 +204,8 @@ private:
|
|||
// Safety net if consensus is not possible or too slow.
|
||||
QTimer *showContentTimer_;
|
||||
QTimer *consensusTimer_;
|
||||
QTimer *syncTimeoutTimer_;
|
||||
QTimer *initialSyncTimer_;
|
||||
QTimer connectivityTimer_;
|
||||
std::atomic_bool isConnected_;
|
||||
|
||||
QString current_room_;
|
||||
QString current_community_;
|
||||
|
|
|
@ -23,12 +23,14 @@ public:
|
|||
|
||||
signals:
|
||||
void communityChanged(const QString &id);
|
||||
void avatarRetrieved(const QString &id, const QPixmap &img);
|
||||
|
||||
public slots:
|
||||
void updateCommunityAvatar(const QString &id, const QPixmap &img);
|
||||
void highlightSelectedCommunity(const QString &id);
|
||||
|
||||
private:
|
||||
void fetchCommunityAvatar(const QString &id, const QString &avatarUrl);
|
||||
void addGlobalItem() { addCommunity(QSharedPointer<Community>(new Community), "world"); }
|
||||
|
||||
//! 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 TextField;
|
||||
|
||||
namespace mtx {
|
||||
namespace responses {
|
||||
struct Login;
|
||||
}
|
||||
}
|
||||
|
||||
class LoginPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -42,12 +48,19 @@ signals:
|
|||
void loggingIn();
|
||||
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:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
public slots:
|
||||
// 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:
|
||||
// Callback for the back button.
|
||||
|
@ -63,13 +76,25 @@ private slots:
|
|||
void onServerAddressEntered();
|
||||
|
||||
// Callback for errors produced during server probing
|
||||
void versionError(QString error_message);
|
||||
|
||||
void versionError(const QString &error_message);
|
||||
// Callback for successful server probing
|
||||
void versionSuccess();
|
||||
void versionOk();
|
||||
|
||||
private:
|
||||
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_;
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ class MainWindow : public QMainWindow
|
|||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
static MainWindow *instance() { return instance_; };
|
||||
void saveCurrentWindowSize();
|
||||
|
@ -96,7 +97,7 @@ private slots:
|
|||
void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); }
|
||||
|
||||
//! 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 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
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
#include <mtx.hpp>
|
||||
#include <mtx/errors.hpp>
|
||||
#include <QMetaType>
|
||||
|
||||
class DownloadMediaProxy : public QObject
|
||||
{
|
||||
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);
|
||||
};
|
||||
#include <mtx/responses.hpp>
|
||||
#include <mtxclient/http/client.hpp>
|
||||
|
||||
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)
|
||||
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
Q_DECLARE_METATYPE(std::string)
|
||||
Q_DECLARE_METATYPE(std::vector<std::string>);
|
||||
|
||||
namespace http {
|
||||
//! Initialize the http module
|
||||
void
|
||||
init();
|
||||
|
||||
//! Retrieve the client instance.
|
||||
MatrixClient *
|
||||
namespace v2 {
|
||||
mtx::http::Client *
|
||||
client();
|
||||
}
|
||||
|
||||
template<class EventBody, mtx::events::EventType EventT>
|
||||
std::shared_ptr<StateEventProxy>
|
||||
MatrixClient::sendStateEvent(const EventBody &body, const QString &roomId, const QString &stateKey)
|
||||
{
|
||||
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;
|
||||
//! Initialize the http module
|
||||
void
|
||||
init();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,11 @@ signals:
|
|||
void backButtonClicked();
|
||||
void errorOccurred();
|
||||
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:
|
||||
void onBackButtonClicked();
|
||||
|
|
|
@ -60,6 +60,8 @@ signals:
|
|||
void acceptInvite(const QString &room_id);
|
||||
void declineInvite(const QString &room_id);
|
||||
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:
|
||||
void updateRoomAvatar(const QString &roomid, const QPixmap &img);
|
||||
|
|
|
@ -129,6 +129,16 @@ public:
|
|||
|
||||
QColor borderColor() const { return borderColor_; }
|
||||
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:
|
||||
void openFileSelection();
|
||||
|
|
|
@ -12,7 +12,7 @@ class ReCaptcha : public QWidget
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ReCaptcha(const QString &server, const QString &session, QWidget *parent = nullptr);
|
||||
ReCaptcha(const QString &session, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
|
|
@ -30,6 +30,9 @@ public:
|
|||
|
||||
signals:
|
||||
void nameChanged(const QString &roomName);
|
||||
void nameEventSentCb(const QString &newName);
|
||||
void topicEventSentCb();
|
||||
void stateEventErrorCb(const QString &msg);
|
||||
|
||||
private:
|
||||
QString roomId_;
|
||||
|
|
|
@ -197,12 +197,24 @@ public:
|
|||
void sendReadReceipt() const
|
||||
{
|
||||
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.
|
||||
void addAvatar();
|
||||
|
||||
signals:
|
||||
void eventRedacted(const QString &event_id);
|
||||
void redactionFailed(const QString &msg);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
|
@ -42,31 +41,13 @@ struct DescInfo;
|
|||
struct PendingMessage
|
||||
{
|
||||
mtx::events::MessageType ty;
|
||||
int txn_id;
|
||||
std::string txn_id;
|
||||
QString body;
|
||||
QString filename;
|
||||
QString mime;
|
||||
uint64_t media_size;
|
||||
QString event_id;
|
||||
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.
|
||||
|
@ -129,7 +110,7 @@ public:
|
|||
const QString &filename,
|
||||
const QString &mime,
|
||||
uint64_t size);
|
||||
void updatePendingMessage(int txn_id, QString event_id);
|
||||
void updatePendingMessage(const std::string &txn_id, const QString &event_id);
|
||||
void scrollDown();
|
||||
QLabel *createDateSeparator(QDateTime datetime);
|
||||
|
||||
|
@ -142,18 +123,21 @@ public slots:
|
|||
void fetchHistory();
|
||||
|
||||
// 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.
|
||||
bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }
|
||||
|
||||
void handleFailedMessage(int txnid);
|
||||
void handleFailedMessage(const std::string &txn_id);
|
||||
|
||||
private slots:
|
||||
void sendNextPendingMessage();
|
||||
|
||||
signals:
|
||||
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:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
@ -165,6 +149,13 @@ private:
|
|||
|
||||
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
|
||||
//! of the timeline.
|
||||
void pushTimelineItem(TimelineItem *item)
|
||||
|
@ -230,8 +221,10 @@ private:
|
|||
uint64_t origin_server_ts,
|
||||
TimelineDirection direction);
|
||||
|
||||
bool isPendingMessage(const QString &txnid, const QString &sender, const QString &userid);
|
||||
void removePendingMessage(const QString &txnid);
|
||||
bool isPendingMessage(const std::string &txn_id,
|
||||
const QString &sender,
|
||||
const QString &userid);
|
||||
void removePendingMessage(const std::string &txn_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.
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -351,10 +350,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
|||
const auto event_id = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
||||
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
|
||||
const auto txn_id = event.unsigned_data.transaction_id;
|
||||
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
|
||||
isDuplicate(event_id)) {
|
||||
removePendingMessage(txnid);
|
||||
removePendingMessage(txn_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -376,10 +375,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
|
|||
const auto event_id = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
|
||||
if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
|
||||
const auto txn_id = event.unsigned_data.transaction_id;
|
||||
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
|
||||
isDuplicate(event_id)) {
|
||||
removePendingMessage(txnid);
|
||||
removePendingMessage(txn_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ signals:
|
|||
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
|
||||
|
||||
public slots:
|
||||
void removeTimelineEvent(const QString &room_id, const QString &event_id);
|
||||
|
||||
void setHistoryView(const QString &room_id);
|
||||
void queueTextMessage(const QString &msg);
|
||||
void queueEmoteMessage(const QString &msg);
|
||||
|
@ -80,10 +82,6 @@ public slots:
|
|||
const QString &mime,
|
||||
uint64_t dsize);
|
||||
|
||||
private slots:
|
||||
void messageSent(const QString &eventid, const QString &roomid, int txnid);
|
||||
void messageSendFailed(const QString &roomid, int txnid);
|
||||
|
||||
private:
|
||||
//! Check if the given room id is managed by a TimelineView.
|
||||
bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); }
|
||||
|
|
|
@ -69,9 +69,14 @@ protected:
|
|||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
signals:
|
||||
void fileDownloadedCb(const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
enum class AudioState
|
||||
{
|
||||
|
|
|
@ -52,15 +52,20 @@ public:
|
|||
QColor iconColor() const { return iconColor_; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
signals:
|
||||
void fileDownloadedCb(const QByteArray &data);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
private:
|
||||
void openUrl();
|
||||
void init();
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
QUrl url_;
|
||||
QString text_;
|
||||
|
|
|
@ -40,13 +40,17 @@ public:
|
|||
uint64_t size,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void setImage(const QPixmap &image);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
public slots:
|
||||
//! Show a save as dialog for the image.
|
||||
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:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
@ -57,7 +61,9 @@ protected:
|
|||
bool isInteractive_ = true;
|
||||
|
||||
private:
|
||||
void init();
|
||||
void openUrl();
|
||||
void downloadMedia(const QUrl &url);
|
||||
|
||||
int max_width_ = 500;
|
||||
int max_height_ = 300;
|
||||
|
|
|
@ -16,17 +16,17 @@
|
|||
*/
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QtConcurrent>
|
||||
#include <memory>
|
||||
|
||||
#include "AvatarProvider.h"
|
||||
#include "Cache.h"
|
||||
#include "Logging.hpp"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
namespace AvatarProvider {
|
||||
|
||||
void
|
||||
AvatarProvider::resolve(const QString &room_id,
|
||||
const QString &user_id,
|
||||
QObject *receiver,
|
||||
std::function<void(QImage)> callback)
|
||||
resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback)
|
||||
{
|
||||
const auto key = QString("%1 %2").arg(room_id).arg(user_id);
|
||||
const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
|
||||
|
@ -43,24 +43,30 @@ AvatarProvider::resolve(const QString &room_id,
|
|||
return;
|
||||
}
|
||||
|
||||
auto proxy = http::client()->fetchUserAvatar(avatarUrl);
|
||||
auto proxy = std::make_shared<AvatarProxy>();
|
||||
QObject::connect(proxy.get(),
|
||||
&AvatarProxy::avatarDownloaded,
|
||||
receiver,
|
||||
[callback](const QByteArray &data) { callback(QImage::fromData(data)); });
|
||||
|
||||
if (proxy.isNull())
|
||||
return;
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = avatarUrl.toStdString();
|
||||
|
||||
connect(proxy.data(),
|
||||
&DownloadMediaProxy::avatarDownloaded,
|
||||
receiver,
|
||||
[user_id, proxy, callback, avatarUrl](const QImage &img) {
|
||||
proxy->deleteLater();
|
||||
QtConcurrent::run([img, avatarUrl]() {
|
||||
QByteArray data;
|
||||
QBuffer buffer(&data);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
img.save(&buffer, "PNG");
|
||||
http::v2::client()->get_thumbnail(
|
||||
opts,
|
||||
[opts, proxy = std::move(proxy)](const std::string &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn("failed to download avatar: {} - ({} {})",
|
||||
opts.mxc_url,
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error);
|
||||
return;
|
||||
}
|
||||
|
||||
cache::client()->saveImage(avatarUrl, data);
|
||||
});
|
||||
callback(img);
|
||||
});
|
||||
cache::client()->saveImage(opts.mxc_url, res);
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
emit proxy->avatarDownloaded(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
112
src/Cache.cc
112
src/Cache.cc
|
@ -19,7 +19,6 @@
|
|||
#include <stdexcept>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QStandardPaths>
|
||||
|
@ -27,6 +26,7 @@
|
|||
#include <variant.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.hpp"
|
||||
#include "Utils.h"
|
||||
|
||||
//! Should be changed when a breaking change occurs in the cache format.
|
||||
|
@ -62,6 +62,14 @@ namespace cache {
|
|||
void
|
||||
init(const QString &user_id)
|
||||
{
|
||||
qRegisterMetaType<SearchResult>();
|
||||
qRegisterMetaType<QVector<SearchResult>>();
|
||||
qRegisterMetaType<RoomMember>();
|
||||
qRegisterMetaType<RoomSearchResult>();
|
||||
qRegisterMetaType<RoomInfo>();
|
||||
qRegisterMetaType<QMap<QString, RoomInfo>>();
|
||||
qRegisterMetaType<std::map<QString, RoomInfo>>();
|
||||
|
||||
if (!instance_)
|
||||
instance_ = std::make_unique<Cache>(user_id);
|
||||
}
|
||||
|
@ -88,7 +96,7 @@ Cache::Cache(const QString &userId, QObject *parent)
|
|||
void
|
||||
Cache::setup()
|
||||
{
|
||||
qDebug() << "Setting up cache";
|
||||
log::db()->debug("setting up cache");
|
||||
|
||||
auto statePath = QString("%1/%2/state")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
|
@ -105,7 +113,7 @@ Cache::setup()
|
|||
env_.set_max_dbs(1024UL);
|
||||
|
||||
if (isInitial) {
|
||||
qDebug() << "First time initializing LMDB";
|
||||
log::db()->info("initializing LMDB");
|
||||
|
||||
if (!QDir().mkpath(statePath)) {
|
||||
throw std::runtime_error(
|
||||
|
@ -121,7 +129,7 @@ Cache::setup()
|
|||
std::string(e.what()));
|
||||
}
|
||||
|
||||
qWarning() << "Resetting cache due to LMDB version mismatch:" << e.what();
|
||||
log::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what());
|
||||
|
||||
QDir stateDir(statePath);
|
||||
|
||||
|
@ -142,29 +150,34 @@ Cache::setup()
|
|||
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
|
||||
notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
|
||||
txn.commit();
|
||||
|
||||
qRegisterMetaType<RoomInfo>();
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveImage(const QString &url, const QByteArray &image)
|
||||
Cache::saveImage(const std::string &url, const std::string &img_data)
|
||||
{
|
||||
auto key = url.toUtf8();
|
||||
if (url.empty() || img_data.empty())
|
||||
return;
|
||||
|
||||
try {
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
||||
lmdb::dbi_put(txn,
|
||||
mediaDb_,
|
||||
lmdb::val(key.data(), key.size()),
|
||||
lmdb::val(image.data(), image.size()));
|
||||
lmdb::val(url.data(), url.size()),
|
||||
lmdb::val(img_data.data(), img_data.size()));
|
||||
|
||||
txn.commit();
|
||||
} catch (const lmdb::error &e) {
|
||||
qCritical() << "saveImage:" << e.what();
|
||||
log::db()->critical("saveImage: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveImage(const QString &url, const QByteArray &image)
|
||||
{
|
||||
saveImage(url.toStdString(), std::string(image.constData(), image.length()));
|
||||
}
|
||||
|
||||
QByteArray
|
||||
Cache::image(lmdb::txn &txn, const std::string &url) const
|
||||
{
|
||||
|
@ -180,7 +193,7 @@ Cache::image(lmdb::txn &txn, const std::string &url) const
|
|||
|
||||
return QByteArray(image.data(), image.size());
|
||||
} catch (const lmdb::error &e) {
|
||||
qCritical() << "image:" << e.what() << QString::fromStdString(url);
|
||||
log::db()->critical("image: {}, {}", e.what(), url);
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
|
@ -208,7 +221,7 @@ Cache::image(const QString &url) const
|
|||
|
||||
return QByteArray(image.data(), image.size());
|
||||
} catch (const lmdb::error &e) {
|
||||
qCritical() << "image:" << e.what() << url;
|
||||
log::db()->critical("image: {} {}", e.what(), url.toStdString());
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
|
@ -271,7 +284,7 @@ Cache::isInitialized() const
|
|||
return res;
|
||||
}
|
||||
|
||||
QString
|
||||
std::string
|
||||
Cache::nextBatchToken() const
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
|
@ -281,13 +294,13 @@ Cache::nextBatchToken() const
|
|||
|
||||
txn.commit();
|
||||
|
||||
return QString::fromUtf8(token.data(), token.size());
|
||||
return std::string(token.data(), token.size());
|
||||
}
|
||||
|
||||
void
|
||||
Cache::deleteData()
|
||||
{
|
||||
qInfo() << "Deleting cache data";
|
||||
log::db()->info("deleting data");
|
||||
|
||||
if (!cacheDirectory_.isEmpty())
|
||||
QDir(cacheDirectory_).removeRecursively();
|
||||
|
@ -309,8 +322,9 @@ Cache::isFormatValid()
|
|||
std::string stored_version(current_version.data(), current_version.size());
|
||||
|
||||
if (stored_version != CURRENT_CACHE_FORMAT_VERSION) {
|
||||
qWarning() << "Stored format version" << QString::fromStdString(stored_version);
|
||||
qWarning() << "There are breaking changes in the cache format.";
|
||||
log::db()->warn("breaking changes in the cache format. stored: {}, current: {}",
|
||||
stored_version,
|
||||
CURRENT_CACHE_FORMAT_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -360,7 +374,7 @@ Cache::readReceipts(const QString &event_id, const QString &room_id)
|
|||
}
|
||||
|
||||
} catch (const lmdb::error &e) {
|
||||
qCritical() << "readReceipts:" << e.what();
|
||||
log::db()->critical("readReceipts: {}", e.what());
|
||||
}
|
||||
|
||||
return receipts;
|
||||
|
@ -410,7 +424,7 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
|
|||
lmdb::val(merged_receipts.data(), merged_receipts.size()));
|
||||
|
||||
} catch (const lmdb::error &e) {
|
||||
qCritical() << "updateReadReceipts:" << e.what();
|
||||
log::db()->critical("updateReadReceipts: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -568,9 +582,9 @@ Cache::singleRoomInfo(const std::string &room_id)
|
|||
|
||||
return tmp;
|
||||
} catch (const json::exception &e) {
|
||||
qWarning()
|
||||
<< "failed to parse room info:" << QString::fromStdString(room_id)
|
||||
<< QString::fromStdString(std::string(data.data(), data.size()));
|
||||
log::db()->warn("failed to parse room info: room_id ({}), {}",
|
||||
room_id,
|
||||
std::string(data.data(), data.size()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,9 +614,9 @@ Cache::getRoomInfo(const std::vector<std::string> &rooms)
|
|||
|
||||
room_info.emplace(QString::fromStdString(room), std::move(tmp));
|
||||
} catch (const json::exception &e) {
|
||||
qWarning()
|
||||
<< "failed to parse room info:" << QString::fromStdString(room)
|
||||
<< QString::fromStdString(std::string(data.data(), data.size()));
|
||||
log::db()->warn("failed to parse room info: room_id ({}), {}",
|
||||
room,
|
||||
std::string(data.data(), data.size()));
|
||||
}
|
||||
} else {
|
||||
// Check if the room is an invite.
|
||||
|
@ -615,10 +629,10 @@ Cache::getRoomInfo(const std::vector<std::string> &rooms)
|
|||
room_info.emplace(QString::fromStdString(room),
|
||||
std::move(tmp));
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << "failed to parse room info for invite:"
|
||||
<< QString::fromStdString(room)
|
||||
<< QString::fromStdString(
|
||||
std::string(data.data(), data.size()));
|
||||
log::db()->warn(
|
||||
"failed to parse room info for invite: room_id ({}), {}",
|
||||
room,
|
||||
std::string(data.data(), data.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -703,7 +717,7 @@ Cache::getRoomAvatarUrl(lmdb::txn &txn,
|
|||
|
||||
return QString::fromStdString(msg.content.url);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.avatar event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,7 +740,7 @@ Cache::getRoomAvatarUrl(lmdb::txn &txn,
|
|||
cursor.close();
|
||||
return QString::fromStdString(m.avatar_url);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse member info: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -753,7 +767,7 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|||
if (!msg.content.name.empty())
|
||||
return QString::fromStdString(msg.content.name);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.name event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -768,7 +782,8 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|||
if (!msg.content.alias.empty())
|
||||
return QString::fromStdString(msg.content.alias);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.canonical_alias event: {}",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -784,7 +799,7 @@ Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|||
try {
|
||||
members.emplace(user_id, json::parse(member_data));
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse member info: {}", e.what());
|
||||
}
|
||||
|
||||
ii++;
|
||||
|
@ -828,7 +843,7 @@ Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|||
json::parse(std::string(event.data(), event.size()));
|
||||
return msg.content.join_rule;
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << e.what();
|
||||
log::db()->warn("failed to parse m.room.join_rule event: {}", e.what());
|
||||
}
|
||||
}
|
||||
return JoinRule::Knock;
|
||||
|
@ -850,7 +865,7 @@ Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|||
json::parse(std::string(event.data(), event.size()));
|
||||
return msg.content.guest_access == AccessState::CanJoin;
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << e.what();
|
||||
log::db()->warn("failed to parse m.room.guest_access event: {}", e.what());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -874,7 +889,7 @@ Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|||
if (!msg.content.topic.empty())
|
||||
return QString::fromStdString(msg.content.topic);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.topic event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -897,7 +912,7 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
|
|||
json::parse(std::string(event.data(), event.size()));
|
||||
return QString::fromStdString(msg.content.name);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.name event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -914,7 +929,7 @@ Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &members
|
|||
|
||||
return QString::fromStdString(tmp.name);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse member info: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -939,7 +954,7 @@ Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &me
|
|||
json::parse(std::string(event.data(), event.size()));
|
||||
return QString::fromStdString(msg.content.url);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.avatar event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -956,7 +971,7 @@ Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &me
|
|||
|
||||
return QString::fromStdString(tmp.avatar_url);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse member info: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -981,7 +996,7 @@ Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
|
|||
json::parse(std::string(event.data(), event.size()));
|
||||
return QString::fromStdString(msg.content.topic);
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << QString::fromStdString(e.what());
|
||||
log::db()->warn("failed to parse m.room.topic event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1017,8 +1032,9 @@ Cache::getRoomAvatar(const std::string &room_id)
|
|||
return QImage();
|
||||
}
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << "failed to parse room info" << e.what()
|
||||
<< QString::fromStdString(std::string(response.data(), response.size()));
|
||||
log::db()->warn("failed to parse room info: {}, {}",
|
||||
e.what(),
|
||||
std::string(response.data(), response.size()));
|
||||
}
|
||||
|
||||
if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) {
|
||||
|
@ -1054,7 +1070,7 @@ void
|
|||
Cache::populateMembers()
|
||||
{
|
||||
auto rooms = joinedRooms();
|
||||
qDebug() << "loading" << rooms.size() << "rooms";
|
||||
log::db()->info("loading {} rooms", rooms.size());
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
||||
|
@ -1182,7 +1198,7 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
|
|||
QString::fromStdString(tmp.name),
|
||||
QImage::fromData(image(txn, tmp.avatar_url))});
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << e.what();
|
||||
log::db()->warn("{}", e.what());
|
||||
}
|
||||
|
||||
currentIndex += 1;
|
||||
|
@ -1253,7 +1269,7 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
|
|||
std::min(min_event_level,
|
||||
(uint16_t)msg.content.state_level(to_string(ty)));
|
||||
} catch (const json::exception &e) {
|
||||
qWarning() << "hasEnoughPowerLevel: " << e.what();
|
||||
log::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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 "Logging.hpp"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
@ -38,17 +40,14 @@ CommunitiesList::CommunitiesList(QWidget *parent)
|
|||
scrollArea_->setWidget(scrollAreaContents_);
|
||||
topLayout_->addWidget(scrollArea_);
|
||||
|
||||
connect(http::client(),
|
||||
&MatrixClient::communityProfileRetrieved,
|
||||
this,
|
||||
[](QString communityId, QJsonObject profile) {
|
||||
http::client()->fetchCommunityAvatar(
|
||||
communityId, QUrl(profile["avatar_url"].toString()));
|
||||
});
|
||||
connect(http::client(),
|
||||
SIGNAL(communityAvatarRetrieved(const QString &, const QPixmap &)),
|
||||
this,
|
||||
SLOT(updateCommunityAvatar(const QString &, const QPixmap &)));
|
||||
// connect(http::client(),
|
||||
// &MatrixClient::communityProfileRetrieved,
|
||||
// this,
|
||||
// [this](QString communityId, QJsonObject profile) {
|
||||
// fetchCommunityAvatar(communityId, profile["avatar_url"].toString());
|
||||
// });
|
||||
connect(
|
||||
this, &CommunitiesList::avatarRetrieved, this, &CommunitiesList::updateCommunityAvatar);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -61,8 +60,8 @@ CommunitiesList::setCommunities(const std::map<QString, QSharedPointer<Community
|
|||
for (const auto &community : communities) {
|
||||
addCommunity(community.second, community.first);
|
||||
|
||||
http::client()->fetchCommunityProfile(community.first);
|
||||
http::client()->fetchCommunityRooms(community.first);
|
||||
// http::client()->fetchCommunityProfile(community.first);
|
||||
// http::client()->fetchCommunityRooms(community.first);
|
||||
}
|
||||
|
||||
communities_["world"]->setPressedState(true);
|
||||
|
@ -77,7 +76,7 @@ CommunitiesList::addCommunity(QSharedPointer<Community> community, const QString
|
|||
|
||||
communities_.emplace(community_id, QSharedPointer<CommunitiesListItem>(list_item));
|
||||
|
||||
http::client()->fetchCommunityAvatar(community_id, community->getAvatar());
|
||||
fetchCommunityAvatar(community_id, community->getAvatar().toString());
|
||||
|
||||
contentsLayout_->insertWidget(contentsLayout_->count() - 1, list_item);
|
||||
|
||||
|
@ -117,3 +116,37 @@ CommunitiesList::highlightSelectedCommunity(const QString &community_id)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl)
|
||||
{
|
||||
auto savedImgData = cache::client()->image(avatarUrl);
|
||||
if (!savedImgData.isNull()) {
|
||||
QPixmap pix;
|
||||
pix.loadFromData(savedImgData);
|
||||
emit avatarRetrieved(id, pix);
|
||||
return;
|
||||
}
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = avatarUrl.toStdString();
|
||||
http::v2::client()->get_thumbnail(
|
||||
opts, [this, opts, id](const std::string &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn("failed to download avatar: {} - ({} {})",
|
||||
opts.mxc_url,
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error);
|
||||
return;
|
||||
}
|
||||
|
||||
cache::client()->saveImage(opts.mxc_url, res);
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
|
||||
QPixmap pix;
|
||||
pix.loadFromData(data);
|
||||
|
||||
emit avatarRetrieved(id, pix);
|
||||
});
|
||||
}
|
||||
|
|
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_);
|
||||
|
||||
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk);
|
||||
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError);
|
||||
connect(this, &LoginPage::loginErrorCb, this, &LoginPage::loginError);
|
||||
|
||||
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
|
||||
connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
|
||||
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(http::client(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString)));
|
||||
connect(http::client(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccurred()));
|
||||
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
|
||||
connect(http::client(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString)));
|
||||
connect(http::client(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess()));
|
||||
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
|
||||
}
|
||||
|
||||
|
@ -180,17 +180,47 @@ LoginPage::onMatrixIdEntered()
|
|||
|
||||
inferredServerAddress_ = homeServer;
|
||||
serverInput_->setText(homeServer);
|
||||
http::client()->setServer(homeServer);
|
||||
http::client()->versions();
|
||||
|
||||
http::v2::client()->set_server(user.hostname());
|
||||
checkHomeserverVersion();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::checkHomeserverVersion()
|
||||
{
|
||||
http::v2::client()->versions(
|
||||
[this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
using namespace boost::beast::http;
|
||||
|
||||
if (err->status_code == status::not_found) {
|
||||
emit versionErrorCb(tr("The required endpoints were not found. "
|
||||
"Possibly not a Matrix server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err->parse_error.empty()) {
|
||||
emit versionErrorCb(tr("Received malformed response. Make sure "
|
||||
"the homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit versionErrorCb(tr(
|
||||
"An unknown error occured. Make sure the homeserver domain is valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit versionOkCb();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
LoginPage::onServerAddressEntered()
|
||||
{
|
||||
error_label_->setText("");
|
||||
http::client()->setServer(serverInput_->text());
|
||||
http::client()->versions();
|
||||
http::v2::client()->set_server(serverInput_->text().toStdString());
|
||||
checkHomeserverVersion();
|
||||
|
||||
serverLayout_->removeWidget(errorIcon_);
|
||||
errorIcon_->hide();
|
||||
|
@ -199,11 +229,8 @@ LoginPage::onServerAddressEntered()
|
|||
}
|
||||
|
||||
void
|
||||
LoginPage::versionError(QString error)
|
||||
LoginPage::versionError(const QString &error)
|
||||
{
|
||||
QUrl currentServer = http::client()->getHomeServer();
|
||||
QString mxidAddress = matrixid_input_->text().split(":").at(1);
|
||||
|
||||
error_label_->setText(error);
|
||||
serverInput_->show();
|
||||
|
||||
|
@ -215,7 +242,7 @@ LoginPage::versionError(QString error)
|
|||
}
|
||||
|
||||
void
|
||||
LoginPage::versionSuccess()
|
||||
LoginPage::versionOk()
|
||||
{
|
||||
serverLayout_->removeWidget(spinner_);
|
||||
matrixidLayout_->removeWidget(spinner_);
|
||||
|
@ -241,8 +268,20 @@ LoginPage::onLoginButtonClicked()
|
|||
if (password_input_->text().isEmpty())
|
||||
return loginError(tr("Empty password"));
|
||||
|
||||
http::client()->setServer(serverInput_->text());
|
||||
http::client()->login(QString::fromStdString(user.localpart()), password_input_->text());
|
||||
http::v2::client()->set_server(serverInput_->text().toStdString());
|
||||
http::v2::client()->login(
|
||||
user.localpart(),
|
||||
password_input_->text().toStdString(),
|
||||
initialDeviceName(),
|
||||
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit loginError(QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
return;
|
||||
}
|
||||
|
||||
emit loginOk(res);
|
||||
});
|
||||
|
||||
emit loggingIn();
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
|
||||
|
@ -26,6 +25,7 @@
|
|||
#include "ChatPage.h"
|
||||
#include "Config.h"
|
||||
#include "LoadingIndicator.h"
|
||||
#include "Logging.hpp"
|
||||
#include "LoginPage.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
|
@ -46,6 +46,15 @@
|
|||
|
||||
MainWindow *MainWindow::instance_ = nullptr;
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (http::v2::client() != nullptr) {
|
||||
http::v2::client()->shutdown();
|
||||
// TODO: find out why waiting for the threads to join is slow.
|
||||
http::v2::client()->close();
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, progressModal_{nullptr}
|
||||
|
@ -54,9 +63,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
setWindowTitle("nheko");
|
||||
setObjectName("MainWindow");
|
||||
|
||||
// Initialize the http client.
|
||||
http::init();
|
||||
|
||||
restoreWindowSize();
|
||||
|
||||
QFont font("Open Sans");
|
||||
|
@ -124,21 +130,13 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
connect(
|
||||
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
|
||||
|
||||
connect(http::client(),
|
||||
SIGNAL(loginSuccess(QString, QString, QString)),
|
||||
this,
|
||||
SLOT(showChatPage(QString, QString, QString)));
|
||||
|
||||
connect(http::client(),
|
||||
SIGNAL(registerSuccess(QString, QString, QString)),
|
||||
this,
|
||||
SLOT(showChatPage(QString, QString, QString)));
|
||||
connect(http::client(), &MatrixClient::invalidToken, this, [this]() {
|
||||
chat_page_->deleteConfigs();
|
||||
showLoginPage();
|
||||
login_page_->loginError("Invalid token detected. Please try to login again.");
|
||||
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
|
||||
http::v2::client()->set_user(res.user_id);
|
||||
showChatPage();
|
||||
});
|
||||
|
||||
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
|
||||
|
||||
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
|
||||
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
|
||||
|
||||
|
@ -157,7 +155,18 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
QString home_server = settings.value("auth/home_server").toString();
|
||||
QString user_id = settings.value("auth/user_id").toString();
|
||||
|
||||
showChatPage(user_id, home_server, token);
|
||||
http::v2::client()->set_access_token(token.toStdString());
|
||||
http::v2::client()->set_server(home_server.toStdString());
|
||||
|
||||
try {
|
||||
using namespace mtx::identifiers;
|
||||
http::v2::client()->set_user(parse<User>(user_id.toStdString()));
|
||||
} catch (const std::invalid_argument &e) {
|
||||
log::main()->critical("bootstrapped with invalid user_id: {}",
|
||||
user_id.toStdString());
|
||||
}
|
||||
|
||||
showChatPage();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,8 +225,13 @@ MainWindow::removeOverlayProgressBar()
|
|||
}
|
||||
|
||||
void
|
||||
MainWindow::showChatPage(QString userid, QString homeserver, QString token)
|
||||
MainWindow::showChatPage()
|
||||
{
|
||||
auto userid = QString::fromStdString(http::v2::client()->user_id().to_string());
|
||||
auto homeserver = QString::fromStdString(http::v2::client()->server() + ":" +
|
||||
std::to_string(http::v2::client()->port()));
|
||||
auto token = QString::fromStdString(http::v2::client()->access_token());
|
||||
|
||||
QSettings settings;
|
||||
settings.setValue("auth/access_token", token);
|
||||
settings.setValue("auth/home_server", homeserver);
|
||||
|
@ -317,7 +331,7 @@ MainWindow::openLeaveRoomDialog(const QString &room_id)
|
|||
leaveRoomModal_->hide();
|
||||
|
||||
if (leaving)
|
||||
http::client()->leaveRoom(roomToLeave);
|
||||
chat_page_->leaveRoom(roomToLeave);
|
||||
});
|
||||
|
||||
leaveRoomModal_ =
|
||||
|
|
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 "FlatButton.h"
|
||||
#include "Logging.hpp"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "RaisedButton.h"
|
||||
|
@ -125,35 +126,53 @@ RegisterPage::RegisterPage(QWidget *parent)
|
|||
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
|
||||
connect(http::client(),
|
||||
SIGNAL(registerError(const QString &)),
|
||||
this,
|
||||
SLOT(registerError(const QString &)));
|
||||
connect(http::client(),
|
||||
&MatrixClient::registrationFlow,
|
||||
this,
|
||||
[this](const QString &user,
|
||||
const QString &pass,
|
||||
const QString &server,
|
||||
const QString &session) {
|
||||
emit errorOccurred();
|
||||
connect(this, &RegisterPage::registerErrorCb, this, &RegisterPage::registerError);
|
||||
connect(
|
||||
this,
|
||||
&RegisterPage::registrationFlow,
|
||||
this,
|
||||
[this](const std::string &user, const std::string &pass, const std::string &session) {
|
||||
emit errorOccurred();
|
||||
|
||||
if (!captchaDialog_) {
|
||||
captchaDialog_ =
|
||||
std::make_shared<dialogs::ReCaptcha>(server, session, this);
|
||||
connect(captchaDialog_.get(),
|
||||
&dialogs::ReCaptcha::closing,
|
||||
this,
|
||||
[this, user, pass, server, session]() {
|
||||
captchaDialog_->close();
|
||||
emit registering();
|
||||
http::client()->registerUser(
|
||||
user, pass, server, session);
|
||||
});
|
||||
}
|
||||
if (!captchaDialog_) {
|
||||
captchaDialog_ = std::make_shared<dialogs::ReCaptcha>(
|
||||
QString::fromStdString(session), this);
|
||||
connect(
|
||||
captchaDialog_.get(),
|
||||
&dialogs::ReCaptcha::closing,
|
||||
this,
|
||||
[this, user, pass, session]() {
|
||||
captchaDialog_->close();
|
||||
emit registering();
|
||||
|
||||
QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
|
||||
});
|
||||
http::v2::client()->flow_response(
|
||||
user,
|
||||
pass,
|
||||
session,
|
||||
"m.login.recaptcha",
|
||||
[this](const mtx::responses::Register &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn(
|
||||
"failed to retrieve registration flows: {}",
|
||||
err->matrix_error.error);
|
||||
emit errorOccurred();
|
||||
emit registerErrorCb(QString::fromStdString(
|
||||
err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
http::v2::client()->set_user(res.user_id);
|
||||
http::v2::client()->set_access_token(
|
||||
res.access_token);
|
||||
|
||||
emit registerOk();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
|
||||
});
|
||||
|
||||
setLayout(top_layout_);
|
||||
}
|
||||
|
@ -185,11 +204,56 @@ RegisterPage::onRegisterButtonClicked()
|
|||
} else if (!server_input_->hasAcceptableInput()) {
|
||||
registerError(tr("Invalid server name"));
|
||||
} else {
|
||||
QString username = username_input_->text();
|
||||
QString password = password_input_->text();
|
||||
QString server = server_input_->text();
|
||||
auto username = username_input_->text().toStdString();
|
||||
auto password = password_input_->text().toStdString();
|
||||
auto server = server_input_->text().toStdString();
|
||||
|
||||
http::v2::client()->set_server(server);
|
||||
http::v2::client()->registration(
|
||||
username,
|
||||
password,
|
||||
[this, username, password](const mtx::responses::Register &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (!err) {
|
||||
http::v2::client()->set_user(res.user_id);
|
||||
http::v2::client()->set_access_token(res.access_token);
|
||||
|
||||
emit registerOk();
|
||||
return;
|
||||
}
|
||||
|
||||
// The server requires registration flows.
|
||||
if (err->status_code == boost::beast::http::status::unauthorized) {
|
||||
http::v2::client()->flow_register(
|
||||
username,
|
||||
password,
|
||||
[this, username, password](
|
||||
const mtx::responses::RegistrationFlows &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (res.session.empty() && err) {
|
||||
log::net()->warn(
|
||||
"failed to retrieve registration flows: ({}) "
|
||||
"{}",
|
||||
static_cast<int>(err->status_code),
|
||||
err->matrix_error.error);
|
||||
emit errorOccurred();
|
||||
emit registerErrorCb(QString::fromStdString(
|
||||
err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
emit registrationFlow(username, password, res.session);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
log::net()->warn("failed to register: status_code ({})",
|
||||
static_cast<int>(err->status_code));
|
||||
|
||||
emit registerErrorCb(QString::fromStdString(err->matrix_error.error));
|
||||
emit errorOccurred();
|
||||
});
|
||||
|
||||
http::client()->registerUser(username, password, server);
|
||||
emit registering();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.hpp"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "OverlayModal.h"
|
||||
|
@ -55,18 +55,7 @@ RoomList::RoomList(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
scrollArea_->setWidget(scrollAreaContents_);
|
||||
topLayout_->addWidget(scrollArea_);
|
||||
|
||||
connect(http::client(),
|
||||
&MatrixClient::roomAvatarRetrieved,
|
||||
this,
|
||||
[this](const QString &room_id,
|
||||
const QPixmap &img,
|
||||
const QString &url,
|
||||
const QByteArray &data) {
|
||||
if (cache::client())
|
||||
cache::client()->saveImage(url, data);
|
||||
|
||||
updateRoomAvatar(room_id, img);
|
||||
});
|
||||
connect(this, &RoomList::updateRoomAvatarCb, this, &RoomList::updateRoomAvatar);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -101,7 +90,28 @@ RoomList::updateAvatar(const QString &room_id, const QString &url)
|
|||
savedImgData = cache::client()->image(url);
|
||||
|
||||
if (savedImgData.isEmpty()) {
|
||||
http::client()->fetchRoomAvatar(room_id, url);
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = url.toStdString();
|
||||
http::v2::client()->get_thumbnail(
|
||||
opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn(
|
||||
"failed to download thumbnail: {}, {} - {}",
|
||||
opts.mxc_url,
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache::client())
|
||||
cache::client()->saveImage(opts.mxc_url, res);
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
QPixmap pixmap;
|
||||
pixmap.loadFromData(data);
|
||||
|
||||
emit updateRoomAvatarCb(room_id, pixmap);
|
||||
});
|
||||
} else {
|
||||
QPixmap img;
|
||||
img.loadFromData(savedImgData);
|
||||
|
@ -131,7 +141,8 @@ void
|
|||
RoomList::updateUnreadMessageCount(const QString &roomid, int count)
|
||||
{
|
||||
if (!roomExists(roomid)) {
|
||||
qWarning() << "UpdateUnreadMessageCount: Unknown roomid";
|
||||
log::main()->warn("updateUnreadMessageCount: unknown room_id {}",
|
||||
roomid.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -156,7 +167,7 @@ RoomList::calculateUnreadMessageCount()
|
|||
void
|
||||
RoomList::initialize(const QMap<QString, RoomInfo> &info)
|
||||
{
|
||||
qDebug() << "initialize room list";
|
||||
log::main()->info("initialize room list");
|
||||
|
||||
rooms_.clear();
|
||||
|
||||
|
@ -209,7 +220,7 @@ RoomList::highlightSelectedRoom(const QString &room_id)
|
|||
emit roomChanged(room_id);
|
||||
|
||||
if (!roomExists(room_id)) {
|
||||
qDebug() << "RoomList: clicked unknown roomid";
|
||||
log::main()->warn("roomlist: clicked unknown room_id");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -232,7 +243,8 @@ void
|
|||
RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img)
|
||||
{
|
||||
if (!roomExists(roomid)) {
|
||||
qWarning() << "Avatar update on non existent room" << roomid;
|
||||
log::main()->warn("avatar update on non-existent room_id: {}",
|
||||
roomid.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -246,7 +258,9 @@ void
|
|||
RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
|
||||
{
|
||||
if (!roomExists(roomid)) {
|
||||
qWarning() << "Description update on non existent room" << roomid << info.body;
|
||||
log::main()->warn("description update on non-existent room_id: {}, {}",
|
||||
roomid.toStdString(),
|
||||
info.body.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -314,7 +328,7 @@ RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias)
|
|||
joinRoomModal_->hide();
|
||||
|
||||
if (isJoining)
|
||||
http::client()->joinRoom(roomAlias);
|
||||
emit joinRoom(roomAlias);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -71,8 +71,6 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
|
|||
this,
|
||||
&FilteredTextEdit::uploadData);
|
||||
|
||||
qRegisterMetaType<SearchResult>();
|
||||
qRegisterMetaType<QVector<SearchResult>>();
|
||||
connect(this, &FilteredTextEdit::resultsRetrieved, this, &FilteredTextEdit::showResults);
|
||||
connect(&popup_, &SuggestionsPopup::itemSelected, this, [this](const QString &text) {
|
||||
popup_.hide();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "Config.h"
|
||||
#include "FlatButton.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "RaisedButton.h"
|
||||
#include "Theme.h"
|
||||
|
||||
|
@ -13,7 +14,7 @@
|
|||
|
||||
using namespace dialogs;
|
||||
|
||||
ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *parent)
|
||||
ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
|
@ -51,12 +52,12 @@ ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *par
|
|||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(openCaptchaBtn_, &QPushButton::clicked, [server, session]() {
|
||||
const auto url =
|
||||
QString(
|
||||
"https://%1/_matrix/client/r0/auth/m.login.recaptcha/fallback/web?session=%2")
|
||||
.arg(server)
|
||||
.arg(session);
|
||||
connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
|
||||
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
|
||||
"fallback/web?session=%3")
|
||||
.arg(QString::fromStdString(http::v2::client()->server()))
|
||||
.arg(http::v2::client()->port())
|
||||
.arg(session);
|
||||
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
|
|
@ -67,6 +67,20 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
|
|||
labelLayout->addWidget(errorField_);
|
||||
layout->addLayout(labelLayout);
|
||||
|
||||
connect(this, &EditModal::stateEventErrorCb, this, [this](const QString &msg) {
|
||||
errorField_->setText(msg);
|
||||
errorField_->show();
|
||||
});
|
||||
connect(this, &EditModal::nameEventSentCb, this, [this](const QString &newName) {
|
||||
errorField_->hide();
|
||||
emit nameChanged(newName);
|
||||
close();
|
||||
});
|
||||
connect(this, &EditModal::topicEventSentCb, this, [this]() {
|
||||
errorField_->hide();
|
||||
close();
|
||||
});
|
||||
|
||||
connect(applyBtn_, &QPushButton::clicked, [this]() {
|
||||
// Check if the values are changed from the originals.
|
||||
auto newName = nameInput_->text().trimmed();
|
||||
|
@ -85,53 +99,37 @@ EditModal::EditModal(const QString &roomId, QWidget *parent)
|
|||
state::Name body;
|
||||
body.name = newName.toStdString();
|
||||
|
||||
auto proxy =
|
||||
http::client()->sendStateEvent<state::Name, EventType::RoomName>(body,
|
||||
roomId_);
|
||||
connect(proxy.get(),
|
||||
&StateEventProxy::stateEventSent,
|
||||
this,
|
||||
[this, proxy, newName]() {
|
||||
Q_UNUSED(proxy);
|
||||
errorField_->hide();
|
||||
emit nameChanged(newName);
|
||||
close();
|
||||
});
|
||||
http::v2::client()->send_state_event<state::Name, EventType::RoomName>(
|
||||
roomId_.toStdString(),
|
||||
body,
|
||||
[this, newName](const mtx::responses::EventId &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit stateEventErrorCb(
|
||||
QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
connect(proxy.get(),
|
||||
&StateEventProxy::stateEventError,
|
||||
this,
|
||||
[this, proxy, newName](const QString &msg) {
|
||||
Q_UNUSED(proxy);
|
||||
errorField_->setText(msg);
|
||||
errorField_->show();
|
||||
});
|
||||
emit nameEventSentCb(newName);
|
||||
});
|
||||
}
|
||||
|
||||
if (newTopic != initialTopic_ && !newTopic.isEmpty()) {
|
||||
state::Topic body;
|
||||
body.topic = newTopic.toStdString();
|
||||
|
||||
auto proxy =
|
||||
http::client()->sendStateEvent<state::Topic, EventType::RoomTopic>(
|
||||
body, roomId_);
|
||||
connect(proxy.get(),
|
||||
&StateEventProxy::stateEventSent,
|
||||
this,
|
||||
[this, proxy, newTopic]() {
|
||||
Q_UNUSED(proxy);
|
||||
errorField_->hide();
|
||||
close();
|
||||
});
|
||||
http::v2::client()->send_state_event<state::Topic, EventType::RoomTopic>(
|
||||
roomId_.toStdString(),
|
||||
body,
|
||||
[this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit stateEventErrorCb(
|
||||
QString::fromStdString(err->matrix_error.error));
|
||||
return;
|
||||
}
|
||||
|
||||
connect(proxy.get(),
|
||||
&StateEventProxy::stateEventError,
|
||||
this,
|
||||
[this, proxy, newTopic](const QString &msg) {
|
||||
Q_UNUSED(proxy);
|
||||
errorField_->setText(msg);
|
||||
errorField_->show();
|
||||
});
|
||||
emit topicEventSentCb();
|
||||
});
|
||||
}
|
||||
});
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close);
|
||||
|
|
46
src/main.cc
46
src/main.cc
|
@ -22,15 +22,17 @@
|
|||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QLibraryInfo>
|
||||
#include <QNetworkProxy>
|
||||
#include <QPalette>
|
||||
#include <QPoint>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Logging.hpp"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "RaisedButton.h"
|
||||
#include "RunGuard.h"
|
||||
#include "version.hpp"
|
||||
|
@ -46,32 +48,6 @@ screenCenter(int width, int height)
|
|||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
void
|
||||
setupProxy()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
/**
|
||||
To set up a SOCKS proxy:
|
||||
[user]
|
||||
proxy\socks\host=<>
|
||||
proxy\socks\port=<>
|
||||
proxy\socks\user=<>
|
||||
proxy\socks\password=<>
|
||||
**/
|
||||
if (settings.contains("user/proxy/socks/host")) {
|
||||
QNetworkProxy proxy;
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
proxy.setHostName(settings.value("user/proxy/socks/host").toString());
|
||||
proxy.setPort(settings.value("user/proxy/socks/port").toInt());
|
||||
if (settings.contains("user/proxy/socks/user"))
|
||||
proxy.setUser(settings.value("user/proxy/socks/user").toString());
|
||||
if (settings.contains("user/proxy/socks/password"))
|
||||
proxy.setPassword(settings.value("user/proxy/socks/password").toString());
|
||||
QNetworkProxy::setApplicationProxy(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -133,7 +109,17 @@ main(int argc, char *argv[])
|
|||
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
|
||||
|
||||
app.setWindowIcon(QIcon(":/logos/nheko.png"));
|
||||
qSetMessagePattern("%{time process}: [%{type}] - %{message}");
|
||||
|
||||
http::init();
|
||||
|
||||
try {
|
||||
log::init(QString("%1/nheko.log")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.toStdString());
|
||||
} catch (const spdlog::spdlog_ex &ex) {
|
||||
std::cout << "Log initialization failed: " << ex.what() << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
|
||||
|
@ -154,8 +140,6 @@ main(int argc, char *argv[])
|
|||
appTranslator.load("nheko_" + lang, ":/translations");
|
||||
app.installTranslator(&appTranslator);
|
||||
|
||||
setupProxy();
|
||||
|
||||
MainWindow w;
|
||||
|
||||
// Move the MainWindow to the center
|
||||
|
@ -167,5 +151,7 @@ main(int argc, char *argv[])
|
|||
|
||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize);
|
||||
|
||||
log::main()->info("starting nheko {}", nheko::version);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -62,9 +62,27 @@ TimelineItem::init()
|
|||
ChatPage::instance()->showReadReceipts(event_id_);
|
||||
});
|
||||
|
||||
connect(this, &TimelineItem::eventRedacted, this, [this](const QString &event_id) {
|
||||
emit ChatPage::instance()->removeTimelineEvent(room_id_, event_id);
|
||||
});
|
||||
connect(this, &TimelineItem::redactionFailed, this, [](const QString &msg) {
|
||||
emit ChatPage::instance()->showNotification(msg);
|
||||
});
|
||||
connect(redactMsg_, &QAction::triggered, this, [this]() {
|
||||
if (!event_id_.isEmpty())
|
||||
http::client()->redactEvent(room_id_, event_id_);
|
||||
http::v2::client()->redact_event(
|
||||
room_id_.toStdString(),
|
||||
event_id_.toStdString(),
|
||||
[this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit redactionFailed(tr("Message redaction failed: %1")
|
||||
.arg(QString::fromStdString(
|
||||
err->matrix_error.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
emit eventRedacted(event_id_);
|
||||
});
|
||||
});
|
||||
|
||||
connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); });
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "ChatPage.h"
|
||||
#include "Config.h"
|
||||
#include "FloatingButton.h"
|
||||
#include "Logging.hpp"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -100,7 +101,7 @@ TimelineView::TimelineView(const QString &room_id, QWidget *parent)
|
|||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
http::client()->messages(room_id_, "");
|
||||
getMessages();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -140,7 +141,7 @@ TimelineView::fetchHistory()
|
|||
return;
|
||||
|
||||
isPaginationInProgress_ = true;
|
||||
http::client()->messages(room_id_, prev_batch_token_);
|
||||
getMessages();
|
||||
paginationTimer_->start(5000);
|
||||
|
||||
return;
|
||||
|
@ -189,18 +190,13 @@ TimelineView::sliderMoved(int position)
|
|||
|
||||
isPaginationInProgress_ = true;
|
||||
|
||||
// FIXME: Maybe move this to TimelineViewManager to remove the
|
||||
// extra calls?
|
||||
http::client()->messages(room_id_, prev_batch_token_);
|
||||
getMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineView::addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs)
|
||||
TimelineView::addBackwardsEvents(const mtx::responses::Messages &msgs)
|
||||
{
|
||||
if (room_id_ != room_id)
|
||||
return;
|
||||
|
||||
// We've reached the start of the timline and there're no more messages.
|
||||
if ((msgs.end == msgs.start) && msgs.chunk.size() == 0) {
|
||||
isTimelineFinished = true;
|
||||
|
@ -427,10 +423,10 @@ TimelineView::init()
|
|||
paginationTimer_ = new QTimer(this);
|
||||
connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory);
|
||||
|
||||
connect(http::client(),
|
||||
&MatrixClient::messagesRetrieved,
|
||||
this,
|
||||
&TimelineView::addBackwardsEvents);
|
||||
connect(this, &TimelineView::messagesRetrieved, this, &TimelineView::addBackwardsEvents);
|
||||
|
||||
connect(this, &TimelineView::messageFailed, this, &TimelineView::handleFailedMessage);
|
||||
connect(this, &TimelineView::messageSent, this, &TimelineView::updatePendingMessage);
|
||||
|
||||
connect(scroll_area_->verticalScrollBar(),
|
||||
SIGNAL(valueChanged(int)),
|
||||
|
@ -442,6 +438,27 @@ TimelineView::init()
|
|||
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
|
||||
TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction)
|
||||
{
|
||||
|
@ -513,7 +530,7 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
|
|||
}
|
||||
|
||||
void
|
||||
TimelineView::updatePendingMessage(int txn_id, QString event_id)
|
||||
TimelineView::updatePendingMessage(const std::string &txn_id, const QString &event_id)
|
||||
{
|
||||
if (!pending_msgs_.isEmpty() &&
|
||||
pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet
|
||||
|
@ -548,8 +565,11 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
|
|||
|
||||
saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
|
||||
|
||||
int txn_id = http::client()->incrementTransactionId();
|
||||
PendingMessage message(ty, txn_id, body, "", "", -1, "", view_item);
|
||||
PendingMessage message;
|
||||
message.ty = ty;
|
||||
message.txn_id = mtx::client::utils::random_token();
|
||||
message.body = body;
|
||||
message.widget = view_item;
|
||||
handleNewUserMessage(message);
|
||||
}
|
||||
|
||||
|
@ -567,19 +587,119 @@ TimelineView::sendNextPendingMessage()
|
|||
if (pending_msgs_.size() == 0)
|
||||
return;
|
||||
|
||||
using namespace mtx::events;
|
||||
|
||||
PendingMessage &m = pending_msgs_.head();
|
||||
switch (m.ty) {
|
||||
case mtx::events::MessageType::Audio:
|
||||
case mtx::events::MessageType::Image:
|
||||
case mtx::events::MessageType::Video:
|
||||
case mtx::events::MessageType::File:
|
||||
// FIXME: Improve the API
|
||||
http::client()->sendRoomMessage(
|
||||
m.ty, m.txn_id, room_id_, m.filename, m.mime, m.media_size, m.body);
|
||||
case mtx::events::MessageType::Audio: {
|
||||
msg::Audio audio;
|
||||
audio.info.mimetype = m.mime.toStdString();
|
||||
audio.info.size = m.media_size;
|
||||
audio.body = m.filename.toStdString();
|
||||
audio.url = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::Audio, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
audio,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
|
||||
break;
|
||||
}
|
||||
case mtx::events::MessageType::Image: {
|
||||
msg::Image image;
|
||||
image.info.mimetype = m.mime.toStdString();
|
||||
image.info.size = m.media_size;
|
||||
image.body = m.filename.toStdString();
|
||||
image.url = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::Image, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
image,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
|
||||
break;
|
||||
}
|
||||
case mtx::events::MessageType::Video: {
|
||||
msg::Video video;
|
||||
video.info.mimetype = m.mime.toStdString();
|
||||
video.info.size = m.media_size;
|
||||
video.body = m.filename.toStdString();
|
||||
video.url = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::Video, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
video,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
|
||||
break;
|
||||
}
|
||||
case mtx::events::MessageType::File: {
|
||||
msg::File file;
|
||||
file.info.mimetype = m.mime.toStdString();
|
||||
file.info.size = m.media_size;
|
||||
file.body = m.filename.toStdString();
|
||||
file.url = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::File, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
file,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
|
||||
break;
|
||||
}
|
||||
case mtx::events::MessageType::Text: {
|
||||
msg::Text text;
|
||||
text.body = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::Text, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
text,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
|
||||
break;
|
||||
}
|
||||
case mtx::events::MessageType::Emote: {
|
||||
msg::Emote emote;
|
||||
emote.body = m.body.toStdString();
|
||||
|
||||
http::v2::client()->send_room_message<msg::Emote, EventType::RoomMessage>(
|
||||
room_id_.toStdString(),
|
||||
m.txn_id,
|
||||
emote,
|
||||
std::bind(&TimelineView::sendRoomMessageHandler,
|
||||
this,
|
||||
m.txn_id,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
http::client()->sendRoomMessage(
|
||||
m.ty, m.txn_id, room_id_, m.body, m.mime, m.media_size);
|
||||
log::main()->warn("cannot send unknown message type: {}", m.body.toStdString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -593,7 +713,7 @@ TimelineView::notifyForLastEvent()
|
|||
if (lastTimelineItem)
|
||||
emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
|
||||
else
|
||||
qWarning() << "Cast to TimelineView failed" << room_id_;
|
||||
log::main()->warn("cast to TimelineView failed: {}", room_id_.toStdString());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -606,29 +726,27 @@ TimelineView::notifyForLastEvent(const TimelineEvent &event)
|
|||
}
|
||||
|
||||
bool
|
||||
TimelineView::isPendingMessage(const QString &txnid,
|
||||
TimelineView::isPendingMessage(const std::string &txn_id,
|
||||
const QString &sender,
|
||||
const QString &local_userid)
|
||||
{
|
||||
if (sender != local_userid)
|
||||
return false;
|
||||
|
||||
auto match_txnid = [txnid](const auto &msg) -> bool {
|
||||
return QString::number(msg.txn_id) == txnid;
|
||||
};
|
||||
auto match_txnid = [txn_id](const auto &msg) -> bool { return msg.txn_id == txn_id; };
|
||||
|
||||
return std::any_of(pending_msgs_.cbegin(), pending_msgs_.cend(), match_txnid) ||
|
||||
std::any_of(pending_sent_msgs_.cbegin(), pending_sent_msgs_.cend(), match_txnid);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineView::removePendingMessage(const QString &txnid)
|
||||
TimelineView::removePendingMessage(const std::string &txn_id)
|
||||
{
|
||||
if (txnid.isEmpty())
|
||||
if (txn_id.empty())
|
||||
return;
|
||||
|
||||
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) {
|
||||
if (QString::number(it->txn_id) == txnid) {
|
||||
if (it->txn_id == txn_id) {
|
||||
int index = std::distance(pending_sent_msgs_.begin(), it);
|
||||
pending_sent_msgs_.removeAt(index);
|
||||
|
||||
|
@ -639,7 +757,7 @@ TimelineView::removePendingMessage(const QString &txnid)
|
|||
}
|
||||
}
|
||||
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
|
||||
if (QString::number(it->txn_id) == txnid) {
|
||||
if (it->txn_id == txn_id) {
|
||||
int index = std::distance(pending_msgs_.begin(), it);
|
||||
pending_msgs_.removeAt(index);
|
||||
return;
|
||||
|
@ -648,9 +766,9 @@ TimelineView::removePendingMessage(const QString &txnid)
|
|||
}
|
||||
|
||||
void
|
||||
TimelineView::handleFailedMessage(int txnid)
|
||||
TimelineView::handleFailedMessage(const std::string &txn_id)
|
||||
{
|
||||
Q_UNUSED(txnid);
|
||||
Q_UNUSED(txn_id);
|
||||
// Note: We do this even if the message has already been echoed.
|
||||
QTimer::singleShot(2000, this, SLOT(sendNextPendingMessage()));
|
||||
}
|
||||
|
@ -673,7 +791,16 @@ TimelineView::readLastEvent() const
|
|||
const auto eventId = getLastEventId();
|
||||
|
||||
if (!eventId.isEmpty())
|
||||
http::client()->readEvent(room_id_, eventId);
|
||||
http::v2::client()->read_event(room_id_.toStdString(),
|
||||
eventId.toStdString(),
|
||||
[this, eventId](mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
log::net()->warn(
|
||||
"failed to read event ({}, {})",
|
||||
room_id_.toStdString(),
|
||||
eventId.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -743,7 +870,8 @@ void
|
|||
TimelineView::removeEvent(const QString &event_id)
|
||||
{
|
||||
if (!eventIds_.contains(event_id)) {
|
||||
qWarning() << "unknown event_id couldn't be removed:" << event_id;
|
||||
log::main()->warn("cannot remove widget with unknown event_id: {}",
|
||||
event_id.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -860,3 +988,16 @@ TimelineView::isDateDifference(const QDateTime &first, const QDateTime &second)
|
|||
|
||||
return diffInSeconds > fifteenMins;
|
||||
}
|
||||
|
||||
void
|
||||
TimelineView::sendRoomMessageHandler(const std::string &txn_id,
|
||||
const mtx::responses::EventId &res,
|
||||
mtx::http::RequestErr err)
|
||||
{
|
||||
if (err) {
|
||||
emit messageFailed(txn_id);
|
||||
return;
|
||||
}
|
||||
|
||||
emit messageSent(txn_id, QString::fromStdString(res.event_id.to_string()));
|
||||
}
|
||||
|
|
|
@ -35,42 +35,15 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
|
|||
: QStackedWidget(parent)
|
||||
{
|
||||
setStyleSheet("border: none;");
|
||||
|
||||
connect(
|
||||
http::client(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent);
|
||||
|
||||
connect(http::client(),
|
||||
&MatrixClient::messageSendFailed,
|
||||
this,
|
||||
&TimelineViewManager::messageSendFailed);
|
||||
|
||||
connect(http::client(),
|
||||
&MatrixClient::redactionCompleted,
|
||||
this,
|
||||
[this](const QString &room_id, const QString &event_id) {
|
||||
auto view = views_[room_id];
|
||||
|
||||
if (view)
|
||||
view->removeEvent(event_id);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::messageSent(const QString &event_id, const QString &roomid, int txn_id)
|
||||
TimelineViewManager::removeTimelineEvent(const QString &room_id, const QString &event_id)
|
||||
{
|
||||
// We save the latest valid transaction ID for later use.
|
||||
QSettings settings;
|
||||
settings.setValue("client/transaction_id", txn_id + 1);
|
||||
auto view = views_[room_id];
|
||||
|
||||
auto view = views_[roomid];
|
||||
view->updatePendingMessage(txn_id, event_id);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::messageSendFailed(const QString &roomid, int txn_id)
|
||||
{
|
||||
auto view = views_[roomid];
|
||||
view->handleFailedMessage(txn_id);
|
||||
if (view)
|
||||
view->removeEvent(event_id);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -50,21 +50,12 @@ AudioItem::init()
|
|||
playIcon_.addFile(":/icons/icons/ui/play-sign.png");
|
||||
pauseIcon_.addFile(":/icons/icons/ui/pause-symbol.png");
|
||||
|
||||
QList<QString> url_parts = url_.toString().split("mxc://");
|
||||
if (url_parts.size() != 2) {
|
||||
qDebug() << "Invalid format for image" << url_.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QString media_params = url_parts[1];
|
||||
url_ = QString("%1/_matrix/media/r0/download/%2")
|
||||
.arg(http::client()->getHomeServer().toString(), media_params);
|
||||
|
||||
player_ = new QMediaPlayer;
|
||||
player_->setMedia(QUrl(url_));
|
||||
player_->setVolume(100);
|
||||
player_->setNotifyInterval(1000);
|
||||
|
||||
connect(this, &AudioItem::fileDownloadedCb, this, &AudioItem::fileDownloaded);
|
||||
connect(player_, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state) {
|
||||
if (state == QMediaPlayer::StoppedState) {
|
||||
state_ = AudioState::Play;
|
||||
|
@ -129,14 +120,19 @@ AudioItem::mousePressEvent(QMouseEvent *event)
|
|||
if (filenameToSave_.isEmpty())
|
||||
return;
|
||||
|
||||
auto proxy = http::client()->downloadFile(url_);
|
||||
connect(proxy.data(),
|
||||
&DownloadMediaProxy::fileDownloaded,
|
||||
this,
|
||||
[proxy, this](const QByteArray &data) {
|
||||
proxy->deleteLater();
|
||||
fileDownloaded(data);
|
||||
});
|
||||
http::v2::client()->download(
|
||||
url_.toString().toStdString(),
|
||||
[this](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
qWarning() << "failed to retrieve m.audio content:" << url_;
|
||||
return;
|
||||
}
|
||||
|
||||
emit fileDownloadedCb(QByteArray(data.data(), data.size()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,17 +49,9 @@ FileItem::init()
|
|||
|
||||
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png");
|
||||
|
||||
QList<QString> url_parts = url_.toString().split("mxc://");
|
||||
if (url_parts.size() != 2) {
|
||||
qDebug() << "Invalid format for image" << url_.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QString media_params = url_parts[1];
|
||||
url_ = QString("%1/_matrix/media/r0/download/%2")
|
||||
.arg(http::client()->getHomeServer().toString(), media_params);
|
||||
|
||||
setFixedHeight(Height);
|
||||
|
||||
connect(this, &FileItem::fileDownloadedCb, this, &FileItem::fileDownloaded);
|
||||
}
|
||||
|
||||
FileItem::FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event, QWidget *parent)
|
||||
|
@ -89,8 +81,15 @@ FileItem::openUrl()
|
|||
if (url_.toString().isEmpty())
|
||||
return;
|
||||
|
||||
if (!QDesktopServices::openUrl(url_))
|
||||
qWarning() << "Could not open url" << url_.toString();
|
||||
auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
|
||||
auto urlToOpen = QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
|
||||
.arg(QString::fromStdString(http::v2::client()->server()))
|
||||
.arg(http::v2::client()->port())
|
||||
.arg(QString::fromStdString(mxc_parts.server))
|
||||
.arg(QString::fromStdString(mxc_parts.media_id));
|
||||
|
||||
if (!QDesktopServices::openUrl(urlToOpen))
|
||||
qWarning() << "Could not open url" << urlToOpen;
|
||||
}
|
||||
|
||||
QSize
|
||||
|
@ -115,14 +114,19 @@ FileItem::mousePressEvent(QMouseEvent *event)
|
|||
if (filenameToSave_.isEmpty())
|
||||
return;
|
||||
|
||||
auto proxy = http::client()->downloadFile(url_);
|
||||
connect(proxy.data(),
|
||||
&DownloadMediaProxy::fileDownloaded,
|
||||
this,
|
||||
[proxy, this](const QByteArray &data) {
|
||||
proxy->deleteLater();
|
||||
fileDownloaded(data);
|
||||
});
|
||||
http::v2::client()->download(
|
||||
url_.toString().toStdString(),
|
||||
[this](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
qWarning() << "failed to retrieve m.file content:" << url_;
|
||||
return;
|
||||
}
|
||||
|
||||
emit fileDownloadedCb(QByteArray(data.data(), data.size()));
|
||||
});
|
||||
} else {
|
||||
openUrl();
|
||||
}
|
||||
|
|
|
@ -30,37 +30,62 @@
|
|||
#include "dialogs/ImageOverlay.h"
|
||||
#include "timeline/widgets/ImageItem.h"
|
||||
|
||||
ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, event_{event}
|
||||
void
|
||||
ImageItem::downloadMedia(const QUrl &url)
|
||||
{
|
||||
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);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
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);
|
||||
text_ = QString::fromStdString(event.content.body);
|
||||
|
||||
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,
|
||||
[this, proxy](const QPixmap &img) {
|
||||
proxy->deleteLater();
|
||||
setImage(img);
|
||||
});
|
||||
init();
|
||||
}
|
||||
|
||||
ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
|
||||
|
@ -69,31 +94,7 @@ ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size,
|
|||
, text_{filename}
|
||||
{
|
||||
Q_UNUSED(size);
|
||||
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setAttribute(Qt::WA_Hover, true);
|
||||
|
||||
QList<QString> url_parts = url_.toString().split("mxc://");
|
||||
|
||||
if (url_parts.size() != 2) {
|
||||
qDebug() << "Invalid format for image" << url_.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QString media_params = url_parts[1];
|
||||
url_ = QString("%1/_matrix/media/r0/download/%2")
|
||||
.arg(http::client()->getHomeServer().toString(), media_params);
|
||||
|
||||
auto proxy = http::client()->downloadImage(url_);
|
||||
|
||||
connect(proxy.data(),
|
||||
&DownloadMediaProxy::imageDownloaded,
|
||||
this,
|
||||
[proxy, this](const QPixmap &img) {
|
||||
proxy->deleteLater();
|
||||
setImage(img);
|
||||
});
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -102,8 +103,15 @@ ImageItem::openUrl()
|
|||
if (url_.toString().isEmpty())
|
||||
return;
|
||||
|
||||
if (!QDesktopServices::openUrl(url_))
|
||||
qWarning() << "Could not open url" << url_.toString();
|
||||
auto mxc_parts = mtx::client::utils::parse_mxc_url(url_.toString().toStdString());
|
||||
auto urlToOpen = QString("https://%1:%2/_matrix/media/r0/download/%3/%4")
|
||||
.arg(QString::fromStdString(http::v2::client()->server()))
|
||||
.arg(http::v2::client()->port())
|
||||
.arg(QString::fromStdString(mxc_parts.server))
|
||||
.arg(QString::fromStdString(mxc_parts.media_id));
|
||||
|
||||
if (!QDesktopServices::openUrl(urlToOpen))
|
||||
qWarning() << "Could not open url" << urlToOpen;
|
||||
}
|
||||
|
||||
QSize
|
||||
|
@ -231,23 +239,17 @@ ImageItem::saveAs()
|
|||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
auto proxy = http::client()->downloadFile(url_);
|
||||
connect(proxy.data(),
|
||||
&DownloadMediaProxy::fileDownloaded,
|
||||
this,
|
||||
[proxy, filename](const QByteArray &data) {
|
||||
proxy->deleteLater();
|
||||
http::v2::client()->download(
|
||||
url_.toString().toStdString(),
|
||||
[this, filename](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
qWarning() << "failed to retrieve image:" << url_;
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
emit imageSaved(filename, QByteArray(data.data(), data.size()));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,15 +27,15 @@
|
|||
void
|
||||
VideoItem::init()
|
||||
{
|
||||
QList<QString> url_parts = url_.toString().split("mxc://");
|
||||
if (url_parts.size() != 2) {
|
||||
qDebug() << "Invalid format for image" << url_.toString();
|
||||
return;
|
||||
}
|
||||
// QList<QString> url_parts = url_.toString().split("mxc://");
|
||||
// if (url_parts.size() != 2) {
|
||||
// qDebug() << "Invalid format for image" << url_.toString();
|
||||
// return;
|
||||
// }
|
||||
|
||||
QString media_params = url_parts[1];
|
||||
url_ = QString("%1/_matrix/media/r0/download/%2")
|
||||
.arg(http::client()->getHomeServer().toString(), media_params);
|
||||
// QString media_params = url_parts[1];
|
||||
// url_ = QString("%1/_matrix/media/r0/download/%2")
|
||||
// .arg(http::client()->getHomeServer().toString(), media_params);
|
||||
}
|
||||
|
||||
VideoItem::VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event, QWidget *parent)
|
||||
|
|
Loading…
Reference in a new issue