From 55703c200778c8729a2f1fe912d5b80352e97ed0 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 30 Dec 2019 23:22:04 +0300 Subject: [PATCH] muc participant avatars --- core/account.cpp | 92 ++++----------- core/account.h | 4 +- core/archive.cpp | 234 +++++++++++++++++++++----------------- core/archive.h | 48 ++++++-- core/conference.cpp | 111 +++++++++++++++++- core/conference.h | 4 + core/contact.cpp | 30 +++++ core/contact.h | 1 + core/rosteritem.cpp | 96 ++++++++++++---- core/rosteritem.h | 16 ++- ui/models/participant.cpp | 55 ++++++++- ui/models/participant.h | 10 ++ ui/models/roster.cpp | 14 ++- ui/squawk.cpp | 1 - ui/utils/message.cpp | 11 +- 15 files changed, 506 insertions(+), 221 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 9811dd2..a7f4801 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -349,13 +349,15 @@ void Core::Account::addedAccount(const QString& jid) {"state", state} }); - if (contact->hasAvatar()) { - if (!contact->isAvatarAutoGenerated()) { + 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()); + cData.insert("avatarPath", contact->avatarPath() + "." + info.type); } else { cData.insert("avatarState", static_cast(Shared::Avatar::empty)); cData.insert("avatarPath", ""); @@ -382,6 +384,7 @@ void Core::Account::handleNewRosterItem(Core::RosterItem* contact) 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) @@ -440,42 +443,9 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) } } else { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { - RosterItem* item = 0; std::map::const_iterator itr = contacts.find(jid); if (itr != contacts.end()) { - item = itr->second; - } else { - std::map::const_iterator citr = conferences.find(jid); - if (citr != conferences.end()) { - item = citr->second; - } - } - - if (item != 0) { - 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 (!item->hasAvatar() || (item->hasAvatar() && !item->isAvatarAutoGenerated())) { - item->setAutoGeneratedAvatar(); - } - break; - case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load - if (item->hasAvatar()) { - if (item->isAvatarAutoGenerated()) { - requestVCard(jid); - } else { - if (item->avatarHash() != p_presence.photoHash()) { - requestVCard(jid); - } - } - } else { - requestVCard(jid); - } - break; - } + itr->second->handlePresence(p_presence); } } } @@ -1333,13 +1303,15 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS {"name", conf->getName()} }; - if (conf->hasAvatar()) { - if (!conf->isAvatarAutoGenerated()) { + Archive::AvatarInfo info; + bool hasAvatar = conf->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", conf->avatarPath()); + cData.insert("avatarPath", conf->avatarPath() + "." + info.type); } else { cData.insert("avatarState", static_cast(Shared::Avatar::empty)); cData.insert("avatarPath", ""); @@ -1406,8 +1378,14 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN void Core::Account::onVCardReceived(const QXmppVCardIq& card) { - QString jid = card.from(); - pendingVCardRequests.erase(jid); + 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 = 0; std::map::const_iterator contItr = contacts.find(jid); @@ -1427,35 +1405,8 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) item = contItr->second; } - QByteArray ava = card.photo(); + Shared::VCard vCard = item->handleResponseVCard(card, resource); - if (ava.size() > 0) { - item->setAvatar(ava); - } else { - if (!item->hasAvatar() || !item->isAvatarAutoGenerated()) { - item->setAutoGeneratedAvatar(); - } - } - - Shared::VCard vCard; - initializeVCard(vCard, card); - - if (item->hasAvatar()) { - if (!item->isAvatarAutoGenerated()) { - vCard.setAvatarType(Shared::Avatar::valid); - } else { - vCard.setAvatarType(Shared::Avatar::autocreated); - } - vCard.setAvatarPath(item->avatarPath()); - } else { - vCard.setAvatarType(Shared::Avatar::empty); - } - - QMap cd = { - {"avatarState", static_cast(vCard.getAvatarType())}, - {"avatarPath", vCard.getAvatarPath()} - }; - emit changeContact(jid, cd); emit receivedVCard(jid, vCard); } @@ -1577,6 +1528,7 @@ void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& p 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(); diff --git a/core/account.h b/core/account.h index ff77455..8978800 100644 --- a/core/account.h +++ b/core/account.h @@ -92,9 +92,11 @@ public: void setRoomAutoJoin(const QString& jid, bool joined); void removeRoomRequest(const QString& jid); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); - void requestVCard(const QString& jid); void uploadVCard(const Shared::VCard& card); +public slots: + void requestVCard(const QString& jid); + signals: void changed(const QMap& data); void connectionStateChanged(int); diff --git a/core/archive.cpp b/core/archive.cpp index 9b7c9eb..0d3e7c4 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -33,10 +33,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent): main(), order(), stats(), - hasAvatar(false), - avatarAutoGenerated(false), - avatarHash(), - avatarType() + avatars() { } @@ -60,7 +57,7 @@ void Core::Archive::open(const QString& account) } } - mdb_env_set_maxdbs(environment, 4); + mdb_env_set_maxdbs(environment, 5); mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); @@ -69,43 +66,25 @@ void Core::Archive::open(const QString& account) mdb_dbi_open(txn, "main", MDB_CREATE, &main); 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_txn_commit(txn); - mdb_txn_begin(environment, NULL, 0, &txn); + mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); try { fromTheBeginning = getStatBoolValue("beginning", txn); } catch (const NotFound& e) { fromTheBeginning = false; } - try { - hasAvatar = getStatBoolValue("hasAvatar", txn); - } catch (const NotFound& e) { - hasAvatar = false; - } - if (hasAvatar) { - try { - avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn); - } catch (const NotFound& e) { - avatarAutoGenerated = false; - } - - avatarType = getStatStringValue("avatarType", txn).c_str(); - if (avatarAutoGenerated) { - avatarHash = ""; - } else { - avatarHash = getStatStringValue("avatarHash", txn).c_str(); - } - } else { - avatarAutoGenerated = false; - avatarHash = ""; - avatarType = ""; - } + + std::string sJid = jid.toStdString(); + AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, sJid, txn); mdb_txn_abort(txn); if (hasAvatar) { - QFile ava(path + "/avatar." + avatarType); + QFile ava(path + "/" + sJid.c_str() + "." + info.type); if (!ava.exists()) { - bool success = dropAvatar(); + bool success = dropAvatar(sJid); if (!success) { qDebug() << "error opening archive" << jid << "for account" << account << ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error"; @@ -120,6 +99,7 @@ void Core::Archive::open(const QString& account) void Core::Archive::close() { if (opened) { + mdb_dbi_close(environment, avatars); mdb_dbi_close(environment, stats); mdb_dbi_close(environment, order); mdb_dbi_close(environment, main); @@ -518,8 +498,9 @@ bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn) if (rc == MDB_NOTFOUND) { throw NotFound(id, jid.toStdString()); } else if (rc) { - qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); - throw 15; //TODO proper exception + std::string err(mdb_strerror(rc)); + qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str(); + throw Unknown(jid.toStdString(), err); } else { uint8_t value = *(uint8_t*)(lmdbData.mv_data); bool is; @@ -546,8 +527,9 @@ std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* tx if (rc == MDB_NOTFOUND) { throw NotFound(id, jid.toStdString()); } else if (rc) { - qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); - throw 15; //TODO proper exception + std::string err(mdb_strerror(rc)); + qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str(); + throw Unknown(jid.toStdString(), err); } else { std::string value((char*)lmdbData.mv_data, lmdbData.mv_size); return value; @@ -588,74 +570,38 @@ bool Core::Archive::setStatValue(const std::string& id, const std::string& value return true; } -bool Core::Archive::getHasAvatar() const -{ - if (!opened) { - throw Closed("getHasAvatar", jid.toStdString()); - } - - return hasAvatar; -} - -bool Core::Archive::getAutoAvatar() const -{ - if (!opened) { - throw Closed("getAutoAvatar", jid.toStdString()); - } - - return avatarAutoGenerated; -} - -QString Core::Archive::getAvatarHash() const -{ - if (!opened) { - throw Closed("getAvatarHash", jid.toStdString()); - } - - return avatarHash; -} - -QString Core::Archive::getAvatarType() const -{ - if (!opened) { - throw Closed("getAvatarType", jid.toStdString()); - } - - return avatarType; -} - -bool Core::Archive::dropAvatar() +bool Core::Archive::dropAvatar(const std::string& resource) { MDB_txn *txn; + MDB_val lmdbKey; mdb_txn_begin(environment, NULL, 0, &txn); - bool success = setStatValue("hasAvatar", false, txn); - success = success && setStatValue("avatarAutoGenerated", false, txn); - success = success && setStatValue("avatarHash", "", txn); - success = success && setStatValue("avatarType", "", txn); - if (!success) { + lmdbKey.mv_size = resource.size(); + lmdbKey.mv_data = (char*)resource.c_str(); + int rc = mdb_del(txn, avatars, &lmdbKey, NULL); + if (rc != 0) { mdb_txn_abort(txn); return false; } else { - hasAvatar = false; - avatarAutoGenerated = false; - avatarHash = ""; - avatarType = ""; mdb_txn_commit(txn); return true; } } -bool Core::Archive::setAvatar(const QByteArray& data, bool generated) +bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QString& resource) { if (!opened) { throw Closed("setAvatar", jid.toStdString()); } + AvatarInfo oldInfo; + bool hasAvatar = readAvatarInfo(oldInfo, resource); + std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString(); + if (data.size() == 0) { if (!hasAvatar) { return false; } else { - return dropAvatar(); + return dropAvatar(res); } } else { const char* cep; @@ -664,14 +610,14 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated) bool needToRemoveOld = false; QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(data); - QString newHash(hash.result()); + QByteArray newHash(hash.result()); if (hasAvatar) { - if (!generated && !avatarAutoGenerated && avatarHash == newHash) { + if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash) { return false; } - QFile oldAvatar(currentPath + "/avatar." + avatarType); + QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type); if (oldAvatar.exists()) { - if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) { + if (oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak")) { needToRemoveOld = true; } else { qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName(); @@ -682,33 +628,36 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated) QMimeDatabase db; QMimeType type = db.mimeTypeForData(data); QString ext = type.preferredSuffix(); - QFile newAvatar(currentPath + "/avatar." + ext); + QFile newAvatar(currentPath + "/" + res.c_str() + "." + ext); if (newAvatar.open(QFile::WriteOnly)) { newAvatar.write(data); newAvatar.close(); MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); - bool success = setStatValue("hasAvatar", true, txn); - success = success && setStatValue("avatarAutoGenerated", generated, txn); - success = success && setStatValue("avatarHash", newHash.toStdString(), txn); - success = success && setStatValue("avatarType", ext.toStdString(), txn); - if (!success) { + + MDB_val lmdbKey, lmdbData; + QByteArray value; + AvatarInfo newInfo(ext, newHash, generated); + newInfo.serialize(&value); + lmdbKey.mv_size = res.size(); + lmdbKey.mv_data = (char*)res.c_str(); + lmdbData.mv_size = value.size(); + lmdbData.mv_data = value.data(); + int rc = mdb_put(txn, avatars, &lmdbKey, &lmdbData, 0); + + if (rc != 0) { qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state"; if (needToRemoveOld) { - QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); - oldAvatar.rename(currentPath + "/avatar." + avatarType); + QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak"); + oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type); } mdb_txn_abort(txn); return false; } else { - hasAvatar = true; - avatarAutoGenerated = generated; - avatarHash = newHash; - avatarType = ext; mdb_txn_commit(txn); if (needToRemoveOld) { - QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); + QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak"); oldAvatar.remove(); } return true; @@ -716,10 +665,91 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated) } else { qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state"; if (needToRemoveOld) { - QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); - oldAvatar.rename(currentPath + "/avatar." + avatarType); + QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak"); + oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type); } return false; } } } + +bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const +{ + if (!opened) { + throw Closed("readAvatarInfo", jid.toStdString()); + } + std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString(); + + MDB_txn *txn; + mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + + try { + bool success = readAvatarInfo(target, res, txn); + mdb_txn_abort(txn); + return success; + } catch (const std::exception& e) { + mdb_txn_abort(txn); + throw e; + } + +} + +bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std::string& res, MDB_txn* txn) const +{ + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = res.size(); + lmdbKey.mv_data = (char*)res.c_str(); + + int rc; + rc = mdb_get(txn, avatars, &lmdbKey, &lmdbData); + if (rc == MDB_NOTFOUND) { + return false; + } else if (rc) { + std::string err(mdb_strerror(rc)); + qDebug() << "error reading avatar info for" << res.c_str() << "resource of" << jid << err.c_str(); + throw Unknown(jid.toStdString(), err); + } else { + target.deserialize((char*)lmdbData.mv_data, lmdbData.mv_size); + return true; + } +} + +Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const +{ + if (!opened) { + throw Closed("readAvatarInfo", jid.toStdString()); + } + + AvatarInfo info; + bool success = readAvatarInfo(info, resource); + if (success) { + return info; + } else { + throw NoAvatar(jid.toStdString(), resource.toStdString()); + } +} + +Core::Archive::AvatarInfo::AvatarInfo(): +type(), +hash(), +autogenerated(false) {} + +Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated): +type(p_type), +hash(p_hash), +autogenerated(p_autogenerated) {} + +void Core::Archive::AvatarInfo::deserialize(char* pointer, uint32_t size) +{ + QByteArray data = QByteArray::fromRawData(pointer, size); + QDataStream in(&data, QIODevice::ReadOnly); + + in >> type >> hash >> autogenerated; +} + +void Core::Archive::AvatarInfo::serialize(QByteArray* ba) const +{ + QDataStream out(ba, QIODevice::WriteOnly); + + out << type << hash << autogenerated; +} diff --git a/core/archive.h b/core/archive.h index e94fac8..da4a96f 100644 --- a/core/archive.h +++ b/core/archive.h @@ -35,6 +35,8 @@ class Archive : public QObject { Q_OBJECT public: + class AvatarInfo; + Archive(const QString& jid, QObject* parent = 0); ~Archive(); @@ -53,11 +55,9 @@ public: std::list getBefore(int count, const QString& id); bool isFromTheBeginning(); void setFromTheBeginning(bool is); - bool getHasAvatar() const; - bool getAutoAvatar() const; - QString getAvatarHash() const; - QString getAvatarType() const; - bool setAvatar(const QByteArray& data, bool generated = false); + bool setAvatar(const QByteArray& data, bool generated = false, const QString& resource = ""); + AvatarInfo getAvatarInfo(const QString& resource = "") const; + bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const; public: const QString jid; @@ -121,6 +121,22 @@ public: std::string key; }; + class NoAvatar: + public Utils::Exception + { + public: + NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){ + if (resource.size() == 0) { + resource = "for himself"; + } + } + + std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;} + private: + std::string element; + std::string resource; + }; + class Unknown: public Utils::Exception { @@ -133,6 +149,20 @@ public: std::string msg; }; + + class AvatarInfo { + public: + AvatarInfo(); + AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated); + + void deserialize(char* pointer, uint32_t size); + void serialize(QByteArray* ba) const; + + QString type; + QByteArray hash; + bool autogenerated; + }; + private: bool opened; bool fromTheBeginning; @@ -140,19 +170,17 @@ private: MDB_dbi main; MDB_dbi order; MDB_dbi stats; - bool hasAvatar; - bool avatarAutoGenerated; - QString avatarHash; - QString avatarType; + MDB_dbi avatars; bool getStatBoolValue(const std::string& id, MDB_txn* txn); std::string getStatStringValue(const std::string& id, MDB_txn* txn); bool setStatValue(const std::string& id, bool value, MDB_txn* txn); bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn); + bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const; void printOrder(); void printKeys(); - bool dropAvatar(); + bool dropAvatar(const std::string& resource); }; } diff --git a/core/conference.cpp b/core/conference.cpp index 56a1e8f..2d10273 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -143,14 +143,31 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) lastInteraction = QDateTime::currentDateTime(); } QXmppMucItem mi = pres.mucItem(); + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, resource); - emit addParticipant(resource, { + QMap cData = { {"lastActivity", lastInteraction}, {"availability", pres.availableStatusType()}, {"status", pres.statusText()}, {"affiliation", mi.affiliation()}, {"role", mi.role()} - }); + }; + + 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", avatarPath(resource) + "." + info.type); + } else { + cData.insert("avatarState", static_cast(Shared::Avatar::empty)); + cData.insert("avatarPath", ""); + requestVCard(p_name); + } + + emit addParticipant(resource, cData); } } @@ -167,6 +184,7 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name) lastInteraction = QDateTime::currentDateTime(); } QXmppMucItem mi = pres.mucItem(); + handlePresence(pres); emit changeParticipant(resource, { {"lastActivity", lastInteraction}, @@ -202,3 +220,92 @@ void Core::Conference::onRoomSubjectChanged(const QString& p_name) { emit subjectChanged(p_name); } + +void Core::Conference::handlePresence(const QXmppPresence& pres) +{ + QString id = pres.from(); + QStringList comps = id.split("/"); + QString jid = comps.front(); + QString resource(""); + if (comps.size() > 1) { + resource = comps.back(); + } + + switch (pres.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 + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, resource); + if (!hasAvatar || !info.autogenerated) { + setAutoGeneratedAvatar(resource); + } + } + break; + case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, resource); + if (hasAvatar) { + if (info.autogenerated || info.hash != pres.photoHash()) { + emit requestVCard(id); + } + } else { + emit requestVCard(id); + } + break; + } + } +} + +bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) +{ + bool result = RosterItem::setAutoGeneratedAvatar(resource); + if (result && resource.size() != 0) { + emit changeParticipant(resource, { + {"avatarState", static_cast(Shared::Avatar::autocreated)}, + {"availability", avatarPath(resource) + ".png"} + }); + } + + return result; +} + +bool Core::Conference::setAvatar(const QByteArray& data, const QString& resource) +{ + bool result = RosterItem::setAvatar(data, resource); + if (result && resource.size() != 0) { + if (data.size() > 0) { + QMimeDatabase db; + QMimeType type = db.mimeTypeForData(data); + QString ext = type.preferredSuffix(); + emit changeParticipant(resource, { + {"avatarState", static_cast(Shared::Avatar::autocreated)}, + {"avatarPath", avatarPath(resource) + "." + ext} + }); + } else { + emit changeParticipant(resource, { + {"avatarState", static_cast(Shared::Avatar::empty)}, + {"avatarPath", ""} + }); + } + + } + + return result; +} + +Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource) +{ + Shared::VCard result = RosterItem::handleResponseVCard(card, resource); + + if (resource.size() > 0) { + emit changeParticipant(resource, { + {"avatarState", static_cast(result.getAvatarType())}, + {"avatarPath", result.getAvatarPath()} + }); + } + + return result; +} diff --git a/core/conference.h b/core/conference.h index 71829b7..c00c472 100644 --- a/core/conference.h +++ b/core/conference.h @@ -44,6 +44,10 @@ public: bool getAutoJoin(); void setAutoJoin(bool p_autoJoin); + void handlePresence(const QXmppPresence & pres) override; + bool setAutoGeneratedAvatar(const QString& resource = "") override; + bool setAvatar(const QByteArray &data, const QString &resource = "") override; + Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override; signals: void nickChanged(const QString& nick); diff --git a/core/contact.cpp b/core/contact.cpp index ba0737e..711c505 100644 --- a/core/contact.cpp +++ b/core/contact.cpp @@ -68,3 +68,33 @@ void Core::Contact::setSubscriptionState(Shared::SubscriptionState state) emit subscriptionStateChanged(subscriptionState); } } + +void Core::Contact::handlePresence(const QXmppPresence& pres) +{ + switch (pres.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 + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info); + if (!hasAvatar || !info.autogenerated) { + setAutoGeneratedAvatar(); + } + } + break; + case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info); + if (hasAvatar) { + if (info.autogenerated || info.hash != pres.photoHash()) { + emit requestVCard(jid); + } + } else { + emit requestVCard(jid); + } + break; + } + } +} diff --git a/core/contact.h b/core/contact.h index d4eb4a1..aa81010 100644 --- a/core/contact.h +++ b/core/contact.h @@ -38,6 +38,7 @@ public: void setSubscriptionState(Shared::SubscriptionState state); Shared::SubscriptionState getSubscriptionState() const; + void handlePresence(const QXmppPresence & pres) override; signals: void groupAdded(const QString& name); diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index ccc3072..a8d48a4 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -17,6 +17,7 @@ */ #include "rosteritem.h" +#include "account.h" #include @@ -334,40 +335,30 @@ bool Core::RosterItem::isMuc() const return muc; } -QString Core::RosterItem::avatarHash() const -{ - return archive->getAvatarHash(); -} - -bool Core::RosterItem::isAvatarAutoGenerated() const -{ - return archive->getAutoAvatar(); -} - -QString Core::RosterItem::avatarPath() const +QString Core::RosterItem::avatarPath(const QString& resource) const { QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType(); + path += "/" + account + "/" + jid + "/" + (resource.size() == 0 ? jid : resource); return path; } -bool Core::RosterItem::hasAvatar() const +bool Core::RosterItem::setAvatar(const QByteArray& data, const QString& resource) { - return archive->getHasAvatar(); -} - -void Core::RosterItem::setAvatar(const QByteArray& data) -{ - if (archive->setAvatar(data, false)) { - if (archive->getHasAvatar()) { + bool result = archive->setAvatar(data, false, resource); + if (resource.size() == 0 && result) { + if (data.size() == 0) { emit avatarChanged(Shared::Avatar::empty, ""); } else { - emit avatarChanged(Shared::Avatar::valid, avatarPath()); + QMimeDatabase db; + QMimeType type = db.mimeTypeForData(data); + QString ext = type.preferredSuffix(); + emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + ext); } } + return result; } -void Core::RosterItem::setAutoGeneratedAvatar() +bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) { QImage image(96, 96, QImage::Format_ARGB32_Premultiplied); QPainter painter(&image); @@ -383,13 +374,68 @@ void Core::RosterItem::setAutoGeneratedAvatar() } else { painter.setPen(Qt::white); } - painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, jid.at(0).toUpper()); + painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper()); QByteArray arr; QBuffer stream(&arr); stream.open(QBuffer::WriteOnly); image.save(&stream, "PNG"); stream.close(); - if (archive->setAvatar(arr, true)) { - emit avatarChanged(Shared::Avatar::autocreated, avatarPath()); + bool result = archive->setAvatar(arr, true, resource); + if (resource.size() == 0 && result) { + emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png"); } + return result; } + +bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const +{ + return archive->readAvatarInfo(target, resource); +} + +Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource) +{ + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, resource); + + QByteArray ava = card.photo(); + Shared::VCard vCard; + initializeVCard(vCard, card); + Shared::Avatar type = Shared::Avatar::empty; + QString path = ""; + + if (ava.size() > 0) { + bool changed = setAvatar(ava, resource); + if (changed) { + type = Shared::Avatar::valid; + QMimeDatabase db; + QMimeType type = db.mimeTypeForData(ava); + QString ext = type.preferredSuffix(); + path = avatarPath(resource) + "." + ext; + } else if (hasAvatar) { + if (info.autogenerated) { + type = Shared::Avatar::autocreated; + path = avatarPath(resource) + ".png"; + } else { + type = Shared::Avatar::valid; + path = avatarPath(resource) + "." + info.type; + } + } + } else { + if (!hasAvatar || !info.autogenerated) { + setAutoGeneratedAvatar(resource); + type = Shared::Avatar::autocreated; + path = avatarPath(resource) + ".png"; + } + } + + + vCard.setAvatarType(type); + vCard.setAvatarPath(path); + + if (resource.size() == 0) { + emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath()); + } + + return vCard; +} + diff --git a/core/rosteritem.h b/core/rosteritem.h index c0a490e..ec8a622 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -28,6 +28,9 @@ #include +#include +#include + #include "../global.h" #include "archive.h" @@ -62,12 +65,12 @@ public: void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId); void requestHistory(int count, const QString& before); void requestFromEmpty(int count, const QString& before); - bool hasAvatar() const; - bool isAvatarAutoGenerated() const; - QString avatarHash() const; - QString avatarPath() const; - void setAvatar(const QByteArray& data); - void setAutoGeneratedAvatar(); + QString avatarPath(const QString& resource = "") const; + bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const; + virtual bool setAvatar(const QByteArray& data, const QString& resource = ""); + virtual bool setAutoGeneratedAvatar(const QString& resource = ""); + virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource); + virtual void handlePresence(const QXmppPresence& pres) = 0; signals: void nameChanged(const QString& name); @@ -75,6 +78,7 @@ signals: void historyResponse(const std::list& messages); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); void avatarChanged(Shared::Avatar, const QString& path); + void requestVCard(const QString& jid); public: const QString jid; diff --git a/ui/models/participant.cpp b/ui/models/participant.cpp index 16f5cb7..3939888 100644 --- a/ui/models/participant.cpp +++ b/ui/models/participant.cpp @@ -34,6 +34,15 @@ Models::Participant::Participant(const QMap& data, Models::It if (itr != data.end()) { setRole(itr.value().toUInt()); } + + itr = data.find("avatarState"); + if (itr != data.end()) { + setAvatarState(itr.value().toUInt()); + } + itr = data.find("avatarPath"); + if (itr != data.end()) { + setAvatarPath(itr.value().toString()); + } } Models::Participant::~Participant() @@ -42,7 +51,7 @@ Models::Participant::~Participant() int Models::Participant::columnCount() const { - return 6; + return 8; } QVariant Models::Participant::data(int column) const @@ -52,6 +61,10 @@ QVariant Models::Participant::data(int column) const return static_cast(affiliation); case 5: return static_cast(role); + case 6: + return static_cast(getAvatarState()); + case 7: + return getAvatarPath(); default: return AbstractParticipant::data(column); } @@ -63,6 +76,10 @@ void Models::Participant::update(const QString& key, const QVariant& value) setAffiliation(value.toUInt()); } else if (key == "role") { setRole(value.toUInt()); + } else if (key == "avatarState") { + setAvatarState(value.toUInt()); + } else if (key == "avatarPath") { + setAvatarPath(value.toString()); } else { AbstractParticipant::update(key, value); } @@ -113,3 +130,39 @@ void Models::Participant::setRole(unsigned int p_role) qDebug() << "An attempt to set wrong role" << p_role << "to the room participant" << name; } } + +QString Models::Participant::getAvatarPath() const +{ + return avatarPath; +} + +Shared::Avatar Models::Participant::getAvatarState() const +{ + return avatarState; +} + +void Models::Participant::setAvatarPath(const QString& path) +{ + if (avatarPath != path) { + avatarPath = path; + changed(7); + } +} + +void Models::Participant::setAvatarState(Shared::Avatar p_state) +{ + if (avatarState != p_state) { + avatarState = p_state; + changed(6); + } +} + +void Models::Participant::setAvatarState(unsigned int p_state) +{ + if (p_state <= static_cast(Shared::Avatar::valid)) { + Shared::Avatar state = static_cast(p_state); + setAvatarState(state); + } else { + qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room participant" << name << ", skipping"; + } +} diff --git a/ui/models/participant.h b/ui/models/participant.h index 9c6ddbc..a93cb6d 100644 --- a/ui/models/participant.h +++ b/ui/models/participant.h @@ -41,10 +41,20 @@ public: Shared::Role getRole() const; void setRole(Shared::Role p_role); void setRole(unsigned int role); + + Shared::Avatar getAvatarState() const; + QString getAvatarPath() const; +protected: + void setAvatarState(Shared::Avatar p_state); + void setAvatarState(unsigned int p_state); + void setAvatarPath(const QString& path); + private: Shared::Affiliation affiliation; Shared::Role role; + Shared::Avatar avatarState; + QString avatarPath; }; } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 97aa192..63bd290 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -66,6 +66,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const case Qt::DisplayRole: { if (index.column() != 0) { + result = ""; break; } switch (item->type) { @@ -139,11 +140,20 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const } break; case Item::participant: { + quint8 col = index.column(); + Participant* p = static_cast(item); + if (col == 0) { + result = p->getStatusIcon(false); + } else if (col == 1) { + QString path = p->getAvatarPath(); + if (path.size() > 0) { + result = QIcon(path); + } + } if (index.column() != 0) { break; } - Participant* p = static_cast(item); - result = p->getStatusIcon(false); + } break; default: diff --git a/ui/squawk.cpp b/ui/squawk.cpp index ce648ba..915c42c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -213,7 +213,6 @@ void Squawk::addContact(const QString& account, const QString& jid, const QStrin settings.beginGroup(account); if (settings.value("expanded", false).toBool()) { QModelIndex ind = rosterModel.getAccountIndex(account); - qDebug() << "expanding account " << ind.data(); m_ui->roster->expand(ind); } settings.endGroup(); diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index ecb54f8..479a3a0 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -23,7 +23,16 @@ #include #include "message.h" -const QRegularExpression urlReg("(?