diff --git a/CMakeLists.txt b/CMakeLists.txt index 85be5df0..f86d7495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,105 +1,105 @@ set(RUQOLA_VERSION "0.9.5") cmake_minimum_required(VERSION 3.1) project(Ruqola VERSION ${RUQOLA_VERSION}) set (CMAKE_CXX_STANDARD 11) set(KF5_VERSION "5.44.0") set(REQUIRED_QT_VERSION "5.9.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${Ruqola_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() -include(KDECompilerSettings) +include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEInstallDirs) include(KDECMakeSettings) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) include(GenerateExportHeader) include(FeatureSummary) option(PLUGINS_AUTHENTICATION_BASED_ON_O2 "Build authentication based on o2 lib (experimental)" FALSE) if (NOT WIN32) option(UNITY_SUPPORT "Build unity support" TRUE) endif() if (UNITY_SUPPORT) add_definitions(-DUNITY_SUPPORT) endif() find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Qml Quick WebSockets Network NetworkAuth Test) find_package(Qt5 CONFIG QUIET OPTIONAL_COMPONENTS QuickCompiler) find_package(KF5 ${KF5_VERSION} REQUIRED COMPONENTS Kirigami2 CoreAddons I18n Crash Notifications SyntaxHighlighting DocTools ) if (WIN32 OR APPLE) find_package(KF5 ${KF5_VERSION} REQUIRED COMPONENTS IconThemes ) endif() find_package(Qt5Keychain) set_package_properties(Qt5Keychain PROPERTIES DESCRIPTION "Provides support for secure credentials storage" URL "https://github.com/frankosterfeld/qtkeychain" TYPE OPTIONAL) if (Qt5Keychain_FOUND) set(HAVE_QT5KEYCHAIN 1) else() set(HAVE_QT5KEYCHAIN 0) endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/restapi) add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII") add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) set(RUQOLA_LIB_VERSION "${RUQOLA_VERSION}") set(RUQOLA_LIB_SOVERSION "0") configure_file(config-ruqola.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ruqola.h) if(BUILD_TESTING) add_definitions(-DBUILD_TESTING) endif(BUILD_TESTING) if (PLUGINS_AUTHENTICATION_BASED_ON_O2) find_package(o2) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() add_subdirectory(doc) install(FILES ruqola.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/rocketchatrestapi-qt5/autotests/starmessagejobtest.h b/src/rocketchatrestapi-qt5/autotests/starmessagejobtest.h index df6bce5d..4c2afe80 100644 --- a/src/rocketchatrestapi-qt5/autotests/starmessagejobtest.h +++ b/src/rocketchatrestapi-qt5/autotests/starmessagejobtest.h @@ -1,39 +1,39 @@ /* Copyright (c) 2018 Montel Laurent This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef STARMESSAGEJOBTEST_H #define STARMESSAGEJOBTEST_H #include class StarMessageJobTest : public QObject { Q_OBJECT public: explicit StarMessageJobTest(QObject *parent = nullptr); ~StarMessageJobTest() = default; -private slots: +private Q_SLOTS: void shouldHaveDefaultValue(); void shouldHaveMessageId(); void shouldGenerateStarMessageRequest(); void shouldGenerateUnStarMessageRequest(); void shouldGenerateJson(); }; #endif // STARMESSAGEJOBTEST_H diff --git a/src/ruqolacore/ddpapi/ddpclient.h b/src/ruqolacore/ddpapi/ddpclient.h index 10184096..0a9915e6 100644 --- a/src/ruqolacore/ddpapi/ddpclient.h +++ b/src/ruqolacore/ddpapi/ddpclient.h @@ -1,252 +1,252 @@ /* * Copyright 2016 Riccardo Iaconelli * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #ifndef DDPCLIENT_H #define DDPCLIENT_H #include "rocketchatmessage.h" #include "libruqolacore_export.h" #include #include class QJsonObject; class QJsonDocument; class RocketChatMessage; class AbstractWebSocket; class RocketChatAccount; class LIBRUQOLACORE_EXPORT DDPClient : public QObject { Q_OBJECT public: enum LoginStatus { NotConnected, LoggingIn, LoggedIn, LoginFailed, LoggedOut }; Q_ENUM(LoginStatus) enum MessageType { Persistent, Ephemeral }; explicit DDPClient(RocketChatAccount *account = nullptr, QObject *parent = nullptr); ~DDPClient(); /** * @brief Call a method with name @param method and parameters @param params and @param messageType with an empty callback * * @param method The name of the method to call Rocket.Chat API for * @param params The parameters * @param messageType The type of message * @return unsigned int, the ID of the called method */ quint64 method(const QString &method, const QJsonDocument ¶ms, DDPClient::MessageType messageType = DDPClient::Ephemeral); /** * @brief Send message over network * * @param method The name of the method to call Rocket.Chat API for * @param params The parameters * @param callback The pointer to callback function * @param messageType The type of message * @return unsigned int, the ID of the called method */ quint64 method(const QString &method, const QJsonDocument ¶ms, std::function callback, DDPClient::MessageType messageType = DDPClient::Ephemeral); quint64 method(const RocketChatMessage::RocketChatMessageResult &result, std::function callback, DDPClient::MessageType messageType = DDPClient::Ephemeral); /** * @brief Subscribes to a collection with name @param collection and parameters @param params * * @param collection The name of the collection * @param params The parameters */ void subscribe(const QString &collection, const QJsonArray ¶ms); /** * @brief Calls method to log in the user with valid username and password */ Q_INVOKABLE void login(); /** * @brief Closes the websocket connection */ void logOut(); /** * @brief Check whether websocket is connected at url * * @return true if connected, else false */ Q_REQUIRED_RESULT bool isConnected() const; /** * @brief Check whether user is logged in * * @return true if user is logged in, else false */ Q_REQUIRED_RESULT bool isLoggedIn() const; /** * @brief Reconnects the websocket to new url */ void onServerURLChange(); /** * @brief Returns the queue used to cache unsent messages * *@return QQueue>, The m_messageQueue object */ Q_REQUIRED_RESULT QQueue > messageQueue() const; /** * @brief Returns standard cache path * *@def QString path */ Q_REQUIRED_RESULT QString cachePath() const; Q_REQUIRED_RESULT quint64 leaveRoom(const QString &roomID); Q_REQUIRED_RESULT quint64 hideRoom(const QString &roomID); Q_REQUIRED_RESULT quint64 clearUnreadMessages(const QString &roomID); Q_REQUIRED_RESULT quint64 informTypingStatus(const QString &room, bool typing, const QString &userName); void setServerUrl(const QString &url); void start(); void setLoginJobId(quint64 jobid); Q_REQUIRED_RESULT LoginStatus loginStatus() const; Q_REQUIRED_RESULT quint64 toggleFavorite(const QString &roomID, bool favorite); Q_REQUIRED_RESULT quint64 createChannel(const QString &name, const QStringList &userList, bool readOnly); Q_REQUIRED_RESULT quint64 createPrivateGroup(const QString &name, const QStringList &userList); Q_REQUIRED_RESULT quint64 joinRoom(const QString &roomId, const QString &joinCode); Q_REQUIRED_RESULT quint64 openDirectChannel(const QString &userId); void subscribeRoomMessage(const QString &roomId); Q_REQUIRED_RESULT quint64 setDefaultStatus(User::PresenceStatus status); Q_REQUIRED_RESULT quint64 listEmojiCustom(); Q_REQUIRED_RESULT quint64 createJitsiConfCall(const QString &roomId); Q_REQUIRED_RESULT quint64 userAutocomplete(const QString &pattern, const QString &exception); Q_REQUIRED_RESULT quint64 deleteMessage(const QString &messageId); Q_REQUIRED_RESULT quint64 eraseRoom(const QString &roomID); Q_REQUIRED_RESULT quint64 setRoomName(const QString &roomId, const QString &name); Q_REQUIRED_RESULT quint64 setRoomTopic(const QString &roomId, const QString &topic); Q_REQUIRED_RESULT quint64 setRoomDescription(const QString &roomId, const QString &description); Q_REQUIRED_RESULT quint64 setRoomAnnouncement(const QString &roomId, const QString &announcement); Q_REQUIRED_RESULT quint64 starMessage(const QString &messageId, const QString &rid, bool starred); Q_REQUIRED_RESULT quint64 setRoomIsReadOnly(const QString &roomId, bool readOnly); Q_REQUIRED_RESULT quint64 archiveRoom(const QString &roomId); Q_REQUIRED_RESULT quint64 setReaction(const QString &emoji, const QString &messageId); Q_REQUIRED_RESULT quint64 getUsersOfRoom(const QString &roomId, bool showAll); Q_REQUIRED_RESULT quint64 loadHistory(const QJsonArray ¶ms); Q_REQUIRED_RESULT quint64 channelAndPrivateAutocomplete(const QString &pattern, const QString &exception); Q_REQUIRED_RESULT quint64 roomFiles(const QString &roomId); Q_REQUIRED_RESULT quint64 addUserToRoom(const QString &userId, const QString &roomId); Q_REQUIRED_RESULT quint64 inputChannelAutocomplete(const QString &pattern, const QString &exceptions); Q_REQUIRED_RESULT quint64 inputUserAutocomplete(const QString &pattern, const QString &exceptions); Q_REQUIRED_RESULT quint64 login(const QString &username, const QString &password); Q_REQUIRED_RESULT quint64 loginProvider(const QString &credentialToken, const QString &credentialSecret); Q_REQUIRED_RESULT quint64 messageSearch(const QString &rid, const QString &pattern); Q_REQUIRED_RESULT quint64 unBlockUser(const QString &rid, const QString &userId); Q_REQUIRED_RESULT quint64 blockUser(const QString &rid, const QString &userId); Q_REQUIRED_RESULT quint64 disableNotifications(const QString &roomId, bool disabled); Q_REQUIRED_RESULT quint64 hideUnreadStatus(const QString &roomId, bool disabled); Q_REQUIRED_RESULT quint64 audioNotifications(const QString &roomId, const QString &value); Q_REQUIRED_RESULT quint64 mobilePushNotifications(const QString &roomId, const QString &value); Q_REQUIRED_RESULT quint64 desktopNotifications(const QString &roomId, const QString &value); Q_REQUIRED_RESULT quint64 emailNotifications(const QString &roomId, const QString &value); Q_REQUIRED_RESULT quint64 unreadAlert(const QString &roomId, const QString &value); Q_REQUIRED_RESULT quint64 deleteFileMessage(const QString &roomId, const QString &fileid, const QString &channelType); Q_REQUIRED_RESULT quint64 setRoomType(const QString &roomId, bool privateChannel); Q_REQUIRED_RESULT quint64 ignoreUser(const QString &roomId, const QString &userId, bool ignore); Q_SIGNALS: void connectedChanged(); void loginStatusChanged(); void disconnected(); void added(const QJsonObject &item); void changed(const QJsonObject &item); void removed(const QJsonObject &item); /** * @brief Emitted whenever a result is received * * @param id The ID received in the method() call * @param result The response sent by server */ void result(quint64 id, const QJsonDocument &result); -private slots: +private Q_SLOTS: void onWSConnected(); void onTextMessageReceived(const QString &message); void onWSclosed(); private: Q_DISABLE_COPY(DDPClient) void initializeWebSocket(); QUrl adaptUrl(const QString &url); void setLoginStatus(LoginStatus l); void pong(); void executeSubsCallBack(const QJsonObject &root); QString mUrl; AbstractWebSocket *mWebSocket = nullptr; /** * @brief Unique message ID for each message sent over network */ quint64 m_uid = 0; /** * @brief Stores callback function associated with each message * * @def QHash unsigned messageID and std::function pointer to callback */ QHash > m_callbackHash; quint64 m_loginJob = 0; LoginStatus m_loginStatus; bool m_connected = false; bool m_attemptedPasswordLogin = false; bool m_attemptedTokenLogin = false; /** * @brief Abstract queue for all requests regarding network management * * @def QPair QString method and QJsonDocument params */ QQueue > m_messageQueue; friend class Ruqola; RocketChatMessage *mRocketChatMessage = nullptr; RocketChatAccount *mRocketChatAccount = nullptr; }; #endif // DDPCLIENT_H diff --git a/src/ruqolacore/messagequeue.cpp b/src/ruqolacore/messagequeue.cpp index 4efc4a7b..d0e5dced 100644 --- a/src/ruqolacore/messagequeue.cpp +++ b/src/ruqolacore/messagequeue.cpp @@ -1,131 +1,131 @@ /* * Copyright 2016 Riccardo Iaconelli * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include "ruqola.h" #include "ddpapi/ddpclient.h" #include "ruqola_debug.h" #include "messagequeue.h" #include "rocketchataccount.h" #include #include #include #include MessageQueue::MessageQueue(RocketChatAccount *account, QObject *parent) : QObject(parent) , mRocketChatAccount(account) { } MessageQueue::~MessageQueue() { QDir cacheDir(mRocketChatAccount->ddp()->cachePath()); qCDebug(RUQOLA_LOG) << "Caching Unsent messages to... " << cacheDir.path(); if (!cacheDir.exists(cacheDir.path())) { if (!cacheDir.mkpath(cacheDir.path())) { qCWarning(RUQOLA_LOG) << "Problem for creating cachedir " << cacheDir; } } QFile f(cacheDir.absoluteFilePath(QStringLiteral("QueueCache"))); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); QQueue > queue = mRocketChatAccount->ddp()->messageQueue(); for (QQueue >::iterator it = queue.begin(), end = queue.end(); it != end; ++it) { const QPair pair = *it; const QByteArray ba = serialize(pair); - out.writeBytes(ba, ba.size()); + out.writeBytes(ba.constData(), ba.size()); } } } void MessageQueue::loadCache() { connect(mRocketChatAccount->ddp(), &DDPClient::loginStatusChanged, this, &MessageQueue::onLoginStatusChanged); QDir cacheDir(mRocketChatAccount->ddp()->cachePath()); // load unsent messages cache if (QFile::exists(cacheDir.absoluteFilePath(QStringLiteral("QueueCache")))) { QFile f(cacheDir.absoluteFilePath(QStringLiteral("QueueCache"))); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char *byteArray; quint32 length; in.readBytes(byteArray, length); QByteArray ba = QByteArray::fromRawData(byteArray, length); QPair pair = MessageQueue::fromJson(QJsonDocument::fromBinaryData(ba).object()); QString method = pair.first; QJsonDocument params = pair.second; mRocketChatAccount->ddp()->messageQueue().enqueue(qMakePair(method, params)); } } } } QPair MessageQueue::fromJson(const QJsonObject &object) { QPair pair; pair.first = object[QStringLiteral("method")].toString(); QJsonArray arr = object[QStringLiteral("params")].toArray(); pair.second = QJsonDocument(arr); return pair; } QByteArray MessageQueue::serialize(const QPair &pair) { QJsonDocument d; QJsonObject o; o[QStringLiteral("method")] = QJsonValue(pair.first); QJsonArray arr; if (pair.second.isArray()) { arr.append(pair.second.array()); } else if (pair.second.isObject()) { arr.append(pair.second.object()); } o[QStringLiteral("params")] = QJsonValue(arr); d.setObject(o); return d.toBinaryData(); } void MessageQueue::onLoginStatusChanged() { //retry sending messages processQueue(); } void MessageQueue::processQueue() { //can be optimized using single shot timer while (mRocketChatAccount->loginStatus() == DDPClient::LoggedIn && !mRocketChatAccount->ddp()->messageQueue().empty()) { QPair pair = mRocketChatAccount->ddp()->messageQueue().head(); QString method = pair.first; QJsonDocument params = pair.second; mRocketChatAccount->ddp()->method(method, params); } } diff --git a/src/ruqolacore/model/messagemodel.cpp b/src/ruqolacore/model/messagemodel.cpp index 9fddc173..6726be49 100644 --- a/src/ruqolacore/model/messagemodel.cpp +++ b/src/ruqolacore/model/messagemodel.cpp @@ -1,349 +1,349 @@ /* * Copyright 2016 Riccardo Iaconelli * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include #include #include #include #include #include "messagemodel.h" #include "ruqolaserverconfig.h" #include "message.h" #include "room.h" #include "ruqola_debug.h" #include "utils.h" #include "rocketchataccount.h" #include "texthighlighter.h" #include "textconverter.h" #include "loadrecenthistorymanager.h" #include #include #include #include //TODO reactivate when we will able to load message between cache and official server. //#define STORE_MESSAGE 1 MessageModel::MessageModel(const QString &roomID, RocketChatAccount *account, Room *room, QObject *parent) : QAbstractListModel(parent) , mRoomID(roomID) , mRocketChatAccount(account) , mRoom(room) { mTextConverter = new TextConverter(mRocketChatAccount ? mRocketChatAccount->emojiManager() : nullptr); mLoadRecentHistoryManager = new LoadRecentHistoryManager; qCDebug(RUQOLA_LOG) << "Creating message Model"; #ifdef STORE_MESSAGE if (mRocketChatAccount) { const QString cachePath = mRocketChatAccount->settings()->cacheBasePath(); if (cachePath.isEmpty()) { qCWarning(RUQOLA_LOG) << " Cache Path is not defined"; return; } QDir cacheDir(cachePath + QStringLiteral("/rooms_cache")); // load cache if (QFile::exists(cacheDir.absoluteFilePath(roomID)) && !roomID.isEmpty()) { QFile f(cacheDir.absoluteFilePath(roomID)); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char *byteArray; quint32 length; in.readBytes(byteArray, length); const QByteArray arr = QByteArray::fromRawData(byteArray, length); Message m = Message::fromJSon(QJsonDocument::fromBinaryData(arr).object()); addMessage(m); } } } } #endif connect(mRoom, &Room::rolesChanged, this, &MessageModel::refresh); connect(mRoom, &Room::ignoredUsersChanged, this, &MessageModel::refresh); } MessageModel::~MessageModel() { #ifdef STORE_MESSAGE if (mRocketChatAccount) { const QString cachePath = mRocketChatAccount->settings()->cacheBasePath(); if (cachePath.isEmpty()) { qCWarning(RUQOLA_LOG) << " Cache Path is not defined"; return; } QDir cacheDir(cachePath + QStringLiteral("/rooms_cache")); qCDebug(RUQOLA_LOG) << "Caching to..." << cacheDir.path(); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath(mRoomID)); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); for (const Message &m : qAsConst(mAllMessages)) { const QByteArray ms = Message::serialize(m); out.writeBytes(ms, ms.size()); } } } #endif delete mTextConverter; delete mLoadRecentHistoryManager; } void MessageModel::refresh() { beginResetModel(); endResetModel(); } QHash MessageModel::roleNames() const { QHash roles; roles[OriginalMessage] = QByteArrayLiteral("originalMessage"); roles[MessageConvertedText] = QByteArrayLiteral("messageConverted"); roles[Username] = QByteArrayLiteral("username"); roles[Timestamp] = QByteArrayLiteral("timestamp"); roles[UserId] = QByteArrayLiteral("userID"); roles[SystemMessageType] = QByteArrayLiteral("type"); roles[MessageId] = QByteArrayLiteral("messageID"); roles[RoomId] = QByteArrayLiteral("roomID"); roles[UpdatedAt] = QByteArrayLiteral("updatedAt"); roles[EditedAt] = QByteArrayLiteral("editedAt"); roles[EditedByUserName] = QByteArrayLiteral("editedByUsername"); roles[EditedByUserId] = QByteArrayLiteral("editedByUserID"); roles[Alias] = QByteArrayLiteral("alias"); roles[Avatar] = QByteArrayLiteral("avatar"); roles[Groupable] = QByteArrayLiteral("groupable"); roles[MessageType] = QByteArrayLiteral("messagetype"); roles[Attachments] = QByteArrayLiteral("attachments"); roles[Urls] = QByteArrayLiteral("urls"); roles[Date] = QByteArrayLiteral("date"); roles[CanEditingMessage] = QByteArrayLiteral("canEditingMessage"); roles[Starred] = QByteArrayLiteral("starred"); roles[UsernameUrl] = QByteArrayLiteral("usernameurl"); roles[Roles] = QByteArrayLiteral("roles"); roles[Reactions] = QByteArrayLiteral("reactions"); roles[Ignored] = QByteArrayLiteral("userIsIgnored"); return roles; } qint64 MessageModel::lastTimestamp() const { if (!mAllMessages.isEmpty()) { //qCDebug(RUQOLA_LOG) << "returning timestamp" << mAllMessages.last().timeStamp(); return mAllMessages.first().timeStamp(); } else { return 0; } } int MessageModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return mAllMessages.size(); } void MessageModel::addMessage(const Message &message) { auto it = std::upper_bound(mAllMessages.begin(), mAllMessages.end(), message, [](const Message &lhs, const Message &rhs) -> bool { return lhs.timeStamp() < rhs.timeStamp(); } ); //When we have 1 element. if (mAllMessages.count() == 1 && (*mAllMessages.begin()).messageId() == message.messageId()) { (*mAllMessages.begin()) = message; //const QModelIndex index = createIndex(0, 0); qCDebug(RUQOLA_LOG) << "Update Message"; //Q_EMIT dataChanged(index, index); //For the moment !!!! It's not optimal but Q_EMIT dataChanged(index, index); doesn't work beginRemoveRows(QModelIndex(), 0, 0); endRemoveRows(); beginInsertRows(QModelIndex(), 0, 0); endInsertRows(); } else if (((it) != mAllMessages.begin() && (*(it - 1)).messageId() == message.messageId())) { qCDebug(RUQOLA_LOG) << "Update Message"; (*(it-1)) = message; //const QModelIndex index = createIndex(it - 1 - mAllMessages.begin(), 0); //For the moment !!!! It's not optimal but Q_EMIT dataChanged(index, index); doesn't work beginRemoveRows(QModelIndex(), it - 1 - mAllMessages.begin(), it - 1 - mAllMessages.begin()); endRemoveRows(); beginInsertRows(QModelIndex(), it - 1 - mAllMessages.begin(), it - 1 - mAllMessages.begin()); endInsertRows(); //Q_EMIT dataChanged(index, index); } else { const int pos = it - mAllMessages.begin(); beginInsertRows(QModelIndex(), pos, pos); mAllMessages.insert(it, message); endInsertRows(); } } QVariant MessageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { qCWarning(RUQOLA_LOG) << "ERROR: invalid index"; return {}; } const int idx = index.row(); switch (role) { case MessageModel::Username: return mAllMessages.at(idx).username(); case MessageModel::OriginalMessage: return mAllMessages.at(idx).text(); case MessageModel::MessageConvertedText: //TODO improve it. if (mAllMessages.at(idx).messageType() == Message::System) { return mAllMessages.at(idx).messageTypeText(); } else { if (mRoom && mRoom->userIsIgnored(mAllMessages.at(idx).userId())) { - return QStringLiteral("") + i18n("Ignored Message") + QStringLiteral(""); + return QString(QStringLiteral("") + i18n("Ignored Message") + QStringLiteral("")); } const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString(); return convertMessageText(mAllMessages.at(idx).text(), mAllMessages.at(idx).mentions(), userName); } case MessageModel::Timestamp: return mAllMessages.at(idx).timeStamp(); case MessageModel::UserId: return mAllMessages.at(idx).userId(); case MessageModel::SystemMessageType: return mAllMessages.at(idx).systemMessageType(); case MessageModel::MessageId: return mAllMessages.at(idx).messageId(); case MessageModel::Alias: return mAllMessages.at(idx).alias(); case MessageModel::MessageType: return mAllMessages.at(idx).messageType(); case MessageModel::Avatar: return mAllMessages.at(idx).avatar(); case MessageModel::EditedAt: return mAllMessages.at(idx).editedAt(); case MessageModel::EditedByUserName: return mAllMessages.at(idx).editedByUsername(); case MessageModel::Attachments: { QVariantList lst; lst.reserve(mAllMessages.at(idx).attachements().count()); for (const MessageAttachment &att : mAllMessages.at(idx).attachements()) { lst.append(QVariant::fromValue(att)); } return lst; } case MessageModel::Urls: { QVariantList lst; lst.reserve(mAllMessages.at(idx).urls().count()); for (const MessageUrl &url : mAllMessages.at(idx).urls()) { lst.append(QVariant::fromValue(url)); } return lst; } case MessageModel::Date: if (idx > 0) { QDateTime previewDate; previewDate.setMSecsSinceEpoch(mAllMessages.at(idx - 1).timeStamp()); QDateTime currentDate; currentDate.setMSecsSinceEpoch(mAllMessages.at(idx).timeStamp()); if (previewDate.date() != currentDate.date()) { return currentDate.date().toString(); } } return QString(); case MessageModel::CanEditingMessage: return (mAllMessages.at(idx).timeStamp() + (mRocketChatAccount ? mRocketChatAccount->ruqolaServerConfig()->blockEditingMessageInMinutes() * 60 * 1000 : 0)) > QDateTime::currentMSecsSinceEpoch(); case MessageModel::Starred: return mAllMessages.at(idx).starred(); case MessageModel::UsernameUrl: return QStringLiteral("@%1").arg(mAllMessages.at(idx).username()); case MessageModel::Roles: return roomRoles(mAllMessages.at(idx).userId()); case MessageModel::Reactions: { QVariantList lst; lst.reserve(mAllMessages.at(idx).reactions().reactions().count()); for (const Reaction &react : mAllMessages.at(idx).reactions().reactions()) { lst.append(QVariant::fromValue(react)); } return lst; } case MessageModel::Ignored: { return (mRoom && mRoom->userIsIgnored(mAllMessages.at(idx).userId())); } } return QString(); } QStringList MessageModel::roomRoles(const QString &userId) const { if (mRoom) { if (userId == mRocketChatAccount->userID()) { //TODO use translated string return mRoom->roles(); } } return QStringList(); } QString MessageModel::convertMessageText(const QString &str, const QMap &mentions, const QString &userName) const { return mTextConverter->convertMessageText(str, mentions, userName); } void MessageModel::setRoomID(const QString &roomID) { mRoomID = roomID; } bool MessageModel::isEmpty() const { return mAllMessages.isEmpty(); } void MessageModel::deleteMessage(const QString &messageId) { for (int i = 0, total = mAllMessages.count(); i < total; ++i) { if (mAllMessages.at(i).messageId() == messageId) { beginRemoveRows(QModelIndex(), i, i); mAllMessages.remove(i); endRemoveRows(); break; } } } qint64 MessageModel::generateNewStartTimeStamp(qint64 lastTimeStamp) { return mLoadRecentHistoryManager->generateNewStartTimeStamp(lastTimeStamp); } diff --git a/src/ruqolacore/model/roommodel.cpp b/src/ruqolacore/model/roommodel.cpp index 9e8c7138..ca5f53bc 100644 --- a/src/ruqolacore/model/roommodel.cpp +++ b/src/ruqolacore/model/roommodel.cpp @@ -1,529 +1,529 @@ /* * Copyright 2016 Riccardo Iaconelli * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . * */ #include "roommodel.h" #include "ruqola_debug.h" #include "rocketchataccount.h" #include "usersforroommodel.h" #include "roomwrapper.h" #include #include #include #include #include RoomModel::RoomModel(RocketChatAccount *account, QObject *parent) : QAbstractListModel(parent) , mRocketChatAccount(account) { } RoomModel::~RoomModel() { if (mRocketChatAccount) { const QString cachePath = mRocketChatAccount->settings()->cacheBasePath(); if (cachePath.isEmpty()) { qCWarning(RUQOLA_LOG) << " Cache Path is not defined"; return; } QDir cacheDir(cachePath); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath(QStringLiteral("rooms"))); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); for (Room *m : qAsConst(mRoomsList)) { qCDebug(RUQOLA_LOG) << " save cache for room " << m->name(); const QByteArray ms = Room::serialize(m); - out.writeBytes(ms, ms.size()); + out.writeBytes(ms.constData(), ms.size()); } } } qDeleteAll(mRoomsList); } void RoomModel::clear() { if (!mRoomsList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); qDeleteAll(mRoomsList); mRoomsList.clear(); endRemoveRows(); } } Room *RoomModel::findRoom(const QString &roomID) const { foreach (Room *r, qAsConst(mRoomsList)) { if (r->roomId() == roomID) { return r; } } return nullptr; } RoomWrapper *RoomModel::findRoomWrapper(const QString &roomID) const { foreach (Room *r, qAsConst(mRoomsList)) { if (r->roomId() == roomID) { RoomWrapper *wrapper = new RoomWrapper(r); return wrapper; } } return nullptr; } // Clear data and refill it with data in the cache, if there is void RoomModel::reset() { clear(); if (!mRocketChatAccount) { return; } if (mRocketChatAccount->settings()->cacheBasePath().isEmpty()) { return; } //Laurent disable cache for the moment /* QDir cacheDir(Ruqola::self()->cacheBasePath()); // load cache if (cacheDir.exists(cacheDir.path())) { QFile f(cacheDir.absoluteFilePath(QStringLiteral("rooms"))); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char *byteArray; quint32 length; in.readBytes(byteArray, length); QByteArray arr = QByteArray::fromRawData(byteArray, length); Room m = Room::fromJSon(QJsonDocument::fromBinaryData(arr).object()); qDebug() <<" Load from cache room name: " << m.name; addRoom(m.id, m.name, m.selected); } } qCDebug(RUQOLA_LOG) << "Cache Loaded"; } */ } QHash RoomModel::roleNames() const { QHash roles; roles[RoomName] = QByteArrayLiteral("name"); roles[RoomID] = QByteArrayLiteral("room_id"); roles[RoomSelected] = QByteArrayLiteral("selected"); roles[RoomUnread] = QByteArrayLiteral("unread"); roles[RoomType] = QByteArrayLiteral("type"); roles[RoomOwnerUserName] = QByteArrayLiteral("username"); roles[RoomOwnerUserID] = QByteArrayLiteral("userID"); roles[RoomTopic] = QByteArrayLiteral("topic"); roles[RoomMutedUsers] = QByteArrayLiteral("mutedUsers"); roles[RoomJitsiTimeout] = QByteArrayLiteral("jitsiTimeout"); roles[RoomRo] = QByteArrayLiteral("readOnly"); roles[RoomAnnouncement] = QByteArrayLiteral("announcement"); roles[RoomOpen] = QByteArrayLiteral("open"); roles[RoomAlert] = QByteArrayLiteral("alert"); roles[RoomOrder] = QByteArrayLiteral("roomorder"); roles[RoomFavorite] = QByteArrayLiteral("favorite"); roles[RoomSection] = QByteArrayLiteral("sectionname"); roles[RoomIcon] = QByteArrayLiteral("channelicon"); roles[RoomUserMentions] = QByteArrayLiteral("userMentions"); return roles; } int RoomModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return mRoomsList.size(); } QVariant RoomModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= mRoomsList.count()) { return QVariant(); } Room *r = mRoomsList.at(index.row()); switch (role) { case RoomModel::RoomName: return r->name(); case RoomModel::RoomID: return r->roomId(); case RoomModel::RoomSelected: return r->selected(); case RoomModel::RoomType: return r->channelType(); case RoomModel::RoomOwnerUserID: return r->roomOwnerUserId(); case RoomModel::RoomOwnerUserName: return r->roomOwnerUserName(); case RoomModel::RoomTopic: return r->topic(); case RoomModel::RoomMutedUsers: return r->mutedUsers(); case RoomModel::RoomJitsiTimeout: return r->jitsiTimeout(); case RoomModel::RoomRo: return r->readOnly(); case RoomModel::RoomAnnouncement: return r->announcement(); case RoomModel::RoomUnread: return r->unread(); case RoomModel::RoomOpen: return r->open(); case RoomModel::RoomAlert: return r->alert(); case RoomModel::RoomFavorite: return r->favorite(); case RoomModel::RoomSection: return sectionName(r); case RoomModel::RoomOrder: return order(r); case RoomModel::RoomIcon: return icon(r); case RoomModel::RoomOtr: //TODO implement it. return {}; case RoomModel::RoomUserMentions: return r->userMentions(); case RoomModel::RoomIgnoredUsers: return r->ignoredUsers(); } return {}; } void RoomModel::addRoom(const QString &roomID, const QString &roomName, bool selected) { if (roomID.isEmpty() || roomName.isEmpty()) { qCDebug(RUQOLA_LOG) << " Impossible to add a room"; return; } qCDebug(RUQOLA_LOG) << "Adding room : roomId: " << roomID << " room Name " << roomName << " isSelected : " << selected; Room *r = createNewRoom(); r->setRoomId(roomID); r->setName(roomName); r->setSelected(selected); addRoom(r); } Room *RoomModel::createNewRoom() { Room *r = new Room(mRocketChatAccount); connect(r, &Room::alertChanged, this, &RoomModel::needToUpdateNotification); connect(r, &Room::unreadChanged, this, &RoomModel::needToUpdateNotification); return r; } void RoomModel::getUnreadAlertFromAccount(bool &hasAlert, int &nbUnread) { for (int i = 0; i < mRoomsList.count(); ++i) { if (mRoomsList.at(i)->open()) { if (mRoomsList.at(i)->alert()) { hasAlert = true; } nbUnread += mRoomsList.at(i)->unread(); } } } void RoomModel::updateSubscriptionRoom(const QJsonObject &roomData) { //TODO fix me! //Use "_id" QString rId = roomData.value(QLatin1String("rid")).toString(); if (rId.isEmpty()) { rId = roomData.value(QLatin1String("_id")).toString(); } if (!rId.isEmpty()) { const int roomCount = mRoomsList.size(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == rId) { qCDebug(RUQOLA_LOG) << " void RoomModel::updateSubscriptionRoom(const QJsonArray &array) room found"; Room *room = mRoomsList.at(i); room->updateSubscriptionRoom(roomData); Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0)); break; } } } else { //qCWarning(RUQOLA_LOG) << "RoomModel::updateRoom incorrect jsonobject "<< roomData; qWarning() << "RoomModel::updateSubscriptionRoom incorrect jsonobject "<< roomData; } } QString RoomModel::insertRoom(const QJsonObject &room) { Room *r = createNewRoom(); r->parseInsertRoom(room); qCDebug(RUQOLA_LOG) << "Inserting room" << r->name() << r->roomId() << r->topic(); addRoom(r); return r->roomId(); } Room *RoomModel::addRoom(const QJsonObject &room) { Room *r = createNewRoom(); r->parseSubscriptionRoom(room); qCDebug(RUQOLA_LOG) << "Adding room subscription" << r->name() << r->roomId() << r->topic(); addRoom(r); return r; } void RoomModel::addRoom(Room *room) { qCDebug(RUQOLA_LOG) << " void RoomModel::addRoom(const Room &room)"<name(); int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == room->roomId()) { delete mRoomsList.takeAt(i); break; } } roomCount = mRoomsList.count(); beginInsertRows(QModelIndex(), roomCount, roomCount); qCDebug(RUQOLA_LOG) << "Inserting room at position" <roomId() == id) { beginRemoveRows(QModelIndex(), i, i); mRoomsList.remove(i); endRemoveRows(); break; } } } else if (actionName == QStringLiteral("inserted")) { qCDebug(RUQOLA_LOG) << "INSERT ROOM name " << roomData.value(QLatin1String("name")) << " rid " << roomData.value(QLatin1String("rid")); //TODO fix me! addRoom(roomData); //addRoom(roomData.value(QLatin1String("rid")).toString(), roomData.value(QLatin1String("name")).toString(), false); } else if (actionName == QStringLiteral("updated")) { //qCDebug(RUQOLA_LOG) << "UPDATE ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid")) << " roomData " << roomData; qDebug() << "UPDATE ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid")) << " roomData " << roomData; updateSubscriptionRoom(roomData); } else if (actionName == QStringLiteral("changed")) { //qDebug() << "CHANGED ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid")) << " roomData " << roomData; qCDebug(RUQOLA_LOG) << "CHANGED ROOM name " << roomData.value(QLatin1String("name")).toString() << " rid " << roomData.value(QLatin1String("rid")) << " roomData " << roomData; qCDebug(RUQOLA_LOG) << "RoomModel::updateSubscription Not implementer changed room yet" << array; updateRoom(roomData); } else { qCDebug(RUQOLA_LOG) << "RoomModel::updateSubscription Undefined type" << actionName; } } void RoomModel::updateRoom(const QJsonObject &roomData) { qCDebug(RUQOLA_LOG) << " void RoomModel::updateRoom(const QJsonObject &roomData)"; //TODO fix me! //Use "_id" QString rId = roomData.value(QLatin1String("rid")).toString(); if (rId.isEmpty()) { rId = roomData.value(QLatin1String("_id")).toString(); } if (!rId.isEmpty()) { const int roomCount = mRoomsList.size(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == rId) { qCDebug(RUQOLA_LOG) << " void RoomModel::updateRoom(const QJsonArray &array) room found"; Room *room = mRoomsList.at(i); room->parseUpdateRoom(roomData); Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0)); break; } } } else { //qCWarning(RUQOLA_LOG) << "RoomModel::updateRoom incorrect jsonobject "<< roomData; qWarning() << "RoomModel::updateRoom incorrect jsonobject "<< roomData; } } void RoomModel::userStatusChanged(const User &user) { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { Room *room = mRoomsList.at(i); if (room->name() == user.userName()) { const QModelIndex idx = createIndex(i, 0); Q_EMIT dataChanged(idx, idx); } room->usersModelForRoom()->userStatusChanged(user); } } UsersForRoomModel *RoomModel::usersModelForRoom(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->usersModelForRoom(); } } qCWarning(RUQOLA_LOG) << " Users model for room undefined !"; return nullptr; } UsersForRoomFilterProxyModel *RoomModel::usersForRoomFilterProxyModel(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->usersModelForRoomProxyModel(); } } return {}; } FilesForRoomFilterProxyModel *RoomModel::filesForRoomFilterProxyModel(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->filesForRoomFilterProxyModel(); } } return {}; } MessageModel *RoomModel::messageModel(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->messageModel(); } } return {}; } FilesForRoomModel *RoomModel::filesModelForRoom(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->filesModelForRoom(); } } return {}; } QString RoomModel::inputMessage(const QString &roomId) const { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->inputMessage(); } } return {}; } void RoomModel::setInputMessage(const QString &roomId, const QString &inputMessage) { const int roomCount = mRoomsList.count(); for (int i = 0; i < roomCount; ++i) { if (mRoomsList.at(i)->roomId() == roomId) { return mRoomsList.at(i)->setInputMessage(inputMessage); } } } QString RoomModel::sectionName(Room *r) const { QString str; if (r->favorite()) { str = i18n("Favorites"); } else { const QString channelTypeStr = r->channelType(); if (channelTypeStr == QLatin1String("c") || channelTypeStr == QLatin1String("p")) { str = i18n("Rooms"); } else if (channelTypeStr == QLatin1String("d")) { str = i18n("Private Message"); } } return str; } int RoomModel::order(Room *r) const { int order = 0; //First item are favorites channels if (!r->favorite()) { order += 3; } const QString channelTypeStr = r->channelType(); if (channelTypeStr == QLatin1String("c") || channelTypeStr == QLatin1String("p")) { order += 1; } else if (channelTypeStr == QLatin1String("d")) { order += 2; } else { qCDebug(RUQOLA_LOG) << r->name() << "has unhandled channel type" << channelTypeStr; order += 3; } return order; } QIcon RoomModel::icon(Room *r) const { if (r->channelType() == QLatin1String("c")) { if (r->unread() > 0 || r->alert()) { return QIcon::fromTheme(QStringLiteral("irc-channel-active")); } else { return QIcon::fromTheme(QStringLiteral("irc-channel-inactive")); } } else if (r->channelType() == QLatin1String("d")) { const QString userStatusIconFileName = mRocketChatAccount ? mRocketChatAccount->userStatusIconFileName(r->name()) : QString(); if (userStatusIconFileName.isEmpty()) { return QIcon::fromTheme(QStringLiteral("user-available")); } else { return QIcon::fromTheme(userStatusIconFileName); } } else if (r->channelType() == QLatin1String("p")) { return QIcon::fromTheme(QStringLiteral("lock")); } return {}; }