// 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 "vcardhandler.h" #include "core/account.h" Core::VCardHandler::VCardHandler(Account* account): QObject(), acc(account), ownVCardRequestInProgress(false), pendingVCardRequests(), avatarHash(), avatarType() { connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived); //for some reason it doesn't work, launching from common handler //connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + acc->name; QDir dir(path); if (!dir.exists()) { bool res = dir.mkpath(path); if (!res) { qDebug() << "Couldn't create a cache directory for account" << acc->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) { acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); acc->presence.setPhotoHash(avatarHash.toUtf8()); } else { acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady); } } Core::VCardHandler::~VCardHandler() { } void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) { QString id = card.from(); QStringList comps = id.split("/"); QString jid = comps.front().toLower(); QString resource(""); if (comps.size() > 1) { resource = comps.back(); } pendingVCardRequests.erase(id); RosterItem* item = acc->rh->getRosterItem(jid); if (item == 0) { if (jid == acc->getBareJid()) { 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 acc->receivedVCard(jid, vCard); } void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) { QByteArray ava = card.photo(); bool avaChanged = false; QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->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" << acc->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" << acc->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" << acc->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" << acc->name << "but can't save it"; } } } else { if (avatarType.size() > 0) { QFile oldAvatar(path + "avatar." + avatarType); if (!oldAvatar.remove()) { qDebug() << "Received vCard for account" << acc->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) { acc->presence.setPhotoHash(avatarHash.toUtf8()); acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); change.insert("avatarPath", path + "avatar." + avatarType); } else { acc->presence.setPhotoHash(""); acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto); change.insert("avatarPath", ""); } acc->client.setClientPresence(acc->presence); emit acc->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 acc->receivedVCard(acc->getBareJid(), vCard); } void Core::VCardHandler::handleOffline() { pendingVCardRequests.clear(); Shared::VCard vCard; //just to show, that there is now more pending request for (const QString& jid : pendingVCardRequests) { emit acc->receivedVCard(jid, vCard); //need to show it better in the future, like with an error } pendingVCardRequests.clear(); ownVCardRequestInProgress = false; } void Core::VCardHandler::requestVCard(const QString& jid) { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { qDebug() << "requesting vCard" << jid; if (jid == acc->getBareJid()) { if (!ownVCardRequestInProgress) { acc->vm->requestClientVCard(); ownVCardRequestInProgress = true; } } else { acc->vm->requestVCard(jid); pendingVCardRequests.insert(jid); } } } void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence) { 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) { acc->vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load if (avatarHash != p_presence.photoHash()) { acc->vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; } } } void Core::VCardHandler::uploadVCard(const Shared::VCard& card) { QXmppVCardIq iq; initializeQXmppVCard(iq, card); 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" << acc->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" << acc->name << ", uploading empty avatar"; } else { data = oA.readAll(); } } } else { data = avatar.readAll(); } } else { if (avatarType.size() > 0) { QFile oA(oldPath); if (!oA.open(QFile::ReadOnly)) { qDebug() << "Couldn't read old avatar of account" << acc->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); } } acc->vm->setClientVCard(iq); onOwnVCardReceived(iq); } QString Core::VCardHandler::getAvatarPath() const { if (avatarType.size() == 0) { return ""; } else { return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType; } }