diff --git a/CMakeLists.txt b/CMakeLists.txt index bf65e943cc37a2baab54e062942b85aefce48b64..dc43377f2647dc2016d23ff1f36ad6e70c22f1de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,6 +331,7 @@ SET( libringclient_LIB_SRCS src/avmodel.cpp src/pluginmodel.cpp src/namedirectory.cpp + src/messageslist.cpp src/smartinfohub.cpp src/chatview.cpp ) @@ -344,6 +345,7 @@ SET( libringclient_LIB_HDRS src/smartinfohub.h src/vcard.h src/namedirectory.h + src/messageslist.h src/shmrenderer.h src/directrenderer.h ) diff --git a/src/api/contact.h b/src/api/contact.h index f9b313a25e435a6e04d6e14c0c5dd3ff903b391b..1abc9009bf82ef789c3a6716665c028c80d60908 100644 --- a/src/api/contact.h +++ b/src/api/contact.h @@ -42,6 +42,7 @@ struct Info bool isTrusted = false; bool isPresent = false; bool isBanned = false; + QString conversationId {}; }; } // namespace contact diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h index 9ce1042bfb1ab1003f8a5f446ec58b60d88194e4..e7cec15406aa34c0034d17559cdc90966baf4506 100644 --- a/src/api/contactmodel.h +++ b/src/api/contactmodel.h @@ -90,18 +90,7 @@ public: * @return all contacts for this account. */ const ContactInfoMap& getAllContacts() const; - /** - * @return if pending requests exists. - */ - bool hasPendingRequests() const; - /** - * @return number of pending requests - */ - int pendingRequestCount() const; - /** - * Search a SIP or a Ring contact from a query. - * @param query - */ + void searchContact(const QString& query); /** * Send a text interaction to a contact over the Dht. @@ -131,6 +120,11 @@ Q_SIGNALS: * @param contactUri */ void contactAdded(const QString& contactUri) const; + /** + * Connect this signal to know when a contact request received + * @param contactUri + */ + void incomingContactRequest(const QString& contactUri) const; /** * Connect this signal to know when a pending contact was accepted. * @param contactUri @@ -155,15 +149,15 @@ Q_SIGNALS: * @param payloads content of the message */ void newAccountMessage(const QString& accountId, - const QString& msgId, const QString& from, + const QString& msgId, const MapStringString& payloads) const; /** * Connect this signal to know when a file transfer interaction is incoming * @param dringId Daemon's ID for incoming transfer * @param transferInfo DataTransferInfo structure from daemon */ - void newAccountTransfer(long long dringId, datatransfer::Info info) const; + void newAccountTransfer(DataTransferId dringId, datatransfer::Info info) const; /** * Connect this signal to know when a contact is banned or unbanned * @param contactUri diff --git a/src/api/conversation.h b/src/api/conversation.h index 97fbe776f21ac20b26f3e1daeca240b8cfc5373a..9bddff558d911025b9184b54cabaf890d6705475 100644 --- a/src/api/conversation.h +++ b/src/api/conversation.h @@ -19,6 +19,7 @@ #pragma once #include "interaction.h" +#include "messageslist.h" #include "typedefs.h" #include <vector> @@ -30,6 +31,27 @@ namespace api { namespace conversation { +enum class Mode { ONE_TO_ONE, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC, NON_SWARM }; + +static inline Mode +to_mode(const int intMode) +{ + switch (intMode) { + case 0: + return Mode::ONE_TO_ONE; + case 1: + return Mode::ADMIN_INVITES_ONLY; + case 2: + return Mode::INVITES_ONLY; + case 3: + return Mode::PUBLIC; + case 4: + return Mode::NON_SWARM; + default: + return Mode::ONE_TO_ONE; + } +} + struct Info { Info() = default; @@ -38,17 +60,22 @@ struct Info Info& operator=(const Info& other) = delete; Info& operator=(Info&& other) = default; + bool allMessagesLoaded = false; QString uid = ""; QString accountId; VectorString participants; QString callId; QString confId; - std::map<QString, interaction::Info> interactions; + MessagesList interactions; QString lastMessageUid = 0; + QHash<QString, QString> parentsId; // pair messageid/parentid for messages without parent loaded std::map<QString, QString> lastDisplayedMessageUid; unsigned int unreadMessages = 0; QString getCallId() { return confId.isEmpty() ? callId : confId; } + + Mode mode = Mode::NON_SWARM; + bool isRequest = false; }; } // namespace conversation diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h index ca24f5d4fc93453b736f948d39930b1dd87cb2e8..4aadd896c2a6c44f8d25c846581e248bbd300e5f 100644 --- a/src/api/conversationmodel.h +++ b/src/api/conversationmodel.h @@ -56,6 +56,9 @@ class NewAccountModel; enum class ConferenceableItem { CALL, CONTACT }; Q_ENUM_NS(ConferenceableItem) +enum class FilterType { INVALID = -1, JAMI, SIP, REQUEST, COUNT__ }; +Q_ENUM_NS(FilterType) + struct AccountConversation { QString convId; @@ -137,7 +140,7 @@ public: * @return conversations filtered */ const ConversationQueueProxy& getFilteredConversations( - const profile::Type& filter = profile::Type::INVALID, + const FilterType& filter = FilterType::INVALID, bool forceUpdate = false, const bool includeBanned = false) const; /** @@ -211,9 +214,9 @@ public: void setFilter(const QString& filter); /** * Modify the current filter (will change the result of getFilteredConversations) - * @param filter the new filter (example: PENDING, RING) + * @param filter the new filter (example: SIP, RING, REQUEST) */ - void setFilter(const profile::Type& filter = profile::Type::INVALID); + void setFilter(const FilterType& filter = FilterType::INVALID); /** * Join participants from A to B and vice-versa. * @note conversations must be in a call. @@ -284,17 +287,17 @@ public: int getNumberOfUnreadMessagesFor(const QString& convUid); /** * Send a composing status - * @param uid conversation's id + * @param convUid conversation's id * @param isComposing if is composing */ - void setIsComposing(const QString& uid, bool isComposing); + void setIsComposing(const QString& convUid, bool isComposing); /** * load messages for conversation * @param conversationId conversation's id * @param size number of messages should be loaded. Default 1 - * @return id for loading request. + * @return id for loading request. -1 if not loaded */ - uint32_t loadConversationMessages(const QString& conversationId, + int loadConversationMessages(const QString& conversationId, const int size = 1); /** * accept request for conversation @@ -304,8 +307,9 @@ public: /** * decline request for conversation * @param conversationId conversation's id + * @param banned. Used for non-swarm and one-to-one conversation to remove contact */ - void declineConversationRequest(const QString& conversationId); + void declineConversationRequest(const QString& conversationId, bool banned = false); /** * add member to conversation * @param conversationId conversation's id @@ -318,6 +322,34 @@ public: * @param memberId members's id */ void removeConversationMember(const QString& conversationId, const QString& memberId); + /** + * get conversation info + * @param conversationId conversation's id + * @return conversation info + */ + MapStringString getConversationInfos(const QString& conversationId); + /** + * create a new swarm conversation + * @param participants conversation's participants + * @param title conversation title + */ + void createConversation(const VectorString& participants, const QString& title = ""); + /** + * update conversation info + * @param conversationId conversation's id + * @param info + */ + void updateConversationInfo(const QString& conversationId, MapStringString info); + + /** + * @return if conversations requests exists. + */ + bool hasPendingRequests() const; + /** + * @return number of conversations requests + */ + int pendingRequestCount() const; + const VectorString peersForConversation(const QString& conversationId); Q_SIGNALS: /** @@ -378,11 +410,11 @@ Q_SIGNALS: */ void allHistoryCleared() const; /** - * Emitted at the end of slotContactAdded to notify that an existing conversation can + * Emitted at the end of slotContactAdded and at conversationReady for swarm conversation to notify that an existing conversation can * be modified * @param uid */ - void conversationReady(QString uid) const; + void conversationReady(QString uid, QString participantURI) const; /** * Emitted when a contact in a conversation is composing a message * @param uid conversation's id @@ -427,6 +459,15 @@ Q_SIGNALS: */ void newMessagesAvailable(const QString& accountId, const QString& conversationId) const; + /** + * Emitted when creation of conversation started, finished with success or finisfed with error + * @param accountId account id + * @param conversationId conversation Id, when conversation creation started conversationId = participantURI + * @param participantURI participant uri + * @param status 0 -started, 1 -created with success, -1 -error + */ + void creatingConversationEvent(const QString& accountId, const QString& conversationId, const QString& participantURI, int status) const; + /** * The following signals are intended for QAbtractListModel compatibility */ diff --git a/src/api/datatransfer.h b/src/api/datatransfer.h index c3438c52a6e38b356e8d4febf4a17617acaf392d..a7595a11c02a67a80d71701663827e451bbe3b99 100644 --- a/src/api/datatransfer.h +++ b/src/api/datatransfer.h @@ -110,6 +110,7 @@ struct Info QString displayName; QString accountId; QString peerUri; + QString conversationId; std::time_t timestamp = 0; }; diff --git a/src/api/datatransfermodel.h b/src/api/datatransfermodel.h index acf99c3079980a9c8156dffdbd577b13d1587806..2794bfad8fdb02623da6dd0888c5a7eab6215138 100644 --- a/src/api/datatransfermodel.h +++ b/src/api/datatransfermodel.h @@ -54,22 +54,36 @@ public: void sendFile(const QString& account_id, const QString& peer_uri, + const QString& conversationId, const QString& file_path, const QString& display_name); - void transferInfo(const QString& accountId, const QString& conversationId, long long ringId, datatransfer::Info& lrc_info); + void transferInfo(const QString& accountId, + const QString& conversationId, + DataTransferId ringId, + datatransfer::Info& lrc_info); - void bytesProgress(const QString& accountId, const QString& conversationId, int interactionId, int64_t& total, int64_t& progress); + void bytesProgress(const QString& accountId, + const QString& conversationId, + const QString& interactionId, + int64_t& total, + int64_t& progress); - QString accept(const QString& accountId, const QString& conversationId, int interactionId, const QString& file_path, std::size_t offset); + QString accept(const QString& accountId, + const QString& conversationId, + const QString& interactionId, + const QString& file_path, + std::size_t offset); - void cancel(const QString& accountId, const QString& conversationId, int interactionId); + void cancel(const QString& accountId, + const QString& conversationId, + const QString& interactionId); - void registerTransferId(long long dringId, int interactionId); + void registerTransferId(DataTransferId dringId, const QString& interactionId); - int getInteractionIdFromDringId(long long dringId); + QString getInteractionIdFromDringId(DataTransferId dringId); - long long getDringIdFromInteractionId(int interactionId); + DataTransferId getDringIdFromInteractionId(const QString& interactionId); /** * Used when images < 20 Mb are automatically accepted and downloaded diff --git a/src/api/interaction.h b/src/api/interaction.h index d6b36e7a5789024f24c0332235f40b12ac536938..d78fa9a6bcc5ffbb2bf30d811de3e11f35732e16 100644 --- a/src/api/interaction.h +++ b/src/api/interaction.h @@ -22,6 +22,7 @@ #include <QObject> #include <ctime> +#include "typedefs.h" namespace lrc { @@ -31,7 +32,7 @@ namespace interaction { Q_NAMESPACE Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") -enum class Type { INVALID, TEXT, CALL, CONTACT, DATA_TRANSFER, COUNT__ }; +enum class Type { INVALID, TEXT, CALL, CONTACT, DATA_TRANSFER, MERGE, COUNT__ }; Q_ENUM_NS(Type) static inline const QString @@ -46,6 +47,8 @@ to_string(const Type& type) return "CONTACT"; case Type::DATA_TRANSFER: return "DATA_TRANSFER"; + case Type::MERGE: + return "MERGE"; case Type::INVALID: case Type::COUNT__: default: @@ -56,14 +59,16 @@ to_string(const Type& type) static inline Type to_type(const QString& type) { - if (type == "TEXT") + if (type == "TEXT" || type == "text/plain") return interaction::Type::TEXT; - else if (type == "CALL") + else if (type == "CALL" || type == "application/call-history+json") return interaction::Type::CALL; - else if (type == "CONTACT") + else if (type == "CONTACT" || type == "member") return interaction::Type::CONTACT; - else if (type == "DATA_TRANSFER") + else if (type == "DATA_TRANSFER" || type == "application/data-transfer+json") return interaction::Type::DATA_TRANSFER; + else if (type == "merge") + return interaction::Type::MERGE; else return interaction::Type::INVALID; } @@ -167,6 +172,60 @@ to_status(const QString& status) return Status::INVALID; } +enum class ContactAction { + ADD, + JOIN, + LEAVE, + BANNED +}; +Q_ENUM_NS(ContactAction) + +static inline const QString +to_string(const ContactAction& action) +{ + switch (action) { + case ContactAction::ADD: + return "ADD"; + case ContactAction::JOIN: + return "JOIN"; + case ContactAction::LEAVE: + return "LEAVE"; + case ContactAction::BANNED: + return "BANNED"; + } +} + +static inline ContactAction +to_action(const QString& action) +{ + if (action == "add") + return ContactAction::ADD; + else if (action == "join") + return ContactAction::JOIN; + else if (action == "remove") + return ContactAction::LEAVE; + else if (action == "baned") + return ContactAction::BANNED; +} + +static inline QString +getContactInteractionString(const QString& authorUri, const ContactAction& action) +{ + switch (action) { + case ContactAction::ADD: + if (authorUri.isEmpty()) { + return QObject::tr("Contact added"); + } + return QObject::tr("Invitation received"); + case ContactAction::JOIN: + return QObject::tr("Invitation accepted"); + case ContactAction::LEAVE: + return QObject::tr("Contact left conversation"); + case ContactAction::BANNED: + return {}; + } +} + /** * @var authorUri * @var body @@ -180,11 +239,56 @@ struct Info { QString authorUri; QString body; + QString parentId = ""; std::time_t timestamp = 0; std::time_t duration = 0; Type type = Type::INVALID; Status status = Status::INVALID; bool isRead = false; + + Info() {} + + Info(QString authorUri, + QString body, + std::time_t timestamp, + std::time_t duration, + Type type, + Status status, + bool isRead) + { + this->authorUri = authorUri; + this->body = body; + this->timestamp = timestamp; + this->duration = duration; + this->type = type; + this->status = status; + this->isRead = isRead; + } + + Info(const MapStringString& message, const QString& accountURI) + { + type = to_type(message["type"]); + if (type == Type::TEXT) { + body = message["body"]; + } + authorUri = accountURI == message["author"] ? "" : message["author"]; + timestamp = message["timestamp"].toInt(); + status = Status::SUCCESS; + parentId = message["linearizedParent"]; + isRead = false; + if (type == Type::CONTACT) { + authorUri = accountURI == message["uri"] ? "" : message["uri"]; + body = getContactInteractionString(authorUri, to_action(message["action"])); + } + if (message["type"] == "initial" && message.find("invited") != message.end()) { + type = Type::CONTACT; + authorUri = accountURI != message["invited"] ? "" : message["invited"]; + body = getContactInteractionString(authorUri, ContactAction::ADD); + } + if (type == Type::CALL) { + duration = message["duration"].toInt() / 1000; + } + } }; static inline bool diff --git a/src/authority/storagehelper.cpp b/src/authority/storagehelper.cpp index f9447ab4e701635e9e294bffb24510f8eb83387c..0e7821190d4d4df9e87989d06e6577d7569d6655 100644 --- a/src/authority/storagehelper.cpp +++ b/src/authority/storagehelper.cpp @@ -514,6 +514,7 @@ addDataTransferToConversation(Database& db, const QString& conversationId, const api::datatransfer::Info& infoFromDaemon) { + auto convId = conversationId.isEmpty() ? NULL : conversationId; return db.insertInto("interactions", {{":author", "author"}, {":conversation", "conversation"}, @@ -521,14 +522,17 @@ addDataTransferToConversation(Database& db, {":body", "body"}, {":type", "type"}, {":status", "status"}, - {":is_read", "is_read"}}, + {":is_read", "is_read"}, + {":daemon_id", "daemon_id"}}, {{":author", infoFromDaemon.isOutgoing ? "" : infoFromDaemon.peerUri}, - {":conversation", conversationId}, + {":conversation", convId}, {":timestamp", toQString(std::time(nullptr))}, {":body", infoFromDaemon.path}, {":type", infoFromDaemon.isOutgoing ? "DATA_TRANSFER" : "DATA_TRANSFER"}, {":status", "TRANSFER_CREATED"}, - {":is_read", "0"}}); + {":is_read", "0"}, + {":daemon_id", infoFromDaemon.uid} + }); } void @@ -556,6 +560,23 @@ getInteractionIdByDaemonId(Database& db, const QString& daemon_id) return ids.empty() ? "" : ids[0]; } +void +updateDataTransferInteractionForDaemonId(Database& db, const QString& daemonId, api::interaction::Info& interaction) +{ + auto result + = db.select("body, status", + "interactions", + "daemon_id=:daemon_id", + {{":daemon_id", daemonId}}).payloads; + if (result.size() < 2) { + return; + } + auto body = result[0]; + auto status = api::interaction::to_status(result[1]); + interaction.body = body; + interaction.status = status; +} + QString getInteractionExtraDataById(Database& db, const QString& id, const QString& key) { diff --git a/src/authority/storagehelper.h b/src/authority/storagehelper.h index bf02bd9903bcd2da343a2906919b575d28982c03..09326ac7894f31cb21ad486fd5d108ccdbd9d123 100644 --- a/src/authority/storagehelper.h +++ b/src/authority/storagehelper.h @@ -235,6 +235,14 @@ QString getInteractionIdByDaemonId(Database& db, const QString& daemon_id); */ QString getInteractionExtraDataById(Database& db, const QString& id, const QString& key = {}); +/** + * update interaction + * @param db + * @param daemon id + * @param interaction + */ +void updateDataTransferInteractionForDaemonId(Database& db, const QString& daemonId, api::interaction::Info& interaction); + /** * Change the body of an interaction * @param db diff --git a/src/callbackshandler.cpp b/src/callbackshandler.cpp index 22bb0da4c990bf299a56189a1a83bc350fcb1c95..01ee01be1716be10ce63e154192c0b2e052c36a7 100644 --- a/src/callbackshandler.cpp +++ b/src/callbackshandler.cpp @@ -282,6 +282,11 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent) this, &CallbacksHandler::slotConversationReady, Qt::QueuedConnection); + connect(&ConfigurationManager::instance(), + &ConfigurationManagerInterface::conversationRemoved, + this, + &CallbacksHandler::slotConversationRemoved, + Qt::QueuedConnection); connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::conversationMemberEvent, this, @@ -303,12 +308,12 @@ CallbacksHandler::subscribeToDebugReceived() void CallbacksHandler::slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const MapStringString& payloads) { - auto from2 = QString(from).replace("@ring.dht", ""); - emit newAccountMessage(accountId, msgId, from2, payloads); + auto peerId2 = QString(peerId).replace("@ring.dht", ""); + emit newAccountMessage(accountId, peerId2, msgId, payloads); } void @@ -364,12 +369,13 @@ CallbacksHandler::slotContactRemoved(const QString& accountId, void CallbacksHandler::slotIncomingContactRequest(const QString& accountId, - const QString& ringId, + const QString& conversationId, + const QString& contactUri, const QByteArray& payload, time_t time) { Q_UNUSED(time) - emit incomingContactRequest(accountId, ringId, payload); + emit incomingContactRequest(accountId, conversationId, contactUri, payload); } void @@ -504,15 +510,16 @@ CallbacksHandler::slotConferenceRemoved(const QString& callId) void CallbacksHandler::slotAccountMessageStatusChanged(const QString& accountId, - const uint64_t id, - const QString& to, + const QString& conversationId, + const QString& peer, + const QString& messageId, int status) { - emit accountMessageStatusChanged(accountId, id, to, status); + emit accountMessageStatusChanged(accountId, conversationId, peer, messageId, status); } void -CallbacksHandler::slotDataTransferEvent(const QString& accountId, const QString& conversationId, qulonglong dringId, uint codeStatus) +CallbacksHandler::slotDataTransferEvent(const QString& accountId, const QString& conversationId, DataTransferId dringId, uint codeStatus) { auto event = DRing::DataTransferEventCode(codeStatus); @@ -525,33 +532,33 @@ CallbacksHandler::slotDataTransferEvent(const QString& accountId, const QString& switch (event) { case DRing::DataTransferEventCode::created: - emit transferStatusCreated(static_cast<long long>(dringId), info); + emit transferStatusCreated(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::closed_by_host: case DRing::DataTransferEventCode::closed_by_peer: - emit transferStatusCanceled(static_cast<long long>(dringId), info); + emit transferStatusCanceled(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::wait_peer_acceptance: - emit transferStatusAwaitingPeer(static_cast<long long>(dringId), info); + emit transferStatusAwaitingPeer(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::wait_host_acceptance: - emit transferStatusAwaitingHost(static_cast<long long>(dringId), info); + emit transferStatusAwaitingHost(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::ongoing: - emit transferStatusOngoing(static_cast<long long>(dringId), info); + emit transferStatusOngoing(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::finished: - emit transferStatusFinished(static_cast<long long>(dringId), info); + emit transferStatusFinished(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::invalid_pathname: case DRing::DataTransferEventCode::unsupported: - emit transferStatusError(static_cast<long long>(dringId), info); + emit transferStatusError(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::timeout_expired: - emit transferStatusTimeoutExpired(static_cast<long long>(dringId), info); + emit transferStatusTimeoutExpired(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::unjoinable_peer: - emit transferStatusUnjoinable(static_cast<long long>(dringId), info); + emit transferStatusUnjoinable(static_cast<DataTransferId>(dringId), info); break; case DRing::DataTransferEventCode::invalid: break; @@ -684,6 +691,12 @@ CallbacksHandler::slotConversationReady(const QString& accountId, const QString& emit conversationReady(accountId, conversationId); } +void +CallbacksHandler::slotConversationRemoved(const QString& accountId, const QString& conversationId) +{ + emit conversationRemoved(accountId, conversationId); +} + void CallbacksHandler::slotConversationMemberEvent(const QString& accountId, const QString& conversationId, diff --git a/src/callbackshandler.h b/src/callbackshandler.h index 1bf1a2f28ab712f4a5b9e0e4cfe083584ffe480b..0cc689eecbb4ff1431fcfe3aaf394181f1f4dcc7 100644 --- a/src/callbackshandler.h +++ b/src/callbackshandler.h @@ -57,8 +57,8 @@ Q_SIGNALS: * @param payloads. */ void newAccountMessage(const QString& accountId, - const QString& msgId, const QString& from, + const QString& msgId, const MapStringString& payloads); /** * Connect this signal to get information when a peer is online. @@ -93,11 +93,12 @@ Q_SIGNALS: /** * Connect this signal to know when an incoming request is added by the daemon * @param accountId the one who got the request - * @param ringId the peer contact + * @param contactUri the peer contact * @param payload the VCard */ void incomingContactRequest(const QString& accountId, - const QString& ringId, + const QString& conversationId, + const QString& contactUri, const QString& payload); /** * Connect this signal to know when a call arrives @@ -192,24 +193,26 @@ Q_SIGNALS: /** * Connect this signal to know when a message sent get a new status * @param accountId, account linked - * @param id of the message - * @param to, peer uri + * @param messageId id of the message + * @param conversationId id of the conversation + * @param peer, peer uri * @param status, new status for this message */ void accountMessageStatusChanged(const QString& accountId, - const uint64_t id, - const QString& to, + const QString& conversationId, + const QString& peer, + const QString& messageId, int status); - void transferStatusCreated(long long dringId, api::datatransfer::Info info); - void transferStatusCanceled(long long dringId, api::datatransfer::Info info); - void transferStatusAwaitingPeer(long long dringId, api::datatransfer::Info info); - void transferStatusAwaitingHost(long long dringId, api::datatransfer::Info info); - void transferStatusOngoing(long long dringId, api::datatransfer::Info info); - void transferStatusFinished(long long dringId, api::datatransfer::Info info); - void transferStatusError(long long dringId, api::datatransfer::Info info); - void transferStatusTimeoutExpired(long long dringId, api::datatransfer::Info info); - void transferStatusUnjoinable(long long dringId, api::datatransfer::Info info); + void transferStatusCreated(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusCanceled(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusAwaitingPeer(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusAwaitingHost(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusOngoing(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusFinished(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusError(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusTimeoutExpired(DataTransferId dringId, api::datatransfer::Info info); + void transferStatusUnjoinable(DataTransferId dringId, api::datatransfer::Info info); /** * Connect this signal to get when a device name changed or a device is added @@ -343,6 +346,7 @@ Q_SIGNALS: const QString& conversationId, const MapStringString& metadatas); void conversationReady(const QString& accountId, const QString& conversationId); + void conversationRemoved(const QString& accountId, const QString& conversationId); void conversationMemberEvent(const QString& accountId, const QString& conversationId, const QString& memberId, @@ -352,13 +356,13 @@ private Q_SLOTS: /** * Emit newAccountMessage * @param accountId + * @param peerId * @param msgId - * @param from * @param payloads of the interaction */ void slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const QMap<QString, QString>& payloads); /** * Emit newBuddySubscription @@ -393,6 +397,7 @@ private Q_SLOTS: * @param time when the request was received */ void slotIncomingContactRequest(const QString& accountId, + const QString& conversationId, const QString& ringId, const QByteArray& payload, time_t time); @@ -496,17 +501,19 @@ private Q_SLOTS: void slotConferenceChanged(const QString& callId, const QString& state); /** * Emit accountMessageStatusChanged - * @param accountId - * @param id of the message for the daemon - * @param to peer uri - * @param status, new status + * @param accountId, account linked + * @param messageId id of the message + * @param conversationId id of the conversation + * @param peer, peer uri + * @param status, new status for this message */ void slotAccountMessageStatusChanged(const QString& accountId, - const uint64_t id, - const QString& to, - int status); + const QString& conversationId, + const QString& peer, + const QString& messageId, + int status); - void slotDataTransferEvent(const QString& accountId, const QString& conversationId, qulonglong id, uint code); + void slotDataTransferEvent(const QString& accountId, const QString& conversationId, DataTransferId id, uint code); /** * Emit knownDevicesChanged @@ -652,6 +659,7 @@ private Q_SLOTS: const QString& conversationId, const MapStringString& metadatas); void slotConversationReady(const QString& accountId, const QString& conversationId); + void slotConversationRemoved(const QString& accountId, const QString& conversationId); void slotConversationMemberEvent(const QString& accountId, const QString& conversationId, const QString& memberId, diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp index 66910488fcce0aa938bd5b4a71cfbe889d3942f5..77cc260c810b63370054c08880dc02311a01f9ce 100644 --- a/src/contactmodel.cpp +++ b/src/contactmodel.cpp @@ -164,6 +164,7 @@ public Q_SLOTS: * @param payload VCard of the contact */ void slotIncomingContactRequest(const QString& accountId, + const QString& conversationId, const QString& contactUri, const QString& payload); /** @@ -178,12 +179,12 @@ public Q_SLOTS: * Listen from callbacksHandler for new account interaction and add pending contact if not present * @param accountId * @param msgId - * @param from + * @param peerId * @param payloads */ void slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const MapStringString& payloads); /** @@ -191,7 +192,7 @@ public Q_SLOTS: * @param dringId Daemon's ID for incoming transfer * @param transferInfo DataTransferInfo structure from daemon */ - void slotNewAccountTransfer(long long dringId, datatransfer::Info info); + void slotNewAccountTransfer(DataTransferId dringId, datatransfer::Info info); /** * Listen from daemon to know when a VCard is received @@ -232,29 +233,6 @@ ContactModel::getAllContacts() const return pimpl_->contacts; } -bool -ContactModel::hasPendingRequests() const -{ - return pendingRequestCount() > 0; -} - -int -ContactModel::pendingRequestCount() const -{ - if (!pimpl_) - return 0; - std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_); - int pendingRequestCount = 0; - std::for_each(pimpl_->contacts.begin(), - pimpl_->contacts.end(), - [&pendingRequestCount](const auto& c) { - if (!c.isBanned) - pendingRequestCount += static_cast<int>(c.profileInfo.type - == profile::Type::PENDING); - }); - return pendingRequestCount; -} - void ContactModel::addContact(contact::Info contactInfo) { @@ -325,7 +303,6 @@ ContactModel::addContact(contact::Info contactInfo) } } emit profileUpdated(profile.uri); - emit contactAdded(profile.uri); } void @@ -974,6 +951,7 @@ ContactModelPimpl::slotRegisteredNameFound(const QString& accountId, void ContactModelPimpl::slotIncomingContactRequest(const QString& accountId, + const QString& conversationId, const QString& contactUri, const QString& payload) { @@ -998,9 +976,8 @@ ContactModelPimpl::slotIncomingContactRequest(const QString& accountId, storage::createOrUpdateProfile(accountId, profileInfo, true); } } - + emit linked.incomingContactRequest(contactUri); if (emitTrust) { - emit linked.contactAdded(contactUri); emit behaviorController.newTrustRequest(linked.owner.id, contactUri); } } @@ -1031,7 +1008,6 @@ ContactModelPimpl::slotIncomingCall(const QString& fromId, } } if (emitContactAdded) { - emit linked.contactAdded(fromId); if (linked.owner.profileInfo.type == profile::Type::RING) { emit behaviorController.newTrustRequest(linked.owner.id, fromId); } @@ -1043,40 +1019,40 @@ ContactModelPimpl::slotIncomingCall(const QString& fromId, void ContactModelPimpl::slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const MapStringString& payloads) { if (accountId != linked.owner.id) return; - QString from2(from); + QString peerId2(peerId); auto emitNewTrust = false; { std::lock_guard<std::mutex> lk(contactsMtx_); - if (contacts.find(from) == contacts.end()) { + if (contacts.find(peerId) == contacts.end()) { // Contact not found, load profile from database. // The conversation model will create an entry and link the incomingCall. if (linked.owner.profileInfo.type == profile::Type::SIP) { - QString potentialContact = sipUriReceivedFilter(from); + QString potentialContact = sipUriReceivedFilter(peerId); if (potentialContact.isEmpty()) { - addToContacts(from, profile::Type::SIP, "", false); + addToContacts(peerId, profile::Type::SIP, "", false); } else { // equivalent uri exist, use that uri - from2 = potentialContact; + peerId2 = potentialContact; } } else { - addToContacts(from, profile::Type::PENDING, "", false); + addToContacts(peerId, profile::Type::PENDING, "", false); emitNewTrust = true; } } } if (emitNewTrust) { - emit behaviorController.newTrustRequest(linked.owner.id, from); + emit behaviorController.newTrustRequest(linked.owner.id, peerId); } - emit linked.newAccountMessage(accountId, msgId, from2, payloads); + emit linked.newAccountMessage(accountId, peerId2, msgId, payloads); } QString @@ -1134,7 +1110,7 @@ ContactModelPimpl::sipUriReceivedFilter(const QString& uri) } void -ContactModelPimpl::slotNewAccountTransfer(long long dringId, datatransfer::Info info) +ContactModelPimpl::slotNewAccountTransfer(DataTransferId dringId, datatransfer::Info info) { if (info.accountId != linked.owner.id) return; @@ -1142,7 +1118,9 @@ ContactModelPimpl::slotNewAccountTransfer(long long dringId, datatransfer::Info bool emitNewTrust = false; { std::lock_guard<std::mutex> lk(contactsMtx_); - if (contacts.find(info.peerUri) == contacts.end()) { + // Note: just add a contact for compatibility (so not for swarm). + if (info.conversationId.isEmpty() && !info.peerUri.isEmpty() + && contacts.find(info.peerUri) == contacts.end()) { // Contact not found, load profile from database. // The conversation model will create an entry and link the incomingCall. auto type = (linked.owner.profileInfo.type == profile::Type::RING) diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp index b6dd99a81098166f9b23b554d3859469e391c611..c3a6ef69e1e06ac2c4e20a77cace11c1b6a38607 100644 --- a/src/conversationmodel.cpp +++ b/src/conversationmodel.cpp @@ -52,6 +52,7 @@ #include <mutex> #include <regex> #include <fstream> +#include <sstream> namespace lrc { @@ -102,6 +103,8 @@ public: * @param participant uri. * @param searchResultIncluded if need to search in contacts and userSearch. * @return a reference to a conversation with the given peer uri. + * @warning we could have multiple swarm conversations for the same peer. This function will + * return an active one-to-one conversation. */ std::reference_wrapper<conversation::Info> getConversationForPeerUri( const QString& uri, const bool searchResultIncluded = false); @@ -136,36 +139,44 @@ public: * @param contactUri */ void addConversationWith(const QString& convId, const QString& contactUri); + /** + * Add a swarm conversation to conversation list + * @param convId + */ + void addSwarmConversation(const QString& convId); /** * Add call interaction for conversation with callId * @param callId * @param duration */ void addOrUpdateCallMessage(const QString& callId, - const QString& from = {}, + const QString& from, + bool incoming, const std::time_t& duration = -1); /** * Add a new message from a peer in the database - * @param from the author uri + * @param peerId the author id * @param body the content of the message * @param timestamp the timestamp of the message * @param daemonId the daemon id * @return msgId generated (in db) */ - QString addIncomingMessage(const QString& from, + QString addIncomingMessage(const QString& peerId, const QString& body, const uint64_t& timestamp = 0, const QString& daemonId = ""); /** * Change the status of an interaction. Listen from callbacksHandler * @param accountId, account linked - * @param id, interaction to update - * @param to, peer uri + * @param messageId, interaction to update + * @param conversationId, conversation + * @param peerId, peer id * @param status, new status for this interaction */ void slotUpdateInteractionStatus(const QString& accountId, - const uint64_t& id, - const QString& to, + const QString& conversationId, + const QString& peerId, + const QString& messageId, int status); /** @@ -186,12 +197,15 @@ public: void updateTransfer(QTimer* timer, const QString& conversation, int conversationIdx, - int interactionId); + const QString& interactionId); - bool usefulDataFromDataTransfer(long long dringId, + bool usefulDataFromDataTransfer(DataTransferId dringId, const datatransfer::Info& info, - int& interactionId, - QString& convId); + QString& interactionId, + QString& conversationId); + void awaitingHost(DataTransferId dringId, datatransfer::Info info); + + bool hasOneOneSwarmWith(const QString& participant); /** * accept a file transfer @@ -200,7 +214,17 @@ public: * @param final name of the file */ void acceptTransfer(const QString& convUid, const QString& interactionId, const QString& path); - + void addConversationRequest(const MapStringString& convRequest); + void addContactRequest(const QString& contactUri); + const VectorString peersForConversation(const conversation::Info& conversation) + const; // filter out ourself from conversation partisipants. + // for each contact we must have one non-swarm conversation or one active one-to-one + // conversation. Where active means peer did not leave the conversation. + bool isCoreDialog(const conversation::Info& conversation) const; + void insertSwarmInteraction(const QString& interactionId, + const interaction::Info& interaction, + conversation::Info& conversation, + bool insertAtBegin); void invalidateModel(); const ConversationModel& linked; @@ -216,10 +240,11 @@ public: ConversationModel::ConversationQueueProxy customFilteredConversations; QString currentFilter; - profile::Type typeFilter; - profile::Type customTypeFilter; + FilterType typeFilter; + FilterType customTypeFilter; std::map<QString, std::mutex> interactionsLocks; ///< {convId, mutex} + MapStringString transfIdToDbIntId; public Q_SLOTS: /** @@ -231,6 +256,11 @@ public Q_SLOTS: * @param uri */ void slotContactAdded(const QString& contactUri); + /** + * Listen from contactModel when receive a new contact request + * @param uri + */ + void slotIncomingContactRequest(const QString& contactUri); /** * Listen from contactModel when a pending contact is accepted * @param uri @@ -266,12 +296,12 @@ public Q_SLOTS: * Listen from CallbacksHandler for new incoming interactions; * @param accountId * @param msgId - * @param from uri + * @param peerId * @param payloads body */ void slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const MapStringString& payloads); /** * Listen from CallbacksHandler for new messages in a SIP call @@ -298,21 +328,23 @@ public Q_SLOTS: * @param isComposing */ void slotComposingStatusChanged(const QString& accountId, + const QString& convId, const QString& contactUri, bool isComposing); - void slotTransferStatusCreated(long long dringId, api::datatransfer::Info info); - void slotTransferStatusCanceled(long long dringId, api::datatransfer::Info info); - void slotTransferStatusAwaitingPeer(long long dringId, api::datatransfer::Info info); - void slotTransferStatusAwaitingHost(long long dringId, api::datatransfer::Info info); - void slotTransferStatusOngoing(long long dringId, api::datatransfer::Info info); - void slotTransferStatusFinished(long long dringId, api::datatransfer::Info info); - void slotTransferStatusError(long long dringId, api::datatransfer::Info info); - void slotTransferStatusTimeoutExpired(long long dringId, api::datatransfer::Info info); - void slotTransferStatusUnjoinable(long long dringId, api::datatransfer::Info info); - void updateTransferStatus(long long dringId, - api::datatransfer::Info info, - interaction::Status newStatus); + void slotTransferStatusCreated(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusCanceled(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusAwaitingPeer(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusAwaitingHost(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusOngoing(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusFinished(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusError(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusTimeoutExpired(DataTransferId dringId, api::datatransfer::Info info); + void slotTransferStatusUnjoinable(DataTransferId dringId, api::datatransfer::Info info); + bool updateTransferStatus(DataTransferId dringId, + datatransfer::Info info, + interaction::Status newStatus, + bool& updated); void slotConversationLoaded(uint32_t requestId, const QString& accountId, const QString& conversationId, @@ -323,7 +355,12 @@ public Q_SLOTS: void slotConversationRequestReceived(const QString& accountId, const QString& conversationId, const MapStringString& metadatas); + void slotConversationMemberEvent(const QString& accountId, + const QString& conversationId, + const QString& memberUri, + int event); void slotConversationReady(const QString& accountId, const QString& conversationId); + void slotConversationRemoved(const QString& accountId, const QString& conversationId); }; ConversationModel::ConversationModel(const account::Info& owner, @@ -376,26 +413,33 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q // add contacts for current account for (const auto& conv : conversations) { // conversations with calls will be added in call section - if (!conv.callId.isEmpty() || !conv.confId.isEmpty()) { + // we want to add only contacts non-swarm or one-to-one conversation + auto& peers = pimpl_->peersForConversation(conv); + if (!conv.callId.isEmpty() || !conv.confId.isEmpty() || !pimpl_->isCoreDialog(conv)) { continue; } - auto contact = owner.contactModel->getContact(conv.participants.front()); - if (contact.isBanned || contact.profileInfo.type == profile::Type::PENDING) { - continue; - } - QVector<AccountConversation> cv; - AccountConversation accConv = {conv.uid, currentAccountID}; - cv.push_back(accConv); - if (filter.isEmpty()) { - contactsVector.push_back(cv); + try { + auto contact = owner.contactModel->getContact(peers.front()); + if (contact.isBanned || contact.profileInfo.type == profile::Type::PENDING) { + continue; + } + QVector<AccountConversation> cv; + AccountConversation accConv = {conv.uid, currentAccountID}; + cv.push_back(accConv); + if (filter.isEmpty()) { + contactsVector.push_back(cv); + continue; + } + bool result = contact.profileInfo.alias.contains(filter) + || contact.profileInfo.uri.contains(filter) + || contact.registeredName.contains(filter); + if (result) { + contactsVector.push_back(cv); + } + } catch (const std::out_of_range& e) { + qDebug() << e.what(); continue; } - bool result = contact.profileInfo.alias.contains(filter) - || contact.profileInfo.uri.contains(filter) - || contact.registeredName.contains(filter); - if (result) { - contactsVector.push_back(cv); - } } if (calls.empty() && conferences.empty()) { @@ -418,9 +462,11 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q for (const auto& account_id : pimpl_->lrc.getAccountModel().getAccountList()) { try { auto& accountInfo = pimpl_->lrc.getAccountModel().getAccountInfo(account_id); - auto accountConv = accountInfo.conversationModel->getFilteredConversations( - accountInfo.profileInfo.type); - accountConv.for_each([filter, + auto type = accountInfo.profileInfo.type == profile::Type::SIP ? FilterType::SIP + : FilterType::JAMI; + auto accountConv = accountInfo.conversationModel->getFilteredConversations(type); + accountConv.for_each([this, + filter, &accountInfo, account_id, currentCallId, @@ -437,8 +483,8 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q bool callFilterPredicate = !conv.callId.isEmpty() && conv.callId != currentCallId && std::find(calls.begin(), calls.end(), conv.callId) != calls.end(); - - if (!confFilterPredicate && !callFilterPredicate) { + auto& peers = pimpl_->peersForConversation(conv); + if ((!confFilterPredicate && !callFilterPredicate) || !pimpl_->isCoreDialog(conv)) { return; } @@ -457,7 +503,7 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q || call.status == lrc::api::call::Status::IN_PROGRESS; } - auto contact = accountInfo.contactModel->getContact(conv.participants.front()); + auto contact = accountInfo.contactModel->getContact(peers.front()); // check if contact satisfy filter bool result = (filter.isEmpty() || isConference) ? true @@ -487,7 +533,11 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q try { auto& account = pimpl_->lrc.getAccountModel().getAccountInfo(accConv.accountId); auto& conv = account.conversationModel->getConversationForUid(accConv.convId)->get(); - auto cont = account.contactModel->getContact(conv.participants.front()); + auto& peers = pimpl_->peersForConversation(conv); + if (!pimpl_->isCoreDialog(conv)) { + continue; + } + auto cont = account.contactModel->getContact(peers.front()); if (cont.profileInfo.alias.contains(filter) || cont.profileInfo.uri.contains(filter) || cont.registeredName.contains(filter)) { callsVector.push_back(it.second); @@ -509,7 +559,7 @@ ConversationModel::getAllSearchResults() const } const ConversationModel::ConversationQueueProxy& -ConversationModel::getFilteredConversations(const profile::Type& filter, +ConversationModel::getFilteredConversations(const FilterType& filter, bool forceUpdate, const bool includeBanned) const { @@ -520,10 +570,29 @@ ConversationModel::getFilteredConversations(const profile::Type& filter, pimpl_->customTypeFilter = filter; return pimpl_->customFilteredConversations.reset(pimpl_->conversations) .filter([this, &includeBanned](const conversation::Info& entry) { - auto contactInfo = owner.contactModel->getContact(entry.participants.front()); - if (!includeBanned && contactInfo.isBanned) + try { + auto& peers = pimpl_->peersForConversation(entry); + if (peers.isEmpty()) { + return false; + } + auto contactInfo = owner.contactModel->getContact(peers.front()); + // do not check blocked contacts for conversation with many participants + if (!includeBanned && (contactInfo.isBanned && peers.size() == 1)) + return false; + switch (pimpl_->customTypeFilter) { + case FilterType::JAMI: + // we have conversation with many participants only for JAMI + return (owner.profileInfo.type == profile::Type::RING && !entry.isRequest); + case FilterType::SIP: + return (owner.profileInfo.type == profile::Type::SIP && !entry.isRequest); + case FilterType::REQUEST: + return entry.isRequest; + default: + break; + } + } catch (...) { return false; - return (contactInfo.profileInfo.type == pimpl_->customTypeFilter); + } }) .validate(); } @@ -542,10 +611,14 @@ OptRef<conversation::Info> ConversationModel::getConversationForPeerUri(const QString& uri) { try { - return std::make_optional( - pimpl_->getConversation([uri](const conversation::Info& conv) - -> bool { return uri == conv.participants.front(); }, - true)); + return std::make_optional(pimpl_->getConversation( + [this, uri](const conversation::Info& conv) -> bool { + if (!pimpl_->isCoreDialog(conv)) { + return false; + } + return uri == pimpl_->peersForConversation(conv).front(); + }, + true)); } catch (const std::out_of_range&) { return std::nullopt; } @@ -599,7 +672,11 @@ ConversationModel::makePermanent(const QString& uid) } // Send contact request if non used - pimpl_->sendContactRequest(conversation.participants.front()); + auto& peers = pimpl_->peersForConversation(conversation); + if (peers.size() != 1) { + return; + } + pimpl_->sendContactRequest(peers.front()); } catch (const std::out_of_range& e) { qDebug() << "make permanent failed. conversation not found"; } @@ -686,11 +763,22 @@ ConversationModel::removeConversation(const QString& uid, bool banned) "participant"; return; } + if (!pimpl_->isCoreDialog(conversation)) { + ConfigurationManager::instance().removeConversation(owner.id, uid); + pimpl_->conversations.erase(pimpl_->conversations.begin() + conversationIdx); + pimpl_->invalidateModel(); + emit conversationRemoved(uid); + return; + } // Remove contact from daemon - // NOTE: this will also remove the conversation into the database. - for (const auto& participant : conversation.participants) - owner.contactModel->removeContact(participant, banned); + // NOTE: this will also remove the conversation into the database for non-swarm and remove + // conversation repository for one-to-one. + auto& peers = pimpl_->peersForConversation(conversation); + if (peers.size() != 1) { + return; + } + owner.contactModel->removeContact(peers.front(), banned); } void @@ -716,7 +804,11 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly) << "ConversationModel::placeCall can't call a conversation without participant"; return; } - + auto& peers = peersForConversation(conversation); + // there is no calls in group with more than 2 participants + if (peers.size() != 1) { + return; + } // Disallow multiple call if (!conversation.callId.isEmpty()) { try { @@ -745,7 +837,7 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly) auto convId = uid; - auto participant = conversation.participants.front(); + auto participant = peers.front(); bool isTemporary = participant == convId; auto contactInfo = linked.owner.contactModel->getContact(participant); auto uri = contactInfo.profileInfo.uri; @@ -763,35 +855,36 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly) uri = "ring:" + uri; // Add the ring: before or it will fail. } - auto cb = std::function<void(QString)>( - [this, isTemporary, uri, isAudioOnly, &conversation](QString convId) { - int contactIndex; - if (isTemporary && (contactIndex = indexOfContact(convId)) < 0) { - qDebug() << "Can't place call: Other participant is not a contact (removed " - "while placing call ?)"; - return; - } + auto cb = ([this, isTemporary, uri, isAudioOnly, &conversation](QString conversationId) { + if (indexOf(conversationId) < 0) { + qDebug() << "Can't place call: conversation not exists"; + return; + } - auto& newConv = isTemporary ? conversations.at(contactIndex) : conversation; - convId = newConv.uid; + auto& newConv = isTemporary ? getConversationForUid(conversationId).get() + : conversation; - newConv.callId = linked.owner.callModel->createCall(uri, isAudioOnly); - if (newConv.callId.isEmpty()) { - qDebug() << "Can't place call (daemon side failure ?)"; - return; - } + newConv.callId = linked.owner.callModel->createCall(uri, isAudioOnly); + if (newConv.callId.isEmpty()) { + qDebug() << "Can't place call (daemon side failure ?)"; + return; + } - invalidateModel(); + invalidateModel(); - emit behaviorController.showIncomingCallView(linked.owner.id, newConv.uid); - }); + emit behaviorController.showIncomingCallView(linked.owner.id, newConv.uid); + }); if (isTemporary) { QMetaObject::Connection* const connection = new QMetaObject::Connection; *connection = connect(&this->linked, &ConversationModel::conversationReady, - [cb, connection](QString convId) { - cb(convId); + [cb, connection, convId](QString conversationId, + QString participantId) { + if (participantId != convId) { + return; + } + cb(conversationId); QObject::disconnect(*connection); if (connection) { delete connection; @@ -821,64 +914,119 @@ ConversationModel::placeCall(const QString& uid) pimpl_->placeCall(uid); } +MapStringString +ConversationModel::getConversationInfos(const QString& conversationId) +{ + MapStringString ret = ConfigurationManager::instance().conversationInfos(owner.id, + conversationId); + return ret; +} + +void +ConversationModel::createConversation(const VectorString& participants, const QString& title) +{ + auto convUid = ConfigurationManager::instance().startConversation(owner.id); + for (const auto& participant : participants) { + ConfigurationManager::instance().addConversationMember(owner.id, convUid, participant); + } + if (!title.isEmpty()) { + MapStringString info = getConversationInfos(convUid); + info["title"] = title; + updateConversationInfo(convUid, info); + } + Q_EMIT beginInsertRows(pimpl_->conversations.size()); + pimpl_->addSwarmConversation(convUid); + emit newConversation(convUid); + Q_EMIT endInsertRows(); + pimpl_->invalidateModel(); + emit modelChanged(); +} +void +ConversationModel::updateConversationInfo(const QString& conversationId, const MapStringString info) +{ + ConfigurationManager::instance().updateConversationInfos(owner.id, conversationId, info); +} + +bool +ConversationModel::hasPendingRequests() const +{ + return pendingRequestCount() > 0; +} + +int +ConversationModel::pendingRequestCount() const +{ + int pendingRequestCount = 0; + std::for_each(pimpl_->conversations.begin(), + pimpl_->conversations.end(), + [&pendingRequestCount](const auto& c) { pendingRequestCount += c.isRequest; }); + return pendingRequestCount; +} + void ConversationModel::sendMessage(const QString& uid, const QString& body, const QString& parentId) { try { auto& conversation = pimpl_->getConversationForUid(uid, true).get(); - - if (conversation.participants.empty()) { + auto& peers = pimpl_->peersForConversation(conversation); + if (peers.isEmpty()) { // Should not qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation " "with no participant"; return; } + /* isTemporary, and conversationReady callback used only for non-swarm conversation, + because for swarm, conversation already configured at this point. + Conversations for new contact from search result are NON_SWARM but after receiving + conversationReady callback could be updated to ONE_TO_ONE. We still use conversationReady + callback for swarm conversation with one participant to check if contact is blocked*/ + if (peers.size() != 1) { + ConfigurationManager::instance().sendMessage(owner.id, uid, body, parentId); + } + auto convId = uid; - // for temporary contact conversation id is the same as participant uri - bool isTemporary = conversation.participants.front() == uid; - - /* Make a copy of participants list: if current conversation is temporary, - it might me destroyed while we are reading it */ - const auto participants = conversation.participants; - - auto cb = std::function<void(QString)>([this, isTemporary, body, &conversation]( - QString convId) { - /* Now we should be able to retrieve the final conversation, in case the previous - one was temporary */ - // FIXME potential race condition between index check and at() call - int contactIndex; - if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) { - qDebug() << "Can't send message: Other participant is not a contact"; + auto& peerId = peers.front(); + bool isTemporary = peerId == convId; + + auto cb = ([this, isTemporary, body, &conversation, parentId, convId]( + QString conversationId) { + if (pimpl_->indexOf(conversationId) < 0) { + return; + } + auto& newConv = isTemporary ? pimpl_->getConversationForUid(conversationId).get() + : conversation; + + if (newConv.mode != conversation::Mode::NON_SWARM) { + ConfigurationManager::instance().sendMessage(owner.id, + conversationId, + body, + parentId); + return; + } + auto& peers = pimpl_->peersForConversation(newConv); + if (peers.isEmpty()) { return; } uint64_t daemonMsgId = 0; auto status = interaction::Status::SENDING; - - auto& newConv = isTemporary ? pimpl_->conversations.at(contactIndex) : conversation; - convId = newConv.uid; - - // Send interaction to each participant - for (const auto& participant : newConv.participants) { - auto contactInfo = owner.contactModel->getContact(participant); - - QStringList callLists = CallManager::instance().getCallList(); // no auto - // workaround: sometimes, it may happen that the daemon delete a call, but lrc - // don't. We check if the call is - // still valid every time the user want to send a message. - if (not newConv.callId.isEmpty() and not callLists.contains(newConv.callId)) - newConv.callId.clear(); - - if (not newConv.callId.isEmpty() - and call::canSendSIPMessage(owner.callModel->getCall(newConv.callId))) { - status = interaction::Status::UNKNOWN; - owner.callModel->sendSipMessage(newConv.callId, body); - - } else { - daemonMsgId = owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, - body); - } + auto convId = newConv.uid; + + QStringList callLists = CallManager::instance().getCallList(); // no auto + // workaround: sometimes, it may happen that the daemon delete a call, but lrc + // don't. We check if the call is + // still valid every time the user want to send a message. + if (not newConv.callId.isEmpty() and not callLists.contains(newConv.callId)) + newConv.callId.clear(); + + if (not newConv.callId.isEmpty() + and call::canSendSIPMessage(owner.callModel->getCall(newConv.callId))) { + status = interaction::Status::UNKNOWN; + owner.callModel->sendSipMessage(newConv.callId, body); + + } else { + daemonMsgId = owner.contactModel->sendDhtMessage(peers.front(), body); } // Add interaction to database @@ -922,28 +1070,19 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS QMetaObject::Connection* const connection = new QMetaObject::Connection; *connection = connect(this, &ConversationModel::conversationReady, - [cb, connection](QString convId) { - cb(convId); + [cb, connection, convId](QString conversationId, + QString participantId) { + if (participantId != convId) { + return; + } + cb(conversationId); QObject::disconnect(*connection); if (connection) { delete connection; } }); } - - /* Check participants list, send contact request if needed. - NOTE: conferences are not implemented yet, so we have only one participant */ - for (const auto& participant : participants) { - auto contactInfo = owner.contactModel->getContact(participant); - - if (contactInfo.isBanned) { - qDebug() << "ContactModel::sendMessage: denied, contact is banned"; - return; - } - - pimpl_->sendContactRequest(participant); - } - + pimpl_->sendContactRequest(peerId); if (!isTemporary) { cb(convId); } @@ -977,7 +1116,7 @@ ConversationModel::setFilter(const QString& filter) } void -ConversationModel::setFilter(const profile::Type& filter) +ConversationModel::setFilter(const FilterType& filter) { // Switch between PENDING, RING and SIP contacts. pimpl_->typeFilter = filter; @@ -1055,10 +1194,14 @@ ConversationModel::clearInteractionFromConversation(const QString& convId, std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]); try { auto& conversation = pimpl_->conversations.at(conversationIdx); + if (conversation.mode != conversation::Mode::NON_SWARM) { + // WARNING: clearInteractionFromConversation not implemented for swarm + return; + } storage::clearInteractionFromConversation(pimpl_->db, convId, interactionId); erased_keys = conversation.interactions.erase(interactionId); auto messageId = conversation.lastDisplayedMessageUid.find( - conversation.participants.front()); + pimpl_->peersForConversation(conversation).front()); if (messageId != conversation.lastDisplayedMessageUid.end() && messageId->second == interactionId) { @@ -1072,8 +1215,9 @@ ConversationModel::clearInteractionFromConversation(const QString& convId, } } updateDisplayedUid = true; - participantURI = conversation.participants.front(); - conversation.lastDisplayedMessageUid.at(conversation.participants.front()) + participantURI = pimpl_->peersForConversation(conversation).front(); + conversation.lastDisplayedMessageUid.at( + pimpl_->peersForConversation(conversation).front()) = newDisplayedUid; } @@ -1117,6 +1261,10 @@ ConversationModel::retryInteraction(const QString& convId, const QString& intera std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]); try { auto& conversation = pimpl_->conversations.at(conversationIdx); + if (conversation.mode != conversation::Mode::NON_SWARM) { + // WARNING: retry interaction is not implemented for swarm + return; + } auto& interactions = conversation.interactions; auto it = interactions.find(interactionId); @@ -1159,14 +1307,15 @@ ConversationModel::isLastDisplayed(const QString& convId, const QString participant) { auto conversationIdx = pimpl_->indexOf(convId); - if (conversationIdx == -1) - return false; try { auto& conversation = pimpl_->conversations.at(conversationIdx); - return conversation.lastDisplayedMessageUid.find(participant)->second == interactionId; + if (conversation.lastDisplayedMessageUid.find(participant) + != conversation.lastDisplayedMessageUid.end()) { + return conversation.lastDisplayedMessageUid.find(participant)->second == interactionId; + } } catch (const std::out_of_range& e) { - return false; } + return false; } void @@ -1176,10 +1325,15 @@ ConversationModel::clearAllHistory() for (auto& conversation : pimpl_->conversations) { { + if (conversation.mode != conversation::Mode::NON_SWARM) { + // WARNING: clear all history is not implemented for swarm + continue; + } std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[conversation.uid]); conversation.interactions.clear(); } storage::getHistory(pimpl_->db, conversation); + Q_EMIT dataChanged(pimpl_->indexOf(conversation.uid)); } emit modelChanged(); } @@ -1210,15 +1364,26 @@ ConversationModel::setInteractionRead(const QString& convId, const QString& inte } if (emitUpdated) { pimpl_->invalidateModel(); - auto daemonId = storage::getDaemonIdByInteractionId(pimpl_->db, interactionId); - if (owner.profileInfo.type != profile::Type::SIP) { - ConfigurationManager::instance() - .setMessageDisplayed(owner.id, - pimpl_->conversations[conversationIdx].participants.front(), - daemonId, - 3); - } - storage::setInteractionRead(pimpl_->db, interactionId); + if (pimpl_->conversations[conversationIdx].mode != conversation::Mode::NON_SWARM) { + ConfigurationManager::instance().setMessageDisplayed(owner.id, + "swarm:" + convId, + interactionId, + 3); + } else { + auto daemonId = storage::getDaemonIdByInteractionId(pimpl_->db, interactionId); + if (owner.profileInfo.type != profile::Type::SIP) { + ConfigurationManager::instance() + .setMessageDisplayed(owner.id, + "jami:" + + pimpl_ + ->peersForConversation( + pimpl_->conversations[conversationIdx]) + .front(), + daemonId, + 3); + } + storage::setInteractionRead(pimpl_->db, interactionId); + } emit interactionStatusUpdated(convId, interactionId, itCopy); emit pimpl_->behaviorController.newReadInteraction(owner.id, convId, interactionId); } @@ -1227,21 +1392,26 @@ ConversationModel::setInteractionRead(const QString& convId, const QString& inte void ConversationModel::clearUnreadInteractions(const QString& convId) { - auto conversationIdx = pimpl_->indexOf(convId); - if (conversationIdx == -1) { + auto conversationOpt = getConversationForUid(convId); + if (!conversationOpt.has_value()) { return; } + auto& conversation = conversationOpt->get(); bool emitUpdated = false; QString lastDisplayed; { std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]); - auto& interactions = pimpl_->conversations[conversationIdx].interactions; + auto& interactions = conversation.interactions; std::for_each(interactions.begin(), interactions.end(), [&](decltype(*interactions.begin())& it) { if (!it.second.isRead) { emitUpdated = true; it.second.isRead = true; + if (conversation.mode != conversation::Mode::NON_SWARM) { + lastDisplayed = it.first; + return; + } if (owner.profileInfo.type != profile::Type::SIP) lastDisplayed = storage::getDaemonIdByInteractionId(pimpl_->db, it.first); @@ -1249,42 +1419,114 @@ ConversationModel::clearUnreadInteractions(const QString& convId) } }); } - if (!lastDisplayed.isEmpty()) - ConfigurationManager::instance() - .setMessageDisplayed(owner.id, - pimpl_->conversations[conversationIdx].participants.front(), - lastDisplayed, - 3); + if (!lastDisplayed.isEmpty()) { + auto to = conversation.mode != conversation::Mode::NON_SWARM + ? "swarm:" + convId + : "jami:" + pimpl_->peersForConversation(conversation).front(); + ConfigurationManager::instance().setMessageDisplayed(owner.id, to, lastDisplayed, 3); + } if (emitUpdated) { - pimpl_->conversations[conversationIdx].unreadMessages = 0; + conversation.unreadMessages = 0; pimpl_->invalidateModel(); emit conversationUpdated(convId); - Q_EMIT dataChanged(conversationIdx); + Q_EMIT dataChanged(pimpl_->indexOf(convId)); } } -uint32_t -ConversationModel::loadConversationMessages(const QString& conversationId, - const int size) +int +ConversationModel::loadConversationMessages(const QString& conversationId, const int size) { - return -1; + auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) { + return -1; + } + auto& conversation = conversationOpt->get(); + if (conversation.allMessagesLoaded) { + return -1; + } + auto lastMsgId = conversation.interactions.empty() ? "" + : conversation.interactions.front().first; + return ConfigurationManager::instance().loadConversationMessages(owner.id, + conversationId, + lastMsgId, + size); } void ConversationModel::acceptConversationRequest(const QString& conversationId) -{} +{ + auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) { + return; + } + auto& conversation = conversationOpt->get(); + auto& peers = pimpl_->peersForConversation(conversation); + if (peers.isEmpty()) { + return; + } + switch (conversation.mode) { + case conversation::Mode::NON_SWARM: + pimpl_->sendContactRequest(peers.front()); + return; + case conversation::Mode::ONE_TO_ONE: { + // add contact if not added. Otherwise, accept the conversation request + try { + auto contact = owner.contactModel->getContact(peers.front()); + auto notAdded = contact.profileInfo.type == profile::Type::TEMPORARY + || contact.profileInfo.type == profile::Type::PENDING; + if (notAdded) { + owner.contactModel->addContact(contact); + return; + } + } catch (std::out_of_range& e) { + } + break; + } + default: + break; + } + ConfigurationManager::instance().acceptConversationRequest(owner.id, conversationId); +} void -ConversationModel::declineConversationRequest(const QString& conversationId) -{} +ConversationModel::declineConversationRequest(const QString& conversationId, bool banned) +{ + auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) { + return; + } + auto& conversation = conversationOpt->get(); + // for non-swarm and one-to-one conversation remove contact. + if (conversation.mode == conversation::Mode::ONE_TO_ONE + || conversation.mode == conversation::Mode::NON_SWARM) { + removeConversation(conversationId, banned); + } else { + ConfigurationManager::instance().declineConversationRequest(owner.id, conversationId); + } +} + +const VectorString +ConversationModel::peersForConversation(const QString& conversationId) +{ + const auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) { + return {}; + } + const auto& conversation = conversationOpt->get(); + return pimpl_->peersForConversation(conversation); +} void ConversationModel::addConversationMember(const QString& conversationId, const QString& memberId) -{} +{ + ConfigurationManager::instance().addConversationMember(owner.id, conversationId, memberId); +} void ConversationModel::removeConversationMember(const QString& conversationId, const QString& memberId) -{} +{ + ConfigurationManager::instance().removeConversationMember(owner.id, conversationId, memberId); +} ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, Lrc& lrc, @@ -1295,8 +1537,8 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, , lrc {lrc} , db(db) , callbacksHandler(callbacksHandler) - , typeFilter(profile::Type::INVALID) - , customTypeFilter(profile::Type::INVALID) + , typeFilter(FilterType::INVALID) + , customTypeFilter(FilterType::INVALID) , behaviorController(behaviorController) { filteredConversations.bindSortCallback(this, &ConversationModelPimpl::sort); @@ -1313,6 +1555,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, &ContactModel::contactAdded, this, &ConversationModelPimpl::slotContactAdded); + connect(&*linked.owner.contactModel, + &ContactModel::incomingContactRequest, + this, + &ConversationModelPimpl::slotIncomingContactRequest); connect(&*linked.owner.contactModel, &ContactModel::pendingContactAccepted, this, @@ -1420,6 +1666,14 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, &CallbacksHandler::conversationReady, this, &ConversationModelPimpl::slotConversationReady); + connect(&callbacksHandler, + &CallbacksHandler::conversationRemoved, + this, + &ConversationModelPimpl::slotConversationRemoved); + connect(&callbacksHandler, + &CallbacksHandler::conversationMemberEvent, + this, + &ConversationModelPimpl::slotConversationMemberEvent); } ConversationModelPimpl::~ConversationModelPimpl() @@ -1433,6 +1687,10 @@ ConversationModelPimpl::~ConversationModelPimpl() &ContactModel::contactAdded, this, &ConversationModelPimpl::slotContactAdded); + disconnect(&*linked.owner.contactModel, + &ContactModel::incomingContactRequest, + this, + &ConversationModelPimpl::slotIncomingContactRequest); disconnect(&*linked.owner.contactModel, &ContactModel::pendingContactAccepted, this, @@ -1540,6 +1798,14 @@ ConversationModelPimpl::~ConversationModelPimpl() &CallbacksHandler::conversationReady, this, &ConversationModelPimpl::slotConversationReady); + disconnect(&callbacksHandler, + &CallbacksHandler::conversationRemoved, + this, + &ConversationModelPimpl::slotConversationRemoved); + disconnect(&callbacksHandler, + &CallbacksHandler::conversationMemberEvent, + this, + &ConversationModelPimpl::slotConversationMemberEvent); } void @@ -1550,17 +1816,32 @@ ConversationModelPimpl::initConversations() if (accountDetails.empty()) return; + // Fill swarm conversations + QStringList swarms = ConfigurationManager::instance().getConversations(linked.owner.id); + for (auto& swarmConv : swarms) { + addSwarmConversation(swarmConv); + } + + VectorMapStringString conversationsRequests = ConfigurationManager::instance() + .getConversationRequests(linked.owner.id); + for (auto& request : conversationsRequests) { + addConversationRequest(request); + } + // Fill conversations for (auto const& c : linked.owner.contactModel->getAllContacts().toStdMap()) { auto conv = storage::getConversationsWithPeer(db, c.second.profileInfo.uri); + if (hasOneOneSwarmWith(c.second.profileInfo.uri)) + continue; if (conv.empty()) { - // Can't find a conversation with this contact. Start it. - auto newConversationsId = storage::beginConversationWithPeer(db, - c.second.profileInfo.uri, - c.second.isTrusted); - conv.push_back(std::move(newConversationsId)); + // Can't find a conversation with this contact + // add pending not swarm conversation + if (c.second.profileInfo.type == profile::Type::PENDING) { + addContactRequest(c.second.profileInfo.uri); + continue; + } + conv.push_back(storage::beginConversationWithPeer(db, c.second.profileInfo.uri)); } - addConversationWith(conv[0], c.first); auto convIdx = indexOf(conv[0]); @@ -1583,6 +1864,7 @@ ConversationModelPimpl::initConversations() } } } + invalidateModel(); filteredConversations.reset(conversations).sort(); @@ -1599,11 +1881,57 @@ ConversationModelPimpl::initConversations() } } +const VectorString +ConversationModelPimpl::peersForConversation(const conversation::Info& conversation) const +{ + VectorString result {}; + switch (conversation.mode) { + case conversation::Mode::NON_SWARM: + return conversation.participants; + default: + break; + } + for (const auto& participant : conversation.participants) { + if (participant != linked.owner.profileInfo.uri) + result.push_back(participant); + } + return result; +} + +bool +ConversationModelPimpl::isCoreDialog(const conversation::Info& conversation) const +{ + switch (conversation.mode) { + case conversation::Mode::NON_SWARM: + return true; + case conversation::Mode::ONE_TO_ONE: + try { + // for active one-to-one conversation conversationId should be set + auto& peers = peersForConversation(conversation); + if (peers.size() != 1) { + return false; + } + auto contactInfo = linked.owner.contactModel->getContact(peers.front()); + return !contactInfo.conversationId.isEmpty(); + } catch (...) { + return true; + } + default: + return false; + } +} + bool ConversationModelPimpl::filter(const conversation::Info& entry) { try { - auto contactInfo = linked.owner.contactModel->getContact(entry.participants.front()); + // TODO: filter for group? + // for now group conversation filtered by first peer + auto& peers = peersForConversation(entry); + if (peers.size() < 1) { + return false; + } + auto contactInfo = linked.owner.contactModel->getContact(peers.front()); auto uri = URI(currentFilter); bool stripScheme = (uri.schemeType() < URI::SchemeType::COUNT__); @@ -1617,7 +1945,8 @@ ConversationModelPimpl::filter(const conversation::Info& entry) // Check contact // If contact is banned, only match if filter is a perfect match - if (contactInfo.isBanned) { + // do not check banned contact for conversation with multiple participants + if (contactInfo.isBanned && peers.size() == 1) { if (currentFilter == "") return false; return contactInfo.profileInfo.uri == currentFilter @@ -1650,23 +1979,20 @@ ConversationModelPimpl::filter(const conversation::Info& entry) }; // Check type - if (typeFilter != profile::Type::PENDING) { - // Remove pending contacts and get the temporary item if filter is not empty - switch (contactInfo.profileInfo.type) { - case profile::Type::COUNT__: - case profile::Type::INVALID: - case profile::Type::PENDING: + switch (typeFilter) { + case FilterType::JAMI: + case FilterType::SIP: + if (entry.isRequest) return false; - case profile::Type::TEMPORARY: + if (contactInfo.profileInfo.type == profile::Type::TEMPORARY) return filterUriAndReg(contactInfo, currentFilter); - case profile::Type::SIP: - case profile::Type::RING: - break; - } - } else { - // We only want pending requests matching with the filter - if (contactInfo.profileInfo.type != profile::Type::PENDING) + break; + case FilterType::REQUEST: + if (!entry.isRequest) return false; + break; + default: + break; } // Otherwise perform usual regex search @@ -1708,8 +2034,16 @@ ConversationModelPimpl::sort(const conversation::Info& convA, const conversation return convA.uid.isEmpty(); if (historyA.empty() && historyB.empty()) { - // If no information to compare, sort by Ring ID - return convA.participants.front() > convB.participants.front(); + // If no information to compare, sort by Ring ID. For group conversation sort by first peer + auto& peersForA = peersForConversation(convA); + auto& peersForB = peersForConversation(convB); + if (peersForA.isEmpty()) { + return false; + } + if (peersForB.isEmpty()) { + return true; + } + return peersForA.front() > peersForB.front(); } if (historyA.empty()) return false; @@ -1740,62 +2074,427 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId, const QString& accountId, const QString& conversationId, const VectorMapStringString& messages) -{} +{ + if (accountId != linked.owner.id) { + return; + } + try { + auto& conversation = getConversationForUid(conversationId).get(); + auto size = messages.size(); + for (int i = size - 1; i >= 0; --i) { + auto message = messages[i]; + if (message["type"].isEmpty()) { + continue; + } + if (message["type"] == "initial") { + conversation.allMessagesLoaded = true; + if (message.find("invited") == message.end()) { + continue; + } + } + auto msgId = message["id"]; + auto msg = interaction::Info(message, linked.owner.profileInfo.uri); + if (msg.type == interaction::Type::DATA_TRANSFER) { + auto daemponID = message["tid"]; + storage::updateDataTransferInteractionForDaemonId(db, daemponID, msg); + } else if (msg.type == interaction::Type::CALL) { + msg.body = storage::getCallInteractionString(msg.authorUri, msg.duration); + } + insertSwarmInteraction(msgId, msg, conversation, true); + } + + for (int j = conversation.interactions.size() - 1; j >= 0; j--) { + if (conversation.interactions.atIndex(j).second.type != interaction::Type::MERGE) { + conversation.lastMessageUid = conversation.interactions.atIndex(j).first; + break; + } + } + invalidateModel(); + emit linked.modelChanged(); + emit linked.newMessagesAvailable(linked.owner.id, conversationId); + auto conversationIdx = indexOf(conversationId); + Q_EMIT linked.dataChanged(conversationIdx); + } catch (const std::exception& e) { + qDebug() << "messages loaded for not existing conversation"; + } +} void ConversationModelPimpl::slotMessageReceived(const QString& accountId, const QString& conversationId, const MapStringString& message) -{} - -void -ConversationModelPimpl::slotConversationRequestReceived(const QString& accountId, - const QString& conversationId, - const MapStringString& metadatas) -{} - -void -ConversationModelPimpl::slotConversationReady(const QString& accountId, - const QString& conversationId) -{} - -void -ConversationModelPimpl::slotContactAdded(const QString& contactUri) { - auto type = linked.owner.profileInfo.type; - profile::Info profileInfo {contactUri, {}, {}, type}; - try { - auto contact = linked.owner.contactModel->getContact(contactUri); - type = contact.profileInfo.type; - profileInfo.alias = contact.profileInfo.alias; - } catch (...) { - } - storage::createOrUpdateProfile(linked.owner.id, profileInfo, true); - auto conv = storage::getConversationsWithPeer(db, profileInfo.uri); - if (conv.empty()) { - // pass conversation UID through only element - conv.push_back(storage::beginConversationWithPeer(db, profileInfo.uri)); - } - // Add the conversation if not already here - if (indexOf(conv[0]) == -1) { - addConversationWith(conv[0], profileInfo.uri); - emit linked.newConversation(conv[0]); + if (accountId != linked.owner.id) { + return; } + try { + auto& conversation = getConversationForUid(conversationId).get(); + if (message["type"].isEmpty()) { + return; + } + if (message["type"] == "initial") { + conversation.allMessagesLoaded = true; + if (message.find("invited") == message.end()) { + return; + } + } + auto msgId = message["id"]; + auto msg = interaction::Info(message, linked.owner.profileInfo.uri); + api::datatransfer::Info info; + DataTransferId transferIdInt; + if (msg.type == interaction::Type::DATA_TRANSFER) { + // save data transfer interaction to db and assosiate daemon id with interaction id, + // conversation id and db id + QString transferId = message["tid"]; + transferIdInt = std::stoull(message["tid"].toStdString()); + lrc.getDataTransferModel().transferInfo(accountId, conversationId, transferIdInt, info); + // create db entry for valid data transfer + if (info.status != datatransfer::Status::INVALID) { + msg.body = info.path; + msg.status = (info.status == datatransfer::Status::on_connection + && !interaction::isOutgoing(msg)) + ? interaction::Status::TRANSFER_AWAITING_HOST + : interaction::Status::TRANSFER_CREATED; + auto interactionId = storage::addDataTransferToConversation(db, + conversationId, + info); + transfIdToDbIntId[transferId] = interactionId; + lrc.getDataTransferModel().registerTransferId(transferIdInt, msgId); + } + } else if (msg.type == interaction::Type::CALL) { + msg.body = storage::getCallInteractionString(msg.authorUri, msg.duration); + } else if (msg.type == interaction::Type::TEXT + && msg.authorUri != linked.owner.profileInfo.uri) { + conversation.unreadMessages++; + } + insertSwarmInteraction(msgId, msg, conversation, false); + if (msg.type == interaction::Type::MERGE) { + invalidateModel(); + return; + } + conversation.lastMessageUid = msgId; + invalidateModel(); + if (!interaction::isOutgoing(msg)) { + emit behaviorController.newUnreadInteraction(linked.owner.id, + conversationId, + msgId, + msg); + } + emit linked.newInteraction(conversationId, msgId, msg); + emit linked.modelChanged(); + if (msg.status == interaction::Status::TRANSFER_AWAITING_HOST) { + awaitingHost(transferIdInt, info); + } + Q_EMIT linked.dataChanged(indexOf(conversationId)); + } catch (const std::exception& e) { + qDebug() << "messages received for not existing conversation"; + } +} + +void +ConversationModelPimpl::insertSwarmInteraction(const QString& interactionId, + const interaction::Info& interaction, + conversation::Info& conversation, + bool insertAtBegin) +{ + std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]); + int index = conversation.interactions.indexOfMessage(interaction.parentId); + if (index >= 0) { + conversation.interactions.insert(index + 1, qMakePair(interactionId, interaction)); + } else { + conversation.interactions.insert(std::make_pair(interactionId, interaction), insertAtBegin); + conversation.parentsId[interactionId] = interaction.parentId; + } + if (!conversation.parentsId.values().contains(interactionId)) { + return; + } + auto msgIds = conversation.parentsId.keys(interactionId); + conversation.interactions.moveMessages(msgIds, interactionId); + for (auto& msg : msgIds) { + conversation.parentsId.remove(msg); + } +} + +void +ConversationModelPimpl::slotConversationRequestReceived(const QString& accountId, + const QString& conversationId, + const MapStringString& metadatas) +{ + if (accountId != linked.owner.id) { + return; + } + addConversationRequest(metadatas); +} + +void +ConversationModelPimpl::slotConversationReady(const QString& accountId, + const QString& conversationId) +{ + // we receive this signal after we accept or after we send a conversation request + if (accountId != linked.owner.id) { + return; + } + // remove non swarm conversation that was added from slotContactAdded + const VectorMapStringString& members = ConfigurationManager::instance() + .getConversationMembers(accountId, conversationId); + const auto& accountURI = linked.owner.profileInfo.uri; + VectorString participants; + // it means conversation with one participant. In this case we could have non swarm conversation + bool shouldRemoveNonSwarmConversation = members.size() == 2; + for (const auto& member : members) { + // this check should be removed once all usage of participants replaced by + // peersForConversation. We should have ourself in participants list + if (member["uri"] == accountURI) { + continue; + } + participants.append(member["uri"]); + if (shouldRemoveNonSwarmConversation) { + try { + auto& conversation = getConversationForPeerUri(member["uri"]).get(); + // remove non swarm conversation + if (conversation.mode == conversation::Mode::NON_SWARM) { + conversations.erase(conversations.begin() + indexOf(conversation.uid)); + storage::removeContact(db, member["uri"]); + invalidateModel(); + emit linked.conversationRemoved(conversation.uid); + } + } catch (...) { + } + } + } + + Q_EMIT linked.beginInsertRows(conversations.size()); + bool conversationExists = indexOf(conversationId) >= 0; + + if (!conversationExists) { + addSwarmConversation(conversationId); + } + auto& conversation = getConversationForUid(conversationId).get(); + if (conversationExists) { + // if swarm request already exists, update participnts + auto& conversation = getConversationForUid(conversationId).get(); + conversation.participants = participants; + const MapStringString& details = ConfigurationManager::instance() + .conversationInfos(accountId, conversationId); + conversation.mode = conversation::to_mode(details["mode"].toInt()); + conversation.isRequest = false; + auto id = ConfigurationManager::instance().loadConversationMessages(linked.owner.id, + conversationId, + "", + 0); + } + invalidateModel(); + // we use conversationReady callback only for conversation with one participant. We could use + // participants.front() + auto& peers = peersForConversation(conversation); + if (peers.size() == 1) { + emit linked.conversationReady(conversationId, peers.front()); + } + emit linked.newConversation(conversationId); + emit linked.modelChanged(); + Q_EMIT linked.endInsertRows(); +} + +void +ConversationModelPimpl::slotConversationRemoved(const QString& accountId, + const QString& conversationId) +{ + auto conversationIndex = indexOf(conversationId); + if (accountId != linked.owner.id || conversationIndex < 0) { + return; + } + try { + auto& conversation = getConversationForUid(conversationId).get(); + // if swarm conversation removed but we still have contact we create non swarm conversation. + // we should create non swarm conversation only for removed one-to-one conversation. + if (conversation.mode != conversation::Mode::ONE_TO_ONE) { + // remove swarm conversation + conversations.erase(conversations.begin() + conversationIndex); + emit linked.conversationRemoved(conversationId); + return; + } + auto& peers = peersForConversation(conversation); + if (peers.isEmpty()) { + // remove swarm conversation + conversations.erase(conversations.begin() + conversationIndex); + emit linked.conversationRemoved(conversationId); + return; + } + auto contactId = peers.first(); + // remove swarm conversation + conversations.erase(conversations.begin() + conversationIndex); + auto contact = linked.owner.contactModel->getContact(contactId); + if (contact.profileInfo.type != api::profile::Type::RING) { + emit linked.conversationRemoved(conversationId); + return; + } + Q_EMIT linked.beginInsertRows(conversations.size()); + auto conv = storage::getConversationsWithPeer(db, contactId); + if (conv.empty()) { + conv.push_back(storage::beginConversationWithPeer(db, contactId)); + } + addConversationWith(conv[0], contactId); + invalidateModel(); + emit linked.conversationRemoved(conversationId); + emit linked.newConversation(conv[0]); + emit linked.modelChanged(); + Q_EMIT linked.endInsertRows(); + } catch (...) { + } +} + +void +ConversationModelPimpl::slotConversationMemberEvent(const QString& accountId, + const QString& conversationId, + const QString& memberUri, + int event) +{ + if (accountId != linked.owner.id || indexOf(conversationId) < 0) { + return; + } + switch (event) { + case 0: // add + // clear search result + for (unsigned int i = 0; i < searchResults.size(); ++i) { + if (searchResults.at(i).uid == memberUri) + searchResults.erase(searchResults.begin() + i); + } + break; + case 1: // joins + break; + case 2: // leave + break; + case 3: // banned + break; + } + // update participants + auto& conversation = getConversationForUid(conversationId).get(); + const VectorMapStringString& members + = ConfigurationManager::instance().getConversationMembers(linked.owner.id, conversationId); + VectorString uris; + auto accountURI = linked.owner.profileInfo.uri; - // delete temporary conversation if it exists and it has the uri of the added contact as uid - if (indexOf(profileInfo.uri) >= 0) { - auto position = indexOf(profileInfo.uri); - Q_EMIT linked.beginRemoveRows(position); - conversations.erase(conversations.begin() + position); - Q_EMIT linked.endRemoveRows(); + for (auto& member : members) { + if (member["uri"] != accountURI) { + uris.append(member["uri"]); + } } - for (unsigned int i = 0; i < searchResults.size(); ++i) { - if (searchResults.at(i).uid == profileInfo.uri) - searchResults.erase(searchResults.begin() + i); + conversation.participants = uris; + invalidateModel(); + emit linked.modelChanged(); + Q_EMIT linked.dataChanged(indexOf(conversationId)); +} + +void +ConversationModelPimpl::slotIncomingContactRequest(const QString& contactUri) +{ + // It is contact request. But for compatibility with swarm conversations we add it like non + // swarm conversation request. + addContactRequest(contactUri); +} + +void +ConversationModelPimpl::slotContactAdded(const QString& contactUri) +{ + auto conv = storage::getConversationsWithPeer(db, contactUri); + bool addConversation = false; + try { + auto& conversation = getConversationForPeerUri(contactUri).get(); + // swarm conversation we update when receive conversation ready signal. + if (conversation.mode != conversation::Mode::NON_SWARM) { + return; + } + if (conv.empty()) { + conv.push_back(storage::beginConversationWithPeer(db, contactUri)); + } + // remove temporary conversation that was added when receiving an incoming request + if (indexOf(contactUri) >= 0) { + conversations.erase(conversations.begin() + indexOf(contactUri)); + } + // add a conversation if not exists + addConversation = indexOf(conv[0]) == -1; + } catch (std::out_of_range&) { + /* + if the conversation does not exists we save it to DB and add non-swarm + conversation to the conversion list. After receiving a conversation request or + conversation ready signal swarm conversation should be updated and removed from DB. + */ + addConversation = true; + if (conv.empty()) { + conv.push_back(storage::beginConversationWithPeer(db, contactUri)); + } } + if (addConversation) { + Q_EMIT linked.beginInsertRows(conversations.size()); + addConversationWith(conv[0], contactUri); + invalidateModel(); + emit linked.conversationReady(conv[0], contactUri); + emit linked.newConversation(conv[0]); + emit linked.modelChanged(); + Q_EMIT linked.endInsertRows(); + } +} + +void +ConversationModelPimpl::addContactRequest(const QString& contactUri) +{ + try { + auto& conv = getConversationForPeerUri(contactUri).get(); + // request from contact already exists, return + return; + } catch (std::out_of_range&) { + // no conversation exists. Add contact request + conversation::Info conversation; + conversation.uid = contactUri; + conversation.accountId = linked.owner.id; + conversation.participants = {contactUri}; + conversation.mode = conversation::Mode::NON_SWARM; + conversation.isRequest = true; + Q_EMIT linked.beginInsertRows(conversations.size()); + conversations.emplace_back(std::move(conversation)); + invalidateModel(); + emit linked.newConversation(contactUri); + Q_EMIT linked.endInsertRows(); + emit linked.modelChanged(); + } +} + +void +ConversationModelPimpl::addConversationRequest(const MapStringString& convRequest) +{ + auto convId = convRequest["id"]; + auto convIdx = indexOf(convId); + if (convIdx != -1) + return; + auto peer = convRequest["from"]; + auto mode = conversation::to_mode(convRequest["mode"].toInt()); + try { + // check if we have contact request for peer + auto& conv = getConversationForPeerUri(peer).get(); + if (conv.mode == conversation::Mode::NON_SWARM) { + // update conversation and remoe conversation from db + conv.mode = mode; + conv.uid = convId; + storage::removeContact(db, peer); + invalidateModel(); + emit linked.modelChanged(); + return; + } + } catch (std::out_of_range&) { + } + conversation::Info conversation; + conversation.uid = convId; + conversation.accountId = linked.owner.id; + conversation.participants = {peer}; + conversation.mode = mode; + conversation.isRequest = true; + Q_EMIT linked.beginInsertRows(conversations.size()); + conversations.emplace_back(std::move(conversation)); + invalidateModel(); + emit linked.newConversation(peer); + Q_EMIT linked.endInsertRows(); emit linked.modelChanged(); - emit linked.conversationReady(profileInfo.uri); } void @@ -1809,9 +2508,7 @@ ConversationModelPimpl::slotPendingContactAccepted(const QString& uri) profile::Info profileInfo {uri, {}, {}, type}; storage::createOrUpdateProfile(linked.owner.id, profileInfo, true); auto convs = storage::getConversationsWithPeer(db, uri); - if (convs.empty()) { - convs.push_back(storage::beginConversationWithPeer(db, uri)); - } else { + if (!convs.empty()) { try { auto contact = linked.owner.contactModel->getContact(uri); auto interaction = interaction::Info {uri, @@ -1825,7 +2522,7 @@ ConversationModelPimpl::slotPendingContactAccepted(const QString& uri) interaction.body = storage::getContactInteractionString(uri, interaction::Status::SUCCESS); auto convIdx = indexOf(convs[0]); - { + if (convIdx >= 0) { std::lock_guard<std::mutex> lk(interactionsLocks[conversations[convIdx].uid]); conversations[convIdx].interactions.emplace(msgId, interaction); } @@ -1853,8 +2550,8 @@ ConversationModelPimpl::slotContactRemoved(const QString& uri) Q_EMIT linked.endRemoveRows(); invalidateModel(); - emit linked.modelChanged(); emit linked.conversationRemoved(conversationUid); + emit linked.modelChanged(); } void @@ -1888,6 +2585,51 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri) emit linked.searchResultUpdated(); } +void +ConversationModelPimpl::addSwarmConversation(const QString& convId) +{ + VectorString participants; + const VectorMapStringString& members = ConfigurationManager::instance() + .getConversationMembers(linked.owner.id, convId); + auto accountURI = linked.owner.profileInfo.uri; + for (auto& member : members) { + // this check should be removed once all usage of participants replaced by + // peersForConversation. We should have ourself in participants list + if (member["uri"] != accountURI) { + participants.append(member["uri"]); + } + } + if (participants.isEmpty()) { + return; + } + conversation::Info conversation; + conversation.uid = convId; + conversation.accountId = linked.owner.id; + conversation.participants = participants; + const MapStringString& details = ConfigurationManager::instance() + .conversationInfos(linked.owner.id, convId); + conversation.mode = conversation::to_mode(details["mode"].toInt()); + // If conversation has only one peer it is possible that non swarm conversation was created. + // remove non swarm conversation + auto& peers = peersForConversation(conversation); + if (peers.size() == 1) { + try { + auto& participantId = peers.front(); + auto& conv = getConversationForPeerUri(participantId).get(); + if (conv.mode == conversation::Mode::NON_SWARM) { + conversations.erase(conversations.begin() + indexOf(conv.uid)); + storage::removeContact(db, participantId); + } + } catch (...) { + } + } + conversations.emplace_back(std::move(conversation)); + auto id = ConfigurationManager::instance().loadConversationMessages(linked.owner.id, + convId, + "", + 5); +} + void ConversationModelPimpl::addConversationWith(const QString& convId, const QString& contactUri) { @@ -1895,6 +2637,7 @@ ConversationModelPimpl::addConversationWith(const QString& convId, const QString conversation.uid = convId; conversation.accountId = linked.owner.id; conversation.participants = {contactUri}; + conversation.mode = conversation::Mode::NON_SWARM; try { conversation.confId = linked.owner.callModel->getConferenceFromURI(contactUri).id; } catch (...) { @@ -1922,9 +2665,9 @@ ConversationModelPimpl::addConversationWith(const QString& convId, const QString try { auto msgId = std::stoull(id.toStdString()); status = ConfigurationManager::instance().getMessageStatus(msgId); - updateSlots.emplace_back([this, msgId, contactUri, status]() -> void { + updateSlots.emplace_back([this, convId, contactUri, id, status]() -> void { auto accId = linked.owner.id; - slotUpdateInteractionStatus(accId, msgId, contactUri, status); + slotUpdateInteractionStatus(accId, convId, contactUri, id, status); }); } catch (const std::exception& e) { qDebug() << "message id was invalid"; @@ -1983,16 +2726,24 @@ std::reference_wrapper<conversation::Info> ConversationModelPimpl::getConversationForPeerUri(const QString& uri, const bool searchResultIncluded) { - return getConversation([uri](const conversation::Info& conv) - -> bool { return uri == conv.participants.front(); }, - searchResultIncluded); + return getConversation( + [this, uri](const conversation::Info& conv) -> bool { + if (!isCoreDialog(conv)) { + return false; + } + return uri == peersForConversation(conv).front(); + }, + searchResultIncluded); } int ConversationModelPimpl::indexOfContact(const QString& uri) const { for (unsigned int i = 0; i < conversations.size(); ++i) { - if (conversations.at(i).participants.front() == uri) + if (!isCoreDialog(conversations.at(i))) { + continue; + } + if (peersForConversation(conversations.at(i)).front() == uri) return i; } return -1; @@ -2001,6 +2752,18 @@ ConversationModelPimpl::indexOfContact(const QString& uri) const void ConversationModelPimpl::slotIncomingCall(const QString& fromId, const QString& callId) { + auto convIds = storage::getConversationsWithPeer(db, fromId); + if (convIds.empty()) { + // in case if we receive call after removing contact add conversation request; + try { + auto contact = linked.owner.contactModel->getContact(fromId); + if (contact.profileInfo.type == profile::Type::PENDING && !contact.isBanned + && fromId != linked.owner.profileInfo.uri) { + addContactRequest(fromId); + } + } catch (const std::out_of_range&) { + } + } auto conversationIdx = indexOfContact(fromId); if (conversationIdx == -1) { @@ -2035,7 +2798,11 @@ ConversationModelPimpl::slotCallStatusChanged(const QString& callId, int code) // or a call was placed via dbus. // We have to update the model for (auto& conversation : conversations) { - if (conversation.participants.front() == call.peerUri) { + auto& peers = peersForConversation(conversation); + if (peers.size() != 1) { + continue; + } + if (peers.front() == call.peerUri) { conversation.callId = callId; // Update interaction status invalidateModel(); @@ -2062,7 +2829,7 @@ ConversationModelPimpl::slotCallStarted(const QString& callId) { try { auto call = linked.owner.callModel->getCall(callId); - addOrUpdateCallMessage(callId, (!call.isOutgoing ? call.peerUri : "")); + addOrUpdateCallMessage(callId, call.peerUri.remove("ring:"), !call.isOutgoing); } catch (std::out_of_range& e) { qDebug() << "ConversationModelPimpl::slotCallStarted can't start inexistant call"; } @@ -2080,7 +2847,7 @@ ConversationModelPimpl::slotCallEnded(const QString& callId) duration = std::chrono::duration_cast<std::chrono::seconds>(duration_ns).count(); } // add or update call interaction with duration - addOrUpdateCallMessage(callId, (!call.isOutgoing ? call.peerUri : ""), duration); + addOrUpdateCallMessage(callId, call.peerUri.remove("ring:"), !call.isOutgoing, duration); /* Reset the callId stored in the conversation. Do not call selectConversation() since it is already done in slotCallStatusChanged. */ for (auto& conversation : conversations) @@ -2099,8 +2866,22 @@ ConversationModelPimpl::slotCallEnded(const QString& callId) void ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId, const QString& from, + bool incoming, const std::time_t& duration) { + // do not save call interaction for swarm conversation + auto convIds = storage::getConversationsWithPeer(db, from); + if (convIds.empty()) { + // in case if we receive call after removing contact add conversation request; + try { + auto contact = linked.owner.contactModel->getContact(from); + if (contact.profileInfo.type == profile::Type::PENDING && !contact.isBanned) { + addContactRequest(from); + } + } catch (const std::out_of_range&) { + } + return; + } // Get conversation auto conv_it = std::find_if(conversations.begin(), conversations.end(), @@ -2111,7 +2892,7 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId, return; } auto uid = conv_it->uid; - auto uriString = storage::prepareUri(from, linked.owner.profileInfo.type); + auto uriString = incoming ? storage::prepareUri(from, linked.owner.profileInfo.type) : ""; auto msg = interaction::Info {uriString, {}, std::time(nullptr), @@ -2145,8 +2926,8 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId, void ConversationModelPimpl::slotNewAccountMessage(const QString& accountId, + const QString& peerId, const QString& msgId, - const QString& from, const MapStringString& payloads) { if (accountId != linked.owner.id) @@ -2154,7 +2935,7 @@ ConversationModelPimpl::slotNewAccountMessage(const QString& accountId, for (const auto& payload : payloads.keys()) { if (payload.contains("text/plain")) { - addIncomingMessage(from, payloads.value(payload), 0, msgId); + addIncomingMessage(peerId, payloads.value(payload), 0, msgId); } } } @@ -2184,16 +2965,25 @@ ConversationModelPimpl::slotIncomingCallMessage(const QString& callId, } QString -ConversationModelPimpl::addIncomingMessage(const QString& from, +ConversationModelPimpl::addIncomingMessage(const QString& peerId, const QString& body, const uint64_t& timestamp, const QString& daemonId) { - auto convIds = storage::getConversationsWithPeer(db, from); + auto convIds = storage::getConversationsWithPeer(db, peerId); if (convIds.empty()) { - convIds.push_back(storage::beginConversationWithPeer(db, from, false)); + // in case if we receive a message after removing contact, add a conversation request + try { + auto contact = linked.owner.contactModel->getContact(peerId); + if (contact.profileInfo.type == profile::Type::PENDING && !contact.isBanned + && peerId != linked.owner.profileInfo.uri) { + addContactRequest(peerId); + } + } catch (const std::out_of_range&) { + } + return ""; } - auto msg = interaction::Info {from, + auto msg = interaction::Info {peerId, body, timestamp == 0 ? std::time(nullptr) : static_cast<time_t>(timestamp), @@ -2208,7 +2998,7 @@ ConversationModelPimpl::addIncomingMessage(const QString& from, auto conversationIdx = indexOf(convIds[0]); // Add the conversation if not already here if (conversationIdx == -1) { - addConversationWith(convIds[0], from); + addConversationWith(convIds[0], peerId); emit linked.newConversation(convIds[0]); } else { { @@ -2246,82 +3036,131 @@ ConversationModelPimpl::slotCallAddedToConference(const QString& callId, const Q void ConversationModelPimpl::slotUpdateInteractionStatus(const QString& accountId, - const uint64_t& daemon_id, - const QString& peer_uri, + const QString& conversationId, + const QString& peerId, + const QString& messageId, int status) { if (accountId != linked.owner.id) { return; } - auto newStatus = interaction::Status::INVALID; - switch (static_cast<DRing::Account::MessageStates>(status)) { - case DRing::Account::MessageStates::SENDING: - newStatus = interaction::Status::SENDING; - break; - case DRing::Account::MessageStates::CANCELLED: - newStatus = interaction::Status::TRANSFER_CANCELED; - break; - case DRing::Account::MessageStates::SENT: - newStatus = interaction::Status::SUCCESS; - break; - case DRing::Account::MessageStates::FAILURE: - newStatus = interaction::Status::FAILURE; - break; - case DRing::Account::MessageStates::DISPLAYED: - newStatus = interaction::Status::DISPLAYED; - break; - case DRing::Account::MessageStates::UNKNOWN: - default: - newStatus = interaction::Status::UNKNOWN; - break; - } - // Update database - auto interactionId = storage::getInteractionIdByDaemonId(db, toQString(daemon_id)); - if (interactionId.isEmpty()) { - return; - } - auto msgId = interactionId; - storage::updateInteractionStatus(db, msgId, newStatus); - // Update conversations - auto convIds = storage::getConversationsWithPeer(db, peer_uri); - if (!convIds.empty()) { + // it may be not swarm conversation check in db + if (conversationId.isEmpty() || conversationId == linked.owner.profileInfo.uri) { + auto convIds = storage::getConversationsWithPeer(db, peerId); + if (convIds.empty()) { + return; + } auto conversationIdx = indexOf(convIds[0]); + auto& conversation = conversations[conversationIdx]; + auto newStatus = interaction::Status::INVALID; + switch (static_cast<DRing::Account::MessageStates>(status)) { + case DRing::Account::MessageStates::SENDING: + newStatus = interaction::Status::SENDING; + break; + case DRing::Account::MessageStates::CANCELLED: + newStatus = interaction::Status::TRANSFER_CANCELED; + break; + case DRing::Account::MessageStates::SENT: + newStatus = interaction::Status::SUCCESS; + break; + case DRing::Account::MessageStates::FAILURE: + newStatus = interaction::Status::FAILURE; + break; + case DRing::Account::MessageStates::DISPLAYED: + newStatus = interaction::Status::DISPLAYED; + break; + case DRing::Account::MessageStates::UNKNOWN: + default: + newStatus = interaction::Status::UNKNOWN; + break; + } + auto idString = messageId; + // for not swarm conversation messageId in hexdesimal string format. Convert to normal string + // TODO messageId should be received from daemon in string format + if (static_cast<DRing::Account::MessageStates>(status) + == DRing::Account::MessageStates::DISPLAYED) { + std::istringstream ss(messageId.toStdString()); + ss >> std::hex; + uint64_t id; + if (!(ss >> id)) + return; + idString = QString::number(id); + } + // Update database + auto interactionId = storage::getInteractionIdByDaemonId(db, idString); + if (interactionId.isEmpty()) { + return; + } + auto msgId = interactionId; + storage::updateInteractionStatus(db, msgId, newStatus); + // Update conversations interaction::Info itCopy; bool emitUpdated = false; bool updateDisplayedUid = false; QString oldDisplayedUid = 0; - if (conversationIdx != -1) { - std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]); - auto& interactions = conversations[conversationIdx].interactions; + { + std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]); + auto& interactions = conversation.interactions; auto it = interactions.find(msgId); - auto messageId = conversations[conversationIdx].lastDisplayedMessageUid.find(peer_uri); + auto messageId = conversation.lastDisplayedMessageUid.find(peerId); if (it != interactions.end()) { it->second.status = newStatus; - if (messageId != conversations[conversationIdx].lastDisplayedMessageUid.end()) { + bool interactionDisplayed = newStatus == interaction::Status::DISPLAYED + && isOutgoing(it->second); + if (messageId != conversation.lastDisplayedMessageUid.end()) { auto lastDisplayedIt = interactions.find(messageId->second); - bool interactionDisplayed = newStatus == interaction::Status::DISPLAYED - && isOutgoing(it->second); bool interactionIsLast = lastDisplayedIt == interactions.end() || lastDisplayedIt->second.timestamp < it->second.timestamp; updateDisplayedUid = interactionDisplayed && interactionIsLast; if (updateDisplayedUid) { oldDisplayedUid = messageId->second; - conversations[conversationIdx].lastDisplayedMessageUid.at(peer_uri) - = it->first; + conversation.lastDisplayedMessageUid.at(peerId) = it->first; } + } else { + oldDisplayedUid = ""; + conversation.lastDisplayedMessageUid[peerId] = it->first; + updateDisplayedUid = true; } emitUpdated = true; itCopy = it->second; } } if (updateDisplayedUid) { - emit linked.displayedInteractionChanged(convIds[0], peer_uri, oldDisplayedUid, msgId); + emit linked.displayedInteractionChanged(conversation.uid, + peerId, + oldDisplayedUid, + msgId); } if (emitUpdated) { invalidateModel(); - emit linked.interactionStatusUpdated(convIds[0], msgId, itCopy); + emit linked.interactionStatusUpdated(conversation.uid, msgId, itCopy); + } + return; + } + try { + auto& conversation = getConversationForUid(conversationId).get(); + if (conversation.mode != conversation::Mode::NON_SWARM) { + if (static_cast<DRing::Account::MessageStates>(status) + == DRing::Account::MessageStates::DISPLAYED) { + if (conversation.lastDisplayedMessageUid.find(peerId) + == conversation.lastDisplayedMessageUid.end()) { + conversation.lastDisplayedMessageUid[peerId] = messageId; + emit linked.displayedInteractionChanged(conversationId, peerId, "", messageId); + } else if (conversation.interactions.indexOfMessage( + conversation.lastDisplayedMessageUid.find(peerId)->second) + < conversation.interactions.indexOfMessage(messageId)) { + auto lastDisplayedMsg = conversation.lastDisplayedMessageUid.find(peerId)->second; + conversation.lastDisplayedMessageUid[peerId] = messageId; + emit linked.displayedInteractionChanged(conversationId, + peerId, + lastDisplayedMsg, + messageId); + } + } } + } catch (const std::out_of_range& e) { + qDebug() << "could not update message status for not existing conversation"; } } @@ -2338,16 +3177,13 @@ ConversationModelPimpl::slotConferenceRemoved(const QString& confId) void ConversationModelPimpl::slotComposingStatusChanged(const QString& accountId, + const QString& convId, const QString& contactUri, bool isComposing) { if (accountId != linked.owner.id) return; - // Check conversation's validity - auto convIds = storage::getConversationsWithPeer(db, contactUri); - if (convIds.empty()) - return; - emit linked.composingStatusChanged(convIds.front(), contactUri, isComposing); + emit linked.composingStatusChanged(convId, contactUri, isComposing); } int @@ -2357,14 +3193,16 @@ ConversationModelPimpl::getNumberOfUnreadMessagesFor(const QString& uid) } void -ConversationModel::setIsComposing(const QString& uid, bool isComposing) +ConversationModel::setIsComposing(const QString& convUid, bool isComposing) { - auto conversationIdx = pimpl_->indexOf(uid); - if (conversationIdx == -1 || !owner.enabled) - return; - - const auto peerUri = pimpl_->conversations[conversationIdx].participants.front(); - ConfigurationManager::instance().setIsComposing(owner.id, peerUri, isComposing); + try { + auto& conversation = pimpl_->getConversationForUid(convUid).get(); + QString to = conversation.mode != conversation::Mode::NON_SWARM + ? "swarm:" + convUid + : "jami:" + pimpl_->peersForConversation(conversation).front(); + ConfigurationManager::instance().setIsComposing(owner.id, to, isComposing); + } catch (...) { + } } void @@ -2372,9 +3210,21 @@ ConversationModel::sendFile(const QString& convUid, const QString& path, const Q { try { auto& conversation = pimpl_->getConversationForUid(convUid, true).get(); - - const auto peerUri = conversation.participants.front(); - bool isTemporary = peerUri == convUid; + auto peers = pimpl_->peersForConversation(conversation); + if (peers.size() < 1) { + qDebug() << "send file error: could not send file in conversation with no participants"; + return; + } + /* isTemporary, and conversationReady callback used only for non-swarm conversation, + because for swarm, conversation already configured at this point. + Conversations for new contact from search result are NON_SWARM but after receiving + conversationReady callback could be updated to ONE_TO_ONE. We still use conversationReady + callback for one_to_one conversation to check if contact is blocked*/ + if (peers.size() > 1) { + pimpl_->lrc.getDataTransferModel().sendFile(owner.id, "", convUid, path, filename); + } + const auto peerId = peers.front(); + bool isTemporary = peerId == convUid; /* It is necessary to make a copy of convUid since it may very well point to a field in the temporary conversation, which is going to be destroyed by @@ -2382,37 +3232,38 @@ ConversationModel::sendFile(const QString& convUid, const QString& path, const Q so may result in use after free/crash. */ auto convUidCopy = convUid; - pimpl_->sendContactRequest(peerUri); + pimpl_->sendContactRequest(peerId); - auto cb = std::function<void(QString)>( - [this, isTemporary, peerUri, path, filename](QString convId) { - int contactIndex; - if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) { - qDebug() << "Can't send file: Other participant is not a contact (removed " - "while sending file ?)"; + auto cb = ([this, isTemporary, peerId, path, filename](QString conversationId) { + try { + auto conversationOpt = getConversationForUid(conversationId); + if (!conversationOpt.has_value()) { + qDebug() << "Can't send file"; return; } - - // Retrieve final peer uri after creation of the conversation - const auto& newPeerUri = isTemporary ? pimpl_->conversations.at(contactIndex) - .participants.front() - : peerUri; - - auto contactInfo = owner.contactModel->getContact(newPeerUri); + auto contactInfo = owner.contactModel->getContact(peerId); if (contactInfo.isBanned) { qDebug() << "ContactModel::sendFile: denied, contact is banned"; return; } - - pimpl_->lrc.getDataTransferModel().sendFile(owner.id, newPeerUri, path, filename); - }); + auto& conversation = conversationOpt->get(); + // for non swarm conversation id should be empty, so peerId will be takin in consideration + auto id = conversation.mode != conversation::Mode::NON_SWARM ? conversationId : ""; + pimpl_->lrc.getDataTransferModel().sendFile(owner.id, peerId, id, path, filename); + } catch (...) { + } + }); if (isTemporary) { QMetaObject::Connection* const connection = new QMetaObject::Connection; *connection = connect(this, &ConversationModel::conversationReady, - [cb, connection](QString convId) { - cb(convId); + [cb, connection, convUidCopy](QString conversationId, + QString participantId) { + if (participantId != convUidCopy) { + return; + } + cb(conversationId); QObject::disconnect(*connection); if (connection) { delete connection; @@ -2466,8 +3317,13 @@ ConversationModel::cancelTransfer(const QString& convUid, const QString& interac } } if (emitUpdated) { + // for swarm conversations we need to provide conversation id to accept file, for not swarm + // conversations we need peer uri + lrc::api::datatransfer::Info info = {}; + getTransferInfo(convUid, interactionId, info); + auto identifier = info.conversationId.isEmpty() ? info.peerUri : convUid; // Forward cancel action to daemon (will invoke slotTransferStatusCanceled) - pimpl_->lrc.getDataTransferModel().cancel(owner.id, convUid, interactionId.toInt()); + pimpl_->lrc.getDataTransferModel().cancel(owner.id, identifier, interactionId); pimpl_->invalidateModel(); emit interactionStatusUpdated(convUid, interactionId, itCopy); emit pimpl_->behaviorController.newReadInteraction(owner.id, convUid, interactionId); @@ -2475,10 +3331,12 @@ ConversationModel::cancelTransfer(const QString& convUid, const QString& interac } void -ConversationModel::getTransferInfo(const QString& conversationId, uint64_t interactionId, datatransfer::Info& info) +ConversationModel::getTransferInfo(const QString& conversationId, + const QString& interactionId, + datatransfer::Info& info) { try { - auto dringId = pimpl_->lrc.getDataTransferModel().getDringIdFromInteractionId(interactionId.toInt()); + auto dringId = pimpl_->lrc.getDataTransferModel().getDringIdFromInteractionId(interactionId); pimpl_->lrc.getDataTransferModel().transferInfo(owner.id, conversationId, dringId, info); } catch (...) { info.status = datatransfer::Status::INVALID; @@ -2492,26 +3350,27 @@ ConversationModel::getNumberOfUnreadMessagesFor(const QString& convUid) } bool -ConversationModelPimpl::usefulDataFromDataTransfer(long long dringId, +ConversationModelPimpl::usefulDataFromDataTransfer(DataTransferId dringId, const datatransfer::Info& info, - int& interactionId, - QString& convId) + QString& interactionId, + QString& conversationId) { if (info.accountId != linked.owner.id) return false; try { interactionId = lrc.getDataTransferModel().getInteractionIdFromDringId(dringId); + conversationId = info.conversationId.isEmpty() + ? storage::conversationIdFromInteractionId(db, interactionId) + : info.conversationId; } catch (const std::out_of_range& e) { qWarning() << "Couldn't get interaction from daemon Id: " << dringId; return false; } - - convId = storage::conversationIdFromInteractionId(db, QString::number(interactionId)); return true; } void -ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusCreated(DataTransferId dringId, datatransfer::Info info) { // check if transfer is for the current account if (info.accountId != linked.owner.id) @@ -2524,7 +3383,17 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe // create a new conversation if needed auto convIds = storage::getConversationsWithPeer(db, info.peerUri); if (convIds.empty()) { - convIds.push_back(storage::beginConversationWithPeer(db, info.peerUri, false)); + // in case if we receive file after removing contact add conversation request. If we have + // swarm request this function will do nothing. + try { + auto contact = linked.owner.contactModel->getContact(info.peerUri); + if (contact.profileInfo.type == profile::Type::PENDING && !contact.isBanned + && info.peerUri != linked.owner.profileInfo.uri) { + addContactRequest(info.peerUri); + } + } catch (const std::out_of_range&) { + } + return; } // add interaction to the db @@ -2532,7 +3401,7 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe auto interactionId = storage::addDataTransferToConversation(db, convId, info); // map dringId and interactionId for latter retrivial from client (that only known the interactionId) - lrc.getDataTransferModel().registerTransferId(dringId, interactionId.toInt()); + lrc.getDataTransferModel().registerTransferId(dringId, interactionId); auto interaction = interaction::Info {info.isOutgoing ? "" : info.peerUri, info.isOutgoing ? info.path : info.displayName, @@ -2567,56 +3436,68 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe } void -ConversationModelPimpl::slotTransferStatusAwaitingPeer(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusAwaitingPeer(DataTransferId dringId, + datatransfer::Info info) { - updateTransferStatus(dringId, info, interaction::Status::TRANSFER_AWAITING_PEER); + bool intUpdated; + updateTransferStatus(dringId, info, interaction::Status::TRANSFER_AWAITING_PEER, intUpdated); } void -ConversationModelPimpl::slotTransferStatusAwaitingHost(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusAwaitingHost(DataTransferId dringId, + datatransfer::Info info) { - int interactionId; - QString convId; - if (not usefulDataFromDataTransfer(dringId, info, interactionId, convId)) + awaitingHost(dringId, info); +} + +bool +ConversationModelPimpl::hasOneOneSwarmWith(const QString& participant) +{ + try { + auto& conversation = getConversationForPeerUri(participant).get(); + return conversation.mode == conversation::Mode::ONE_TO_ONE; + } catch (std::out_of_range&) { + return false; + } +} + +void +ConversationModelPimpl::awaitingHost(DataTransferId dringId, datatransfer::Info info) +{ + QString interactionId; + QString conversationId; + if (not usefulDataFromDataTransfer(dringId, info, interactionId, conversationId)) return; - auto newStatus = interaction::Status::TRANSFER_AWAITING_HOST; - storage::updateInteractionStatus(db, QString::number(interactionId), newStatus); + bool intUpdated; - auto conversationIdx = indexOf(convId); - if (conversationIdx != -1) { - bool emitUpdated = false; - interaction::Info itCopy; - { - std::lock_guard<std::mutex> lk(interactionsLocks[convId]); - auto& interactions = conversations[conversationIdx].interactions; - auto it = interactions.find(QString::number(interactionId)); - if (it != interactions.end()) { - emitUpdated = true; - it->second.status = newStatus; - itCopy = it->second; - } + if (!updateTransferStatus(dringId, + info, + interaction::Status::TRANSFER_AWAITING_HOST, + intUpdated)) { + return; + } + if (!intUpdated) { + return; + } + auto conversationIdx = indexOf(conversationId); + auto& peers = peersForConversation(conversations[conversationIdx]); + // Only accept if contact is added or it is a group conversation + if (lrc.getDataTransferModel().acceptFromUnstrusted && peers.size() == 1) { + try { + auto contactUri = peers.front(); + auto contactInfo = linked.owner.contactModel->getContact(contactUri); + if (contactInfo.profileInfo.type == profile::Type::PENDING) + return; + } catch (...) { + return; } - if (emitUpdated) { - invalidateModel(); - emit linked.interactionStatusUpdated(convId, QString::number(interactionId), itCopy); - // Only accept if contact is added - if (!lrc.getDataTransferModel().acceptFromUnstrusted) { - try { - auto contactUri = conversations[conversationIdx].participants.front(); - auto contactInfo = linked.owner.contactModel->getContact(contactUri); - if (contactInfo.profileInfo.type == profile::Type::PENDING) - return; - } catch (...) { - return; - } - } - // If it's an accepted file type and less than 20 MB, accept transfer. - if (lrc.getDataTransferModel().automaticAcceptTransfer) { - if (lrc.getDataTransferModel().acceptBehindMb == 0 - || info.totalSize < lrc.getDataTransferModel().acceptBehindMb * 1024 * 1024) - acceptTransfer(convId, QString::number(interactionId), info.displayName); - } + } + // If it's an accepted file type and less than 20 MB, accept transfer. + if (lrc.getDataTransferModel().automaticAcceptTransfer) { + if (lrc.getDataTransferModel().acceptBehindMb == 0 + || info.totalSize < lrc.getDataTransferModel().acceptBehindMb * 1024 * 1024) { + acceptTransfer(conversationId, interactionId, info.displayName); } } } @@ -2638,14 +3519,27 @@ ConversationModelPimpl::acceptTransfer(const QString& convUid, QDir dir = QFileInfo(destinationDir + path).absoluteDir(); if (!dir.exists()) dir.mkpath("."); -] auto acceptedFilePath = lrc.getDataTransferModel().accept(linked.owner.id, - convUid, - interactionId.toInt(), + auto& conversation = getConversationForUid(convUid).get(); + // for swarm conversation we need converstion id to accept file and for not swarm participant uri + auto identifier = conversation.mode != conversation::Mode::NON_SWARM + ? convUid + : peersForConversation(conversation).front(); + auto acceptedFilePath = lrc.getDataTransferModel().accept(linked.owner.id, + identifier, + interactionId, destinationDir + path, 0); - storage::updateInteractionBody(db, interactionId, acceptedFilePath); - storage::updateInteractionStatus(db, interactionId, interaction::Status::TRANSFER_ACCEPTED); - + auto dringId = lrc.getDataTransferModel().getDringIdFromInteractionId(interactionId); + if (transfIdToDbIntId.find(QString::number(dringId)) != transfIdToDbIntId.end()) { + auto dbInteractionId = transfIdToDbIntId[QString::number(dringId)]; + storage::updateInteractionBody(db, dbInteractionId, acceptedFilePath); + storage::updateInteractionStatus(db, + dbInteractionId, + interaction::Status::TRANSFER_ACCEPTED); + } else { + storage::updateInteractionBody(db, interactionId, acceptedFilePath); + storage::updateInteractionStatus(db, interactionId, interaction::Status::TRANSFER_ACCEPTED); + } // prepare interaction Info and emit signal for the client auto conversationIdx = indexOf(convUid); interaction::Info itCopy; @@ -2662,7 +3556,9 @@ ConversationModelPimpl::acceptTransfer(const QString& convUid, } } if (emitUpdated) { - sendContactRequest(conversations[conversationIdx].participants.front()); + if (isCoreDialog(conversations[conversationIdx])) { + sendContactRequest(peersForConversation(conversations[conversationIdx]).front()); + } invalidateModel(); emit linked.interactionStatusUpdated(convUid, interactionId, itCopy); emit behaviorController.newReadInteraction(linked.owner.id, convUid, interactionId); @@ -2677,60 +3573,45 @@ ConversationModelPimpl::invalidateModel() } void -ConversationModelPimpl::slotTransferStatusOngoing(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusOngoing(DataTransferId dringId, datatransfer::Info info) { - int interactionId; - QString convId; - if (not usefulDataFromDataTransfer(dringId, info, interactionId, convId)) + QString interactionId; + QString conversationId; + if (not usefulDataFromDataTransfer(dringId, info, interactionId, conversationId)) return; + bool intUpdated; - auto newStatus = interaction::Status::TRANSFER_ONGOING; - storage::updateInteractionStatus(db, QString::number(interactionId), newStatus); - - auto conversationIdx = indexOf(convId); - if (conversationIdx != -1) { - bool emitUpdated = false; - interaction::Info itCopy; - { - std::lock_guard<std::mutex> lk(interactionsLocks[convId]); - auto& interactions = conversations[conversationIdx].interactions; - auto it = interactions.find(QString::number(interactionId)); - if (it != interactions.end()) { - emitUpdated = true; - it->second.status = newStatus; - itCopy = it->second; - } - } - if (emitUpdated) { - auto* timer = new QTimer(); - connect(timer, &QTimer::timeout, [=] { - updateTransfer(timer, convId, conversationIdx, interactionId); - }); - timer->start(1000); - invalidateModel(); - emit linked.interactionStatusUpdated(convId, QString::number(interactionId), itCopy); - } + if (!updateTransferStatus(dringId, info, interaction::Status::TRANSFER_ONGOING, intUpdated)) { + return; } + if (!intUpdated) { + return; + } + auto conversationIdx = indexOf(conversationId); + auto* timer = new QTimer(); + connect(timer, &QTimer::timeout, [=] { + updateTransfer(timer, conversationId, conversationIdx, interactionId); + }); + timer->start(1000); } void -ConversationModelPimpl::slotTransferStatusFinished(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusFinished(DataTransferId dringId, datatransfer::Info info) { - int interactionId; - QString convId; - if (not usefulDataFromDataTransfer(dringId, info, interactionId, convId)) + QString interactionId; + QString conversationId; + if (not usefulDataFromDataTransfer(dringId, info, interactionId, conversationId)) return; - // prepare interaction Info and emit signal for the client - auto conversationIdx = indexOf(convId); + auto conversationIdx = indexOf(conversationId); if (conversationIdx != -1) { bool emitUpdated = false; auto newStatus = interaction::Status::TRANSFER_FINISHED; interaction::Info itCopy; { - std::lock_guard<std::mutex> lk(interactionsLocks[convId]); + std::lock_guard<std::mutex> lk(interactionsLocks[conversationId]); auto& interactions = conversations[conversationIdx].interactions; - auto it = interactions.find(QString::number(interactionId)); + auto it = interactions.find(interactionId); if (it != interactions.end()) { // We need to check if current status is ONGOING as CANCELED must not be // transformed into FINISHED @@ -2743,76 +3624,99 @@ ConversationModelPimpl::slotTransferStatusFinished(long long dringId, datatransf } if (emitUpdated) { invalidateModel(); - storage::updateInteractionStatus(db, QString::number(interactionId), newStatus); - emit linked.interactionStatusUpdated(convId, QString::number(interactionId), itCopy); + if (conversations[conversationIdx].mode != conversation::Mode::NON_SWARM) { + if (transfIdToDbIntId.find(QString::number(dringId)) != transfIdToDbIntId.end()) { + auto dbIntId = transfIdToDbIntId[QString::number(dringId)]; + storage::updateInteractionStatus(db, dbIntId, newStatus); + } + } else { + storage::updateInteractionStatus(db, interactionId, newStatus); + } + emit linked.interactionStatusUpdated(conversationId, interactionId, itCopy); + transfIdToDbIntId.remove(QString::number(dringId)); } } } void -ConversationModelPimpl::slotTransferStatusCanceled(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusCanceled(DataTransferId dringId, datatransfer::Info info) { - updateTransferStatus(dringId, info, interaction::Status::TRANSFER_CANCELED); + bool intUpdated; + updateTransferStatus(dringId, info, interaction::Status::TRANSFER_CANCELED, intUpdated); } void -ConversationModelPimpl::slotTransferStatusError(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusError(DataTransferId dringId, datatransfer::Info info) { - updateTransferStatus(dringId, info, interaction::Status::TRANSFER_ERROR); + bool intUpdated; + updateTransferStatus(dringId, info, interaction::Status::TRANSFER_ERROR, intUpdated); } void -ConversationModelPimpl::slotTransferStatusUnjoinable(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusUnjoinable(DataTransferId dringId, datatransfer::Info info) { - updateTransferStatus(dringId, info, interaction::Status::TRANSFER_UNJOINABLE_PEER); + bool intUpdated; + updateTransferStatus(dringId, info, interaction::Status::TRANSFER_UNJOINABLE_PEER, intUpdated); } void -ConversationModelPimpl::slotTransferStatusTimeoutExpired(long long dringId, datatransfer::Info info) +ConversationModelPimpl::slotTransferStatusTimeoutExpired(DataTransferId dringId, + datatransfer::Info info) { - updateTransferStatus(dringId, info, interaction::Status::TRANSFER_TIMEOUT_EXPIRED); + bool intUpdated; + updateTransferStatus(dringId, info, interaction::Status::TRANSFER_TIMEOUT_EXPIRED, intUpdated); } -void -ConversationModelPimpl::updateTransferStatus(long long dringId, +bool +ConversationModelPimpl::updateTransferStatus(DataTransferId dringId, datatransfer::Info info, - interaction::Status newStatus) + interaction::Status newStatus, + bool& updated) { - int interactionId; - QString convId; - if (not usefulDataFromDataTransfer(dringId, info, interactionId, convId)) - return; - - // update information in the db - storage::updateInteractionStatus(db, QString::number(interactionId), newStatus); + QString interactionId; + QString conversationId; + if (not usefulDataFromDataTransfer(dringId, info, interactionId, conversationId)) { + return false; + } - // prepare interaction Info and emit signal for the client - auto conversationIdx = indexOf(convId); - if (conversationIdx != -1) { - bool emitUpdated = false; - interaction::Info itCopy; - { - std::lock_guard<std::mutex> lk(interactionsLocks[convId]); - auto& interactions = conversations[conversationIdx].interactions; - auto it = interactions.find(QString::number(interactionId)); - if (it != interactions.end()) { - emitUpdated = true; - it->second.status = newStatus; - itCopy = it->second; - } + auto conversationIdx = indexOf(conversationId); + if (conversationIdx < 0) { + return false; + } + if (conversations[conversationIdx].mode != conversation::Mode::NON_SWARM) { + if (transfIdToDbIntId.find(QString::number(dringId)) == transfIdToDbIntId.end()) { + return false; } - if (emitUpdated) { - invalidateModel(); - emit linked.interactionStatusUpdated(convId, QString::number(interactionId), itCopy); + auto dbIntId = transfIdToDbIntId[QString::number(dringId)]; + storage::updateInteractionStatus(db, dbIntId, newStatus); + } else { + storage::updateInteractionStatus(db, interactionId, newStatus); + } + bool emitUpdated = false; + interaction::Info itCopy; + { + std::lock_guard<std::mutex> lk(interactionsLocks[conversationId]); + auto& interactions = conversations[conversationIdx].interactions; + auto it = interactions.find(interactionId); + if (it != interactions.end()) { + emitUpdated = true; + it->second.status = newStatus; + itCopy = it->second; } } + if (emitUpdated) { + invalidateModel(); + emit linked.interactionStatusUpdated(conversationId, interactionId, itCopy); + } + updated = emitUpdated; + return true; } void ConversationModelPimpl::updateTransfer(QTimer* timer, const QString& conversation, int conversationIdx, - int interactionId) + const QString& interactionId) { try { bool emitUpdated = false; @@ -2820,17 +3724,15 @@ ConversationModelPimpl::updateTransfer(QTimer* timer, { std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]); const auto& interactions = conversations[conversationIdx].interactions; - const auto& it = interactions.find(QString::number(interactionId)); - if (it != std::cend(interactions) + const auto& it = interactions.find(interactionId); + if (it != interactions.cend() and it->second.status == interaction::Status::TRANSFER_ONGOING) { emitUpdated = true; itCopy = it->second; } } if (emitUpdated) { - emit linked.interactionStatusUpdated(conversation, - QString::number(interactionId), - itCopy); + emit linked.interactionStatusUpdated(conversation, interactionId, itCopy); return; } } catch (...) { diff --git a/src/datatransfermodel.cpp b/src/datatransfermodel.cpp index a5cb4ac515fbde97b2c742636f06771ebc6a544e..c691cb56a1444400e7ac7e69f241a2064b4b6467 100644 --- a/src/datatransfermodel.cpp +++ b/src/datatransfermodel.cpp @@ -82,8 +82,8 @@ public: QString getUniqueFilePath(const QString& filename); DataTransferModel& upLink; - std::map<long long, int> dring2lrcIdMap; - std::map<int, long long> lrc2dringIdMap; // stricly the reverse map of dring2lrcIdMap + std::map<DataTransferId, QString> dring2lrcIdMap; + std::map<QString, DataTransferId> lrc2dringIdMap; // stricly the reverse map of dring2lrcIdMap }; DataTransferModel::Impl::Impl(DataTransferModel& up_link) @@ -113,7 +113,7 @@ DataTransferModel::Impl::getUniqueFilePath(const QString& filename) } void -DataTransferModel::registerTransferId(long long dringId, int interactionId) +DataTransferModel::registerTransferId(DataTransferId dringId, const QString& interactionId) { pimpl_->dring2lrcIdMap.emplace(dringId, interactionId); pimpl_->lrc2dringIdMap.erase(interactionId); // Because a file transfer can be retried @@ -128,11 +128,18 @@ DataTransferModel::DataTransferModel() DataTransferModel::~DataTransferModel() = default; void -DataTransferModel::transferInfo(const QString& accountId, const QString& conversationId, long long ringId, datatransfer::Info& lrc_info) +DataTransferModel::transferInfo(const QString& accountId, + const QString& conversationId, + DataTransferId ringId, + datatransfer::Info& lrc_info) { DataTransferInfo infoFromDaemon; - if (ConfigurationManager::instance().dataTransferInfo(accountId, conversationId, ringId, infoFromDaemon) == 0) { - // lrc_info.uid = ? + if (ConfigurationManager::instance().dataTransferInfo(accountId, + conversationId, + ringId, + infoFromDaemon) + == 0) { + lrc_info.uid = QString::number(ringId); lrc_info.status = convertDataTransferEvent( DRing::DataTransferEventCode(infoFromDaemon.lastEvent)); lrc_info.isOutgoing = !(infoFromDaemon.flags @@ -143,6 +150,7 @@ DataTransferModel::transferInfo(const QString& accountId, const QString& convers lrc_info.displayName = infoFromDaemon.displayName; lrc_info.accountId = infoFromDaemon.accountId; lrc_info.peerUri = infoFromDaemon.peer; + lrc_info.conversationId = infoFromDaemon.conversationId; // lrc_info.timestamp = ? return; } @@ -153,6 +161,7 @@ DataTransferModel::transferInfo(const QString& accountId, const QString& convers void DataTransferModel::sendFile(const QString& account_id, const QString& peer_uri, + const QString& conversationId, const QString& file_path, const QString& display_name) { @@ -165,6 +174,7 @@ DataTransferModel::sendFile(const QString& account_id, info.accountId = account_id; info.peer = peer_uri; info.path = file_path; + info.conversationId = conversationId; info.displayName = display_name; info.bytesProgress = 0; if (ConfigurationManager::instance().sendFile(info, id) != 0) { @@ -174,42 +184,62 @@ DataTransferModel::sendFile(const QString& account_id, } void -DataTransferModel::bytesProgress(const QString& accountId, const QString& conversationId, int interactionId, int64_t& total, int64_t& progress) +DataTransferModel::bytesProgress(const QString& accountId, + const QString& conversationId, + const QString& interactionId, + int64_t& total, + int64_t& progress) { ConfigurationManager::instance() #ifdef ENABLE_LIBWRAP - .dataTransferBytesProgress(accountId, conversationId, pimpl_->lrc2dringIdMap.at(interactionId), total, progress); + .dataTransferBytesProgress(accountId, + conversationId, + pimpl_->lrc2dringIdMap.at(interactionId), + total, + progress); #else - .dataTransferBytesProgress(accountId, conversationId, pimpl_->lrc2dringIdMap.at(interactionId), + .dataTransferBytesProgress(accountId, + conversationId, + pimpl_->lrc2dringIdMap.at(interactionId), reinterpret_cast<qlonglong&>(total), reinterpret_cast<qlonglong&>(progress)); #endif } QString -DataTransferModel::accept(const QString& accountId, const QString& conversationId, int interactionId, const QString& file_path, std::size_t offset) +DataTransferModel::accept(const QString& accountId, + const QString& conversationId, + const QString& interactionId, + const QString& file_path, + std::size_t offset) { auto unique_file_path = pimpl_->getUniqueFilePath(file_path); auto dring_id = pimpl_->lrc2dringIdMap.at(interactionId); - ConfigurationManager::instance().acceptFileTransfer(accountId, conversationId, dring_id, unique_file_path, offset); + ConfigurationManager::instance().acceptFileTransfer(accountId, + conversationId, + dring_id, + unique_file_path, + offset); return unique_file_path; } void -DataTransferModel::cancel(const QString& accountId, const QString& conversationId, int interactionId) +DataTransferModel::cancel(const QString& accountId, + const QString& conversationId, + const QString& interactionId) { auto dring_id = pimpl_->lrc2dringIdMap.at(interactionId); ConfigurationManager::instance().cancelDataTransfer(accountId, conversationId, dring_id); } -int -DataTransferModel::getInteractionIdFromDringId(long long dringId) +QString +DataTransferModel::getInteractionIdFromDringId(DataTransferId dringId) { return pimpl_->dring2lrcIdMap.at(dringId); } -long long -DataTransferModel::getDringIdFromInteractionId(int interactionId) +DataTransferId +DataTransferModel::getDringIdFromInteractionId(const QString& interactionId) { return pimpl_->lrc2dringIdMap.at(interactionId); } diff --git a/src/dbus/metatypes.h b/src/dbus/metatypes.h index 8c8b39263d6f9fb11ff7ad3097b62a8f4a28fb37..ab2e86ebcf998c209e63e01a6d094eb426784a16 100644 --- a/src/dbus/metatypes.h +++ b/src/dbus/metatypes.h @@ -55,7 +55,9 @@ operator<<(QDBusArgument& argument, const DataTransferInfo& info) argument << info.flags; argument << info.totalSize; argument << info.bytesProgress; + argument << info.author; argument << info.peer; + argument << info.conversationId; argument << info.displayName; argument << info.path; argument << info.mimetype; @@ -73,7 +75,9 @@ operator>>(const QDBusArgument& argument, DataTransferInfo& info) argument >> info.flags; argument >> info.totalSize; argument >> info.bytesProgress; + argument >> info.author; argument >> info.peer; + argument >> info.conversationId; argument >> info.displayName; argument >> info.path; argument >> info.mimetype; diff --git a/src/messageslist.cpp b/src/messageslist.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6fdc88ba05d2510ced936e99f6fa568fa8ffbf1e --- /dev/null +++ b/src/messageslist.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <QCoreApplication> + +#include "messageslist.h" +#include "api/interaction.h" + +namespace lrc { + +using namespace api; + +QPair<iterator, bool> +MessagesList::emplace(QString msgId, interaction::Info message, bool beginning) +{ + iterator it; + for (it = interactions_.begin(); it != interactions_.end(); ++it) { + if (it->first == msgId) { + return qMakePair(it, false); + } + } + auto iter = beginning ? interactions_.begin() : interactions_.end(); + auto iterator = interactions_.insert(iter, qMakePair(msgId, message)); + return qMakePair(iterator, true); +} +iterator +MessagesList::find(QString msgId) +{ + iterator it; + for (it = interactions_.begin(); it != interactions_.end(); ++it) { + if (it->first == msgId) { + return it; + } + } + return interactions_.end(); +} + +constIterator +MessagesList::find(QString msgId) const +{ + constIterator it; + for (it = interactions_.cbegin(); it != interactions_.cend(); ++it) { + if (it->first == msgId) { + return it; + } + } + return interactions_.cend(); +} +QPair<iterator, bool> +MessagesList::insert(std::pair<QString, interaction::Info> message, bool beginning) +{ + return emplace(message.first, message.second, beginning); +} + +int +MessagesList::erase(QString msgId) +{ + iterator it; + for (it = interactions_.begin(); it != interactions_.end(); ++it) { + if (it->first == msgId) { + interactions_.erase(it); + return 1; + } + } +} +interaction::Info& +MessagesList::operator[](QString messageId) +{ + for (auto it = interactions_.cbegin(); it != interactions_.cend(); ++it) { + if (it->first == messageId) { + return const_cast<interaction::Info&>(it->second); + } + } + // element not find, add it to the end + interaction::Info newMessage = {}; + interactions_.insert(interactions_.end(), qMakePair(messageId, newMessage)); + if (interactions_.last().first == messageId) { + return const_cast<interaction::Info&>(interactions_.last().second); + } +} + +iterator +MessagesList::end() +{ + return interactions_.end(); +} + +constIterator +MessagesList::end() const +{ + return interactions_.end(); +} + +constIterator +MessagesList::cend() const +{ + return interactions_.cend(); +} + +iterator +MessagesList::begin() +{ + return interactions_.begin(); +} + +constIterator +MessagesList::begin() const +{ + return interactions_.begin(); +} + +reverseIterator +MessagesList::rbegin() +{ + return interactions_.rbegin(); +} + +int +MessagesList::size() const +{ + return interactions_.size(); +} + +void +MessagesList::clear() +{ + interactions_.clear(); +} + +bool +MessagesList::empty() const +{ + return interactions_.empty(); +} + +interaction::Info +MessagesList::at(QString msgId) const +{ + for (auto it = interactions_.cbegin(); it != interactions_.cend(); ++it) { + if (it->first == msgId) { + return it->second; + } + } + return {}; +} + +QPair<QString, interaction::Info> +MessagesList::front() const +{ + return interactions_.front(); +} + +QPair<QString, interaction::Info> +MessagesList::last() const +{ + return interactions_.last(); +} + +QPair<QString, interaction::Info> +MessagesList::atIndex(int index) +{ + return interactions_.at(index); +} + +QPair<iterator, bool> +MessagesList::insert(int it, QPair<QString, interaction::Info> message) +{ + iterator itr; + for (itr = interactions_.begin(); itr != interactions_.end(); ++itr) { + if (itr->first == message.first) { + return qMakePair(itr, false); + } + } + if (it >= size()) { + auto iterator = interactions_.insert(interactions_.end(), message); + return qMakePair(iterator, true); + } + interactions_.insert(it, message); + return qMakePair(interactions_.end(), true); +} + +int +MessagesList::indexOfMessage(QString msgId, bool reverse) const +{ + auto getIndex = [reverse, &msgId](const auto& start, const auto& end) -> int { + auto it = std::find_if(start, end, [&msgId] (const auto& it) { return it.first == msgId; }); + if (it == end) { + return -1; + } + return reverse ? std::distance(it, end) - 1 : std::distance(start, it); + }; + return reverse ? + getIndex(interactions_.rbegin(), interactions_.rend()) : + getIndex(interactions_.begin(), interactions_.end()); +} + +void +MessagesList::moveMessages(QList<QString> msgIds, QString parentId) +{ + for (auto msgId : msgIds) { + moveMessage(msgId, parentId); + } +} + +void +MessagesList::moveMessage(QString msgId, QString parentId) +{ + int currentIndex = indexOfMessage(msgId); + + // if we have a next element check if it is a child interaction + QString childMessageIdToMove; + if (currentIndex < (interactions_.size() - 1)) { + const auto& next = interactions_.at(currentIndex + 1); + if (next.second.parentId == msgId) { + childMessageIdToMove = next.first; + } + } + + // move a message + int newIndex = indexOfMessage(parentId) + 1; + if (newIndex >= interactions_.size()) { + newIndex = interactions_.size() - 1; + } + interactions_.move(currentIndex, newIndex); + + // move a child message + if (!childMessageIdToMove.isEmpty()) { + moveMessage(childMessageIdToMove, msgId); + } +} +} // namespace lrc diff --git a/src/messageslist.h b/src/messageslist.h new file mode 100644 index 0000000000000000000000000000000000000000..fa09b57ce5b30607d740faf1900d014fe15a1a0e --- /dev/null +++ b/src/messageslist.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +namespace lrc { +namespace api { + +namespace interaction { +struct Info; +} + +typedef QList<QPair<QString, interaction::Info>>::ConstIterator constIterator; +typedef QList<QPair<QString, interaction::Info>>::Iterator iterator; +typedef QList<QPair<QString, interaction::Info>>::reverse_iterator reverseIterator; +class MessagesList +{ +public: + // map functions + QPair<iterator, bool> emplace(QString msgId, interaction::Info message, bool beginning = false); + iterator find(QString msgId); + constIterator find(QString msgId) const; + QPair<iterator, bool> insert(std::pair<QString, interaction::Info> message, + bool beginning = false); + int erase(QString msgId); + interaction::Info& operator[](QString); + iterator end(); + constIterator end() const; + constIterator cend() const; + iterator begin(); + constIterator begin() const; + reverseIterator rbegin(); + int size() const; + void clear(); + bool empty() const; + interaction::Info at(QString intId) const; + QPair<QString, interaction::Info> front() const; + QPair<QString, interaction::Info> last() const; + QPair<QString, interaction::Info> atIndex(int index); + // jami functions + QPair<iterator, bool> insert(int it, QPair<QString, interaction::Info> message); + int indexOfMessage(QString msgId, bool reverse = true) const; + void moveMessages(QList<QString> msgIds, QString parentId); + +private: + QList<QPair<QString, interaction::Info>> interactions_; + void moveMessage(QString msgId, QString parentId); +}; +} // namespace api +} // namespace lrc diff --git a/src/qtwrapper/configurationmanager_wrap.h b/src/qtwrapper/configurationmanager_wrap.h index 8c1bb9d8e9c20e9757adf369fa76d55f74d16a8d..7a833ab9c01f29a6c81775962149a093a8096961 100644 --- a/src/qtwrapper/configurationmanager_wrap.h +++ b/src/qtwrapper/configurationmanager_wrap.h @@ -118,18 +118,25 @@ public: }); }), exportable_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>( - [this](const std::string& accountID, uint64_t id, const std::string& to, int status) { - Q_EMIT this->accountMessageStatusChanged(QString(accountID.c_str()), - id, - QString(to.c_str()), - status); + [this](const std::string& account_id, + const std::string& conversation_id, + const std::string& peer, + const std::string message_id, + int state) { + Q_EMIT this->accountMessageStatusChanged(QString(account_id.c_str()), + QString(conversation_id.c_str()), + QString(peer.c_str()), + QString(message_id.c_str()), + state); }), exportable_callback<ConfigurationSignal::IncomingTrustRequest>( [this](const std::string& accountId, + const std::string& conversationId, const std::string& certId, const std::vector<uint8_t>& payload, time_t timestamp) { Q_EMIT this->incomingTrustRequest(QString(accountId.c_str()), + QString(conversationId.c_str()), QString(certId.c_str()), QByteArray(reinterpret_cast<const char*>( payload.data()), @@ -180,12 +187,12 @@ public: }), exportable_callback<ConfigurationSignal::IncomingAccountMessage>( [this](const std::string& account_id, - const std::string& msgId, const std::string& from, + const std::string& msgId, const std::map<std::string, std::string>& payloads) { Q_EMIT this->incomingAccountMessage(QString(account_id.c_str()), - QString(msgId.c_str()), QString(from.c_str()), + QString(msgId.c_str()), convertMap(payloads)); }), exportable_callback<ConfigurationSignal::MediaParametersChanged>( @@ -232,6 +239,7 @@ public: const std::string& from, int status) { Q_EMIT this->composingStatusChanged(QString(account_id.c_str()), + QString(convId.c_str()), QString(from.c_str()), status > 0 ? true : false); }), @@ -249,8 +257,14 @@ public: dataXferHandlers = { exportable_callback<DataTransferSignal::DataTransferEvent>( - [this]( const std::string& accountId, const std::string& conversationId, const uint64_t& transfer_id, const uint32_t& code) { - Q_EMIT this->dataTransferEvent(QString(accountId.c_str()),QString(conversationId.c_str()),transfer_id, code); + [this](const std::string& accountId, + const std::string& conversationId, + const uint64_t& transfer_id, + const uint32_t& code) { + Q_EMIT this->dataTransferEvent(QString(accountId.c_str()), + QString(conversationId.c_str()), + transfer_id, + code); }), }; conversationsHandlers @@ -280,19 +294,26 @@ public: QString(conversationId.c_str()), convertMap(metadata)); }), - exportable_callback<ConversationSignal::ConversationReady>( - [this](const std::string& accountId, const std::string& conversationId) { - Q_EMIT conversationReady(QString(accountId.c_str()), - QString(conversationId.c_str())); - }), - exportable_callback<ConversationSignal::ConversationMemberEvent>( - [this](const std::string& accountId, const std::string& conversationId, const std::string& memberId, int event) { - Q_EMIT conversationMemberEvent(QString(accountId.c_str()), - QString(conversationId.c_str()), - QString(memberId.c_str()), - event); - }) - }; + exportable_callback<ConversationSignal::ConversationReady>( + [this](const std::string& accountId, const std::string& conversationId) { + Q_EMIT conversationReady(QString(accountId.c_str()), + QString(conversationId.c_str())); + }), + exportable_callback<ConversationSignal::ConversationRemoved>( + [this](const std::string& accountId, const std::string& conversationId) { + Q_EMIT conversationRemoved(QString(accountId.c_str()), + QString(conversationId.c_str())); + }), + exportable_callback<ConversationSignal::ConversationMemberEvent>( + [this](const std::string& accountId, + const std::string& conversationId, + const std::string& memberId, + int event) { + Q_EMIT conversationMemberEvent(QString(accountId.c_str()), + QString(conversationId.c_str()), + QString(memberId.c_str()), + event); + })}; } ~ConfigurationManagerInterface() {} @@ -782,41 +803,69 @@ public Q_SLOTS: // METHODS dring_info.totalSize = lrc_info.totalSize; dring_info.bytesProgress = lrc_info.bytesProgress; dring_info.peer = lrc_info.peer.toStdString(); + dring_info.conversationId = lrc_info.conversationId.toStdString(); dring_info.displayName = lrc_info.displayName.toStdString(); dring_info.path = lrc_info.path.toStdString(); dring_info.mimetype = lrc_info.mimetype.toStdString(); return uint32_t(DRing::sendFile(dring_info, id)); } - uint32_t dataTransferInfo(QString accountId, QString conversationId, const DRing::DataTransferId& transfer_id, DataTransferInfo& lrc_info) + uint32_t dataTransferInfo(QString accountId, + QString conversationId, + const DRing::DataTransferId& transfer_id, + DataTransferInfo& lrc_info) { DRing::DataTransferInfo dring_info; - auto error = uint32_t(DRing::dataTransferInfo(accountId.toStdString(), conversationId.toStdString(), transfer_id, dring_info)); + auto error = uint32_t(DRing::dataTransferInfo(accountId.toStdString(), + conversationId.toStdString(), + transfer_id, + dring_info)); lrc_info.accountId = QString::fromStdString(dring_info.accountId); lrc_info.lastEvent = quint32(dring_info.lastEvent); lrc_info.flags = dring_info.flags; lrc_info.totalSize = dring_info.totalSize; lrc_info.bytesProgress = dring_info.bytesProgress; lrc_info.peer = QString::fromStdString(dring_info.peer); + lrc_info.conversationId = QString::fromStdString(dring_info.conversationId); lrc_info.displayName = QString::fromStdString(dring_info.displayName); lrc_info.path = QString::fromStdString(dring_info.path); lrc_info.mimetype = QString::fromStdString(dring_info.mimetype); return error; } - uint64_t dataTransferBytesProgress(QString accountId, QString conversationId, const DRing::DataTransferId& transfer_id, int64_t& total, int64_t& progress) + uint64_t dataTransferBytesProgress(QString accountId, + QString conversationId, + const DRing::DataTransferId& transfer_id, + int64_t& total, + int64_t& progress) { - return uint32_t(DRing::dataTransferBytesProgress(accountId.toStdString(), conversationId.toStdString(), transfer_id, total, progress)); + return uint32_t(DRing::dataTransferBytesProgress(accountId.toStdString(), + conversationId.toStdString(), + transfer_id, + total, + progress)); } - uint32_t acceptFileTransfer(QString accountId, QString conversationId, const DRing::DataTransferId& transfer_id, const QString& file_path, int64_t offset) + uint32_t acceptFileTransfer(QString accountId, + QString conversationId, + const DRing::DataTransferId& transfer_id, + const QString& file_path, + int64_t offset) { - return uint32_t(DRing::acceptFileTransfer(accountId.toStdString(), conversationId.toStdString(), transfer_id, file_path.toStdString(), offset)); + return uint32_t(DRing::acceptFileTransfer(accountId.toStdString(), + conversationId.toStdString(), + transfer_id, + file_path.toStdString(), + offset)); } - uint32_t cancelDataTransfer(QString accountId, QString conversationId, const DRing::DataTransferId& transfer_id) + uint32_t cancelDataTransfer(QString accountId, + QString conversationId, + const DRing::DataTransferId& transfer_id) { - return uint32_t(DRing::cancelDataTransfer(accountId.toStdString(), conversationId.toStdString(), transfer_id)); + return uint32_t(DRing::cancelDataTransfer(accountId.toStdString(), + conversationId.toStdString(), + transfer_id)); } void enableProxyClient(const QString& accountID, bool enable) @@ -854,7 +903,7 @@ public Q_SLOTS: // METHODS { return DRing::searchUser(accountId.toStdString(), query.toStdString()); } - //swarm + // swarm QString startConversation(const QString& accountId) { auto convId = DRing::startConversation(accountId.toStdString()); @@ -872,10 +921,10 @@ public Q_SLOTS: // METHODS { return DRing::removeConversation(accountId.toStdString(), conversationId.toStdString()); } - VectorString getConversations(const QString& accountId) + QStringList getConversations(const QString& accountId) { auto conversations = DRing::getConversations(accountId.toStdString()); - return convertVectorString(conversations); + return convertStringList(conversations); } VectorMapStringString getConversationRequests(const QString& accountId) { @@ -908,12 +957,12 @@ public Q_SLOTS: // METHODS void sendMessage(const QString& accountId, const QString& conversationId, const QString& message, - const QString& parrent) + const QString& parent) { DRing::sendMessage(accountId.toStdString(), conversationId.toStdString(), message.toStdString(), - parrent.toStdString()); + parent.toStdString()); } uint32_t loadConversationMessages(const QString& accountId, const QString& conversationId, @@ -956,6 +1005,20 @@ public Q_SLOTS: // METHODS return DRing::isAllModerators(accountID.toStdString()); } + MapStringString conversationInfos(const QString& accountId, const QString& conversationId) + { + return convertMap( + DRing::conversationInfos(accountId.toStdString(), conversationId.toStdString())); + } + + void updateConversationInfos(const QString& accountId, + const QString& conversationId, + const MapStringString& info) + { + DRing::updateConversationInfos(accountId.toStdString(), + conversationId.toStdString(), + convertMap(info)); + } Q_SIGNALS: // SIGNALS void volumeChanged(const QString& device, double value); void accountsChanged(); @@ -976,21 +1039,23 @@ Q_SIGNALS: // SIGNALS const QString& certId, const QString& status); void incomingTrustRequest(const QString& accountId, + const QString& conversdationId, const QString& from, const QByteArray& payload, qulonglong timeStamp); void knownDevicesChanged(const QString& accountId, const MapStringString& devices); void exportOnRingEnded(const QString& accountId, int status, const QString& pin); void incomingAccountMessage(const QString& accountId, - const QString msgId, const QString& from, + const QString msgId, const MapStringString& payloads); void mediaParametersChanged(const QString& accountId); void audioDeviceEvent(); void audioMeter(const QString& id, float level); void accountMessageStatusChanged(const QString& accountId, - const uint64_t id, - const QString& to, + const QString& conversationId, + const QString& peer, + const QString& messageId, int status); void nameRegistrationEnded(const QString& accountId, int status, const QString& name); void registeredNameFound(const QString& accountId, @@ -1001,20 +1066,24 @@ Q_SIGNALS: // SIGNALS void contactAdded(const QString& accountID, const QString& uri, bool banned); void contactRemoved(const QString& accountID, const QString& uri, bool banned); void profileReceived(const QString& accountID, const QString& peer, const QString& vCard); - void dataTransferEvent(const QString& accountId, const QString& conversationId, qulonglong transfer_id, uint code); + void dataTransferEvent(const QString& accountId, + const QString& conversationId, + DataTransferId transfer_id, + uint code); void deviceRevocationEnded(const QString& accountId, const QString& deviceId, int status); void accountProfileReceived(const QString& accountId, const QString& displayName, const QString& userPhoto); void messageSend(const QString& message); void composingStatusChanged(const QString& accountId, + const QString& convId, const QString& contactId, bool isComposing); void userSearchEnded(const QString& accountId, int status, const QString& query, VectorMapStringString results); - //swarm + // swarm void conversationLoaded(uint32_t requestId, const QString& accountId, const QString& conversationId, @@ -1026,6 +1095,7 @@ Q_SIGNALS: // SIGNALS const QString& conversationId, const MapStringString& metadatas); void conversationReady(const QString& accountId, const QString& conversationId); + void conversationRemoved(const QString& accountId, const QString& conversationId); void conversationMemberEvent(const QString& accountId, const QString& conversationId, const QString& memberId, diff --git a/src/typedefs.h b/src/typedefs.h index f81b39c16bf90c13f14049b740b0b573b7da427d..37f4b17c0ccf3341d29a45fca445382f9dd5251b 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -39,6 +39,7 @@ typedef QMap<QString, QVector<QString>> MapStringVectorString; typedef QMap<QString, QMap<QString, QStringList>> MapStringMapStringStringList; typedef QMap<QString, QStringList> MapStringStringList; typedef QVector<QByteArray> VectorVectorByte; +typedef uint64_t DataTransferId; // Adapted from libring DRing::DataTransferInfo struct DataTransferInfo @@ -48,7 +49,9 @@ struct DataTransferInfo quint32 flags; qlonglong totalSize; qlonglong bytesProgress; + QString author; QString peer; + QString conversationId; QString displayName; QString path; QString mimetype; @@ -213,4 +216,4 @@ private: DO_PRAGMA(GCC diagnostic pop) #include <functional> -typedef std::function<void()> MigrationCb; \ No newline at end of file +typedef std::function<void()> MigrationCb;