mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-15 21:15:24 +02:00
misc: fix ups for conversationmodel/messagelistmodel
Removes some remaining excess complexity in the way interactions are managed by the client. Removes raw iterator access and provides thread-safe alternatives. Change-Id: I482bf599de869245f96c4aab418127f30508ef41
This commit is contained in:
parent
009a3902cb
commit
46a955aa3d
14 changed files with 870 additions and 1059 deletions
|
@ -108,35 +108,38 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
|||
case Role::UnreadMessagesCount:
|
||||
return QVariant(item.unreadMessages);
|
||||
case Role::LastInteractionTimeStamp: {
|
||||
if (!item.interactions->empty()) {
|
||||
auto ts = static_cast<qint32>(item.interactions->rbegin()->second.timestamp);
|
||||
return QVariant(ts);
|
||||
}
|
||||
break;
|
||||
qint32 ts = 0;
|
||||
item.interactions->withLast([&ts](const QString&, const interaction::Info& interaction) {
|
||||
ts = interaction.timestamp;
|
||||
});
|
||||
return QVariant(ts);
|
||||
}
|
||||
case Role::LastInteraction: {
|
||||
if (!item.interactions->empty()) {
|
||||
auto interaction = item.interactions->rbegin()->second;
|
||||
QString lastInteractionBody;
|
||||
item.interactions->withLast([&](const QString&, const interaction::Info& interaction) {
|
||||
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
|
||||
if (interaction.type == interaction::Type::DATA_TRANSFER) {
|
||||
return QVariant(interaction.commit.value("displayName"));
|
||||
if (interaction.type == interaction::Type::UPDATE_PROFILE) {
|
||||
lastInteractionBody = interaction::getProfileUpdatedString();
|
||||
} else if (interaction.type == interaction::Type::DATA_TRANSFER) {
|
||||
lastInteractionBody = interaction.commit.value("displayName");
|
||||
} else if (interaction.type == lrc::api::interaction::Type::CALL) {
|
||||
return QVariant(interaction::getCallInteractionString(interaction.authorUri
|
||||
== accInfo.profileInfo.uri,
|
||||
interaction));
|
||||
const auto isOutgoing = interaction.authorUri == accInfo.profileInfo.uri;
|
||||
lastInteractionBody = interaction::getCallInteractionString(isOutgoing, interaction);
|
||||
} else if (interaction.type == lrc::api::interaction::Type::CONTACT) {
|
||||
auto bestName = interaction.authorUri == accInfo.profileInfo.uri
|
||||
? accInfo.accountModel->bestNameForAccount(accInfo.id)
|
||||
: accInfo.contactModel->bestNameForContact(
|
||||
interaction.authorUri);
|
||||
return QVariant(
|
||||
interaction::getContactInteractionString(bestName,
|
||||
interaction::to_action(
|
||||
interaction.commit["action"])));
|
||||
lastInteractionBody
|
||||
= interaction::getContactInteractionString(bestName,
|
||||
interaction::to_action(
|
||||
interaction.commit["action"]));
|
||||
} else {
|
||||
lastInteractionBody = interaction.body.isEmpty() ? tr("(deleted message)")
|
||||
: interaction.body;
|
||||
}
|
||||
return QVariant(interaction.body);
|
||||
}
|
||||
break;
|
||||
});
|
||||
return QVariant(lastInteractionBody);
|
||||
}
|
||||
case Role::IsSwarm:
|
||||
return QVariant(item.isSwarm());
|
||||
|
|
|
@ -37,7 +37,6 @@ JamiListView {
|
|||
|
||||
function loadMoreMsgsIfNeeded() {
|
||||
if (atYBeginning && !CurrentConversation.allMessagesLoaded) {
|
||||
print("load more messages", atYBeginning, CurrentConversation.allMessagesLoaded)
|
||||
MessagesAdapter.loadMoreMessages()
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +174,8 @@ JamiListView {
|
|||
Connections {
|
||||
target: CurrentConversation
|
||||
function onScrollTo(id) {
|
||||
var idx = MessagesAdapter.getMessageIndexFromId(id)
|
||||
// Get the filtered index from the interaction ID.
|
||||
var idx = MessagesAdapter.messageListModel.getDisplayIndex(id)
|
||||
positionViewAtIndex(idx, ListView.Visible)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,6 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
|||
filteredMsgListModel_->setSourceModel(conversation.interactions.get());
|
||||
|
||||
set_currentConvComposingList(conversationTypersUrlToName(conversation.typers));
|
||||
mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
|
||||
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
|
||||
});
|
||||
|
||||
connect(messageParser_, &MessageParser::messageParsed, this, &MessagesAdapter::onMessageParsed);
|
||||
|
@ -83,9 +81,10 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
|
|||
connect(timestampTimer_, &QTimer::timeout, this, &MessagesAdapter::timestampUpdated);
|
||||
timestampTimer_->start(timestampUpdateIntervalMs_);
|
||||
|
||||
connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, this, [this]() {
|
||||
connectConversationModel();
|
||||
});
|
||||
connect(lrcInstance_,
|
||||
&LRCInstance::currentAccountIdChanged,
|
||||
this,
|
||||
&MessagesAdapter::connectConversationModel);
|
||||
|
||||
connectConversationModel();
|
||||
}
|
||||
|
@ -142,6 +141,9 @@ MessagesAdapter::connectConversationModel()
|
|||
this,
|
||||
&MessagesAdapter::onMessagesFoundProcessed,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
mediaInteractions_.reset(new MessageListModel(&lrcInstance_->getCurrentAccountInfo(), this));
|
||||
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -379,18 +381,7 @@ QVariant
|
|||
MessagesAdapter::dataForInteraction(const QString& interactionId, int role) const
|
||||
{
|
||||
if (auto* model = getMsgListSourceModel()) {
|
||||
auto idx = model->indexOfMessage(interactionId);
|
||||
if (idx != -1)
|
||||
return model->data(idx, role);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int
|
||||
MessagesAdapter::getIndexOfMessage(const QString& interactionId) const
|
||||
{
|
||||
if (auto* model = getMsgListSourceModel()) {
|
||||
return model->indexOfMessage(interactionId);
|
||||
return model->data(interactionId, role);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -584,7 +575,7 @@ MessagesAdapter::onMessagesFoundProcessed(const QString& accountId,
|
|||
bool isSearchInProgress = messageInformation.size();
|
||||
if (isSearchInProgress) {
|
||||
for (auto it = messageInformation.begin(); it != messageInformation.end(); it++) {
|
||||
mediaInteractions_->insert(qMakePair(it.key(), it.value()));
|
||||
mediaInteractions_->append(it.key(), it.value());
|
||||
}
|
||||
} else {
|
||||
set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
|
||||
|
@ -745,23 +736,6 @@ MessagesAdapter::startSearch(const QString& text, bool isMedia)
|
|||
}
|
||||
}
|
||||
|
||||
int
|
||||
MessagesAdapter::getMessageIndexFromId(const QString& id)
|
||||
{
|
||||
const QString& convId = lrcInstance_->get_selectedConvUid();
|
||||
const auto& conversation = lrcInstance_->getConversationFromConvUid(convId);
|
||||
auto allInteractions = conversation.interactions.get();
|
||||
int index = 0;
|
||||
for (auto it = allInteractions->rbegin(); it != allInteractions->rend(); it++) {
|
||||
if (interaction::isDisplayedInChatview(it->second.type)) {
|
||||
if (it->first == id)
|
||||
return index;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
MessageListModel*
|
||||
MessagesAdapter::getMsgListSourceModel() const
|
||||
{
|
||||
|
|
|
@ -45,12 +45,19 @@ public:
|
|||
auto index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
auto type = static_cast<interaction::Type>(
|
||||
sourceModel()->data(index, MessageList::Role::Type).toInt());
|
||||
return interaction::isDisplayedInChatview(type);
|
||||
return interaction::isTypeDisplayable(type);
|
||||
};
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override
|
||||
{
|
||||
return left.row() > right.row();
|
||||
};
|
||||
|
||||
Q_INVOKABLE int getDisplayIndex(const QString& id)
|
||||
{
|
||||
auto sourceRow = ((MessageListModel*) sourceModel())->indexOfMessage(id);
|
||||
auto index = mapFromSource(sourceModel()->index(sourceRow, 0));
|
||||
return index.row();
|
||||
};
|
||||
};
|
||||
|
||||
class MessagesAdapter final : public QmlAdapterBase
|
||||
|
@ -129,13 +136,11 @@ protected:
|
|||
const QColor& linkColor = QColor(0x06, 0x45, 0xad),
|
||||
const QColor& backgroundColor = QColor(0x0, 0x0, 0x0));
|
||||
Q_INVOKABLE void onPaste();
|
||||
Q_INVOKABLE int getIndexOfMessage(const QString& messageId) const;
|
||||
Q_INVOKABLE QString getStatusString(int status);
|
||||
Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int);
|
||||
Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId,
|
||||
int role = Qt::DisplayRole) const;
|
||||
Q_INVOKABLE void startSearch(const QString& text, bool isMedia);
|
||||
Q_INVOKABLE int getMessageIndexFromId(const QString& id);
|
||||
|
||||
// Run corrsponding js functions, c++ to qml.
|
||||
void setMessagesImageContent(const QString& path, bool isBased64 = false);
|
||||
|
|
|
@ -436,7 +436,7 @@ Utils::contactPhoto(LRCInstance* instance,
|
|||
auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName;
|
||||
photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
} catch (const std::exception&) {
|
||||
photo = fallbackAvatar("jami:" + contactUri, QString(), size);
|
||||
}
|
||||
return Utils::scaleAndFrame(photo, size);
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
#endif
|
||||
|
||||
#include "api/account.h"
|
||||
#include "api/contact.h"
|
||||
#include "api/contactmodel.h"
|
||||
#include "api/conversationmodel.h"
|
||||
|
||||
|
|
|
@ -309,6 +309,7 @@ set(LIBCLIENT_HEADERS_API
|
|||
api/contactmodel.h
|
||||
api/conversationmodel.h
|
||||
api/datatransfermodel.h
|
||||
api/messagelistmodel.h
|
||||
api/datatransfer.h
|
||||
api/interaction.h
|
||||
api/lrc.h
|
||||
|
|
|
@ -20,12 +20,11 @@
|
|||
|
||||
#include "interaction.h"
|
||||
#include "messagelistmodel.h"
|
||||
#include "account.h"
|
||||
#include "member.h"
|
||||
#include "typedefs.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace lrc {
|
||||
|
||||
|
|
|
@ -47,12 +47,11 @@ enum class Type {
|
|||
COUNT__
|
||||
};
|
||||
Q_ENUM_NS(Type)
|
||||
|
||||
static inline bool
|
||||
isDisplayedInChatview(const Type& type)
|
||||
isTypeDisplayable(const Type& type)
|
||||
{
|
||||
return type != interaction::Type::MERGE && type != interaction::Type::EDITED
|
||||
&& type != interaction::Type::REACTION && type != interaction::Type::VOTE
|
||||
&& type != interaction::Type::UPDATE_PROFILE && type != interaction::Type::INVALID;
|
||||
return type != interaction::Type::VOTE && type != interaction::Type::UPDATE_PROFILE;
|
||||
}
|
||||
|
||||
static inline const QString
|
||||
|
@ -380,7 +379,7 @@ struct Info
|
|||
QString react_to;
|
||||
QVector<Body> previousBodies;
|
||||
|
||||
Info() {}
|
||||
Info() = default;
|
||||
|
||||
Info(QString authorUri,
|
||||
QString body,
|
||||
|
@ -399,6 +398,11 @@ struct Info
|
|||
this->isRead = isRead;
|
||||
}
|
||||
|
||||
Info(const Info& other) = default;
|
||||
Info(Info&& other) = default;
|
||||
Info& operator=(const Info& other) = delete;
|
||||
Info& operator=(Info&& other) = default;
|
||||
|
||||
void init(const MapStringString& message, const QString& accountURI)
|
||||
{
|
||||
type = to_type(message["type"]);
|
||||
|
@ -479,6 +483,13 @@ getCallInteractionString(bool isSelf, const Info& info)
|
|||
return getCallInteractionStringNonSwarm(isSelf, info.duration);
|
||||
}
|
||||
|
||||
static inline QString
|
||||
getProfileUpdatedString()
|
||||
{
|
||||
// Perhaps one day this will be more detailed.
|
||||
return QObject::tr("(profile updated)");
|
||||
}
|
||||
|
||||
} // namespace interaction
|
||||
} // namespace api
|
||||
} // namespace lrc
|
||||
|
|
157
src/libclient/api/messagelistmodel.h
Normal file
157
src/libclient/api/messagelistmodel.h
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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
|
||||
|
||||
#include "api/interaction.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace lrc {
|
||||
namespace api {
|
||||
|
||||
namespace account {
|
||||
struct Info;
|
||||
}
|
||||
|
||||
#define MSG_ROLES \
|
||||
X(Id) \
|
||||
X(Author) \
|
||||
X(Body) \
|
||||
X(ParentId) \
|
||||
X(Timestamp) \
|
||||
X(Duration) \
|
||||
X(Type) \
|
||||
X(Status) \
|
||||
X(IsRead) \
|
||||
X(ContactAction) \
|
||||
X(ActionUri) \
|
||||
X(ConfId) \
|
||||
X(DeviceId) \
|
||||
X(LinkPreviewInfo) \
|
||||
X(ParsedBody) \
|
||||
X(PreviousBodies) \
|
||||
X(Reactions) \
|
||||
X(ReplyTo) \
|
||||
X(ReplyToBody) \
|
||||
X(ReplyToAuthor) \
|
||||
X(TotalSize) \
|
||||
X(TransferName) \
|
||||
X(FileExtension) \
|
||||
X(Readers) \
|
||||
X(IsEmojiOnly) \
|
||||
X(Index)
|
||||
|
||||
namespace MessageList {
|
||||
Q_NAMESPACE
|
||||
enum Role {
|
||||
DummyRole = Qt::UserRole + 1,
|
||||
#define X(role) role,
|
||||
MSG_ROLES
|
||||
#undef X
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace MessageList
|
||||
|
||||
class MessageListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// A pair of message id and interaction info.
|
||||
using item_t = QPair<QString, interaction::Info>;
|
||||
using container_t = QList<item_t>;
|
||||
using iterator = container_t::iterator;
|
||||
|
||||
explicit MessageListModel(const account::Info* account, QObject* parent = nullptr);
|
||||
~MessageListModel() = default;
|
||||
|
||||
// QAbstractListModel interface
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
// QAbstractListModel helpers
|
||||
Q_INVOKABLE QVariant data(const QString& id, int role = Qt::DisplayRole) const;
|
||||
|
||||
// Basic container + mutation/update methods.
|
||||
bool empty() const;
|
||||
int indexOfMessage(const QString& messageId) const;
|
||||
void clear();
|
||||
void reloadHistory();
|
||||
bool insert(const QString& id, const interaction::Info& interaction, int index = -1);
|
||||
bool append(const QString& id, const interaction::Info& interaction);
|
||||
bool update(const QString& id, const interaction::Info& interaction);
|
||||
bool updateStatus(const QString& id, interaction::Status newStatus, const QString& newBody = {});
|
||||
QPair<bool, bool> addOrUpdate(const QString& id, const interaction::Info& interaction);
|
||||
|
||||
// Thread-safe access to interactions.
|
||||
// Note: be careful when using these functions to modify interactions, as
|
||||
// the dataChanged() signal is not emitted. Use add/update/remove instead
|
||||
// if per-message UI updates are required. Also, DO NOT use these to
|
||||
// mutate the interactions_ container.
|
||||
using InteractionCb = std::function<void(const QString&, interaction::Info&)>;
|
||||
void forEach(const InteractionCb&);
|
||||
// Operations on a single interaction. Returns true if the interaction is found.
|
||||
// Note: if idHint is an empty string, the last interaction is used.
|
||||
bool with(const QString& idHint, const InteractionCb&);
|
||||
// A convenience function to access the last interaction.
|
||||
bool withLast(const InteractionCb&);
|
||||
|
||||
// Used when sorting conversations by timestamp, where locking multiple
|
||||
// interactions simultaneously is required.
|
||||
std::recursive_mutex& getMutex();
|
||||
|
||||
// Methods to manage message metadata.
|
||||
void addHyperlinkInfo(const QString& messageId, const QVariantMap& info);
|
||||
void addReaction(const QString& messageId, const MapStringString& reaction);
|
||||
void rmReaction(const QString& messageId, const QString& reactionId);
|
||||
void setParsedMessage(const QString& messageId, const QString& parsed);
|
||||
void setRead(const QString& peer, const QString& messageId);
|
||||
QString getRead(const QString& peer);
|
||||
QString lastSelfMessageId(const QString& id) const;
|
||||
QPair<QString, time_t> getDisplayedInfoForPeer(const QString& peerId);
|
||||
|
||||
private:
|
||||
using Role = MessageList::Role;
|
||||
|
||||
container_t interactions_;
|
||||
mutable std::recursive_mutex mutex_;
|
||||
const account::Info* account_;
|
||||
|
||||
// Note: because read status are updated even if interaction is not loaded we need to
|
||||
// keep track of these status outside the interaction::Info to allow quick access.
|
||||
// lastDisplayedMessageUid_ is used to keep track of the last message displayed for each
|
||||
// peer. This is used to update the far end read status of the interaction. messageToReaders_
|
||||
// is used to keep track of the readers of each message. This is a different view of
|
||||
// lastDisplayedMessageUid_, and is used to update the read status of the interaction.
|
||||
QMap<QString, QString> lastDisplayedMessageUid_; // {"peerId": "messageId"}
|
||||
QMap<QString, QStringList> messageToReaders_; // {"messageId": ["peer1", "peer2"]}
|
||||
QMap<QString, QSet<QString>> replyTo_;
|
||||
|
||||
iterator find(const QString& msgId);
|
||||
int move(iterator it, const QString& newParentId);
|
||||
QVariant data(int idx, int role = Qt::DisplayRole) const;
|
||||
QVariant dataForItem(const item_t& item, int indexRow, int role = Qt::DisplayRole) const;
|
||||
void updateReplies(const item_t& message);
|
||||
};
|
||||
} // namespace api
|
||||
} // namespace lrc
|
||||
Q_DECLARE_METATYPE(lrc::api::MessageListModel*)
|
|
@ -496,7 +496,7 @@ getHistory(Database& db, api::conversation::Info& conversation, const QString& l
|
|||
type,
|
||||
status,
|
||||
(payloads[i + 6] == "1" ? true : false)});
|
||||
conversation.interactions->emplace(payloads[i], std::move(msg));
|
||||
conversation.interactions->append(payloads[i], std::move(msg));
|
||||
if (status != api::interaction::Status::DISPLAYED || !payloads[i + 1].isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace lrc {
|
||||
|
@ -172,11 +171,11 @@ public:
|
|||
* @param peerId, peer id
|
||||
* @param status, new status for this interaction
|
||||
*/
|
||||
void slotUpdateInteractionStatus(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const QString& peerId,
|
||||
const QString& messageId,
|
||||
int status);
|
||||
void updateInteractionStatus(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const QString& peerId,
|
||||
const QString& messageId,
|
||||
int status);
|
||||
|
||||
/**
|
||||
* place a call
|
||||
|
@ -236,7 +235,6 @@ public:
|
|||
FilterType typeFilter;
|
||||
FilterType customTypeFilter;
|
||||
|
||||
std::map<QString, std::mutex> interactionsLocks; ///< {convId, mutex}
|
||||
MapStringString transfIdToDbIntId;
|
||||
uint32_t mediaResearchRequestId;
|
||||
uint32_t msgResearchRequestId;
|
||||
|
@ -493,8 +491,8 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q
|
|||
// filter out calls from conference
|
||||
for (const auto& c : conferences) {
|
||||
for (const auto& subcall : owner.callModel->getConferenceSubcalls(c)) {
|
||||
auto position = std::find(calls.begin(), calls.end(), subcall);
|
||||
if (position != calls.end()) {
|
||||
const auto position = std::find(calls.cbegin(), calls.cend(), subcall);
|
||||
if (position != calls.cend()) {
|
||||
calls.erase(position);
|
||||
}
|
||||
}
|
||||
|
@ -567,12 +565,12 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q
|
|||
} catch (...) {
|
||||
}
|
||||
}
|
||||
for (auto it : tempConferences.toStdMap()) {
|
||||
for (const auto& it : tempConferences.toStdMap()) {
|
||||
if (filter.isEmpty()) {
|
||||
callsVector.push_back(it.second);
|
||||
continue;
|
||||
}
|
||||
for (AccountConversation accConv : it.second) {
|
||||
for (const AccountConversation& accConv : it.second) {
|
||||
try {
|
||||
auto& account = pimpl_->lrc.getAccountModel().getAccountInfo(accConv.accountId);
|
||||
auto& conv = account.conversationModel->getConversationForUid(accConv.convId)->get();
|
||||
|
@ -889,8 +887,8 @@ ConversationModel::joinCall(const QString& uid,
|
|||
isAudioOnly);
|
||||
// Update interaction status
|
||||
pimpl_->invalidateModel();
|
||||
emit selectConversation(uid);
|
||||
emit conversationUpdated(uid);
|
||||
selectConversation(uid);
|
||||
Q_EMIT conversationUpdated(uid);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
@ -913,8 +911,8 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly)
|
|||
|
||||
// Update interaction status
|
||||
invalidateModel();
|
||||
emit linked.selectConversation(conversation.uid);
|
||||
emit linked.conversationUpdated(conversation.uid);
|
||||
linked.selectConversation(conversation.uid);
|
||||
Q_EMIT linked.conversationUpdated(conversation.uid);
|
||||
Q_EMIT linked.dataChanged(indexOf(conversation.uid));
|
||||
return;
|
||||
}
|
||||
|
@ -1158,11 +1156,13 @@ ConversationModel::notificationsCount() const
|
|||
void
|
||||
ConversationModel::reloadHistory() const
|
||||
{
|
||||
std::for_each(pimpl_->conversations.begin(), pimpl_->conversations.end(), [&](const auto& c) {
|
||||
c.interactions->reloadHistory();
|
||||
Q_EMIT conversationUpdated(c.uid);
|
||||
Q_EMIT dataChanged(pimpl_->indexOf(c.uid));
|
||||
});
|
||||
std::for_each(pimpl_->conversations.begin(),
|
||||
pimpl_->conversations.end(),
|
||||
[&](const conversation::Info& c) {
|
||||
c.interactions->reloadHistory();
|
||||
Q_EMIT conversationUpdated(c.uid);
|
||||
Q_EMIT dataChanged(pimpl_->indexOf(c.uid));
|
||||
});
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -1345,19 +1345,8 @@ ConversationModel::sendMessage(const QString& uid, const QString& body, const QS
|
|||
storage::addDaemonMsgId(pimpl_->db, msgId, toQString(daemonMsgId));
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[newConv.uid]);
|
||||
ret = newConv.interactions->insert(std::pair<QString, interaction::Info>(msgId, msg))
|
||||
.second;
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
qDebug()
|
||||
<< "ConversationModel::sendMessage failed to send message because an existing "
|
||||
"key was already present in the database key ="
|
||||
<< msgId;
|
||||
if (!newConv.interactions->append(msgId, msg)) {
|
||||
qWarning() << Q_FUNC_INFO << "Append failed: duplicate ID";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1503,10 +1492,7 @@ ConversationModel::clearHistory(const QString& uid)
|
|||
// Remove all TEXT interactions from database
|
||||
storage::clearHistory(pimpl_->db, uid);
|
||||
// Update conversation
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[conversation.uid]);
|
||||
conversation.interactions->clear();
|
||||
}
|
||||
conversation.interactions->clear();
|
||||
storage::getHistory(pimpl_->db,
|
||||
conversation,
|
||||
pimpl_->linked.owner.profileInfo.uri); // will contain "Conversation started"
|
||||
|
@ -1541,7 +1527,6 @@ ConversationModel::clearAllHistory()
|
|||
// 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, pimpl_->linked.owner.profileInfo.uri);
|
||||
|
@ -1558,37 +1543,30 @@ ConversationModel::clearUnreadInteractions(const QString& convId)
|
|||
return;
|
||||
}
|
||||
auto& conversation = conversationOpt->get();
|
||||
bool emitUpdated = false;
|
||||
QString lastDisplayed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]);
|
||||
auto& interactions = conversation.interactions;
|
||||
if (conversation.isSwarm()) {
|
||||
emitUpdated = true;
|
||||
if (!interactions->empty())
|
||||
lastDisplayed = interactions->rbegin()->first;
|
||||
} else {
|
||||
std::for_each(interactions->begin(),
|
||||
interactions->end(),
|
||||
[&](decltype(*interactions->begin())& it) {
|
||||
if (!it.second.isRead) {
|
||||
emitUpdated = true;
|
||||
it.second.isRead = true;
|
||||
if (owner.profileInfo.type != profile::Type::SIP)
|
||||
lastDisplayed = storage::getDaemonIdByInteractionId(pimpl_->db,
|
||||
it.first);
|
||||
storage::setInteractionRead(pimpl_->db, it.first);
|
||||
}
|
||||
});
|
||||
}
|
||||
bool updated = false;
|
||||
QString lastDisplayedId;
|
||||
if (conversation.isSwarm()) {
|
||||
updated = true;
|
||||
conversation.interactions->withLast(
|
||||
[&](const QString& id, interaction::Info&) { lastDisplayedId = id; });
|
||||
} else {
|
||||
conversation.interactions->forEach([&](const QString& id, interaction::Info& interaction) {
|
||||
if (interaction.isRead)
|
||||
return;
|
||||
updated = true;
|
||||
interaction.isRead = true;
|
||||
if (owner.profileInfo.type != profile::Type::SIP)
|
||||
lastDisplayedId = storage::getDaemonIdByInteractionId(pimpl_->db, id);
|
||||
storage::setInteractionRead(pimpl_->db, id);
|
||||
});
|
||||
}
|
||||
if (!lastDisplayed.isEmpty()) {
|
||||
if (!lastDisplayedId.isEmpty()) {
|
||||
auto to = conversation.isSwarm()
|
||||
? "swarm:" + convId
|
||||
: "jami:" + pimpl_->peersForConversation(conversation).front();
|
||||
ConfigurationManager::instance().setMessageDisplayed(owner.id, to, lastDisplayed, 3);
|
||||
ConfigurationManager::instance().setMessageDisplayed(owner.id, to, lastDisplayedId, 3);
|
||||
}
|
||||
if (emitUpdated) {
|
||||
if (updated) {
|
||||
conversation.unreadMessages = 0;
|
||||
pimpl_->invalidateModel();
|
||||
Q_EMIT conversationUpdated(convId);
|
||||
|
@ -1607,8 +1585,9 @@ ConversationModel::loadConversationMessages(const QString& conversationId, const
|
|||
if (conversation.allMessagesLoaded) {
|
||||
return -1;
|
||||
}
|
||||
auto lastMsgId = conversation.interactions->empty() ? ""
|
||||
: conversation.interactions->front().first;
|
||||
QString lastMsgId;
|
||||
conversation.interactions->withLast(
|
||||
[&lastMsgId](const QString& id, interaction::Info&) { lastMsgId = id; });
|
||||
return ConfigurationManager::instance().loadConversation(owner.id,
|
||||
conversationId,
|
||||
lastMsgId,
|
||||
|
@ -1719,7 +1698,7 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
|
|||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::accountMessageStatusChanged,
|
||||
this,
|
||||
&ConversationModelPimpl::slotUpdateInteractionStatus);
|
||||
&ConversationModelPimpl::updateInteractionStatus);
|
||||
|
||||
// Call related
|
||||
connect(&*linked.owner.contactModel,
|
||||
|
@ -1886,7 +1865,7 @@ ConversationModelPimpl::~ConversationModelPimpl()
|
|||
disconnect(&callbacksHandler,
|
||||
&CallbacksHandler::accountMessageStatusChanged,
|
||||
this,
|
||||
&ConversationModelPimpl::slotUpdateInteractionStatus);
|
||||
&ConversationModelPimpl::updateInteractionStatus);
|
||||
|
||||
// Call related
|
||||
disconnect(&*linked.owner.contactModel,
|
||||
|
@ -2061,23 +2040,22 @@ ConversationModelPimpl::initConversations()
|
|||
|
||||
auto convIdx = indexOf(conv[0]);
|
||||
|
||||
// Check if file transfer interactions were left in an incorrect state
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[convIdx].uid]);
|
||||
for (auto& interaction : *(conversations[convIdx].interactions)) {
|
||||
if (interaction.second.status == interaction::Status::TRANSFER_CREATED
|
||||
|| interaction.second.status == interaction::Status::TRANSFER_AWAITING_HOST
|
||||
|| interaction.second.status == interaction::Status::TRANSFER_AWAITING_PEER
|
||||
|| interaction.second.status == interaction::Status::TRANSFER_ONGOING
|
||||
|| interaction.second.status == interaction::Status::TRANSFER_ACCEPTED) {
|
||||
// Resolve any file transfer interactions were left in an incorrect state
|
||||
auto& interactions = conversations[convIdx].interactions;
|
||||
interactions->forEach([&](const QString& id, interaction::Info& interaction) {
|
||||
if (interaction.status == interaction::Status::TRANSFER_CREATED
|
||||
|| interaction.status == interaction::Status::TRANSFER_AWAITING_HOST
|
||||
|| interaction.status == interaction::Status::TRANSFER_AWAITING_PEER
|
||||
|| interaction.status == interaction::Status::TRANSFER_ONGOING
|
||||
|| interaction.status == interaction::Status::TRANSFER_ACCEPTED) {
|
||||
// If a datatransfer was left in a non-terminal status in DB, we switch this status
|
||||
// to ERROR
|
||||
// TODO : Improve for DBus clients as daemon and transfer may still be ongoing
|
||||
storage::updateInteractionStatus(db,
|
||||
interaction.first,
|
||||
interaction::Status::TRANSFER_ERROR);
|
||||
interaction.second.status = interaction::Status::TRANSFER_ERROR;
|
||||
storage::updateInteractionStatus(db, id, interaction::Status::TRANSFER_ERROR);
|
||||
|
||||
interaction.status = interaction::Status::TRANSFER_ERROR;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
invalidateModel();
|
||||
|
||||
|
@ -2226,15 +2204,13 @@ ConversationModelPimpl::sort(const conversation::Info& convA, const conversation
|
|||
if (convA.uid == convB.uid)
|
||||
return false;
|
||||
|
||||
auto& mtxA = interactionsLocks[convA.uid];
|
||||
auto& mtxB = interactionsLocks[convB.uid];
|
||||
std::lock(mtxA, mtxB);
|
||||
std::lock_guard<std::mutex> lockConvA(mtxA, std::adopt_lock);
|
||||
std::lock_guard<std::mutex> lockConvB(mtxB, std::adopt_lock);
|
||||
|
||||
auto& historyA = convA.interactions;
|
||||
auto& historyB = convB.interactions;
|
||||
|
||||
std::lock(historyA->getMutex(), historyB->getMutex());
|
||||
std::lock_guard<std::recursive_mutex> lockConvA(historyA->getMutex(), std::adopt_lock);
|
||||
std::lock_guard<std::recursive_mutex> lockConvB(historyB->getMutex(), std::adopt_lock);
|
||||
|
||||
// A or B is a new conversation (without CONTACT interaction)
|
||||
if (convA.uid.isEmpty() || convB.uid.isEmpty())
|
||||
return convA.uid.isEmpty();
|
||||
|
@ -2256,14 +2232,14 @@ ConversationModelPimpl::sort(const conversation::Info& convA, const conversation
|
|||
if (historyB->empty())
|
||||
return true;
|
||||
// Sort by last Interaction
|
||||
try {
|
||||
auto lastMessageA = historyA->rbegin()->second;
|
||||
auto lastMessageB = historyB->rbegin()->second;
|
||||
return lastMessageA.timestamp > lastMessageB.timestamp;
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << "ConversationModel::sortConversations(), can't get lastMessage";
|
||||
return false;
|
||||
}
|
||||
time_t timestampA, timestampB;
|
||||
historyA->withLast([&](const QString&, const interaction::Info& interaction) {
|
||||
timestampA = interaction.timestamp;
|
||||
});
|
||||
historyB->withLast([&](const QString&, const interaction::Info& interaction) {
|
||||
timestampB = interaction.timestamp;
|
||||
});
|
||||
return timestampA > timestampB;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2319,18 +2295,10 @@ ConversationModelPimpl::slotSwarmLoaded(uint32_t requestId,
|
|||
downloadFile = (bytesProgress == 0);
|
||||
}
|
||||
|
||||
{
|
||||
// If message is loaded, insert message at beginning
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
auto itExists = conversation.interactions->find(msgId);
|
||||
// If found, nothing to do.
|
||||
if (itExists != conversation.interactions->end())
|
||||
continue;
|
||||
|
||||
auto result = conversation.interactions->insert(std::make_pair(msgId, msg), true);
|
||||
if (!result.second) {
|
||||
continue;
|
||||
}
|
||||
// If message is loaded, insert message at beginning
|
||||
if (!conversation.interactions->insert(msgId, msg, 0)) {
|
||||
qDebug() << Q_FUNC_INFO << "Insert failed: duplicate ID";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (downloadFile) {
|
||||
|
@ -2380,13 +2348,13 @@ ConversationModelPimpl::slotMessagesFound(uint32_t requestId,
|
|||
bytesProgress);
|
||||
intInfo.body = path;
|
||||
}
|
||||
messageDetailedInformation[msg["id"]] = intInfo;
|
||||
messageDetailedInformation[msg["id"]] = std::move(intInfo);
|
||||
}
|
||||
} else if (requestId == msgResearchRequestId) {
|
||||
Q_FOREACH (const MapStringString& msg, messageIds) {
|
||||
auto intInfo = interaction::Info(msg, "");
|
||||
if (intInfo.type == interaction::Type::TEXT) {
|
||||
messageDetailedInformation[msg["id"]] = intInfo;
|
||||
messageDetailedInformation[msg["id"]] = std::move(intInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2437,23 +2405,11 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
|
|||
linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
|
||||
}
|
||||
|
||||
{
|
||||
// If message is received, insert message after its parent.
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
auto itExists = conversation.interactions->find(msgId);
|
||||
// If found, nothing to do.
|
||||
if (itExists != conversation.interactions->end())
|
||||
return;
|
||||
int index = conversation.interactions->indexOfMessage(msg.parentId);
|
||||
if (index >= 0) {
|
||||
auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
|
||||
if (!result.second) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (!conversation.interactions->append(msgId, msg)) {
|
||||
qDebug() << Q_FUNC_INFO << "Append failed: duplicate ID" << msgId;
|
||||
return;
|
||||
}
|
||||
|
||||
auto updateUnread = msg.authorUri != linked.owner.profileInfo.uri;
|
||||
if (updateUnread)
|
||||
conversation.unreadMessages++;
|
||||
|
@ -2491,26 +2447,11 @@ ConversationModelPimpl::slotMessageUpdated(const QString& accountId,
|
|||
QString msgId = message.id;
|
||||
auto msg = interaction::Info(message, linked.owner.profileInfo.uri);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
auto itExists = conversation.interactions->find(msgId);
|
||||
// If not found, nothing to do.
|
||||
if (itExists == conversation.interactions->end())
|
||||
return;
|
||||
// Now there is two cases:
|
||||
// ParentId changed, in this case, remove previous message and re-insert at new place
|
||||
// Else, just update body
|
||||
conversation.interactions->erase(itExists);
|
||||
int index = conversation.interactions->indexOfMessage(msg.parentId);
|
||||
if (index >= 0) {
|
||||
auto result = conversation.interactions->insert(index + 1, qMakePair(msgId, msg));
|
||||
if (!result.second) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (!conversation.interactions->update(msgId, msg)) {
|
||||
qDebug() << "message not found or could not be reparented";
|
||||
return;
|
||||
}
|
||||
// The conversation is updated, so we need to notify the view.
|
||||
invalidateModel();
|
||||
Q_EMIT linked.modelChanged();
|
||||
Q_EMIT linked.dataChanged(indexOf(conversationId));
|
||||
|
@ -2870,11 +2811,7 @@ ConversationModelPimpl::addConversationRequest(const MapStringString& convReques
|
|||
{"linearizedParent", ""},
|
||||
};
|
||||
auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
|
||||
conversation.interactions->insert(std::make_pair(convId, msg), true);
|
||||
}
|
||||
conversation.interactions->insert(convId, msg);
|
||||
|
||||
// add the author to the contact model's contact list as a PENDING
|
||||
// if they aren't already a contact
|
||||
|
@ -2935,8 +2872,7 @@ ConversationModelPimpl::slotPendingContactAccepted(const QString& 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);
|
||||
conversations[convIdx].interactions->append(msgId, interaction);
|
||||
}
|
||||
filteredConversations.invalidate();
|
||||
Q_EMIT linked.newInteraction(convs[0], msgId, interaction);
|
||||
|
@ -2958,7 +2894,7 @@ ConversationModelPimpl::slotContactRemoved(const QString& uri)
|
|||
}
|
||||
|
||||
// actually remove them from the list
|
||||
for (auto id : convIdsToRemove) {
|
||||
for (const auto& id : convIdsToRemove) {
|
||||
eraseConversation(id);
|
||||
Q_EMIT linked.conversationRemoved(id);
|
||||
}
|
||||
|
@ -3092,11 +3028,7 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
|
|||
{"linearizedParent", ""},
|
||||
};
|
||||
auto msg = interaction::Info(messageMap, linked.owner.profileInfo.uri);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
|
||||
conversation.interactions->insert(std::make_pair(convId, msg), true);
|
||||
}
|
||||
conversation.interactions->append(convId, msg);
|
||||
conversation.needsSyncing = true;
|
||||
Q_EMIT linked.conversationUpdated(conversation.uid);
|
||||
Q_EMIT linked.dataChanged(indexOf(conversation.uid));
|
||||
|
@ -3127,34 +3059,31 @@ ConversationModelPimpl::addConversationWith(const QString& convId,
|
|||
conversation.callId = "";
|
||||
}
|
||||
storage::getHistory(db, conversation, linked.owner.profileInfo.uri);
|
||||
std::vector<std::function<void(void)>> updateSlots;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
for (auto& interaction : (*(conversation.interactions))) {
|
||||
if (interaction.second.status != interaction::Status::SENDING) {
|
||||
continue;
|
||||
}
|
||||
// Get the message status from daemon, else unknown
|
||||
auto id = storage::getDaemonIdByInteractionId(db, interaction.first);
|
||||
int status = 0;
|
||||
if (id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto msgId = std::stoull(id.toStdString());
|
||||
status = ConfigurationManager::instance().getMessageStatus(msgId);
|
||||
updateSlots.emplace_back([this, convId, contactUri, id, status]() -> void {
|
||||
auto accId = linked.owner.id;
|
||||
slotUpdateInteractionStatus(accId, convId, contactUri, id, status);
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << "message id was invalid";
|
||||
}
|
||||
|
||||
QList<std::function<void(void)>> toUpdate;
|
||||
conversation.interactions->forEach([&](const QString& id, interaction::Info& interaction) {
|
||||
if (interaction.status != interaction::Status::SENDING) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& s : updateSlots) {
|
||||
s();
|
||||
}
|
||||
// Get the message status from daemon, else unknown
|
||||
auto daemonId = storage::getDaemonIdByInteractionId(db, id);
|
||||
int status = 0;
|
||||
if (daemonId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto msgId = std::stoull(daemonId.toStdString());
|
||||
status = ConfigurationManager::instance().getMessageStatus(msgId);
|
||||
toUpdate.emplace_back([this, convId, contactUri, daemonId, status]() {
|
||||
auto accId = linked.owner.id;
|
||||
updateInteractionStatus(accId, convId, contactUri, daemonId, status);
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
qWarning() << Q_FUNC_INFO << "Failed: message id was invalid";
|
||||
}
|
||||
});
|
||||
Q_FOREACH (const auto& func, toUpdate)
|
||||
func();
|
||||
|
||||
conversation.unreadMessages = getNumberOfUnreadMessagesFor(convId);
|
||||
|
||||
|
@ -3296,7 +3225,7 @@ ConversationModelPimpl::slotCallStatusChanged(const QString& callId, int code)
|
|||
if (i != conversations.end()) {
|
||||
// Update interaction status
|
||||
invalidateModel();
|
||||
Q_EMIT linked.selectConversation(i->uid);
|
||||
linked.selectConversation(i->uid);
|
||||
Q_EMIT linked.conversationUpdated(i->uid);
|
||||
Q_EMIT linked.dataChanged(indexOf(i->uid));
|
||||
}
|
||||
|
@ -3378,7 +3307,6 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
|
|||
// do not save call interaction for swarm conversation
|
||||
if (conv_it->isSwarm())
|
||||
return;
|
||||
auto uid = conv_it->uid;
|
||||
auto uriString = incoming ? storage::prepareUri(from, linked.owner.profileInfo.type)
|
||||
: linked.owner.profileInfo.uri;
|
||||
auto msg = interaction::Info {uriString,
|
||||
|
@ -3393,20 +3321,12 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
|
|||
// now set the formatted call message string in memory only
|
||||
msg.body = interaction::getCallInteractionString(msg.authorUri == linked.owner.profileInfo.uri,
|
||||
msg);
|
||||
bool newInteraction = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
|
||||
auto interactionIt = conv_it->interactions->find(msgId);
|
||||
newInteraction = interactionIt == conv_it->interactions->end();
|
||||
if (newInteraction) {
|
||||
conv_it->interactions->emplace(msgId, msg);
|
||||
} else {
|
||||
interactionIt->second = msg;
|
||||
conv_it->interactions->emitDataChanged(interactionIt);
|
||||
}
|
||||
auto [added, success] = conv_it->interactions->addOrUpdate(msgId, msg);
|
||||
if (!success) {
|
||||
qWarning() << Q_FUNC_INFO << QString("Failed: to %1 msg").arg(added ? "add" : "update");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newInteraction)
|
||||
if (added)
|
||||
Q_EMIT linked.newInteraction(conv_it->uid, msgId, msg);
|
||||
|
||||
invalidateModel();
|
||||
|
@ -3423,11 +3343,12 @@ ConversationModelPimpl::slotNewAccountMessage(const QString& accountId,
|
|||
if (accountId != linked.owner.id)
|
||||
return;
|
||||
|
||||
for (const auto& payload : payloads.keys()) {
|
||||
for (auto it = payloads.constBegin(); it != payloads.constEnd(); ++it) {
|
||||
const auto& payload = it.key();
|
||||
if (payload.contains(TEXT_PLAIN)) {
|
||||
addIncomingMessage(peerId, payloads.value(payload), 0, msgId);
|
||||
addIncomingMessage(peerId, it.value(), 0, msgId);
|
||||
} else {
|
||||
qWarning() << payload;
|
||||
qDebug() << payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3504,10 +3425,8 @@ ConversationModelPimpl::addIncomingMessage(const QString& peerId,
|
|||
addConversationWith(convIds[0], peerId, isRequest);
|
||||
Q_EMIT linked.newConversation(convIds[0]);
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
|
||||
conversations[conversationIdx].interactions->emplace(msgId, msg);
|
||||
}
|
||||
// Maybe check if this is failing?
|
||||
conversations[conversationIdx].interactions->append(msgId, msg);
|
||||
conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convIds[0]);
|
||||
}
|
||||
|
||||
|
@ -3532,25 +3451,25 @@ ConversationModelPimpl::slotCallAddedToConference(const QString& callId, const Q
|
|||
MapStringString confDetails = CallManager::instance()
|
||||
.getConferenceDetails(linked.owner.id, confId);
|
||||
if (confDetails["STATE"] == "ACTIVE_ATTACHED")
|
||||
Q_EMIT linked.selectConversation(conversation.uid);
|
||||
linked.selectConversation(conversation.uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModelPimpl::slotUpdateInteractionStatus(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const QString& peerId,
|
||||
const QString& messageId,
|
||||
int status)
|
||||
ConversationModelPimpl::updateInteractionStatus(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const QString& peerUri,
|
||||
const QString& messageId,
|
||||
int status)
|
||||
{
|
||||
if (accountId != linked.owner.id) {
|
||||
return;
|
||||
}
|
||||
// it may be not swarm conversation check in db
|
||||
// non-swarm conversation
|
||||
if (conversationId.isEmpty() || conversationId == linked.owner.profileInfo.uri) {
|
||||
auto convIds = storage::getConversationsWithPeer(db, peerId);
|
||||
auto convIds = storage::getConversationsWithPeer(db, peerUri);
|
||||
if (convIds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -3591,79 +3510,83 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const QString& accountId,
|
|||
idString = QString::number(id);
|
||||
}
|
||||
// Update database
|
||||
auto interactionId = storage::getInteractionIdByDaemonId(db, idString);
|
||||
if (interactionId.isEmpty()) {
|
||||
auto msgId = storage::getInteractionIdByDaemonId(db, idString);
|
||||
if (msgId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto msgId = interactionId;
|
||||
storage::updateInteractionStatus(db, msgId, newStatus);
|
||||
// Update conversations
|
||||
bool emitUpdated = false;
|
||||
bool updated = false;
|
||||
bool updateDisplayedUid = false;
|
||||
QString oldDisplayedUid = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
auto& interactions = conversation.interactions;
|
||||
auto it = interactions->find(msgId);
|
||||
auto messageId = conversation.interactions->getRead(peerId);
|
||||
if (it != interactions->end()) {
|
||||
it->second.status = newStatus;
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
bool interactionDisplayed = newStatus == interaction::Status::DISPLAYED
|
||||
&& isOutgoing(it->second);
|
||||
if (messageId != "") {
|
||||
auto lastDisplayedIt = interactions->find(messageId);
|
||||
bool interactionIsLast = lastDisplayedIt == interactions->end()
|
||||
|| lastDisplayedIt->second.timestamp
|
||||
< it->second.timestamp;
|
||||
updateDisplayedUid = interactionDisplayed && interactionIsLast;
|
||||
if (updateDisplayedUid) {
|
||||
oldDisplayedUid = messageId;
|
||||
if (peerId != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerId, it->first);
|
||||
// Try to update the status.
|
||||
if (interactions->updateStatus(msgId, newStatus)) {
|
||||
updated = true;
|
||||
interactions->with(msgId, [&](const QString& id, interaction::Info& interaction) {
|
||||
// Determine if the interaction is outgoing and has been displayed.
|
||||
bool interactionIsDisplayed = newStatus == interaction::Status::DISPLAYED
|
||||
&& interaction::isOutgoing(interaction);
|
||||
|
||||
// Get the last displayed interaction ID and timestamp for this peer.
|
||||
auto [lastIdForPeer, lastTimestampForPeer]
|
||||
= interactions->getDisplayedInfoForPeer(peerUri);
|
||||
|
||||
if (lastIdForPeer.isEmpty()) {
|
||||
oldDisplayedUid = "";
|
||||
if (peerUri != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerUri, msgId);
|
||||
updateDisplayedUid = true;
|
||||
} else {
|
||||
bool interactionIsLast = lastTimestampForPeer < interaction.timestamp;
|
||||
updateDisplayedUid = interactionIsDisplayed && interactionIsLast;
|
||||
if (updateDisplayedUid) {
|
||||
oldDisplayedUid = messageId;
|
||||
if (peerUri != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerUri, msgId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
oldDisplayedUid = "";
|
||||
if (peerId != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerId, it->first);
|
||||
updateDisplayedUid = true;
|
||||
}
|
||||
emitUpdated = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (updateDisplayedUid) {
|
||||
Q_EMIT linked.displayedInteractionChanged(conversation.uid,
|
||||
peerId,
|
||||
peerUri,
|
||||
oldDisplayedUid,
|
||||
msgId);
|
||||
}
|
||||
if (emitUpdated) {
|
||||
if (updated) {
|
||||
invalidateModel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// swarm conversation
|
||||
try {
|
||||
auto& conversation = getConversationForUid(conversationId).get();
|
||||
if (conversation.mode != conversation::Mode::NON_SWARM) {
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
|
||||
if (conversation.isSwarm()) {
|
||||
using namespace libjami::Account;
|
||||
auto msgState = static_cast<MessageStates>(status);
|
||||
auto& interactions = conversation.interactions;
|
||||
auto it = interactions->find(messageId);
|
||||
if (it != interactions->end() && it->second.type == interaction::Type::TEXT) {
|
||||
if (static_cast<libjami::Account::MessageStates>(status)
|
||||
== libjami::Account::MessageStates::SENDING) {
|
||||
it->second.status = interaction::Status::SENDING;
|
||||
} else if (static_cast<libjami::Account::MessageStates>(status)
|
||||
== libjami::Account::MessageStates::SENT) {
|
||||
it->second.status = interaction::Status::SUCCESS;
|
||||
}
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
}
|
||||
interactions->with(messageId,
|
||||
[&](const QString& id, const interaction::Info& interaction) {
|
||||
if (interaction.type == interaction::Type::TEXT) {
|
||||
interaction::Status newState;
|
||||
if (msgState == MessageStates::SENDING) {
|
||||
newState = interaction::Status::SENDING;
|
||||
} else if (msgState == MessageStates::SENT) {
|
||||
newState = interaction::Status::SUCCESS;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
interactions->updateStatus(id, newState);
|
||||
}
|
||||
});
|
||||
|
||||
if (static_cast<libjami::Account::MessageStates>(status)
|
||||
== libjami::Account::MessageStates::DISPLAYED) {
|
||||
auto previous = conversation.interactions->getRead(peerId);
|
||||
if (peerId != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerId, messageId);
|
||||
if (msgState == MessageStates::DISPLAYED) {
|
||||
auto previous = conversation.interactions->getRead(peerUri);
|
||||
if (peerUri != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerUri, messageId);
|
||||
else {
|
||||
// Here, this means that the daemon synced the displayed message
|
||||
// so, compute the number of unread messages.
|
||||
|
@ -3672,11 +3595,11 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const QString& accountId,
|
|||
conversationId,
|
||||
messageId,
|
||||
"",
|
||||
peerId);
|
||||
peerUri);
|
||||
Q_EMIT linked.dataChanged(indexOf(conversationId));
|
||||
}
|
||||
Q_EMIT linked.displayedInteractionChanged(conversationId,
|
||||
peerId,
|
||||
peerUri,
|
||||
previous,
|
||||
messageId);
|
||||
}
|
||||
|
@ -3843,13 +3766,8 @@ ConversationModel::cancelTransfer(const QString& convUid, const QString& fileId)
|
|||
auto conversationIdx = pimpl_->indexOf(convUid);
|
||||
bool emitUpdated = false;
|
||||
if (conversationIdx != -1) {
|
||||
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convUid]);
|
||||
auto& interactions = pimpl_->conversations[conversationIdx].interactions;
|
||||
auto it = interactions->find(fileId);
|
||||
if (it != interactions->end()) {
|
||||
it->second.status = interaction::Status::TRANSFER_CANCELED;
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
|
||||
if (interactions->updateStatus(fileId, interaction::Status::TRANSFER_CANCELED)) {
|
||||
// update information in the db
|
||||
storage::updateInteractionStatus(pimpl_->db,
|
||||
fileId,
|
||||
|
@ -3901,14 +3819,7 @@ ConversationModel::removeFile(const QString& conversationId,
|
|||
return;
|
||||
|
||||
QFile::remove(path);
|
||||
|
||||
std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convOpt->get().uid]);
|
||||
auto& interactions = convOpt->get().interactions;
|
||||
auto it = interactions->find(interactionId);
|
||||
if (it != interactions->end()) {
|
||||
it->second.status = interaction::Status::TRANSFER_AWAITING_HOST;
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
}
|
||||
convOpt->get().interactions->updateStatus(interactionId, interaction::Status::TRANSFER_CANCELED);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -3991,10 +3902,7 @@ ConversationModelPimpl::slotTransferStatusCreated(const QString& fileId, datatra
|
|||
addConversationWith(convId, info.peerUri, isRequest);
|
||||
Q_EMIT linked.newConversation(convId);
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
|
||||
conversations[conversationIdx].interactions->emplace(interactionId, interaction);
|
||||
}
|
||||
conversations[conversationIdx].interactions->append(interactionId, interaction);
|
||||
conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convId);
|
||||
}
|
||||
Q_EMIT behaviorController.newUnreadInteraction(linked.owner.id,
|
||||
|
@ -4087,15 +3995,18 @@ ConversationModelPimpl::acceptTransfer(const QString& convUid, const QString& in
|
|||
if (conversation.isLegacy()) // Ignore legacy
|
||||
return;
|
||||
|
||||
auto interaction = conversation.interactions->find(interactionId);
|
||||
if (interaction != conversation.interactions->end()) {
|
||||
auto fileId = interaction->second.commit["fileId"];
|
||||
if (fileId.isEmpty()) {
|
||||
qWarning() << "Cannot download file without fileId";
|
||||
return;
|
||||
}
|
||||
linked.owner.dataTransferModel->download(linked.owner.id, convUid, interactionId, fileId);
|
||||
} else {
|
||||
auto& interactions = conversation.interactions;
|
||||
if (!interactions->with(interactionId, [&](const QString& id, interaction::Info& interaction) {
|
||||
auto fileId = interaction.commit["fileId"];
|
||||
if (fileId.isEmpty()) {
|
||||
qWarning() << "Cannot download file without fileId";
|
||||
return;
|
||||
}
|
||||
linked.owner.dataTransferModel->download(linked.owner.id,
|
||||
convUid,
|
||||
interactionId,
|
||||
fileId);
|
||||
})) {
|
||||
qWarning() << "Cannot download file without valid interaction";
|
||||
}
|
||||
}
|
||||
|
@ -4150,7 +4061,7 @@ ConversationModelPimpl::slotTransferStatusOngoing(const QString& fileId, datatra
|
|||
}
|
||||
auto conversationIdx = indexOf(conversationId);
|
||||
auto* timer = new QTimer();
|
||||
connect(timer, &QTimer::timeout, [=] {
|
||||
connect(timer, &QTimer::timeout, this, [=] {
|
||||
updateTransferProgress(timer, conversationIdx, interactionId);
|
||||
});
|
||||
timer->start(1000);
|
||||
|
@ -4170,20 +4081,15 @@ ConversationModelPimpl::slotTransferStatusFinished(const QString& fileId, datatr
|
|||
if (conversationIdx != -1) {
|
||||
bool emitUpdated = false;
|
||||
auto newStatus = interaction::Status::TRANSFER_FINISHED;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
|
||||
auto& interactions = conversations[conversationIdx].interactions;
|
||||
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
|
||||
if (it->second.status == interaction::Status::TRANSFER_ONGOING) {
|
||||
emitUpdated = true;
|
||||
it->second.status = newStatus;
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
}
|
||||
auto& interactions = conversations[conversationIdx].interactions;
|
||||
interactions->with(interactionId, [&](const QString& id, interaction::Info& interaction) {
|
||||
// We need to check if current status is ONGOING as CANCELED must not be
|
||||
// transformed into FINISHED
|
||||
if (interaction.status == interaction::Status::TRANSFER_ONGOING) {
|
||||
emitUpdated = true;
|
||||
interactions->updateStatus(id, newStatus);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (emitUpdated) {
|
||||
invalidateModel();
|
||||
if (conversations[conversationIdx].mode != conversation::Mode::NON_SWARM) {
|
||||
|
@ -4256,23 +4162,10 @@ ConversationModelPimpl::updateTransferStatus(const QString& fileId,
|
|||
if (conversation.isLegacy()) {
|
||||
storage::updateInteractionStatus(db, interactionId, newStatus);
|
||||
}
|
||||
bool emitUpdated = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
|
||||
auto& interactions = conversations[conversationIdx].interactions;
|
||||
auto it = interactions->find(interactionId);
|
||||
if (it != interactions->end()) {
|
||||
emitUpdated = true;
|
||||
VectorInt roles;
|
||||
it->second.status = newStatus;
|
||||
roles += MessageList::Role::Status;
|
||||
if (conversation.isSwarm()) {
|
||||
it->second.body = info.path;
|
||||
roles += MessageList::Role::Body;
|
||||
}
|
||||
interactions->emitDataChanged(it, roles);
|
||||
}
|
||||
}
|
||||
auto& interactions = conversations[conversationIdx].interactions;
|
||||
bool emitUpdated = interactions->updateStatus(interactionId,
|
||||
newStatus,
|
||||
conversation.isSwarm() ? info.path : QString());
|
||||
if (emitUpdated) {
|
||||
invalidateModel();
|
||||
}
|
||||
|
@ -4288,15 +4181,13 @@ ConversationModelPimpl::updateTransferProgress(QTimer* timer,
|
|||
try {
|
||||
bool emitUpdated = false;
|
||||
{
|
||||
auto convId = conversations[conversationIdx].uid;
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
|
||||
const auto& interactions = conversations[conversationIdx].interactions;
|
||||
const auto& it = interactions->find(interactionId);
|
||||
if (it != interactions->cend()
|
||||
and it->second.status == interaction::Status::TRANSFER_ONGOING) {
|
||||
interactions->emitDataChanged(it, {MessageList::Role::Status});
|
||||
emitUpdated = true;
|
||||
}
|
||||
interactions->with(interactionId, [&](const QString& id, interaction::Info& interaction) {
|
||||
if (interaction.status == interaction::Status::TRANSFER_ONGOING) {
|
||||
emitUpdated = true;
|
||||
interactions->updateStatus(id, interaction::Status::TRANSFER_ONGOING);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (emitUpdated)
|
||||
return;
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
|
||||
* Author: Trevor Tabah <trevor.tabah@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
|
||||
|
@ -19,334 +16,16 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "messagelistmodel.h"
|
||||
#include "api/messagelistmodel.h"
|
||||
|
||||
#include "authority/storagehelper.h"
|
||||
#include "api/accountmodel.h"
|
||||
#include "api/contactmodel.h"
|
||||
#include "api/conversationmodel.h"
|
||||
#include "api/interaction.h"
|
||||
#include "qtwrapper/conversions_wrap.hpp"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QFileInfo>
|
||||
|
||||
namespace lrc {
|
||||
|
||||
using namespace api;
|
||||
|
||||
using constIterator = MessageListModel::constIterator;
|
||||
using iterator = MessageListModel::iterator;
|
||||
using reverseIterator = MessageListModel::reverseIterator;
|
||||
|
||||
MessageListModel::MessageListModel(const account::Info* account, QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, account_(account)
|
||||
{}
|
||||
|
||||
QPair<iterator, bool>
|
||||
MessageListModel::emplace(const 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 = insertMessage(iter, qMakePair(msgId, message));
|
||||
return qMakePair(iterator, true);
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::find(const QString& msgId)
|
||||
{
|
||||
iterator it;
|
||||
for (it = interactions_.begin(); it != interactions_.end(); ++it) {
|
||||
if (it->first == msgId) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return interactions_.end();
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::findActiveCall(const MapStringString& commit)
|
||||
{
|
||||
iterator it;
|
||||
for (it = interactions_.begin(); it != interactions_.end(); ++it) {
|
||||
const auto& itCommit = it->second.commit;
|
||||
if (itCommit["confId"] == commit["confId"] && itCommit["uri"] == commit["uri"]
|
||||
&& itCommit["device"] == commit["device"]) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return interactions_.end();
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::erase(const iterator& it)
|
||||
{
|
||||
auto index = std::distance(begin(), it);
|
||||
Q_EMIT beginRemoveRows(QModelIndex(), index, index);
|
||||
auto erased = interactions_.erase(it);
|
||||
Q_EMIT endRemoveRows();
|
||||
return erased;
|
||||
}
|
||||
|
||||
constIterator
|
||||
MessageListModel::find(const 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>
|
||||
MessageListModel::insert(std::pair<QString, interaction::Info> message, bool beginning)
|
||||
{
|
||||
return emplace(message.first, message.second, beginning);
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::erase(const QString& msgId)
|
||||
{
|
||||
iterator it;
|
||||
int index = 0;
|
||||
for (it = interactions_.begin(); it != interactions_.end(); ++it) {
|
||||
if (it->first == msgId) {
|
||||
removeMessage(index, it);
|
||||
return 1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
interaction::Info&
|
||||
MessageListModel::operator[](const 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 = {};
|
||||
insertMessage(interactions_.end(), qMakePair(messageId, newMessage));
|
||||
if (interactions_.last().first == messageId) {
|
||||
return const_cast<interaction::Info&>(interactions_.last().second);
|
||||
}
|
||||
throw std::out_of_range("Cannot find message");
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::end()
|
||||
{
|
||||
return interactions_.end();
|
||||
}
|
||||
|
||||
constIterator
|
||||
MessageListModel::end() const
|
||||
{
|
||||
return interactions_.end();
|
||||
}
|
||||
|
||||
reverseIterator
|
||||
MessageListModel::rend()
|
||||
{
|
||||
return interactions_.rend();
|
||||
}
|
||||
|
||||
constIterator
|
||||
MessageListModel::cend() const
|
||||
{
|
||||
return interactions_.cend();
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::begin()
|
||||
{
|
||||
return interactions_.begin();
|
||||
}
|
||||
|
||||
constIterator
|
||||
MessageListModel::begin() const
|
||||
{
|
||||
return interactions_.begin();
|
||||
}
|
||||
|
||||
reverseIterator
|
||||
MessageListModel::rbegin()
|
||||
{
|
||||
return interactions_.rbegin();
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::size() const
|
||||
{
|
||||
return interactions_.size();
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::clear()
|
||||
{
|
||||
Q_EMIT beginResetModel();
|
||||
interactions_.clear();
|
||||
replyTo_.clear();
|
||||
Q_EMIT endResetModel();
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::reloadHistory()
|
||||
{
|
||||
Q_EMIT beginResetModel();
|
||||
for (auto& interaction : interactions_) {
|
||||
interaction.second.linkPreviewInfo.clear();
|
||||
}
|
||||
Q_EMIT endResetModel();
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::empty() const
|
||||
{
|
||||
return interactions_.empty();
|
||||
}
|
||||
|
||||
interaction::Info
|
||||
MessageListModel::at(const QString& msgId) const
|
||||
{
|
||||
for (auto it = interactions_.cbegin(); it != interactions_.cend(); ++it) {
|
||||
if (it->first == msgId) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QPair<QString, interaction::Info>
|
||||
MessageListModel::front() const
|
||||
{
|
||||
return interactions_.front();
|
||||
}
|
||||
|
||||
QPair<QString, interaction::Info>
|
||||
MessageListModel::last() const
|
||||
{
|
||||
return interactions_.last();
|
||||
}
|
||||
|
||||
QPair<QString, interaction::Info>
|
||||
MessageListModel::atIndex(int index) const
|
||||
{
|
||||
return interactions_.at(index);
|
||||
}
|
||||
|
||||
QPair<iterator, bool>
|
||||
MessageListModel::insert(int index, 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 (index >= size()) {
|
||||
auto iterator = insertMessage(interactions_.end(), message);
|
||||
return qMakePair(iterator, true);
|
||||
}
|
||||
insertMessage(index, message);
|
||||
return qMakePair(interactions_.end(), true);
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::indexOfMessage(const 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
|
||||
MessageListModel::updateReplies(item_t& message)
|
||||
{
|
||||
auto replyId = message.second.commit["reply-to"];
|
||||
auto commitId = message.second.commit["id"];
|
||||
if (!replyId.isEmpty()) {
|
||||
replyTo_[replyId].insert(commitId);
|
||||
}
|
||||
for (const auto& msgId : replyTo_[commitId]) {
|
||||
int index = getIndexOfMessage(msgId);
|
||||
if (index == -1)
|
||||
continue;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToAuthor, Role::ReplyToBody});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::insertMessage(int index, item_t& message)
|
||||
{
|
||||
Q_EMIT beginInsertRows(QModelIndex(), index, index);
|
||||
interactions_.insert(index, message);
|
||||
Q_EMIT endInsertRows();
|
||||
updateReplies(message);
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::insertMessage(iterator it, item_t& message)
|
||||
{
|
||||
auto index = std::distance(begin(), it);
|
||||
Q_EMIT beginInsertRows(QModelIndex(), index, index);
|
||||
auto insertion = interactions_.insert(it, message);
|
||||
Q_EMIT endInsertRows();
|
||||
updateReplies(message);
|
||||
return insertion;
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::removeMessage(int index, iterator it)
|
||||
{
|
||||
Q_EMIT beginRemoveRows(QModelIndex(), index, index);
|
||||
interactions_.erase(it);
|
||||
Q_EMIT endRemoveRows();
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::contains(const QString& msgId)
|
||||
{
|
||||
return find(msgId) != interactions_.end();
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return interactions_.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
MessageListModel::roleNames() const
|
||||
{
|
||||
using namespace MessageList;
|
||||
QHash<int, QByteArray> roles;
|
||||
#define X(role) roles[role] = #role;
|
||||
MSG_ROLES
|
||||
#undef X
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::isOnlyEmoji(const QString& text) const
|
||||
static bool
|
||||
isOnlyEmoji(const QString& text)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return false;
|
||||
|
@ -370,14 +49,408 @@ MessageListModel::isOnlyEmoji(const QString& text) const
|
|||
return true;
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::dataForItem(item_t item, int, int role) const
|
||||
namespace lrc {
|
||||
|
||||
using namespace api;
|
||||
|
||||
MessageListModel::MessageListModel(const account::Info* account, QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, account_(account)
|
||||
{}
|
||||
|
||||
int
|
||||
MessageListModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
QString replyId = item.second.commit["reply-to"];
|
||||
int repliedMsg = -1;
|
||||
if (!replyId.isEmpty() && (role == Role::ReplyToAuthor || role == Role::ReplyToBody)) {
|
||||
repliedMsg = getIndexOfMessage(replyId);
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
return interactions_.size();
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
|
||||
return {};
|
||||
}
|
||||
return dataForItem(interactions_.at(index.row()), index.row(), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
MessageListModel::roleNames() const
|
||||
{
|
||||
using namespace MessageList;
|
||||
QHash<int, QByteArray> roles;
|
||||
#define X(role) roles[role] = #role;
|
||||
MSG_ROLES
|
||||
#undef X
|
||||
return roles;
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::data(const QString& id, int role) const
|
||||
{
|
||||
return data(indexOfMessage(id), role);
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::empty() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
return interactions_.empty();
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::indexOfMessage(const QString& messageId) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto it = std::find_if(interactions_.rbegin(),
|
||||
interactions_.rend(),
|
||||
[&messageId](const auto& it) { return it.first == messageId; });
|
||||
if (it == interactions_.rend()) {
|
||||
return -1;
|
||||
}
|
||||
return std::distance(it, interactions_.rend()) - 1;
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::clear()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
beginResetModel();
|
||||
interactions_.clear();
|
||||
replyTo_.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::reloadHistory()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
beginResetModel();
|
||||
for (auto& interaction : interactions_) {
|
||||
interaction.second.linkPreviewInfo.clear();
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::insert(const QString& id, const interaction::Info& interaction, int index)
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
// If the index parameter is -1, then insert at the parent of the message.
|
||||
if (index == -1) {
|
||||
index = indexOfMessage(interaction.parentId);
|
||||
}
|
||||
// The index should be valid and don't add duplicate messages.
|
||||
if (index < 0 || index > interactions_.size() || find(id) != interactions_.end()) {
|
||||
return false;
|
||||
}
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
interactions_.emplace(interactions_.cbegin() + index, id, interaction);
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::append(const QString& id, const interaction::Info& interaction)
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
// Don't add duplicate messages.
|
||||
if (find(id) != interactions_.end()) {
|
||||
return false;
|
||||
}
|
||||
beginInsertRows(QModelIndex(), interactions_.size(), interactions_.size());
|
||||
interactions_.emplace_back(id, interaction);
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::update(const QString& id, const interaction::Info& interaction)
|
||||
{
|
||||
// There are two cases: a) Parent ID changed, b) body changed (edit/delete).
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto it = find(id);
|
||||
if (find(id) == interactions_.end()) {
|
||||
return false;
|
||||
}
|
||||
interaction::Info& current = it->second;
|
||||
if (current.parentId != interaction.parentId) {
|
||||
// Parent ID changed, in this case, move the interaction to the new parent.
|
||||
it->second.parentId = interaction.parentId;
|
||||
auto newIndex = move(it, interaction.parentId);
|
||||
if (newIndex >= 0) {
|
||||
// The iterator is invalid now. But we can update all the roles.
|
||||
auto modelIndex = QAbstractListModel::index(newIndex);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, roleNames().keys());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Just update bodies notify the view.
|
||||
current.body = interaction.body;
|
||||
current.previousBodies = interaction.previousBodies;
|
||||
current.parsedBody = interaction.parsedBody;
|
||||
auto modelIndex = QAbstractListModel::index(indexOfMessage(id), 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Body, Role::PreviousBodies, Role::ParsedBody});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::updateStatus(const QString& id,
|
||||
interaction::Status newStatus,
|
||||
const QString& newBody)
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto it = find(id);
|
||||
if (it == interactions_.end()) {
|
||||
return false;
|
||||
}
|
||||
VectorInt roles;
|
||||
it->second.status = newStatus;
|
||||
roles.push_back(Role::Status);
|
||||
if (!newBody.isEmpty()) {
|
||||
it->second.body = newBody;
|
||||
roles.push_back(Role::Body);
|
||||
}
|
||||
auto modelIndex = QAbstractListModel::index(indexOfMessage(id), 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, roles);
|
||||
return true;
|
||||
}
|
||||
|
||||
QPair<bool, bool>
|
||||
MessageListModel::addOrUpdate(const QString& id, const interaction::Info& interaction)
|
||||
{
|
||||
if (find(id) == interactions_.end()) {
|
||||
// The ID doesn't exist, appending cannot fail here.
|
||||
return {true, append(id, interaction)};
|
||||
} else {
|
||||
// Update can only fail if the new parent ID is invalid.
|
||||
return {false, update(id, interaction)};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::forEach(const InteractionCb& callback)
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
for (auto& interaction : interactions_) {
|
||||
callback(interaction.first, interaction.second);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::with(const QString& idHint, const InteractionCb& callback)
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
if (interactions_.empty()) {
|
||||
return false;
|
||||
}
|
||||
// If the ID is empty, then return the last interaction.
|
||||
auto it = idHint.isEmpty() ? std::prev(interactions_.end()) : find(idHint);
|
||||
if (it == interactions_.end()) {
|
||||
return false;
|
||||
}
|
||||
callback(it->first, it->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MessageListModel::withLast(const InteractionCb& callback)
|
||||
{
|
||||
return with(QString(), callback);
|
||||
}
|
||||
|
||||
std::recursive_mutex&
|
||||
MessageListModel::getMutex()
|
||||
{
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::addHyperlinkInfo(const QString& messageId, const QVariantMap& info)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
int index = indexOfMessage(messageId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
interactions_[index].second.linkPreviewInfo = info;
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::LinkPreviewInfo});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::addReaction(const QString& messageId, const MapStringString& reaction)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
int index = indexOfMessage(messageId);
|
||||
if (index == -1)
|
||||
return;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
auto emoji = api::interaction::Emoji {reaction["id"], reaction["body"]};
|
||||
auto& pList = interactions_[index].second.reactions[reaction["author"]];
|
||||
QList<QVariant> newList = pList.toList();
|
||||
newList.emplace_back(QVariant::fromValue(emoji));
|
||||
pList = QVariantList::fromVector(newList);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::rmReaction(const QString& messageId, const QString& reactionId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
int index = indexOfMessage(messageId);
|
||||
if (index == -1)
|
||||
return;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
auto& reactions = interactions_[index].second.reactions;
|
||||
for (auto reactionIt = reactions.begin(); reactionIt != reactions.end(); ++reactionIt) {
|
||||
// Use a temporary QList to store updated emojis
|
||||
QList<QVariant> updatedEmojis;
|
||||
bool found = false;
|
||||
for (const auto& item : reactionIt.value().toList()) {
|
||||
auto emoji = item.value<api::interaction::Emoji>();
|
||||
if (emoji.commitId != reactionId || found)
|
||||
updatedEmojis.append(item);
|
||||
else {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
// Update the reactions with the modified list
|
||||
reactionIt.value() = QVariant::fromValue(updatedEmojis);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::setParsedMessage(const QString& messageId, const QString& parsed)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
int index = indexOfMessage(messageId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
interactions_[index].second.parsedBody = parsed;
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ParsedBody});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::setRead(const QString& peer, const QString& messageId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto i = lastDisplayedMessageUid_.find(peer);
|
||||
if (i != lastDisplayedMessageUid_.end()) {
|
||||
auto old = i.value();
|
||||
messageToReaders_[old].removeAll(peer);
|
||||
auto msgIdx = indexOfMessage(old);
|
||||
// Remove from latest read
|
||||
if (msgIdx != -1) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Readers});
|
||||
}
|
||||
}
|
||||
// update map
|
||||
lastDisplayedMessageUid_[peer] = messageId;
|
||||
messageToReaders_[messageId].append(peer);
|
||||
// update interaction
|
||||
auto msgIdx = indexOfMessage(messageId);
|
||||
// Remove from latest read
|
||||
if (msgIdx != -1) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Readers});
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
MessageListModel::getRead(const QString& peer)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto i = lastDisplayedMessageUid_.find(peer);
|
||||
if (i != lastDisplayedMessageUid_.end())
|
||||
return i.value();
|
||||
return "";
|
||||
}
|
||||
|
||||
QString
|
||||
MessageListModel::lastSelfMessageId(const QString& id) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
for (auto it = interactions_.rbegin(); it != interactions_.rend(); ++it) {
|
||||
auto lastType = it->second.type;
|
||||
if (lastType == interaction::Type::TEXT and !it->second.body.isEmpty()
|
||||
and (it->second.authorUri.isEmpty() || it->second.authorUri == id)) {
|
||||
return it->first;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QPair<QString, time_t>
|
||||
MessageListModel::getDisplayedInfoForPeer(const QString& peerId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lk(mutex_);
|
||||
auto it = lastDisplayedMessageUid_.find(peerId);
|
||||
if (it == lastDisplayedMessageUid_.end())
|
||||
return {};
|
||||
const auto interaction = find(it.value());
|
||||
if (interaction == interactions_.end())
|
||||
return {};
|
||||
return {it.value(), interaction->second.timestamp};
|
||||
}
|
||||
|
||||
MessageListModel::iterator
|
||||
MessageListModel::find(const QString& msgId)
|
||||
{
|
||||
// Note: assumes that the caller has locked the mutex.
|
||||
return std::find_if(interactions_.begin(), interactions_.end(), [&msgId](const auto& it) {
|
||||
return it.first == msgId;
|
||||
});
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::move(iterator it, const QString& newParentId)
|
||||
{
|
||||
// Note: assumes the new parent exists and that the caller has locked the mutex.
|
||||
auto oldIndex = indexOfMessage(it->first);
|
||||
auto newIndex = indexOfMessage(newParentId) + 1;
|
||||
if (newIndex >= 0 && oldIndex != newIndex) {
|
||||
qDebug() << "Moving message" << it->first << "from" << oldIndex << "to" << newIndex;
|
||||
beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
|
||||
interactions_.move(oldIndex, newIndex);
|
||||
endMoveRows();
|
||||
return newIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::data(int idx, int role) const
|
||||
{
|
||||
QModelIndex index = QAbstractListModel::index(idx, 0);
|
||||
return data(index, role);
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::dataForItem(const item_t& item, int, int role) const
|
||||
{
|
||||
// Used only for reply roles.
|
||||
const auto getReplyIndex = [this, &item, &role]() -> int {
|
||||
QString replyId = item.second.commit["reply-to"];
|
||||
int repliedMsgIndex = -1;
|
||||
if (!replyId.isEmpty() && (role == Role::ReplyToAuthor || role == Role::ReplyToBody)) {
|
||||
repliedMsgIndex = indexOfMessage(replyId);
|
||||
}
|
||||
return repliedMsgIndex;
|
||||
};
|
||||
|
||||
switch (role) {
|
||||
case Role::Id:
|
||||
return QVariant(item.first);
|
||||
|
@ -440,16 +513,19 @@ MessageListModel::dataForItem(item_t item, int, int role) const
|
|||
return variantList;
|
||||
}
|
||||
case Role::ReplyTo:
|
||||
return QVariant(replyId);
|
||||
case Role::ReplyToAuthor:
|
||||
return repliedMsg == -1 ? QVariant("") : QVariant(data(repliedMsg, Role::Author));
|
||||
return QVariant(item.second.commit["reply-to"]);
|
||||
case Role::ReplyToAuthor: {
|
||||
const auto replyIndex = getReplyIndex();
|
||||
return replyIndex == -1 ? QVariant("") : data(replyIndex, Role::Author);
|
||||
}
|
||||
case Role::ReplyToBody: {
|
||||
if (repliedMsg == -1)
|
||||
const auto replyIndex = getReplyIndex();
|
||||
if (replyIndex == -1)
|
||||
return QVariant("");
|
||||
auto parsed = data(repliedMsg, Role::ParsedBody).toString();
|
||||
auto parsed = data(replyIndex, Role::ParsedBody).toString();
|
||||
if (!parsed.isEmpty())
|
||||
return QVariant(parsed);
|
||||
return QVariant(data(repliedMsg, Role::Body).toString());
|
||||
return QVariant(data(replyIndex, Role::Body).toString());
|
||||
}
|
||||
case Role::TotalSize:
|
||||
return QVariant(item.second.commit["totalSize"].toInt());
|
||||
|
@ -460,173 +536,35 @@ MessageListModel::dataForItem(item_t item, int, int role) const
|
|||
case Role::Readers:
|
||||
return QVariant(messageToReaders_[item.first]);
|
||||
case Role::IsEmojiOnly:
|
||||
return QVariant(replyId.isEmpty() && item.second.previousBodies.isEmpty()
|
||||
&& isOnlyEmoji(item.second.body));
|
||||
return QVariant(item.second.commit["reply-to"].isEmpty()
|
||||
&& item.second.previousBodies.isEmpty() && isOnlyEmoji(item.second.body));
|
||||
case Role::Reactions:
|
||||
return QVariant(item.second.reactions);
|
||||
case Role::Index:
|
||||
// For DEBUG only
|
||||
return QVariant(indexOfMessage(item.first));
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::data(int idx, int role) const
|
||||
{
|
||||
QModelIndex index = QAbstractListModel::index(idx, 0);
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
|
||||
return {};
|
||||
}
|
||||
return dataForItem(interactions_.at(index.row()), index.row(), role);
|
||||
}
|
||||
|
||||
QVariant
|
||||
MessageListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
|
||||
return {};
|
||||
}
|
||||
return dataForItem(interactions_.at(index.row()), index.row(), role);
|
||||
}
|
||||
|
||||
int
|
||||
MessageListModel::getIndexOfMessage(const QString& messageId) const
|
||||
{
|
||||
for (int i = 0; i < interactions_.size(); i++) {
|
||||
if (atIndex(i).first == messageId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::addHyperlinkInfo(const QString& messageId, const QVariantMap& info)
|
||||
MessageListModel::updateReplies(const item_t& message)
|
||||
{
|
||||
int index = getIndexOfMessage(messageId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
auto replyId = message.second.commit["reply-to"];
|
||||
auto commitId = message.second.commit["id"];
|
||||
if (!replyId.isEmpty()) {
|
||||
replyTo_[replyId].insert(commitId);
|
||||
}
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
interactions_[index].second.linkPreviewInfo = info;
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::LinkPreviewInfo});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::addReaction(const QString& messageId, const MapStringString& reaction)
|
||||
{
|
||||
int index = getIndexOfMessage(messageId);
|
||||
if (index == -1)
|
||||
return;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
auto emoji = api::interaction::Emoji {reaction["id"], reaction["body"]};
|
||||
auto& pList = interactions_[index].second.reactions[reaction["author"]];
|
||||
QList<QVariant> newList = pList.toList();
|
||||
newList.emplace_back(QVariant::fromValue(emoji));
|
||||
pList = QVariantList::fromVector(newList);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::rmReaction(const QString& messageId, const QString& reactionId)
|
||||
{
|
||||
int index = getIndexOfMessage(messageId);
|
||||
if (index == -1)
|
||||
return;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
|
||||
auto& reactions = interactions_[index].second.reactions;
|
||||
for (const auto& key : reactions.keys()) {
|
||||
QList<QVariant> emojis = reactions[key].toList();
|
||||
for (auto it = emojis.begin(); it != emojis.end(); ++it) {
|
||||
auto emoji = it->value<api::interaction::Emoji>();
|
||||
if (emoji.commitId == reactionId) {
|
||||
emojis.erase(it);
|
||||
reactions[key] = emojis;
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Reactions});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Use a const reference to avoid detaching
|
||||
const auto& replies = replyTo_[commitId];
|
||||
for (const auto& msgId : replies) {
|
||||
int index = indexOfMessage(msgId);
|
||||
if (index == -1)
|
||||
continue;
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ReplyToAuthor, Role::ReplyToBody});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::setParsedMessage(const QString& messageId, const QString& parsed)
|
||||
{
|
||||
int index = getIndexOfMessage(messageId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
interactions_[index].second.parsedBody = parsed;
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::ParsedBody});
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::setRead(const QString& peer, const QString& messageId)
|
||||
{
|
||||
auto i = lastDisplayedMessageUid_.find(peer);
|
||||
if (i != lastDisplayedMessageUid_.end()) {
|
||||
auto old = i.value();
|
||||
messageToReaders_[old].removeAll(peer);
|
||||
auto msgIdx = getIndexOfMessage(old);
|
||||
// Remove from latest read
|
||||
if (msgIdx != -1) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Readers});
|
||||
}
|
||||
}
|
||||
// update map
|
||||
lastDisplayedMessageUid_[peer] = messageId;
|
||||
messageToReaders_[messageId].append(peer);
|
||||
// update interaction
|
||||
auto msgIdx = getIndexOfMessage(messageId);
|
||||
// Remove from latest read
|
||||
if (msgIdx != -1) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(msgIdx, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::Readers});
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
MessageListModel::getRead(const QString& peer)
|
||||
{
|
||||
auto i = lastDisplayedMessageUid_.find(peer);
|
||||
if (i != lastDisplayedMessageUid_.end())
|
||||
return i.value();
|
||||
return "";
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::emitDataChanged(iterator it, VectorInt roles)
|
||||
{
|
||||
auto index = std::distance(begin(), it);
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, roles);
|
||||
}
|
||||
|
||||
void
|
||||
MessageListModel::emitDataChanged(const QString& msgId, VectorInt roles)
|
||||
{
|
||||
int index = getIndexOfMessage(msgId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, roles);
|
||||
}
|
||||
|
||||
QString
|
||||
MessageListModel::lastSelfMessageId(const QString& id) const
|
||||
{
|
||||
for (auto it = interactions_.rbegin(); it != interactions_.rend(); ++it) {
|
||||
auto lastType = it->second.type;
|
||||
if (lastType == interaction::Type::TEXT and !it->second.body.isEmpty()
|
||||
and (it->second.authorUri.isEmpty() || it->second.authorUri == id)) {
|
||||
return it->first;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace lrc
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2023 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
|
||||
* Author: Trevor Tabah <trevor.tabah@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
|
||||
|
||||
#include "api/interaction.h"
|
||||
#include "api/account.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
namespace lrc {
|
||||
namespace api {
|
||||
|
||||
namespace interaction {
|
||||
struct Info;
|
||||
}
|
||||
|
||||
#define MSG_ROLES \
|
||||
X(Id) \
|
||||
X(Author) \
|
||||
X(Body) \
|
||||
X(ParentId) \
|
||||
X(Timestamp) \
|
||||
X(Duration) \
|
||||
X(Type) \
|
||||
X(Status) \
|
||||
X(IsRead) \
|
||||
X(ContactAction) \
|
||||
X(ActionUri) \
|
||||
X(ConfId) \
|
||||
X(DeviceId) \
|
||||
X(LinkPreviewInfo) \
|
||||
X(ParsedBody) \
|
||||
X(PreviousBodies) \
|
||||
X(Reactions) \
|
||||
X(ReplyTo) \
|
||||
X(ReplyToBody) \
|
||||
X(ReplyToAuthor) \
|
||||
X(TotalSize) \
|
||||
X(TransferName) \
|
||||
X(FileExtension) \
|
||||
X(Readers) \
|
||||
X(IsEmojiOnly)
|
||||
|
||||
namespace MessageList {
|
||||
Q_NAMESPACE
|
||||
enum Role {
|
||||
DummyRole = Qt::UserRole + 1,
|
||||
#define X(role) role,
|
||||
MSG_ROLES
|
||||
#undef X
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace MessageList
|
||||
|
||||
class MessageListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using item_t = const QPair<QString, interaction::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;
|
||||
|
||||
explicit MessageListModel(const account::Info* account, QObject* parent = nullptr);
|
||||
~MessageListModel() = default;
|
||||
|
||||
// map functions
|
||||
QPair<iterator, bool> emplace(const QString& msgId,
|
||||
interaction::Info message,
|
||||
bool beginning = false);
|
||||
iterator find(const QString& msgId);
|
||||
iterator findActiveCall(const MapStringString& commit);
|
||||
iterator erase(const iterator& it);
|
||||
|
||||
constIterator find(const QString& msgId) const;
|
||||
QPair<iterator, bool> insert(std::pair<QString, interaction::Info> message,
|
||||
bool beginning = false);
|
||||
Q_INVOKABLE int erase(const QString& msgId);
|
||||
interaction::Info& operator[](const QString& messageId);
|
||||
iterator end();
|
||||
constIterator end() const;
|
||||
reverseIterator rend();
|
||||
|
||||
constIterator cend() const;
|
||||
iterator begin();
|
||||
constIterator begin() const;
|
||||
reverseIterator rbegin();
|
||||
Q_INVOKABLE int size() const;
|
||||
void clear();
|
||||
void reloadHistory();
|
||||
bool empty() const;
|
||||
interaction::Info at(const QString& intId) const;
|
||||
QPair<QString, interaction::Info> front() const;
|
||||
QPair<QString, interaction::Info> last() const;
|
||||
QPair<QString, interaction::Info> atIndex(int index) const;
|
||||
|
||||
QPair<iterator, bool> insert(int index, QPair<QString, interaction::Info> message);
|
||||
int indexOfMessage(const QString& msgId, bool reverse = true) const;
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
Q_INVOKABLE virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||
Q_INVOKABLE virtual QVariant data(int idx, int role = Qt::DisplayRole) const;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
QVariant dataForItem(item_t item, int indexRow, int role = Qt::DisplayRole) const;
|
||||
bool contains(const QString& msgId);
|
||||
int getIndexOfMessage(const QString& messageId) const;
|
||||
void addHyperlinkInfo(const QString& messageId, const QVariantMap& info);
|
||||
void addReaction(const QString& messageId, const MapStringString& reaction);
|
||||
void rmReaction(const QString& messageId, const QString& reactionId);
|
||||
void setParsedMessage(const QString& messageId, const QString& parsed);
|
||||
|
||||
void setRead(const QString& peer, const QString& messageId);
|
||||
QString getRead(const QString& peer);
|
||||
|
||||
// use these if the underlying data model is changed from conversationmodel
|
||||
// Note: this is not ideal, and this class should be refactored into a proper
|
||||
// view model and absorb the interaction management logic to avoid exposing
|
||||
// these emission wrappers
|
||||
void emitDataChanged(iterator it, VectorInt roles = {});
|
||||
void emitDataChanged(const QString& msgId, VectorInt roles = {});
|
||||
bool isOnlyEmoji(const QString& text) const;
|
||||
|
||||
QVariantMap convertReactMessagetoQVariant(const QSet<QString>&);
|
||||
QString lastSelfMessageId(const QString& id) const;
|
||||
|
||||
protected:
|
||||
using Role = MessageList::Role;
|
||||
|
||||
private:
|
||||
QList<QPair<QString, interaction::Info>> interactions_;
|
||||
// Note: because read status are updated even if interaction is not loaded
|
||||
// we need to keep track of these status outside the interaction::Info
|
||||
// lastDisplayedMessageUid_ stores: {"peerId":"messageId"}
|
||||
// messageToReaders_ caches: "messageId":["peer1", "peer2"]
|
||||
// to allow quick access.
|
||||
QMap<QString, QString> lastDisplayedMessageUid_;
|
||||
QMap<QString, QStringList> messageToReaders_;
|
||||
QMap<QString, QSet<QString>> replyTo_;
|
||||
const account::Info* account_;
|
||||
void updateReplies(item_t& message);
|
||||
|
||||
void insertMessage(int index, item_t& message);
|
||||
iterator insertMessage(iterator it, item_t& message);
|
||||
void removeMessage(int index, iterator it);
|
||||
};
|
||||
} // namespace api
|
||||
} // namespace lrc
|
||||
Q_DECLARE_METATYPE(lrc::api::MessageListModel*)
|
Loading…
Add table
Reference in a new issue