diff --git a/src/MessageDb.cpp b/src/MessageDb.cpp
index f5a46ee..adfcedf 100644
--- a/src/MessageDb.cpp
+++ b/src/MessageDb.cpp
@@ -1,290 +1,295 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see .
*/
#include "MessageDb.h"
// Kaidan
#include "Globals.h"
#include "Message.h"
#include "Utils.h"
// Qt
#include
#include
#include
#include
#include
MessageDb::MessageDb(QObject *parent)
: QObject(parent)
{
connect(this, &MessageDb::fetchMessagesRequested,
this, &MessageDb::fetchMessages);
}
void MessageDb::parseMessagesFromQuery(QSqlQuery &query, QVector &msgs)
{
// get indexes of attributes
QSqlRecord rec = query.record();
int idxFrom = rec.indexOf("author");
int idxTo = rec.indexOf("recipient");
int idxStamp = rec.indexOf("timestamp");
int idxId = rec.indexOf("id");
int idxBody = rec.indexOf("message");
int idxIsSent = rec.indexOf("isSent");
int idxIsDelivered = rec.indexOf("isDelivered");
int idxMediaType = rec.indexOf("type");
int idxOutOfBandUrl = rec.indexOf("mediaUrl");
int idxMediaContentType = rec.indexOf("mediaContentType");
int idxMediaLocation = rec.indexOf("mediaLocation");
int idxMediaSize = rec.indexOf("mediaSize");
int idxMediaLastModified = rec.indexOf("mediaLastModified");
int idxIsEdited = rec.indexOf("edited");
int idxSpoilerHint = rec.indexOf("spoilerHint");
int idxIsSpoiler = rec.indexOf("isSpoiler");
while (query.next()) {
Message msg;
msg.setFrom(query.value(idxFrom).toString());
msg.setTo(query.value(idxTo).toString());
msg.setStamp(QDateTime::fromString(
query.value(idxStamp).toString(),
Qt::ISODate
));
msg.setId(query.value(idxId).toString());
msg.setBody(query.value(idxBody).toString());
msg.setIsSent(query.value(idxIsSent).toBool());
msg.setIsDelivered(query.value(idxIsDelivered).toBool());
msg.setMediaType(static_cast(query.value(idxMediaType).toInt()));
msg.setOutOfBandUrl(query.value(idxOutOfBandUrl).toString());
msg.setMediaContentType(query.value(idxMediaContentType).toString());
msg.setMediaLocation(query.value(idxMediaLocation).toString());
msg.setMediaSize(query.value(idxMediaSize).toLongLong());
msg.setMediaLastModified(QDateTime::fromMSecsSinceEpoch(
query.value(idxMediaLastModified).toLongLong()
));
msg.setIsEdited(query.value(idxIsEdited).toBool());
msg.setSpoilerHint(query.value(idxSpoilerHint).toString());
msg.setIsSpoiler(query.value(idxIsSpoiler).toBool());
msgs << msg;
}
}
QSqlRecord MessageDb::createUpdateRecord(const Message &oldMsg, const Message &newMsg)
{
QSqlRecord rec;
if (oldMsg.from() != newMsg.from())
rec.append(Utils::createSqlField("author", newMsg.from()));
if (oldMsg.to() != newMsg.to())
rec.append(Utils::createSqlField("recipient", newMsg.to()));
if (oldMsg.stamp() != newMsg.stamp())
rec.append(Utils::createSqlField(
"timestamp",
newMsg.stamp().toString(Qt::ISODate)
));
- if (oldMsg.id() != newMsg.id())
- rec.append(Utils::createSqlField("id", newMsg.id()));
+ if (oldMsg.id() != newMsg.id()) {
+ // TODO: remove as soon as 'NOT NULL' was removed from id column
+ if (newMsg.id().isEmpty())
+ rec.append(Utils::createSqlField("id", QStringLiteral(" ")));
+ else
+ rec.append(Utils::createSqlField("id", newMsg.id()));
+ }
if (oldMsg.body() != newMsg.body())
rec.append(Utils::createSqlField("message", newMsg.body()));
if (oldMsg.isSent() != newMsg.isSent())
rec.append(Utils::createSqlField("isSent", newMsg.isSent()));
if (oldMsg.isDelivered() != newMsg.isDelivered())
rec.append(Utils::createSqlField("isDelivered", newMsg.isDelivered()));
if (oldMsg.mediaType() != newMsg.mediaType())
rec.append(Utils::createSqlField("type", int(newMsg.mediaType())));
if (oldMsg.outOfBandUrl() != newMsg.outOfBandUrl())
rec.append(Utils::createSqlField("mediaUrl", newMsg.outOfBandUrl()));
if (oldMsg.mediaContentType() != newMsg.mediaContentType())
rec.append(Utils::createSqlField(
"mediaContentType",
newMsg.mediaContentType()
));
if (oldMsg.mediaLocation() != newMsg.mediaLocation())
rec.append(Utils::createSqlField(
"mediaLocation",
newMsg.mediaLocation()
));
if (oldMsg.mediaSize() != newMsg.mediaSize())
rec.append(Utils::createSqlField("mediaSize", newMsg.mediaSize()));
if (oldMsg.mediaLastModified() != newMsg.mediaLastModified())
rec.append(Utils::createSqlField(
"mediaLastModified",
newMsg.mediaLastModified().toMSecsSinceEpoch()
));
if (oldMsg.isEdited() != newMsg.isEdited())
rec.append(Utils::createSqlField("edited", newMsg.isEdited()));
if (oldMsg.spoilerHint() != newMsg.spoilerHint())
rec.append(Utils::createSqlField("spoilerHint", newMsg.spoilerHint()));
if (oldMsg.isSpoiler() != newMsg.isSpoiler())
rec.append(Utils::createSqlField("isSpoiler", newMsg.isSpoiler()));
return rec;
}
void MessageDb::fetchMessages(const QString &user1, const QString &user2, int index)
{
QSqlQuery query(QSqlDatabase::database(DB_CONNECTION));
query.setForwardOnly(true);
QMap bindValues;
bindValues[":user1"] = user1;
bindValues[":user2"] = user2;
bindValues[":index"] = index;
bindValues[":limit"] = DB_MSG_QUERY_LIMIT;
Utils::execQuery(
query,
"SELECT * FROM Messages "
"WHERE (author = :user1 AND recipient = :user2) OR "
"(author = :user2 AND recipient = :user1) "
"ORDER BY timestamp DESC "
"LIMIT :index, :limit",
bindValues
);
QVector messages;
parseMessagesFromQuery(query, messages);
emit messagesFetched(messages);
}
void MessageDb::addMessage(const Message &msg)
{
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlRecord record = db.record(DB_TABLE_MESSAGES);
record.setValue("author", msg.from());
record.setValue("recipient", msg.to());
record.setValue("timestamp", msg.stamp().toString(Qt::ISODate));
record.setValue("message", msg.body());
record.setValue("id", msg.id().isEmpty() ? " " : msg.id());
record.setValue("isSent", msg.isSent());
record.setValue("isDelivered", msg.isDelivered());
record.setValue("type", int(msg.mediaType()));
record.setValue("edited", msg.isEdited());
record.setValue("isSpoiler", msg.isSpoiler());
record.setValue("spoilerHint", msg.spoilerHint());
record.setValue("mediaUrl", msg.outOfBandUrl());
record.setValue("mediaContentType", msg.mediaContentType());
record.setValue("mediaLocation", msg.mediaLocation());
record.setValue("mediaSize", msg.mediaSize());
record.setValue("mediaLastModified", msg.mediaLastModified().toMSecsSinceEpoch());
QSqlQuery query(db);
Utils::execQuery(query, db.driver()->sqlStatement(
QSqlDriver::InsertStatement,
DB_TABLE_MESSAGES,
record,
false
));
}
void MessageDb::removeMessage(const QString &id)
{
QSqlQuery query(QSqlDatabase::database(DB_CONNECTION));
Utils::execQuery(
query,
"DELETE FROM Messages WHERE id = ?",
QVector() << id
);
}
void MessageDb::updateMessage(const QString &id,
const std::function &updateMsg)
{
// load current message item from db
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
query.setForwardOnly(true);
Utils::execQuery(
query,
"SELECT * FROM Messages WHERE id = ? LIMIT 1",
QVector() << id
);
QVector msgs;
parseMessagesFromQuery(query, msgs);
// update loaded item
if (!msgs.isEmpty()) {
Message msg = msgs.first();
updateMsg(msg);
// replace old message with updated one, if message has changed
if (msgs.first() != msg) {
// create an SQL record with only the differences
QSqlRecord rec = createUpdateRecord(msgs.first(), msg);
Utils::execQuery(
query,
db.driver()->sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_MESSAGES,
rec,
false
) +
Utils::simpleWhereStatement(db.driver(), "id", id)
);
}
}
}
void MessageDb::updateMessageRecord(const QString &id,
const QSqlRecord &updateRecord)
{
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
Utils::execQuery(
query,
db.driver()->sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_MESSAGES,
updateRecord,
false
) +
Utils::simpleWhereStatement(db.driver(), "id", id)
);
}
void MessageDb::setMessageAsSent(const QString &msgId)
{
QSqlRecord rec;
rec.append(Utils::createSqlField("isSent", true));
updateMessageRecord(msgId, rec);
}
void MessageDb::setMessageAsDelivered(const QString &msgId)
{
QSqlRecord rec;
rec.append(Utils::createSqlField("isDelivered", true));
updateMessageRecord(msgId, rec);
}
diff --git a/src/RosterDb.cpp b/src/RosterDb.cpp
index 31a5c3b..ce14c6c 100644
--- a/src/RosterDb.cpp
+++ b/src/RosterDb.cpp
@@ -1,256 +1,256 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see .
*/
#include "RosterDb.h"
// Kaidan
#include "Database.h"
#include "Globals.h"
#include "Utils.h"
// Qt
#include
#include
#include
#include
#include
RosterDb::RosterDb(Database *db, QObject *parent)
: QObject(parent),
m_db(db)
{
connect(this, &RosterDb::fetchItemsRequested, this, &RosterDb::fetchItems);
}
void RosterDb::parseItemsFromQuery(QSqlQuery &query, QVector &items)
{
QSqlRecord rec = query.record();
int idxJid = rec.indexOf("jid");
int idxName = rec.indexOf("name");
int idxLastExchanged = rec.indexOf("lastExchanged");
int idxUnreadMessages = rec.indexOf("unreadMessages");
int idxLastMessage = rec.indexOf("lastMessage");
while (query.next()) {
RosterItem item;
item.setJid(query.value(idxJid).toString());
item.setName(query.value(idxName).toString());
item.setLastExchanged(QDateTime::fromString(
query.value(idxLastExchanged).toString(),
Qt::ISODateWithMs
));
item.setUnreadMessages(query.value(idxUnreadMessages).toInt());
item.setLastMessage(query.value(idxLastMessage).toString());
items << item;
}
}
QSqlRecord RosterDb::createUpdateRecord(const RosterItem &oldItem, const RosterItem &newItem)
{
QSqlRecord rec;
if (oldItem.jid() != newItem.jid())
rec.append(Utils::createSqlField("jid", newItem.jid()));
if (oldItem.name() != newItem.name())
rec.append(Utils::createSqlField("name", oldItem.name()));
if (oldItem.lastMessage() != newItem.lastMessage())
rec.append(Utils::createSqlField("lastMessage", newItem.lastMessage()));
if (oldItem.lastExchanged() != newItem.lastExchanged())
rec.append(Utils::createSqlField(
- "lastExchanged",
- oldItem.lastExchanged().toString(Qt::ISODateWithMs)
+ "lastExchanged",
+ newItem.lastExchanged().toString(Qt::ISODateWithMs)
));
if (oldItem.unreadMessages() != newItem.unreadMessages())
rec.append(Utils::createSqlField(
- "unreadMessages",
- newItem.unreadMessages()
+ "unreadMessages",
+ newItem.unreadMessages()
));
return rec;
}
void RosterDb::addItem(const RosterItem &item)
{
addItems(QVector() << item);
}
void RosterDb::addItems(const QVector &items)
{
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
m_db->transaction();
QSqlQuery query(db);
Utils::prepareQuery(query, db.driver()->sqlStatement(
QSqlDriver::InsertStatement,
DB_TABLE_ROSTER,
db.record(DB_TABLE_ROSTER),
true
));
for (const auto &item : items) {
query.addBindValue(item.jid());
query.addBindValue(item.name());
query.addBindValue(item.lastExchanged().toString(Qt::ISODateWithMs));
query.addBindValue(item.unreadMessages());
query.addBindValue(item.lastMessage());
Utils::execQuery(query);
}
m_db->commit();
}
void RosterDb::removeItem(const QString &jid)
{
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
Utils::execQuery(
query,
"DELETE FROM Roster WHERE jid = ?",
QVector() << jid
);
}
void RosterDb::updateItem(const QString &jid,
const std::function &updateItem)
{
// load current roster item from db
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
query.setForwardOnly(true);
Utils::execQuery(
query,
"SELECT * FROM Roster WHERE jid = ? LIMIT 1",
QVector() << jid
);
QVector items;
parseItemsFromQuery(query, items);
// update loaded item
if (!items.isEmpty()) {
RosterItem item = items.first();
updateItem(item);
// replace old item with updated one, if item has changed
if (items.first() != item) {
// create an SQL record with only the differences
QSqlRecord rec = createUpdateRecord(items.first(), item);
Utils::execQuery(
query,
db.driver()->sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_ROSTER,
rec,
false
) +
Utils::simpleWhereStatement(db.driver(), "jid", jid)
);
}
}
}
void RosterDb::replaceItems(const QHash &items)
{
// load current items
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
query.setForwardOnly(true);
Utils::execQuery(query, "SELECT * FROM Roster");
QVector currentItems;
parseItemsFromQuery(query, currentItems);
m_db->transaction();
QSet newJids = items.keys().toSet();
for (const auto &oldItem : qAsConst(currentItems)) {
// We will remove the already existing JIDs, so we get a set of JIDs that
// are completely new.
//
// By calling remove(), we also find out whether the JID is already
// existing or not.
if (newJids.remove(oldItem.jid())) {
// item is also included in newJids -> update
// name is (currently) the only attribute that is defined by the
// XMPP roster and so could cause a change
if (oldItem.name() != items[oldItem.jid()].name())
setItemName(oldItem.jid(), items[oldItem.jid()].name());
} else {
// item is not included in newJids -> delete
removeItem(oldItem.jid());
}
}
// now add the completely new JIDs
for (const QString &jid : newJids)
addItem(items[jid]);
m_db->commit();
}
void RosterDb::setItemName(const QString &jid, const QString &name)
{
QSqlDatabase db = QSqlDatabase::database(DB_CONNECTION);
QSqlQuery query(db);
QSqlRecord rec;
rec.append(Utils::createSqlField("name", name));
Utils::execQuery(
query,
db.driver()->sqlStatement(
QSqlDriver::UpdateStatement,
DB_TABLE_ROSTER,
rec,
false
) +
Utils::simpleWhereStatement(db.driver(), "jid", jid)
);
}
void RosterDb::clearAll()
{
QSqlQuery query(QSqlDatabase::database(DB_CONNECTION));
Utils::execQuery(query, "DELETE FROM Roster");
}
void RosterDb::fetchItems()
{
QSqlQuery query(QSqlDatabase::database(DB_CONNECTION));
query.setForwardOnly(true);
- Utils::execQuery(query, "SELECT * FROM Roster ORDER BY lastExchanged");
+ Utils::execQuery(query, "SELECT * FROM Roster");
QVector items;
parseItemsFromQuery(query, items);
emit itemsFetched(items);
}
diff --git a/src/RosterModel.cpp b/src/RosterModel.cpp
index f89ab09..e18fdd1 100644
--- a/src/RosterModel.cpp
+++ b/src/RosterModel.cpp
@@ -1,237 +1,244 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see .
*/
#include "RosterModel.h"
// Kaidan
#include "RosterDb.h"
#include "MessageModel.h"
// C++
#include
// Qt
#include
#include
#include
#include
RosterModel::RosterModel(RosterDb *rosterDb, QObject *parent)
: QAbstractListModel(parent),
rosterDb(rosterDb)
{
connect(rosterDb, &RosterDb::itemsFetched,
this, &RosterModel::handleItemsFetched);
connect(this, &RosterModel::addItemRequested, this, &RosterModel::addItem);
connect(this, &RosterModel::addItemRequested, rosterDb, &RosterDb::addItem);
connect(this, &RosterModel::removeItemRequested,
this, &RosterModel::removeItem);
connect(this, &RosterModel::removeItemRequested,
rosterDb, &RosterDb::removeItem);
connect(this, &RosterModel::updateItemRequested,
this, &RosterModel::updateItem);
connect(this, &RosterModel::updateItemRequested,
rosterDb, &RosterDb::updateItem);
connect(this, &RosterModel::replaceItemsRequested,
this, &RosterModel::replaceItems);
connect(this, &RosterModel::replaceItemsRequested,
rosterDb, &RosterDb::replaceItems);
emit rosterDb->fetchItemsRequested();
}
void RosterModel::setMessageModel(MessageModel *model)
{
connect(model, &MessageModel::chatPartnerChanged,
this, [=] (const QString &chatPartner) {
// reset unread message counter
emit updateItemRequested(chatPartner,
[] (RosterItem &item) {
item.setUnreadMessages(0);
});
});
}
bool RosterModel::isEmpty() const
{
return m_items.isEmpty();
}
int RosterModel::rowCount(const QModelIndex&) const
{
return m_items.length();
}
QHash RosterModel::roleNames() const
{
QHash roles;
roles[JidRole] = "jid";
roles[NameRole] = "name";
roles[LastExchangedRole] = "lastExchanged";
roles[UnreadMessagesRole] = "unreadMessages";
roles[LastMessageRole] = "lastMessage";
return roles;
}
QVariant RosterModel::data(const QModelIndex &index, int role) const
{
if (!hasIndex(index.row(), index.column(), index.parent())) {
qWarning() << "Could not get data from roster model." << index << role;
return {};
}
switch (role) {
case JidRole:
return m_items.at(index.row()).jid();
case NameRole:
return m_items.at(index.row()).name();
case LastExchangedRole:
return m_items.at(index.row()).lastExchanged();
case UnreadMessagesRole:
return m_items.at(index.row()).unreadMessages();
case LastMessageRole:
return m_items.at(index.row()).lastMessage();
}
return {};
}
void RosterModel::handleItemsFetched(const QVector &items)
{
beginResetModel();
m_items = items;
+ std::sort(
+ m_items.begin(),
+ m_items.end(),
+ [] (const RosterItem &a, const RosterItem &b) {
+ return a.lastExchanged() > b.lastExchanged();
+ }
+ );
endResetModel();
}
void RosterModel::addItem(const RosterItem &item)
{
// prepend the item, if no timestamp is set
if (item.lastExchanged().isNull()) {
insertContact(0, item);
return;
}
// index where to add the new contact
int i = 0;
for (const auto &itrItem : qAsConst(m_items)) {
if (item.lastExchanged().toMSecsSinceEpoch() >= itrItem.lastExchanged().toMSecsSinceEpoch()) {
insertContact(i, item);
return;
}
i++;
}
// append the item to the end of the list
insertContact(i, item);
}
void RosterModel::removeItem(const QString &jid)
{
QMutableVectorIterator itr(m_items);
int i = 0;
while (itr.hasNext()) {
if (itr.next().jid() == jid) {
beginRemoveRows(QModelIndex(), i, i);
itr.remove();
endRemoveRows();
return;
}
i++;
}
}
void RosterModel::updateItem(const QString &jid,
const std::function &updateItem)
{
for (int i = 0; i < m_items.length(); i++) {
if (m_items.at(i).jid() == jid) {
// update item
RosterItem item = m_items.at(i);
updateItem(item);
// check if item was actually modified
if (m_items.at(i) == item)
return;
// check, if the position of the new item may be different
if (item.lastExchanged() == m_items.at(i).lastExchanged()) {
beginRemoveRows(QModelIndex(), i, i);
m_items.removeAt(i);
endRemoveRows();
// add the item at the same position
insertContact(i, item);
} else {
beginRemoveRows(QModelIndex(), i, i);
m_items.removeAt(i);
endRemoveRows();
// put to new position
addItem(item);
}
break;
}
}
}
void RosterModel::replaceItems(const QHash &items)
{
QVector newItems;
for (auto item : items) {
// find old item
auto oldItem = std::find_if(
m_items.begin(),
m_items.end(),
[&] (const RosterItem &oldItem) {
return oldItem.jid() == item.jid();
}
);
// use the old item's values, if found
if (oldItem != m_items.end()) {
item.setLastMessage(oldItem->lastMessage());
item.setLastExchanged(oldItem->lastExchanged());
item.setUnreadMessages(oldItem->unreadMessages());
}
newItems << item;
}
// replace all items
handleItemsFetched(newItems);
}
void RosterModel::insertContact(int i, const RosterItem &item)
{
beginInsertRows(QModelIndex(), i, i);
m_items.insert(i, item);
endInsertRows();
}