20 Commits

Author SHA1 Message Date
Blue 1706b93b59 Merge pull request 'Add Brazilian Portuguese translations' (#49) from brunofontes/squawk:ptBR_translations into master 3 days ago
Bruno F. Fontes 87c216b491
Add Brazilian Portuguese translations 2 months ago
Blue ef1a9846bf found a way to avoid requesting all the MUCs history alltogether on startup 1 year ago
Blue 3a120c773a reconnection issues 1 year ago
Blue 5f64321c2a fix compilation on older qt versions 1 year ago
Blue a543eb1aef 0.1.5 1 year ago
Blue 480c78cf61 non lower cased jids error handled 1 year ago
Blue 0dcfc5eedc some better cleanup and restore state on connect disconnect, workaround for that wired undefined condition error on every other reconnection 1 year ago
Blue 87426ee20f account refactoring 1 year ago
Blue 20bcae5ab2 finally history works in mucs 1 year ago
Blue 6b65910ded stanzaId based muc archive request, account object refactoring 1 year ago
Blue 9ca4aa29d4 started account refactoring 1 year ago
Blue a625ecb47b Merge pull request 'rosterReferences' (#39) from rosterReferences into master 1 year ago
Blue 55ae5858b5 some workaround about disconnection segfault 1 year ago
Blue 9c855553c5 referencing seems to be working now 1 year ago
Blue 83a2e6af85 first prototype 1 year ago
Blue 494afcf2b5 initial class reference 1 year ago
Blue a8698cc94f minor bugfixes 1 year ago
Blue b50ce146b8 attaching messages fix, bad alloc fix 1 year ago
Blue 6657dc79ce qxmpp bump, ifdef to assemble on lower versions 1 year ago
  1. 22
      CHANGELOG.md
  2. 2
      core/CMakeLists.txt
  3. 1299
      core/account.cpp
  4. 97
      core/account.h
  5. 150
      core/archive.cpp
  6. 14
      core/archive.h
  7. 371
      core/handlers/messagehandler.cpp
  8. 76
      core/handlers/messagehandler.h
  9. 599
      core/handlers/rosterhandler.cpp
  10. 115
      core/handlers/rosterhandler.h
  11. 13
      core/networkaccess.cpp
  12. 103
      core/rosteritem.cpp
  13. 4
      core/rosteritem.h
  14. 56
      core/squawk.cpp
  15. 2
      external/qxmpp
  16. 8
      external/simpleCrypt/simplecrypt.cpp
  17. 1
      external/simpleCrypt/simplecrypt.h
  18. 2
      main.cpp
  19. 4
      packaging/Archlinux/PKGBUILD
  20. 2
      packaging/squawk.desktop
  21. 53
      shared/message.cpp
  22. 3
      shared/message.h
  23. 4
      signalcatcher.cpp
  24. 795
      translations/squawk.pt_BR.ts
  25. 12
      translations/squawk.ru.ts
  26. 1
      ui/CMakeLists.txt
  27. 3
      ui/models/account.cpp
  28. 3
      ui/models/account.h
  29. 42
      ui/models/contact.cpp
  30. 7
      ui/models/contact.h
  31. 42
      ui/models/group.cpp
  32. 6
      ui/models/group.h
  33. 139
      ui/models/item.cpp
  34. 30
      ui/models/item.h
  35. 177
      ui/models/reference.cpp
  36. 66
      ui/models/reference.h
  37. 282
      ui/models/roster.cpp
  38. 3
      ui/models/roster.h
  39. 12
      ui/squawk.cpp
  40. 2
      ui/utils/flowlayout.cpp
  41. 18
      ui/utils/messageline.cpp
  42. 13
      ui/widgets/chat.cpp
  43. 2
      ui/widgets/chat.h
  44. 44
      ui/widgets/conversation.cpp
  45. 3
      ui/widgets/conversation.h
  46. 14
      ui/widgets/room.cpp
  47. 2
      ui/widgets/room.h

22
CHANGELOG.md

@ -1,5 +1,27 @@
# Changelog
## Squawk 0.2.0 (Unreleased)
### Bug fixes
- carbon copies switches on again after reconnection
- requesting the history of the current chat after reconnection
- global availability (in drop down list) gets restored after reconnection
- status icon in active chat changes when presence of the pen pal changes
### Improvements
- slightly reduced the traffic on the startup by not requesting history of all MUCs
## Squawk 0.1.5 (Jul 29, 2020)
### Bug fixes
- error with sending attached files to the conference
- error with building on lower versions of QXmpp
- error on the first access to the conference database
- quit now actually quits the app
- history in MUC now works properly and requests messages from server
- error with handling non lower cased JIDs
- some workaround upon reconnection
## Squawk 0.1.4 (Apr 14, 2020)
### New features
- message line now is in the same window with roster (new window dialog is still able to opened on context menu)

2
core/CMakeLists.txt

@ -18,6 +18,8 @@ set(squawkCORE_SRC
storage.cpp
networkaccess.cpp
adapterFuctions.cpp
handlers/messagehandler.cpp
handlers/rosterhandler.cpp
)
add_subdirectory(passwordStorageEngines)

1299
core/account.cpp
File diff suppressed because it is too large
View File

97
core/account.h

@ -25,6 +25,7 @@
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QDir>
#include <QTimer>
#include <map>
#include <set>
@ -38,7 +39,6 @@
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include <QXmppUploadRequestManager.h>
#include <QXmppHttpUploadIq.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h>
@ -48,12 +48,17 @@
#include "conference.h"
#include "networkaccess.h"
#include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h"
namespace Core
{
class Account : public QObject
{
Q_OBJECT
friend class MessageHandler;
friend class RosterHandler;
public:
Account(
const QString& p_login,
@ -64,10 +69,6 @@ public:
QObject* parent = 0);
~Account();
void connect();
void disconnect();
void reconnect();
Shared::ConnectionState getState() const;
QString getName() const;
QString getLogin() const;
@ -86,10 +87,9 @@ public:
void setAvailability(Shared::Availability avail);
void setPasswordType(Shared::AccountPassword pt);
QString getFullJid() const;
void sendMessage(Shared::Message data);
void sendMessage(const Shared::Message& data);
void sendMessage(const Shared::Message& data, const QString& path);
void requestArchive(const QString& jid, int count, const QString& before);
void setReconnectTimes(unsigned int times);
void subscribeToContact(const QString& jid, const QString& reason);
void unsubscribeFromContact(const QString& jid, const QString& reason);
void removeContactRequest(const QString& jid);
@ -105,6 +105,9 @@ public:
void uploadVCard(const Shared::VCard& card);
public slots:
void connect();
void disconnect();
void reconnect();
void requestVCard(const QString& jid);
signals:
@ -135,12 +138,11 @@ signals:
private:
QString name;
std::map<QString, QString> achiveQueries;
std::map<QString, QString> archiveQueries;
QXmppClient client;
QXmppConfiguration config;
QXmppPresence presence;
Shared::ConnectionState state;
std::map<QString, std::set<QString>> groups;
QXmppCarbonManager* cm;
QXmppMamManager* am;
QXmppMucManager* mm;
@ -150,94 +152,41 @@ private:
QXmppUploadRequestManager* um;
QXmppDiscoveryManager* dm;
QXmppMessageReceiptManager* rcpm;
std::map<QString, Contact*> contacts;
std::map<QString, Conference*> conferences;
unsigned int maxReconnectTimes;
unsigned int reconnectTimes;
bool reconnectScheduled;
QTimer* reconnectTimer;
std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts;
std::set<QString> pendingVCardRequests;
std::map<QString, Shared::Message> pendingMessages;
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
QString avatarHash;
QString avatarType;
bool ownVCardRequestInProgress;
NetworkAccess* network;
std::map<QString, QString> pendingStateMessages;
Shared::AccountPassword passwordType;
MessageHandler* mh;
RosterHandler* rh;
private slots:
void onClientConnected();
void onClientDisconnected();
void onClientStateChange(QXmppClient::State state);
void onClientError(QXmppClient::Error err);
void onRosterReceived();
void onRosterItemAdded(const QString& bareJid);
void onRosterItemChanged(const QString& bareJid);
void onRosterItemRemoved(const QString& bareJid);
void onRosterPresenceChanged(const QString& bareJid, const QString& resource);
void onPresenceReceived(const QXmppPresence& presence);
void onMessageReceived(const QXmppMessage& message);
void onCarbonMessageReceived(const QXmppMessage& message);
void onCarbonMessageSent(const QXmppMessage& message);
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
void onMamMessageReceived(const QString& bareJid, const QXmppMessage& message);
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
void onMucRoomAdded(QXmppMucRoom* room);
void onMucJoinedChanged(bool joined);
void onMucAutoJoinChanged(bool autoJoin);
void onMucNickNameChanged(const QString& nickName);
void onMucSubjectChanged(const QString& subject);
void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucRemoveParticipant(const QString& nickName);
void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
void onContactGroupAdded(const QString& group);
void onContactGroupRemoved(const QString& group);
void onContactNameChanged(const QString& name);
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list);
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
void onContactAvatarChanged(Shared::Avatar, const QString& path);
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
void onVCardReceived(const QXmppVCardIq& card);
void onOwnVCardReceived(const QXmppVCardIq& card);
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
void onFileUploaded(const QString& messageId, const QString& url);
void onFileUploadError(const QString& messageId, const QString& errMsg);
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
void onReceiptReceived(const QString& jid, const QString &id);
private:
void addedAccount(const QString &bareJid);
void handleNewContact(Contact* contact);
void handleNewRosterItem(RosterItem* contact);
void handleNewConference(Conference* contact);
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
void addToGroup(const QString& jid, const QString& group);
void removeFromGroup(const QString& jid, const QString& group);
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const;
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void storeConferences();
void clearConferences();
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
RosterItem* getRosterItem(const QString& jid);
void handleDisconnection();
void onReconnectTimer();
};
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);

150
core/archive.cpp

@ -67,6 +67,7 @@ void Core::Archive::open(const QString& account)
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
mdb_txn_commit(txn);
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
@ -99,6 +100,7 @@ void Core::Archive::open(const QString& account)
void Core::Archive::close()
{
if (opened) {
mdb_dbi_close(environment, sid);
mdb_dbi_close(environment, avatars);
mdb_dbi_close(environment, stats);
mdb_dbi_close(environment, order);
@ -139,12 +141,36 @@ bool Core::Archive::addElement(const Shared::Message& message)
mdb_txn_abort(txn);
return false;
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
if (message.getStanzaId().size() > 0) {
const std::string& szid = message.getStanzaId().toStdString();
lmdbKey.mv_size = szid.size();
lmdbKey.mv_data = (char*)szid.c_str();
lmdbData.mv_size = id.size();
lmdbData.mv_data = (uint8_t*)id.data();
rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc) {
qDebug() << "An element stanzaId to id pair couldn't be inserted into the archive" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
}
return true;
}
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
}
return true;
}
return true;
}
} else {
qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
@ -164,10 +190,12 @@ void Core::Archive::clear()
mdb_drop(txn, main, 0);
mdb_drop(txn, order, 0);
mdb_drop(txn, stats, 0);
mdb_drop(txn, avatars, 0);
mdb_drop(txn, sid, 0);
mdb_txn_commit(txn);
}
Shared::Message Core::Archive::getElement(const QString& id)
Shared::Message Core::Archive::getElement(const QString& id) const
{
if (!opened) {
throw Closed("getElement", jid.toStdString());
@ -186,7 +214,27 @@ Shared::Message Core::Archive::getElement(const QString& id)
}
}
Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn)
bool Core::Archive::hasElement(const QString& id) const
{
if (!opened) {
throw Closed("hasElement", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
bool has;
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.toStdString().c_str();
int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
has = rc == 0;
mdb_txn_abort(txn);
return has;
}
Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn) const
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
@ -220,6 +268,7 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
std::string strId(id.toStdString());
try {
Shared::Message msg = getMessage(strId, txn);
bool hadStanzaId = msg.getStanzaId().size() > 0;
QDateTime oTime = msg.getTime();
bool idChange = msg.change(data);
@ -250,6 +299,19 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
}
if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
const std::string& szid = msg.getStanzaId().toStdString();
lmdbData.mv_size = szid.size();
lmdbData.mv_data = (char*)szid.c_str();
rc = mdb_put(txn, sid, &lmdbData, &lmdbKey, 0);
if (rc != 0) {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
};
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
@ -395,7 +457,20 @@ unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messag
if (rc) {
qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
} else {
//qDebug() << "element added with id" << message.getId() << "stamp" << message.getTime();
if (message.getStanzaId().size() > 0) {
const std::string& szid = message.getStanzaId().toStdString();
lmdbKey.mv_size = szid.size();
lmdbKey.mv_data = (char*)szid.c_str();
lmdbData.mv_size = id.size();
lmdbData.mv_data = (uint8_t*)id.data();
rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc) {
qDebug() << "During bulk add an element stanzaId to id pair couldn't be inserted into the archive, continuing without stanzaId" << mdb_strerror(rc);
}
}
success++;
}
} else {
@ -536,6 +611,46 @@ void Core::Archive::setFromTheBeginning(bool is)
}
}
QString Core::Archive::idByStanzaId(const QString& stanzaId) const
{
if (!opened) {
throw Closed("idByStanzaId", jid.toStdString());
}
QString id;
std::string ssid = stanzaId.toStdString();
MDB_txn *txn;
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = ssid.size();
lmdbKey.mv_data = (char*)ssid.c_str();
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
int rc = mdb_get(txn, sid, &lmdbKey, &lmdbData);
if (rc == 0) {
id = QString::fromStdString(std::string((char*)lmdbData.mv_data, lmdbData.mv_size));
}
mdb_txn_abort(txn);
return id;
}
QString Core::Archive::stanzaIdById(const QString& id) const
{
if (!opened) {
throw Closed("stanzaIdById", jid.toStdString());
}
try {
Shared::Message msg = getElement(id);
return msg.getStanzaId();
} catch (const NotFound& e) {
return QString();
} catch (const Empty& e) {
return QString();
} catch (...) {
throw;
}
}
void Core::Archive::printOrder()
{
qDebug() << "Printing order";
@ -817,15 +932,16 @@ void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data)
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
mdb_cursor_open(txn, avatars, &cursor);
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
do {
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
QString res(sId.c_str());
if (res != jid) {
data.emplace(res, AvatarInfo());
data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
}
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
if (rc == 0) { //the db might be empty yet
do {
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
QString res(sId.c_str());
if (res != jid) {
data.emplace(res, AvatarInfo());
data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
}
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);

14
core/archive.h

@ -45,7 +45,8 @@ public:
bool addElement(const Shared::Message& message);
unsigned int addElements(const std::list<Shared::Message>& messages);
Shared::Message getElement(const QString& id);
Shared::Message getElement(const QString& id) const;
bool hasElement(const QString& id) const;
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
Shared::Message oldest();
QString oldestId();
@ -60,6 +61,8 @@ public:
AvatarInfo getAvatarInfo(const QString& resource = "") const;
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
QString idByStanzaId(const QString& stanzaId) const;
QString stanzaIdById(const QString& id) const;
public:
const QString jid;
@ -169,10 +172,11 @@ private:
bool opened;
bool fromTheBeginning;
MDB_env* environment;
MDB_dbi main;
MDB_dbi order;
MDB_dbi main; //id to message
MDB_dbi order; //time to id
MDB_dbi stats;
MDB_dbi avatars;
MDB_dbi avatars;
MDB_dbi sid; //stanzaId to id
bool getStatBoolValue(const std::string& id, MDB_txn* txn);
std::string getStatStringValue(const std::string& id, MDB_txn* txn);
@ -183,7 +187,7 @@ private:
void printOrder();
void printKeys();
bool dropAvatar(const std::string& resource);
Shared::Message getMessage(const std::string& id, MDB_txn* txn);
Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
Shared::Message edge(bool end);
};

371
core/handlers/messagehandler.cpp

@ -0,0 +1,371 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "messagehandler.h"
#include "core/account.h"
Core::MessageHandler::MessageHandler(Core::Account* account):
QObject(),
acc(account),
pendingStateMessages(),
pendingMessages(),
uploadingSlotsQueue()
{
}
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
{
bool handled = false;
switch (msg.type()) {
case QXmppMessage::Normal:
qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
break;
case QXmppMessage::Chat:
handled = handleChatMessage(msg);
break;
case QXmppMessage::GroupChat:
handled = handleGroupMessage(msg);
break;
case QXmppMessage::Error: {
QString id = msg.id();
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
if (itr != pendingStateMessages.end()) {
QString jid = itr->second;
RosterItem* cnt = acc->rh->getRosterItem(jid);
QMap<QString, QVariant> cData = {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", msg.error().text()}
};
if (cnt != 0) {
cnt->changeMessage(id, cData);
}
;
emit acc->changeMessage(jid, id, cData);
pendingStateMessages.erase(itr);
handled = true;
} else {
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
}
}
break;
case QXmppMessage::Headline:
qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
break;
}
if (!handled) {
logMessage(msg);
}
}
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
{
const QString& body(msg.body());
if (body.size() != 0) {
Shared::Message sMsg(Shared::Message::chat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Contact* cnt = acc->rh->getContact(jid);
if (cnt == 0) {
cnt = acc->rh->addOutOfRosterContact(jid);
}
if (outgoing) {
if (forwarded) {
sMsg.setState(Shared::Message::State::sent);
}
} else {
sMsg.setState(Shared::Message::State::delivered);
}
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
emit acc->message(sMsg);
}
return true;
}
return false;
}
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
{
const QString& body(msg.body());
if (body.size() != 0) {
QString id = msg.id();
Shared::Message sMsg(Shared::Message::groupChat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Conference* cnt = acc->rh->getConference(jid);
if (cnt == 0) {
return false;
}
std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
if (pItr != pendingStateMessages.end()) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
cnt->changeMessage(id, cData);
pendingStateMessages.erase(pItr);
emit acc->changeMessage(jid, id, cData);
} else {
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
if (sMsg.getTime() > minAgo) { //otherwise it's considered a delayed delivery, most probably MUC history receipt
emit acc->message(sMsg);
} else {
//qDebug() << "Delayed delivery: ";
}
}
}
return true;
}
return false;
}
void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
{
const QDateTime& time(source.stamp());
QString id;
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
id = source.originId();
if (id.size() == 0) {
id = source.id();
}
target.setStanzaId(source.stanzaId());
#else
id = source.id();
#endif
target.setId(id);
if (target.getId().size() == 0) {
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
}
target.setFrom(source.from());
target.setTo(source.to());
target.setBody(source.body());
target.setForwarded(forwarded);
target.setOutOfBandUrl(source.outOfBandUrl());
if (guessing) {
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
outgoing = true;
} else {
outgoing = false;
}
}
target.setOutgoing(outgoing);
if (time.isValid()) {
target.setTime(time);
} else {
target.setCurrentTime();
}
}
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
{
qDebug() << reason;
qDebug() << "- from: " << msg.from();
qDebug() << "- to: " << msg.to();
qDebug() << "- body: " << msg.body();
qDebug() << "- type: " << msg.type();
qDebug() << "- state: " << msg.state();
qDebug() << "- stamp: " << msg.stamp();
qDebug() << "- id: " << msg.id();
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
qDebug() << "- stanzaId: " << msg.stanzaId();
#endif
qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
qDebug() << "==============================";
}
void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
{
handleChatMessage(msg, false, true);
}
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
{
handleChatMessage(msg, true, true);
}
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
{
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
if (itr != pendingStateMessages.end()) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
RosterItem* ri = acc->rh->getRosterItem(itr->second);
if (ri != 0) {
ri->changeMessage(id, cData);
}
pendingStateMessages.erase(itr);
emit acc->changeMessage(itr->second, id, cData);
}
}
void Core::MessageHandler::sendMessage(Shared::Message data)
{
QString jid = data.getPenPalJid();
QString id = data.getId();
RosterItem* ri = acc->rh->getRosterItem(jid);
if (acc->state == Shared::ConnectionState::connected) {
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
msg.setOriginId(id);
#endif
msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
msg.setReceiptRequested(true);
bool sent = acc->client.sendPacket(msg);
if (sent) {
data.setState(Shared::Message::State::sent);
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("Couldn't send message via QXMPP library check out logs");
}
if (ri != 0) {
ri->appendMessageToArchive(data);
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
}
}
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("You are is offline or reconnecting");
}
emit acc->changeMessage(jid, id, {
{"state", static_cast<uint>(data.getState())},
{"errorText", data.getErrorText()}
});
}
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
{
if (acc->state == Shared::ConnectionState::connected) {
QString url = acc->network->getFileRemoteUrl(path);
if (url.size() != 0) {
sendMessageWithLocalUploadedFile(data, url);
} else {
if (acc->network->isUploading(path, data.getId())) {
pendingMessages.emplace(data.getId(), data);
} else {
if (acc->um->serviceFound()) {
QFileInfo file(path);
if (file.exists() && file.isReadable()) {
uploadingSlotsQueue.emplace_back(path, data);
if (uploadingSlotsQueue.size() == 1) {
acc->um->requestUploadSlot(file);
}
} else {
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
}
} else {
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
}
}
}
} else {
onFileUploadError(data.getId(), "Account is offline or reconnecting");
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
}
}
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
{
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
} else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
const QString& mId = pair.second.getId();
acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
pendingMessages.emplace(mId, pair.second);
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
}
}
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
{
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
qDebug() << request.error().text();
} else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
uploadingSlotsQueue.pop_front();
}
}
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
{
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
if (itr != pendingMessages.end()) {
sendMessageWithLocalUploadedFile(itr->second, url);
pendingMessages.erase(itr);
}
}
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
{
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
if (itr != pendingMessages.end()) {
pendingMessages.erase(itr);
}
}
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
{
msg.setOutOfBandUrl(url);
if (msg.getBody().size() == 0) {
msg.setBody(url);
}
sendMessage(msg);
//TODO removal/progress update
}

76
core/handlers/messagehandler.h

@ -0,0 +1,76 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_MESSAGEHANDLER_H
#define CORE_MESSAGEHANDLER_H
#include <QObject>
#include <deque>
#include <map>
#include <QXmppMessage.h>
#include <QXmppHttpUploadIq.h>
#include <shared/message.h>
namespace Core {
/**
* @todo write docs
*/
class Account;
class MessageHandler : public QObject
{
Q_OBJECT
public:
MessageHandler(Account* account);
public:
void sendMessage(Shared::Message data);
void sendMessage(const Shared::Message& data, const QString& path);
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
public slots:
void onMessageReceived(const QXmppMessage& message);
void onCarbonMessageReceived(const QXmppMessage& message);
void onCarbonMessageSent(const QXmppMessage& message);
void onReceiptReceived(const QString& jid, const QString& id);
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
void onFileUploaded(const QString& messageId, const QString& url);
void onFileUploadError(const QString& messageId, const QString& errMsg);
private:
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
private:
Account* acc;
std::map<QString, QString> pendingStateMessages;
std::map<QString, Shared::Message> pendingMessages;
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
};
}
#endif // CORE_MESSAGEHANDLER_H

599
core/handlers/rosterhandler.cpp

@ -0,0 +1,599 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rosterhandler.h"
#include "core/account.h"
Core::RosterHandler::RosterHandler(Core::Account* account):
QObject(),
acc(account),
contacts(),
conferences(),
groups(),
queuedContacts(),
outOfRosterContacts()
{
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
}
Core::RosterHandler::~RosterHandler()
{
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
delete itr->second;
}
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
delete itr->second;
}
}
void Core::RosterHandler::onRosterReceived()
{
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
acc->ownVCardRequestInProgress = true;
QStringList bj = acc->rm->getRosterBareJids();
for (int i = 0; i < bj.size(); ++i) {
const QString& jid = bj[i];
addedAccount(jid.toLower());
}
}
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
{
QString lcJid = bareJid.toLower();
addedAccount(lcJid);
std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
if (itr != queuedContacts.end()) {
acc->rm->subscribe(lcJid, itr->second);
queuedContacts.erase(itr);
}
}
void Core::RosterHandler::addedAccount(const QString& jid)
{
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
Contact* contact;
bool newContact = false;
if (itr == contacts.end()) {
newContact = true;
contact = new Contact(jid, acc->name);
contacts.insert(std::make_pair(jid, contact));
} else {
contact = itr->second;
}
QSet<QString> gr = re.groups();
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
contact->setGroups(gr);
contact->setSubscriptionState(state);
contact->setName(re.name());
if (newContact) {
QMap<QString, QVariant> cData({
{"name", re.name()},
{"state", QVariant::fromValue(state)}
});
careAboutAvatar(contact, cData);
int grCount = 0;
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
const QString& groupName = *itr;
addToGroup(jid, groupName);
emit acc->addContact(jid, groupName, cData);
grCount++;
}
if (grCount == 0) {
emit acc->addContact(jid, "", cData);
}
handleNewContact(contact);
}
}
void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
{
QXmppMucRoom* room = acc->mm->addRoom(jid);
QString lNick = nick;
if (lNick.size() == 0) {
lNick = acc->getName();
}
Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
conferences.insert(std::make_pair(jid, conf));
handleNewConference(conf);
QMap<QString, QVariant> cData = {
{"autoJoin", conf->getAutoJoin()},
{"joined", conf->getJoined()},
{"nick", conf->getNick()},
{"name", conf->getName()},
{"avatars", conf->getAllAvatars()}
};
careAboutAvatar(conf, cData);
emit acc->addRoom(jid, cData);
}
void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data)
{
Archive::AvatarInfo info;
bool hasAvatar = item->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
}
data.insert("avatarPath", item->avatarPath() + "." + info.type);
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
data.insert("avatarPath", "");
acc->requestVCard(item->jid);
}
}
void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
{
if (acc->state == Shared::ConnectionState::connected) {
std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
if (itr != queuedContacts.end()) {
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is already queued for adding, skipping";
} else {
queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here;
acc->rm->addItem(jid, name, groups);
}
} else {
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is not in the connected state, skipping";
}
}
void Core::RosterHandler::removeContactRequest(const QString& jid)
{
QString lcJid = jid.toLower();
if (acc->state == Shared::ConnectionState::connected) {
std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
if (itr != outOfRosterContacts.end()) {
outOfRosterContacts.erase(itr);
onRosterItemRemoved(lcJid);
} else {
acc->rm->removeItem(lcJid);
}
} else {
qDebug() << "An attempt to remove contact " << lcJid << " from account " << acc->name << " but the account is not in the connected state, skipping";
}
}
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
{
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
}
void Core::RosterHandler::handleNewContact(Core::Contact* contact)
{
handleNewRosterItem(contact);
connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
}
void Core::RosterHandler::handleNewConference(Core::Conference* contact)
{
handleNewRosterItem(contact);
connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
connect(contact, &Conference::joinedChanged, this, &RosterHandler::onMucJoinedChanged);
connect(contact, &Conference::autoJoinChanged, this, &RosterHandler::onMucAutoJoinChanged);
connect(contact, &Conference::addParticipant, this, &RosterHandler::onMucAddParticipant);
connect(contact, &Conference::changeParticipant, this, &RosterHandler::onMucChangeParticipant);
connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
}
void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->addRoomParticipant(room->jid, nickName, data);
}
void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoomParticipant(room->jid, nickName, data);
}
void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->removeRoomParticipant(room->jid, nickName);
}
void Core::RosterHandler::onMucSubjectChanged(const QString& subject)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, {
{"subject", subject}
});
}
void Core::RosterHandler::onContactGroupAdded(const QString& group)
{
Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 1) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
}
QMap<QString, QVariant> cData({
{"name", contact->getName()},
{"state", QVariant::fromValue(contact->getSubscriptionState())}
});
addToGroup(contact->jid, group);
emit acc->addContact(contact->jid, group, cData);
}
void Core::RosterHandler::onContactGroupRemoved(const QString& group)
{
Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 0) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
}
emit acc->removeContact(contact->jid, group);
removeFromGroup(contact->jid, group);
}
void Core::RosterHandler::onContactNameChanged(const QString& cname)
{
Contact* contact = static_cast<Contact*>(sender());
QMap<QString, QVariant> cData({
{"name", cname},
});
emit acc->changeContact(contact->jid, cData);
}
void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
{
Contact* contact = static_cast<Contact*>(sender());
QMap<QString, QVariant> cData({
{"state", QVariant::fromValue(cstate)},
});
emit acc->changeContact(contact->jid, cData);
}
void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
{
std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
if (gItr == groups.end()) {
gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
emit acc->addGroup(group);
}
gItr->second.insert(jid.toLower());
}
void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
{
QSet<QString> toRemove;
std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
if (itr == groups.end()) {
qDebug() << "An attempt to remove contact" << jid << "of account" << acc->name << "from non existing group" << group << ", skipping";
return;
}
std::set<QString> contacts = itr->second;
std::set<QString>::const_iterator cItr = contacts.find(jid.toLower());
if (cItr != contacts.end()) {
contacts.erase(cItr);
if (contacts.size() == 0) {
emit acc->removeGroup(group);
groups.erase(group);
}
}
}
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
{
RosterItem* contact = static_cast<RosterItem*>(sender());
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
emit acc->responseArchive(contact->jid, list);
}
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
{
RosterItem* item = 0;
QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
if (citr != contacts.end()) {
item = citr->second;
} else {
std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
if (coitr != conferences.end()) {
item = coitr->second;
}
}
return item;
}
Core::Conference * Core::RosterHandler::getConference(const QString& jid)
{
Conference* item = 0;
std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
if (coitr != conferences.end()) {
item = coitr->second;
}
return item;
}
Core::Contact * Core::RosterHandler::getContact(const QString& jid)
{
Contact* item = 0;
std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
if (citr != contacts.end()) {
item = citr->second;
}
return item;
}
Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
{
QString lcJid = jid.toLower();
Contact* cnt = new Contact(lcJid, acc->name);
contacts.insert(std::make_pair(lcJid, cnt));
outOfRosterContacts.insert(lcJid);
cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
{"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
}));
handleNewContact(cnt);
return cnt;
}
void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
{
QString lcJid = bareJid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);