/* * Squawk messenger. * Copyright (C) 2019 Yury Gubich * * 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 . */ #include "account.h" #include #include #include using namespace Core; Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent): QObject(parent), name(p_name), achiveQueries(), client(), config(), presence(), state(Shared::ConnectionState::disconnected), groups(), cm(new QXmppCarbonManager()), am(new QXmppMamManager()), mm(new QXmppMucManager()), bm(new QXmppBookmarkManager()), rm(client.findExtension()), vm(client.findExtension()), um(new QXmppUploadRequestManager()), dm(client.findExtension()), rcpm(new QXmppMessageReceiptManager()), contacts(), conferences(), maxReconnectTimes(0), reconnectTimes(0), queuedContacts(), outOfRosterContacts(), pendingMessages(), uploadingSlotsQueue(), avatarHash(), avatarType(), ownVCardRequestInProgress(false), network(p_net), pendingStateMessages(), passwordType(Shared::AccountPassword::plain) { config.setUser(p_login); config.setDomain(p_server); config.setPassword(p_password); config.setAutoAcceptSubscriptions(true); config.setAutoReconnectionEnabled(false); QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected); QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected); QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived); QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived); QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError); QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived); QObject::connect(rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded); QObject::connect(rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved); QObject::connect(rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged); //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged); client.addExtension(cm); QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived); QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent); client.addExtension(am); QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog); QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived); QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived); client.addExtension(mm); QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded); client.addExtension(bm); QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived); QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived); //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler client.addExtension(um); QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived); QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed); QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived); QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived); QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded); QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError); client.addExtension(rcpm); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, this, &Account::onReceiptReceived); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + name; QDir dir(path); if (!dir.exists()) { bool res = dir.mkpath(path); if (!res) { qDebug() << "Couldn't create a cache directory for account" << name; throw 22; } } QFile* avatar = new QFile(path + "/avatar.png"); QString type = "png"; if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.jpg"); type = "jpg"; if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.jpeg"); type = "jpeg"; if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.gif"); type = "gif"; } } } if (avatar->exists()) { if (avatar->open(QFile::ReadOnly)) { QCryptographicHash sha1(QCryptographicHash::Sha1); sha1.addData(avatar); avatarHash = sha1.result(); avatarType = type; } } if (avatarType.size() != 0) { presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); presence.setPhotoHash(avatarHash.toUtf8()); } else { presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady); } } Account::~Account() { QObject::disconnect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded); QObject::disconnect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError); for (std::map::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) { delete itr->second; } for (std::map::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) { delete itr->second; } delete rcpm; delete dm; delete um; delete bm; delete mm; delete am; delete cm; } Shared::ConnectionState Core::Account::getState() const { return state; } void Core::Account::connect() { if (state == Shared::ConnectionState::disconnected) { reconnectTimes = maxReconnectTimes; state = Shared::ConnectionState::connecting; client.connectToServer(config, presence); emit connectionStateChanged(state); } else { qDebug("An attempt to connect an account which is already connected, skipping"); } } void Core::Account::disconnect() { reconnectTimes = 0; if (state != Shared::ConnectionState::disconnected) { clearConferences(); client.disconnectFromServer(); state = Shared::ConnectionState::disconnected; emit connectionStateChanged(state); } } void Core::Account::onClientConnected() { if (state == Shared::ConnectionState::connecting) { reconnectTimes = maxReconnectTimes; state = Shared::ConnectionState::connected; dm->requestItems(getServer()); dm->requestInfo(getServer()); emit connectionStateChanged(state); } else { qDebug() << "Something weird had happened - xmpp client reported about successful connection but account wasn't in" << state << "state"; } } void Core::Account::onClientDisconnected() { clearConferences(); if (state != Shared::ConnectionState::disconnected) { if (reconnectTimes > 0) { qDebug() << "Account" << name << "is reconnecting for" << reconnectTimes << "more times"; --reconnectTimes; state = Shared::ConnectionState::connecting; client.connectToServer(config, presence); emit connectionStateChanged(state); } else { qDebug() << "Account" << name << "has been disconnected"; state = Shared::ConnectionState::disconnected; emit connectionStateChanged(state); } } else { //qDebug("Something weird had happened - xmpp client reported about being disconnection but account was already in disconnected state"); } } void Core::Account::reconnect() { if (state == Shared::ConnectionState::connected) { ++reconnectTimes; client.disconnectFromServer(); } else { qDebug() << "An attempt to reconnect account" << getName() << "which was not connected"; } } QString Core::Account::getName() const { return name; } QString Core::Account::getLogin() const { return config.user(); } QString Core::Account::getPassword() const { return config.password(); } QString Core::Account::getServer() const { return config.domain(); } Shared::AccountPassword Core::Account::getPasswordType() const { return passwordType; } void Core::Account::onRosterReceived() { vm->requestClientVCard(); //TODO need to make sure server actually supports vCards ownVCardRequestInProgress = true; QStringList bj = rm->getRosterBareJids(); for (int i = 0; i < bj.size(); ++i) { const QString& jid = bj[i]; addedAccount(jid); } } void Core::Account::setReconnectTimes(unsigned int times) { maxReconnectTimes = times; if (state == Shared::ConnectionState::connected) { reconnectTimes = times; } } void Core::Account::onRosterItemAdded(const QString& bareJid) { addedAccount(bareJid); std::map::const_iterator itr = queuedContacts.find(bareJid); if (itr != queuedContacts.end()) { rm->subscribe(bareJid, itr->second); queuedContacts.erase(itr); } } void Core::Account::setPasswordType(Shared::AccountPassword pt) { passwordType = pt; } void Core::Account::onRosterItemChanged(const QString& bareJid) { std::map::const_iterator itr = contacts.find(bareJid); if (itr == contacts.end()) { qDebug() << "An attempt to change non existing contact" << bareJid << "from account" << name << ", skipping"; return; } Contact* contact = itr->second; QXmppRosterIq::Item re = rm->getRosterEntry(bareJid); Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType()); contact->setGroups(re.groups()); contact->setSubscriptionState(state); contact->setName(re.name()); } void Core::Account::onRosterItemRemoved(const QString& bareJid) { std::map::const_iterator itr = contacts.find(bareJid); if (itr == contacts.end()) { qDebug() << "An attempt to remove non existing contact" << bareJid << "from account" << name << ", skipping"; return; } Contact* contact = itr->second; contacts.erase(itr); QSet cGroups = contact->getGroups(); for (QSet::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) { removeFromGroup(bareJid, *itr); } emit removeContact(bareJid); contact->deleteLater(); } void Core::Account::addedAccount(const QString& jid) { std::map::const_iterator itr = contacts.find(jid); QXmppRosterIq::Item re = rm->getRosterEntry(jid); Contact* contact; bool newContact = false; if (itr == contacts.end()) { newContact = true; contact = new Contact(jid, name); contacts.insert(std::make_pair(jid, contact)); } else { contact = itr->second; } QSet gr = re.groups(); Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType()); contact->setGroups(gr); contact->setSubscriptionState(state); contact->setName(re.name()); if (newContact) { QMap cData({ {"name", re.name()}, {"state", QVariant::fromValue(state)} }); Archive::AvatarInfo info; bool hasAvatar = contact->readAvatarInfo(info); if (hasAvatar) { if (info.autogenerated) { cData.insert("avatarState", static_cast(Shared::Avatar::valid)); } else { cData.insert("avatarState", static_cast(Shared::Avatar::autocreated)); } cData.insert("avatarPath", contact->avatarPath() + "." + info.type); } else { cData.insert("avatarState", static_cast(Shared::Avatar::empty)); cData.insert("avatarPath", ""); requestVCard(jid); } int grCount = 0; for (QSet::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) { const QString& groupName = *itr; addToGroup(jid, groupName); emit addContact(jid, groupName, cData); grCount++; } if (grCount == 0) { emit addContact(jid, "", cData); } handleNewContact(contact); } } void Core::Account::handleNewRosterItem(Core::RosterItem* contact) { QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory); QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse); QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged); QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged); QObject::connect(contact, &RosterItem::requestVCard, this, &Account::requestVCard); } void Core::Account::handleNewContact(Core::Contact* contact) { handleNewRosterItem(contact); QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded); QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved); QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged); } void Core::Account::handleNewConference(Core::Conference* contact) { handleNewRosterItem(contact); QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged); QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged); QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged); QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged); QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant); QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant); QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant); } void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) { QString id = p_presence.from(); QStringList comps = id.split("/"); QString jid = comps.front(); QString resource = comps.back(); QString myJid = getLogin() + "@" + getServer(); if (jid == myJid) { if (resource == getResource()) { emit availabilityChanged(static_cast(p_presence.availableStatusType())); } else { if (!ownVCardRequestInProgress) { switch (p_presence.vCardUpdateType()) { case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo break; case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here break; case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any if (avatarType.size() > 0) { vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load if (avatarHash != p_presence.photoHash()) { vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; } } } } else { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { std::map::const_iterator itr = contacts.find(jid); if (itr != contacts.end()) { itr->second->handlePresence(p_presence); } } } switch (p_presence.type()) { case QXmppPresence::Error: qDebug() << "An error reported by presence from" << id << p_presence.error().text(); break; case QXmppPresence::Available:{ QDateTime lastInteraction = p_presence.lastUserInteraction(); if (!lastInteraction.isValid()) { lastInteraction = QDateTime::currentDateTimeUtc(); } emit addPresence(jid, resource, { {"lastActivity", lastInteraction}, {"availability", p_presence.availableStatusType()}, //TODO check and handle invisible {"status", p_presence.statusText()} }); } break; case QXmppPresence::Unavailable: emit removePresence(jid, resource); break; case QXmppPresence::Subscribe: qDebug("xmpp presence \"subscribe\" received, do not yet know what to do, skipping"); case QXmppPresence::Subscribed: qDebug("xmpp presence \"subscribed\" received, do not yet know what to do, skipping"); case QXmppPresence::Unsubscribe: qDebug("xmpp presence \"unsubscribe\" received, do not yet know what to do, skipping"); case QXmppPresence::Unsubscribed: qDebug("xmpp presence \"unsubscribed\" received, do not yet know what to do, skipping"); case QXmppPresence::Probe: qDebug("xmpp presence \"probe\" received, do not yet know what to do, skipping"); break; } } void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QString& resource) { //not used for now; qDebug() << "presence changed for " << bareJid << " resource " << resource; const QXmppPresence& presence = rm->getPresence(bareJid, resource); } void Core::Account::setLogin(const QString& p_login) { config.setUser(p_login); } void Core::Account::setName(const QString& p_name) { name = p_name; } void Core::Account::setPassword(const QString& p_password) { config.setPassword(p_password); } void Core::Account::setServer(const QString& p_server) { config.setDomain(p_server); } Shared::Availability Core::Account::getAvailability() const { if (state == Shared::ConnectionState::connected) { QXmppPresence::AvailableStatusType pres = presence.availableStatusType(); return static_cast(pres); //they are compatible; } else { return Shared::Availability::offline; } } void Core::Account::setAvailability(Shared::Availability avail) { if (avail == Shared::Availability::offline) { disconnect(); //TODO not sure how to do here - changing state may cause connection or disconnection } else { QXmppPresence::AvailableStatusType pres = static_cast(avail); presence.setAvailableStatusType(pres); if (state != Shared::ConnectionState::disconnected) { client.setClientPresence(presence); } } } QString Core::Account::getResource() const { return config.resource(); } void Core::Account::setResource(const QString& p_resource) { config.setResource(p_resource); } void Core::Account::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::const_iterator itr = pendingStateMessages.find(id); if (itr != pendingStateMessages.end()) { QString jid = itr->second; RosterItem* cnt = getRosterItem(jid); QMap cData = { {"state", static_cast(Shared::Message::State::error)}, {"errorText", msg.error().text()} }; if (cnt != 0) { cnt->changeMessage(id, cData); } ; emit 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); } } void Core::Account::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(); qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl(); qDebug() << "- isAttentionRequested: " << msg.isAttentionRequested(); qDebug() << "- isReceiptRequested: " << msg.isReceiptRequested(); qDebug() << "- receiptId: " << msg.receiptId(); qDebug() << "- subject: " << msg.subject(); qDebug() << "- thread: " << msg.thread(); qDebug() << "- isMarkable: " << msg.isMarkable(); qDebug() << "=============================="; } QString Core::Account::getFullJid() const { return getLogin() + "@" + getServer() + "/" + getResource(); } void Core::Account::sendMessage(Shared::Message data) { QString jid = data.getPenPalJid(); QString id = data.getId(); RosterItem* ri = getRosterItem(jid); if (state == Shared::ConnectionState::connected) { QXmppMessage msg(getFullJid(), data.getTo(), data.getBody(), data.getThread()); msg.setId(id); msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible msg.setOutOfBandUrl(data.getOutOfBandUrl()); msg.setReceiptRequested(true); bool sent = 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 changeMessage(jid, id, { {"state", static_cast(data.getState())}, {"errorText", data.getErrorText()} }); } void Core::Account::sendMessage(const Shared::Message& data, const QString& path) { if (state == Shared::ConnectionState::connected) { QString url = network->getFileRemoteUrl(path); if (url.size() != 0) { sendMessageWithLocalUploadedFile(data, url); } else { if (network->isUploading(path, data.getId())) { pendingMessages.emplace(data.getId(), data); } else { if (um->serviceFound()) { QFileInfo file(path); if (file.exists() && file.isReadable()) { uploadingSlotsQueue.emplace_back(path, data); if (uploadingSlotsQueue.size() == 1) { um->requestUploadSlot(file); } } else { emit onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it"); qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable"; } } else { emit onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account"); qDebug() << "Requested upload slot in account" << name << "for file" << path << "but upload manager didn't discover any upload services"; } } } } else { emit onFileUploadError(data.getId(), "Account is offline or reconnecting"); qDebug() << "An attempt to send message with not connected account " << name << ", skipping"; } } void Core::Account::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url) { msg.setOutOfBandUrl(url); if (msg.getBody().size() == 0) { msg.setBody(url); } sendMessage(msg); //TODO removal/progress update } void Core::Account::onCarbonMessageReceived(const QXmppMessage& msg) { handleChatMessage(msg, false, true); } void Core::Account::onCarbonMessageSent(const QXmppMessage& msg) { handleChatMessage(msg, true, true); } bool Core::Account::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(); std::map::const_iterator itr = contacts.find(jid); Contact* cnt; if (itr != contacts.end()) { cnt = itr->second; } else { cnt = new Contact(jid, name); contacts.insert(std::make_pair(jid, cnt)); outOfRosterContacts.insert(jid); cnt->setSubscriptionState(Shared::SubscriptionState::unknown); emit addContact(jid, "", QMap({ {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)} })); handleNewContact(cnt); } 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 cData = { {"body", sMsg.getBody()}, {"stamp", sMsg.getTime()} }; cnt->correctMessageInArchive(oId, sMsg); emit changeMessage(jid, oId, cData); } else { cnt->appendMessageToArchive(sMsg); emit message(sMsg); } return true; } return false; } bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) { const QString& body(msg.body()); if (body.size() != 0) { const QString& id(msg.id()); Shared::Message sMsg(Shared::Message::groupChat); initializeMessage(sMsg, msg, outgoing, forwarded, guessing); QString jid = sMsg.getPenPalJid(); std::map::const_iterator itr = conferences.find(jid); Conference* cnt; if (itr != conferences.end()) { cnt = itr->second; } else { return false; } std::map::const_iterator pItr = pendingStateMessages.find(id); if (pItr != pendingStateMessages.end()) { QMap cData = {{"state", static_cast(Shared::Message::State::delivered)}}; cnt->changeMessage(id, cData); pendingStateMessages.erase(pItr); emit changeMessage(jid, id, cData); } else { QString oId = msg.replaceId(); if (oId.size() > 0) { QMap cData = { {"body", sMsg.getBody()}, {"stamp", sMsg.getTime()} }; cnt->correctMessageInArchive(oId, sMsg); emit 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 message(sMsg); } else { //qDebug() << "Delayed delivery: "; } } } return true; } return false; } void Core::Account::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const { const QDateTime& time(source.stamp()); QString id = source.id(); if (id.size() == 0) { target.generateRandomId(); } else { target.setId(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() == getLogin() + "@" + getServer()) { outgoing = true; } else { outgoing = false; } } target.setOutgoing(outgoing); if (time.isValid()) { target.setTime(time); } else { target.setCurrentTime(); } } void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg) { if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) { std::map::const_iterator itr = achiveQueries.find(queryId); QString jid = itr->second; RosterItem* item = getRosterItem(jid); Shared::Message sMsg(static_cast(msg.type())); initializeMessage(sMsg, msg, false, true, true); sMsg.setState(Shared::Message::State::sent); QString oId = msg.replaceId(); if (oId.size() > 0) { item->correctMessageInArchive(oId, sMsg); } else { item->addMessageToArchive(sMsg); } } //handleChatMessage(msg, false, true, true); } Core::RosterItem * Core::Account::getRosterItem(const QString& jid) { RosterItem* item = 0; std::map::const_iterator citr = contacts.find(jid); if (citr != contacts.end()) { item = citr->second; } else { std::map::const_iterator coitr = conferences.find(jid); if (coitr != conferences.end()) { item = coitr->second; } } return item; } void Core::Account::requestArchive(const QString& jid, int count, const QString& before) { qDebug() << "An archive request for " << jid << ", before " << before; RosterItem* contact = getRosterItem(jid); if (contact == 0) { qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping"; emit responseArchive(contact->jid, std::list()); return; } if (contact->getArchiveState() == RosterItem::empty && before.size() == 0) { QXmppMessage msg(getFullJid(), jid, "", ""); QString last = Shared::generateUUID(); msg.setId(last); if (contact->isMuc()) { msg.setType(QXmppMessage::GroupChat); } else { msg.setType(QXmppMessage::Chat); } msg.setState(QXmppMessage::Active); client.sendPacket(msg); QTimer* timer = new QTimer; QObject::connect(timer, &QTimer::timeout, [timer, contact, count, last](){ contact->requestFromEmpty(count, last); timer->deleteLater(); }); timer->setSingleShot(true); timer->start(1000); } else { contact->requestHistory(count, before); } } void Core::Account::onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at) { RosterItem* contact = static_cast(sender()); QString to = ""; QXmppResultSetQuery query; query.setMax(100); if (before.size() > 0) { query.setBefore(before); } QDateTime start; if (after.size() > 0) { //there is some strange behavior of ejabberd server returning empty result set if (at.isValid()) { //there can be some useful information about it here https://github.com/processone/ejabberd/issues/2924 start = at; } else { query.setAfter(after); } } qDebug() << "Remote query from" << after << ", to" << before; QString q = am->retrieveArchivedMessages(to, "", contact->jid, start, QDateTime(), query); achiveQueries.insert(std::make_pair(q, contact->jid)); } void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete) { std::map::const_iterator itr = achiveQueries.find(queryId); QString jid = itr->second; achiveQueries.erase(itr); RosterItem* ri = getRosterItem(jid); if (ri != 0) { qDebug() << "Flushing messages for" << jid; ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last()); } } void Core::Account::onMamLog(QXmppLogger::MessageType type, const QString& msg) { qDebug() << "MAM MESSAGE LOG::"; qDebug() << msg; } void Core::Account::onContactGroupAdded(const QString& group) { Contact* contact = static_cast(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 cData({ {"name", contact->getName()}, {"state", QVariant::fromValue(contact->getSubscriptionState())} }); addToGroup(contact->jid, group); emit addContact(contact->jid, group, cData); } void Core::Account::onContactGroupRemoved(const QString& group) { Contact* contact = static_cast(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 removeContact(contact->jid, group); removeFromGroup(contact->jid, group); } void Core::Account::onContactNameChanged(const QString& cname) { Contact* contact = static_cast(sender()); QMap cData({ {"name", cname}, }); emit changeContact(contact->jid, cData); } void Core::Account::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) { Contact* contact = static_cast(sender()); QMap cData({ {"state", QVariant::fromValue(cstate)}, }); emit changeContact(contact->jid, cData); } void Core::Account::addToGroup(const QString& jid, const QString& group) { std::map>::iterator gItr = groups.find(group); if (gItr == groups.end()) { gItr = groups.insert(std::make_pair(group, std::set())).first; emit addGroup(group); } gItr->second.insert(jid); } void Core::Account::removeFromGroup(const QString& jid, const QString& group) { QSet toRemove; std::map>::iterator itr = groups.find(group); if (itr == groups.end()) { qDebug() << "An attempt to remove contact" << jid << "of account" << name << "from non existing group" << group << ", skipping"; return; } std::set contacts = itr->second; std::set::const_iterator cItr = contacts.find(jid); if (cItr != contacts.end()) { contacts.erase(cItr); if (contacts.size() == 0) { emit removeGroup(group); groups.erase(group); } } } Shared::SubscriptionState Core::Account::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const { Shared::SubscriptionState state; if (qs == QXmppRosterIq::Item::NotSet) { state = Shared::SubscriptionState::unknown; } else { state = static_cast(qs); } return state; } void Core::Account::onContactHistoryResponse(const std::list& list) { RosterItem* contact = static_cast(sender()); qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements"; emit responseArchive(contact->jid, list); } void Core::Account::onClientError(QXmppClient::Error err) { qDebug() << "Error"; QString errorText; QString errorType; switch (err) { case QXmppClient::SocketError: errorText = client.socketErrorString(); errorType = "Client socket error"; break; case QXmppClient::XmppStreamError: { QXmppStanza::Error::Condition cnd = client.xmppStreamError(); switch (cnd) { case QXmppStanza::Error::BadRequest: errorText = "Bad request"; break; case QXmppStanza::Error::Conflict: errorText = "Conflict"; break; case QXmppStanza::Error::FeatureNotImplemented: errorText = "Feature is not implemented"; break; case QXmppStanza::Error::Forbidden: errorText = "Forbidden"; break; case QXmppStanza::Error::Gone: errorText = "Gone"; break; case QXmppStanza::Error::InternalServerError: errorText = "Internal server error"; break; case QXmppStanza::Error::ItemNotFound: errorText = "Item was not found"; break; case QXmppStanza::Error::JidMalformed: errorText = "Malformed JID"; break; case QXmppStanza::Error::NotAcceptable: errorText = "Not acceptable"; break; case QXmppStanza::Error::NotAllowed: errorText = "Not allowed"; break; case QXmppStanza::Error::NotAuthorized: errorText = "Authentication error"; break; case QXmppStanza::Error::PaymentRequired: errorText = "Payment is required"; break; case QXmppStanza::Error::RecipientUnavailable: errorText = "Recipient is unavailable"; break; case QXmppStanza::Error::Redirect: errorText = "Redirected"; break; case QXmppStanza::Error::RegistrationRequired: errorText = "Registration is required"; break; case QXmppStanza::Error::RemoteServerNotFound: errorText = "Remote server was not found"; break; case QXmppStanza::Error::RemoteServerTimeout: errorText = "Remote server timeout"; break; case QXmppStanza::Error::ResourceConstraint: errorText = "Resource constraint"; break; case QXmppStanza::Error::ServiceUnavailable: errorText = "Redirected"; break; case QXmppStanza::Error::SubscriptionRequired: errorText = "Subscription is required"; break; case QXmppStanza::Error::UndefinedCondition: errorText = "Undefined condition"; break; case QXmppStanza::Error::UnexpectedRequest: errorText = "Unexpected request"; break; #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0) case QXmppStanza::Error::PolicyViolation: errorText = "Policy violation"; break; #endif } errorType = "Client stream error"; } break; case QXmppClient::KeepAliveError: errorText = "Client keep alive error"; break; case QXmppClient::NoError: break; //not exactly sure what to do here } qDebug() << errorType << errorText; emit error(errorText); } void Core::Account::subscribeToContact(const QString& jid, const QString& reason) { if (state == Shared::ConnectionState::connected) { rm->subscribe(jid, reason); } else { qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping"; } } void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason) { if (state == Shared::ConnectionState::connected) { rm->unsubscribe(jid, reason); } else { qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping"; } } void Core::Account::removeContactRequest(const QString& jid) { if (state == Shared::ConnectionState::connected) { std::set::const_iterator itr = outOfRosterContacts.find(jid); if (itr != outOfRosterContacts.end()) { outOfRosterContacts.erase(itr); onRosterItemRemoved(jid); } else { rm->removeItem(jid); } } else { qDebug() << "An attempt to remove contact " << jid << " from account " << name << " but the account is not in the connected state, skipping"; } } void Core::Account::addContactRequest(const QString& jid, const QString& name, const QSet& groups) { if (state == Shared::ConnectionState::connected) { std::map::const_iterator itr = queuedContacts.find(jid); if (itr != queuedContacts.end()) { qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping"; } else { queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here; rm->addItem(jid, name, groups); } } else { qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is not in the connected state, skipping"; } } void Core::Account::onMucRoomAdded(QXmppMucRoom* room) { qDebug() << "room" << room->jid() << "added with name" << room->name() << ", account" << getName() << "joined:" << room->isJoined(); } void Core::Account::bookmarksReceived(const QXmppBookmarkSet& bookmarks) { QList confs = bookmarks.conferences(); for (QList::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) { const QXmppBookmarkConference& c = *itr; QString jid = c.jid(); std::map::const_iterator cItr = conferences.find(jid); if (cItr == conferences.end()) { addNewRoom(jid, c.nickName(), c.name(), c.autoJoin()); } else { qDebug() << "Received a bookmark to a MUC " << jid << " which is already booked by another bookmark, skipping"; } } } void Core::Account::onMucJoinedChanged(bool joined) { Conference* room = static_cast(sender()); emit changeRoom(room->jid, { {"joined", joined} }); } void Core::Account::onMucAutoJoinChanged(bool autoJoin) { storeConferences(); Conference* room = static_cast(sender()); emit changeRoom(room->jid, { {"autoJoin", autoJoin} }); } void Core::Account::onMucNickNameChanged(const QString& nickName) { storeConferences(); Conference* room = static_cast(sender()); emit changeRoom(room->jid, { {"nick", nickName} }); } void Core::Account::setRoomAutoJoin(const QString& jid, bool joined) { std::map::const_iterator cItr = conferences.find(jid); if (cItr == conferences.end()) { qDebug() << "An attempt to set auto join to the non existing room" << jid << "of the account" << getName() << ", skipping"; return; } cItr->second->setAutoJoin(joined); } void Core::Account::setRoomJoined(const QString& jid, bool joined) { std::map::const_iterator cItr = conferences.find(jid); if (cItr == conferences.end()) { qDebug() << "An attempt to set joined to the non existing room" << jid << "of the account" << getName() << ", skipping"; return; } cItr->second->setJoined(joined); } void Core::Account::onMucAddParticipant(const QString& nickName, const QMap& data) { Conference* room = static_cast(sender()); emit addRoomParticipant(room->jid, nickName, data); } void Core::Account::onMucChangeParticipant(const QString& nickName, const QMap& data) { Conference* room = static_cast(sender()); emit changeRoomParticipant(room->jid, nickName, data); } void Core::Account::onMucRemoveParticipant(const QString& nickName) { Conference* room = static_cast(sender()); emit removeRoomParticipant(room->jid, nickName); } void Core::Account::onMucSubjectChanged(const QString& subject) { Conference* room = static_cast(sender()); emit changeRoom(room->jid, { {"subject", subject} }); } void Core::Account::storeConferences() { QXmppBookmarkSet bms = bm->bookmarks(); QList confs; for (std::map::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) { Conference* conference = itr->second; QXmppBookmarkConference conf; conf.setJid(conference->jid); conf.setName(conference->getName()); conf.setNickName(conference->getNick()); conf.setAutoJoin(conference->getAutoJoin()); confs.push_back(conf); } bms.setConferences(confs); bm->setBookmarks(bms); } void Core::Account::clearConferences() { for (std::map::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) { itr->second->deleteLater(); emit removeRoom(itr->first); } conferences.clear(); } void Core::Account::removeRoomRequest(const QString& jid) { std::map::const_iterator itr = conferences.find(jid); if (itr == conferences.end()) { qDebug() << "An attempt to remove non existing room" << jid << "from account" << name << ", skipping"; } itr->second->deleteLater(); conferences.erase(itr); emit removeRoom(jid); storeConferences(); } void Core::Account::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) { std::map::const_iterator cItr = conferences.find(jid); if (cItr == conferences.end()) { addNewRoom(jid, nick, "", autoJoin); storeConferences(); } else { qDebug() << "An attempt to add a MUC " << jid << " which is already present in the rester, skipping"; } } void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin) { QXmppMucRoom* room = mm->addRoom(jid); QString lNick = nick; if (lNick.size() == 0) { lNick = getName(); } Conference* conf = new Conference(jid, getName(), autoJoin, roomName, lNick, room); conferences.insert(std::make_pair(jid, conf)); handleNewConference(conf); QMap cData = { {"autoJoin", conf->getAutoJoin()}, {"joined", conf->getJoined()}, {"nick", conf->getNick()}, {"name", conf->getName()}, {"avatars", conf->getAllAvatars()} }; Archive::AvatarInfo info; bool hasAvatar = conf->readAvatarInfo(info); if (hasAvatar) { if (info.autogenerated) { cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated)); } else { cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid)); } cData.insert("avatarPath", conf->avatarPath() + "." + info.type); } else { cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty)); cData.insert("avatarPath", ""); requestVCard(jid); } emit addRoom(jid, cData); } void Core::Account::addContactToGroupRequest(const QString& jid, const QString& groupName) { std::map::const_iterator itr = contacts.find(jid); if (itr == contacts.end()) { qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping"; } else { QXmppRosterIq::Item item = rm->getRosterEntry(jid); QSet groups = item.groups(); if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib groups.insert(groupName); item.setGroups(groups); QXmppRosterIq iq; iq.setType(QXmppIq::Set); iq.addItem(item); client.sendPacket(iq); } else { qDebug() << "An attempt to add contact" << jid << "of account" << name << "to the group" << groupName << "but it's already in that group, skipping"; } } } void Core::Account::removeContactFromGroupRequest(const QString& jid, const QString& groupName) { std::map::const_iterator itr = contacts.find(jid); if (itr == contacts.end()) { qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping"; } else { QXmppRosterIq::Item item = rm->getRosterEntry(jid); QSet groups = item.groups(); QSet::const_iterator gItr = groups.find(groupName); if (gItr != groups.end()) { groups.erase(gItr); item.setGroups(groups); QXmppRosterIq iq; iq.setType(QXmppIq::Set); iq.addItem(item); client.sendPacket(iq); } else { qDebug() << "An attempt to remove contact" << jid << "of account" << name << "from the group" << groupName << "but it's not in that group, skipping"; } } } void Core::Account::renameContactRequest(const QString& jid, const QString& newName) { std::map::const_iterator itr = contacts.find(jid); if (itr == contacts.end()) { qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping"; } else { rm->renameItem(jid, newName); } } void Core::Account::onVCardReceived(const QXmppVCardIq& card) { QString id = card.from(); QStringList comps = id.split("/"); QString jid = comps.front(); QString resource(""); if (comps.size() > 1) { resource = comps.back(); } pendingVCardRequests.erase(id); RosterItem* item = getRosterItem(jid); if (item == 0) { if (jid == getLogin() + "@" + getServer()) { onOwnVCardReceived(card); } else { qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; } return; } Shared::VCard vCard = item->handleResponseVCard(card, resource); emit receivedVCard(jid, vCard); } void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) { QByteArray ava = card.photo(); bool avaChanged = false; QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/"; if (ava.size() > 0) { QCryptographicHash sha1(QCryptographicHash::Sha1); sha1.addData(ava); QString newHash(sha1.result()); QMimeDatabase db; QMimeType newType = db.mimeTypeForData(ava); if (avatarType.size() > 0) { if (avatarHash != newHash) { QString oldPath = path + "avatar." + avatarType; QFile oldAvatar(oldPath); bool oldToRemove = false; if (oldAvatar.exists()) { if (oldAvatar.rename(oldPath + ".bak")) { oldToRemove = true; } else { qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing"; } } QFile newAvatar(path + "avatar." + newType.preferredSuffix()); if (newAvatar.open(QFile::WriteOnly)) { newAvatar.write(ava); newAvatar.close(); avatarHash = newHash; avatarType = newType.preferredSuffix(); avaChanged = true; } else { qDebug() << "Received new avatar for account" << name << "but can't save it"; if (oldToRemove) { qDebug() << "rolling back to the old avatar"; if (!oldAvatar.rename(oldPath)) { qDebug() << "Couldn't roll back to the old avatar in account" << name; } } } } } else { QFile newAvatar(path + "avatar." + newType.preferredSuffix()); if (newAvatar.open(QFile::WriteOnly)) { newAvatar.write(ava); newAvatar.close(); avatarHash = newHash; avatarType = newType.preferredSuffix(); avaChanged = true; } else { qDebug() << "Received new avatar for account" << name << "but can't save it"; } } } else { if (avatarType.size() > 0) { QFile oldAvatar(path + "avatar." + avatarType); if (!oldAvatar.remove()) { qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing"; } else { avatarType = ""; avatarHash = ""; avaChanged = true; } } } if (avaChanged) { QMap change; if (avatarType.size() > 0) { presence.setPhotoHash(avatarHash.toUtf8()); presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); change.insert("avatarPath", path + "avatar." + avatarType); } else { presence.setPhotoHash(""); presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto); change.insert("avatarPath", ""); } client.setClientPresence(presence); emit changed(change); } ownVCardRequestInProgress = false; Shared::VCard vCard; initializeVCard(vCard, card); if (avatarType.size() > 0) { vCard.setAvatarType(Shared::Avatar::valid); vCard.setAvatarPath(path + "avatar." + avatarType); } else { vCard.setAvatarType(Shared::Avatar::empty); } emit receivedVCard(getLogin() + "@" + getServer(), vCard); } QString Core::Account::getAvatarPath() const { if (avatarType.size() == 0) { return ""; } else { return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType; } } void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path) { RosterItem* item = static_cast(sender()); QMap cData({ {"avatarState", static_cast(type)}, {"avatarPath", path} }); emit changeContact(item->jid, cData); } void Core::Account::requestVCard(const QString& jid) { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { qDebug() << "requesting vCard" << jid; if (jid == getLogin() + "@" + getServer()) { if (!ownVCardRequestInProgress) { vm->requestClientVCard(); ownVCardRequestInProgress = true; } } else { vm->requestVCard(jid); pendingVCardRequests.insert(jid); } } } void Core::Account::uploadVCard(const Shared::VCard& card) { QXmppVCardIq iq; initializeQXmppVCard(iq, card); bool avatarChanged = false; if (card.getAvatarType() != Shared::Avatar::empty) { QString newPath = card.getAvatarPath(); QString oldPath = getAvatarPath(); QByteArray data; QString type; if (newPath != oldPath) { QFile avatar(newPath); if (!avatar.open(QFile::ReadOnly)) { qDebug() << "An attempt to upload new vCard to account" << name << "but it wasn't possible to read file" << newPath << "which was supposed to be new avatar, uploading old avatar"; if (avatarType.size() > 0) { QFile oA(oldPath); if (!oA.open(QFile::ReadOnly)) { qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; } else { data = oA.readAll(); } } } else { data = avatar.readAll(); avatarChanged = true; } } else { if (avatarType.size() > 0) { QFile oA(oldPath); if (!oA.open(QFile::ReadOnly)) { qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; } else { data = oA.readAll(); } } } if (data.size() > 0) { QMimeDatabase db; type = db.mimeTypeForData(data).name(); iq.setPhoto(data); iq.setPhotoType(type); } } vm->setClientVCard(iq); onOwnVCardReceived(iq); } void Core::Account::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) { if (uploadingSlotsQueue.size() == 0) { qDebug() << "HTTP Upload manager of account" << name << "reports about success requesting upload slot, but none was requested"; } else { const std::pair& pair = uploadingSlotsQueue.front(); const QString& mId = pair.second.getId(); network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders()); pendingMessages.emplace(mId, pair.second); uploadingSlotsQueue.pop_front(); if (uploadingSlotsQueue.size() > 0) { um->requestUploadSlot(uploadingSlotsQueue.front().first); } } } void Core::Account::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) { if (uploadingSlotsQueue.size() == 0) { qDebug() << "HTTP Upload manager of account" << name << "reports about an error requesting upload slot, but none was requested"; qDebug() << request.error().text(); } else { const std::pair& pair = uploadingSlotsQueue.front(); qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << name << ":" << request.error().text(); emit uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text()); if (uploadingSlotsQueue.size() > 0) { um->requestUploadSlot(uploadingSlotsQueue.front().first); } uploadingSlotsQueue.pop_front(); } } void Core::Account::onFileUploaded(const QString& messageId, const QString& url) { std::map::const_iterator itr = pendingMessages.find(messageId); if (itr != pendingMessages.end()) { sendMessageWithLocalUploadedFile(itr->second, url); pendingMessages.erase(itr); } } void Core::Account::onFileUploadError(const QString& messageId, const QString& errMsg) { std::map::const_iterator itr = pendingMessages.find(messageId); if (itr != pendingMessages.end()) { pendingMessages.erase(itr); } } void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items) { for (QXmppDiscoveryIq::Item item : items.items()) { if (item.jid() != getServer()) { dm->requestInfo(item.jid()); } } } void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) { if (info.from() == getServer()) { if (info.features().contains("urn:xmpp:carbons:2")) { cm->setCarbonsEnabled(true); } } } void Core::Account::onReceiptReceived(const QString& jid, const QString& id) { std::map::const_iterator itr = pendingStateMessages.find(id); if (itr != pendingStateMessages.end()) { QMap cData = {{"state", static_cast(Shared::Message::State::delivered)}}; RosterItem* ri = getRosterItem(itr->second); if (ri != 0) { ri->changeMessage(id, cData); } pendingStateMessages.erase(itr); emit changeMessage(itr->second, id, cData); } }