1
0
forked from blue/squawk

muc participant avatars

This commit is contained in:
Blue 2019-12-30 23:22:04 +03:00
parent efc90e18c3
commit 55703c2007
15 changed files with 506 additions and 221 deletions

View File

@ -349,13 +349,15 @@ void Core::Account::addedAccount(const QString& jid)
{"state", state} {"state", state}
}); });
if (contact->hasAvatar()) { Archive::AvatarInfo info;
if (!contact->isAvatarAutoGenerated()) { bool hasAvatar = contact->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
} else { } else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
} }
cData.insert("avatarPath", contact->avatarPath()); cData.insert("avatarPath", contact->avatarPath() + "." + info.type);
} else { } else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", ""); 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::historyResponse, this, &Account::onContactHistoryResponse);
QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged); QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged); QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
QObject::connect(contact, &RosterItem::requestVCard, this, &Account::requestVCard);
} }
void Core::Account::handleNewContact(Core::Contact* contact) void Core::Account::handleNewContact(Core::Contact* contact)
@ -440,42 +443,9 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
} }
} else { } else {
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
RosterItem* item = 0;
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid); std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
if (itr != contacts.end()) { if (itr != contacts.end()) {
item = itr->second; itr->second->handlePresence(p_presence);
} else {
std::map<QString, Conference*>::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;
}
} }
} }
} }
@ -1333,13 +1303,15 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
{"name", conf->getName()} {"name", conf->getName()}
}; };
if (conf->hasAvatar()) { Archive::AvatarInfo info;
if (!conf->isAvatarAutoGenerated()) { bool hasAvatar = conf->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
} else { } else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
} }
cData.insert("avatarPath", conf->avatarPath()); cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
} else { } else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty)); cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", ""); cData.insert("avatarPath", "");
@ -1406,8 +1378,14 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
void Core::Account::onVCardReceived(const QXmppVCardIq& card) void Core::Account::onVCardReceived(const QXmppVCardIq& card)
{ {
QString jid = card.from(); QString id = card.from();
pendingVCardRequests.erase(jid); QStringList comps = id.split("/");
QString jid = comps.front();
QString resource("");
if (comps.size() > 1) {
resource = comps.back();
}
pendingVCardRequests.erase(id);
RosterItem* item = 0; RosterItem* item = 0;
std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid); std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
@ -1427,35 +1405,8 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
item = contItr->second; 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<QString, QVariant> cd = {
{"avatarState", static_cast<quint8>(vCard.getAvatarType())},
{"avatarPath", vCard.getAvatarPath()}
};
emit changeContact(jid, cd);
emit receivedVCard(jid, vCard); 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) void Core::Account::requestVCard(const QString& jid)
{ {
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
qDebug() << "requesting vCard" << jid;
if (jid == getLogin() + "@" + getServer()) { if (jid == getLogin() + "@" + getServer()) {
if (!ownVCardRequestInProgress) { if (!ownVCardRequestInProgress) {
vm->requestClientVCard(); vm->requestClientVCard();

View File

@ -92,9 +92,11 @@ public:
void setRoomAutoJoin(const QString& jid, bool joined); void setRoomAutoJoin(const QString& jid, bool joined);
void removeRoomRequest(const QString& jid); void removeRoomRequest(const QString& jid);
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void requestVCard(const QString& jid);
void uploadVCard(const Shared::VCard& card); void uploadVCard(const Shared::VCard& card);
public slots:
void requestVCard(const QString& jid);
signals: signals:
void changed(const QMap<QString, QVariant>& data); void changed(const QMap<QString, QVariant>& data);
void connectionStateChanged(int); void connectionStateChanged(int);

View File

@ -33,10 +33,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
main(), main(),
order(), order(),
stats(), stats(),
hasAvatar(false), avatars()
avatarAutoGenerated(false),
avatarHash(),
avatarType()
{ {
} }
@ -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_set_mapsize(environment, 512UL * 1024UL * 1024UL);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); 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, "main", MDB_CREATE, &main);
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order); mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats); mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
mdb_txn_commit(txn); mdb_txn_commit(txn);
mdb_txn_begin(environment, NULL, 0, &txn); mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try { try {
fromTheBeginning = getStatBoolValue("beginning", txn); fromTheBeginning = getStatBoolValue("beginning", txn);
} catch (const NotFound& e) { } catch (const NotFound& e) {
fromTheBeginning = false; 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(); std::string sJid = jid.toStdString();
if (avatarAutoGenerated) { AvatarInfo info;
avatarHash = ""; bool hasAvatar = readAvatarInfo(info, sJid, txn);
} else {
avatarHash = getStatStringValue("avatarHash", txn).c_str();
}
} else {
avatarAutoGenerated = false;
avatarHash = "";
avatarType = "";
}
mdb_txn_abort(txn); mdb_txn_abort(txn);
if (hasAvatar) { if (hasAvatar) {
QFile ava(path + "/avatar." + avatarType); QFile ava(path + "/" + sJid.c_str() + "." + info.type);
if (!ava.exists()) { if (!ava.exists()) {
bool success = dropAvatar(); bool success = dropAvatar(sJid);
if (!success) { if (!success) {
qDebug() << "error opening archive" << jid << "for account" << account 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"; << ". 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() void Core::Archive::close()
{ {
if (opened) { if (opened) {
mdb_dbi_close(environment, avatars);
mdb_dbi_close(environment, stats); mdb_dbi_close(environment, stats);
mdb_dbi_close(environment, order); mdb_dbi_close(environment, order);
mdb_dbi_close(environment, main); mdb_dbi_close(environment, main);
@ -518,8 +498,9 @@ bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
if (rc == MDB_NOTFOUND) { if (rc == MDB_NOTFOUND) {
throw NotFound(id, jid.toStdString()); throw NotFound(id, jid.toStdString());
} else if (rc) { } else if (rc) {
qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); std::string err(mdb_strerror(rc));
throw 15; //TODO proper exception qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
throw Unknown(jid.toStdString(), err);
} else { } else {
uint8_t value = *(uint8_t*)(lmdbData.mv_data); uint8_t value = *(uint8_t*)(lmdbData.mv_data);
bool is; bool is;
@ -546,8 +527,9 @@ std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* tx
if (rc == MDB_NOTFOUND) { if (rc == MDB_NOTFOUND) {
throw NotFound(id, jid.toStdString()); throw NotFound(id, jid.toStdString());
} else if (rc) { } else if (rc) {
qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); std::string err(mdb_strerror(rc));
throw 15; //TODO proper exception qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
throw Unknown(jid.toStdString(), err);
} else { } else {
std::string value((char*)lmdbData.mv_data, lmdbData.mv_size); std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
return value; return value;
@ -588,74 +570,38 @@ bool Core::Archive::setStatValue(const std::string& id, const std::string& value
return true; return true;
} }
bool Core::Archive::getHasAvatar() const bool Core::Archive::dropAvatar(const std::string& resource)
{
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()
{ {
MDB_txn *txn; MDB_txn *txn;
MDB_val lmdbKey;
mdb_txn_begin(environment, NULL, 0, &txn); mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("hasAvatar", false, txn); lmdbKey.mv_size = resource.size();
success = success && setStatValue("avatarAutoGenerated", false, txn); lmdbKey.mv_data = (char*)resource.c_str();
success = success && setStatValue("avatarHash", "", txn); int rc = mdb_del(txn, avatars, &lmdbKey, NULL);
success = success && setStatValue("avatarType", "", txn); if (rc != 0) {
if (!success) {
mdb_txn_abort(txn); mdb_txn_abort(txn);
return false; return false;
} else { } else {
hasAvatar = false;
avatarAutoGenerated = false;
avatarHash = "";
avatarType = "";
mdb_txn_commit(txn); mdb_txn_commit(txn);
return true; 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) { if (!opened) {
throw Closed("setAvatar", jid.toStdString()); 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 (data.size() == 0) {
if (!hasAvatar) { if (!hasAvatar) {
return false; return false;
} else { } else {
return dropAvatar(); return dropAvatar(res);
} }
} else { } else {
const char* cep; const char* cep;
@ -664,14 +610,14 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
bool needToRemoveOld = false; bool needToRemoveOld = false;
QCryptographicHash hash(QCryptographicHash::Sha1); QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(data); hash.addData(data);
QString newHash(hash.result()); QByteArray newHash(hash.result());
if (hasAvatar) { if (hasAvatar) {
if (!generated && !avatarAutoGenerated && avatarHash == newHash) { if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash) {
return false; return false;
} }
QFile oldAvatar(currentPath + "/avatar." + avatarType); QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type);
if (oldAvatar.exists()) { if (oldAvatar.exists()) {
if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) { if (oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak")) {
needToRemoveOld = true; needToRemoveOld = true;
} else { } else {
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName(); 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; QMimeDatabase db;
QMimeType type = db.mimeTypeForData(data); QMimeType type = db.mimeTypeForData(data);
QString ext = type.preferredSuffix(); QString ext = type.preferredSuffix();
QFile newAvatar(currentPath + "/avatar." + ext); QFile newAvatar(currentPath + "/" + res.c_str() + "." + ext);
if (newAvatar.open(QFile::WriteOnly)) { if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(data); newAvatar.write(data);
newAvatar.close(); newAvatar.close();
MDB_txn *txn; MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn); mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("hasAvatar", true, txn);
success = success && setStatValue("avatarAutoGenerated", generated, txn); MDB_val lmdbKey, lmdbData;
success = success && setStatValue("avatarHash", newHash.toStdString(), txn); QByteArray value;
success = success && setStatValue("avatarType", ext.toStdString(), txn); AvatarInfo newInfo(ext, newHash, generated);
if (!success) { 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"; qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) { if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/avatar." + avatarType); oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
} }
mdb_txn_abort(txn); mdb_txn_abort(txn);
return false; return false;
} else { } else {
hasAvatar = true;
avatarAutoGenerated = generated;
avatarHash = newHash;
avatarType = ext;
mdb_txn_commit(txn); mdb_txn_commit(txn);
if (needToRemoveOld) { if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.remove(); oldAvatar.remove();
} }
return true; return true;
@ -716,10 +665,91 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
} else { } else {
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state"; qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) { if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak"); QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/avatar." + avatarType); oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
} }
return false; 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;
}

View File

@ -35,6 +35,8 @@ class Archive : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
class AvatarInfo;
Archive(const QString& jid, QObject* parent = 0); Archive(const QString& jid, QObject* parent = 0);
~Archive(); ~Archive();
@ -53,11 +55,9 @@ public:
std::list<Shared::Message> getBefore(int count, const QString& id); std::list<Shared::Message> getBefore(int count, const QString& id);
bool isFromTheBeginning(); bool isFromTheBeginning();
void setFromTheBeginning(bool is); void setFromTheBeginning(bool is);
bool getHasAvatar() const; bool setAvatar(const QByteArray& data, bool generated = false, const QString& resource = "");
bool getAutoAvatar() const; AvatarInfo getAvatarInfo(const QString& resource = "") const;
QString getAvatarHash() const; bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
QString getAvatarType() const;
bool setAvatar(const QByteArray& data, bool generated = false);
public: public:
const QString jid; const QString jid;
@ -121,6 +121,22 @@ public:
std::string key; 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: class Unknown:
public Utils::Exception public Utils::Exception
{ {
@ -133,6 +149,20 @@ public:
std::string msg; 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: private:
bool opened; bool opened;
bool fromTheBeginning; bool fromTheBeginning;
@ -140,19 +170,17 @@ private:
MDB_dbi main; MDB_dbi main;
MDB_dbi order; MDB_dbi order;
MDB_dbi stats; MDB_dbi stats;
bool hasAvatar; MDB_dbi avatars;
bool avatarAutoGenerated;
QString avatarHash;
QString avatarType;
bool getStatBoolValue(const std::string& id, MDB_txn* txn); bool getStatBoolValue(const std::string& id, MDB_txn* txn);
std::string getStatStringValue(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, bool value, MDB_txn* txn);
bool setStatValue(const std::string& id, const std::string& 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 printOrder();
void printKeys(); void printKeys();
bool dropAvatar(); bool dropAvatar(const std::string& resource);
}; };
} }

View File

@ -143,14 +143,31 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
lastInteraction = QDateTime::currentDateTime(); lastInteraction = QDateTime::currentDateTime();
} }
QXmppMucItem mi = pres.mucItem(); QXmppMucItem mi = pres.mucItem();
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, resource);
emit addParticipant(resource, { QMap<QString, QVariant> cData = {
{"lastActivity", lastInteraction}, {"lastActivity", lastInteraction},
{"availability", pres.availableStatusType()}, {"availability", pres.availableStatusType()},
{"status", pres.statusText()}, {"status", pres.statusText()},
{"affiliation", mi.affiliation()}, {"affiliation", mi.affiliation()},
{"role", mi.role()} {"role", mi.role()}
}); };
if (hasAvatar) {
if (info.autogenerated) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
}
cData.insert("avatarPath", avatarPath(resource) + "." + info.type);
} else {
cData.insert("avatarState", static_cast<uint>(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(); lastInteraction = QDateTime::currentDateTime();
} }
QXmppMucItem mi = pres.mucItem(); QXmppMucItem mi = pres.mucItem();
handlePresence(pres);
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"lastActivity", lastInteraction}, {"lastActivity", lastInteraction},
@ -202,3 +220,92 @@ void Core::Conference::onRoomSubjectChanged(const QString& p_name)
{ {
emit subjectChanged(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<uint>(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<uint>(Shared::Avatar::autocreated)},
{"avatarPath", avatarPath(resource) + "." + ext}
});
} else {
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(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<uint>(result.getAvatarType())},
{"avatarPath", result.getAvatarPath()}
});
}
return result;
}

View File

@ -44,6 +44,10 @@ public:
bool getAutoJoin(); bool getAutoJoin();
void setAutoJoin(bool p_autoJoin); 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: signals:
void nickChanged(const QString& nick); void nickChanged(const QString& nick);

View File

@ -68,3 +68,33 @@ void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
emit subscriptionStateChanged(subscriptionState); 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;
}
}
}

View File

@ -38,6 +38,7 @@ public:
void setSubscriptionState(Shared::SubscriptionState state); void setSubscriptionState(Shared::SubscriptionState state);
Shared::SubscriptionState getSubscriptionState() const; Shared::SubscriptionState getSubscriptionState() const;
void handlePresence(const QXmppPresence & pres) override;
signals: signals:
void groupAdded(const QString& name); void groupAdded(const QString& name);

View File

@ -17,6 +17,7 @@
*/ */
#include "rosteritem.h" #include "rosteritem.h"
#include "account.h"
#include <QDebug> #include <QDebug>
@ -334,40 +335,30 @@ bool Core::RosterItem::isMuc() const
return muc; return muc;
} }
QString Core::RosterItem::avatarHash() const QString Core::RosterItem::avatarPath(const QString& resource) const
{
return archive->getAvatarHash();
}
bool Core::RosterItem::isAvatarAutoGenerated() const
{
return archive->getAutoAvatar();
}
QString Core::RosterItem::avatarPath() const
{ {
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType(); path += "/" + account + "/" + jid + "/" + (resource.size() == 0 ? jid : resource);
return path; return path;
} }
bool Core::RosterItem::hasAvatar() const bool Core::RosterItem::setAvatar(const QByteArray& data, const QString& resource)
{ {
return archive->getHasAvatar(); bool result = archive->setAvatar(data, false, resource);
} if (resource.size() == 0 && result) {
if (data.size() == 0) {
void Core::RosterItem::setAvatar(const QByteArray& data)
{
if (archive->setAvatar(data, false)) {
if (archive->getHasAvatar()) {
emit avatarChanged(Shared::Avatar::empty, ""); emit avatarChanged(Shared::Avatar::empty, "");
} else { } 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); QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image); QPainter painter(&image);
@ -383,13 +374,68 @@ void Core::RosterItem::setAutoGeneratedAvatar()
} else { } else {
painter.setPen(Qt::white); 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; QByteArray arr;
QBuffer stream(&arr); QBuffer stream(&arr);
stream.open(QBuffer::WriteOnly); stream.open(QBuffer::WriteOnly);
image.save(&stream, "PNG"); image.save(&stream, "PNG");
stream.close(); stream.close();
if (archive->setAvatar(arr, true)) { bool result = archive->setAvatar(arr, true, resource);
emit avatarChanged(Shared::Avatar::autocreated, avatarPath()); 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;
}

View File

@ -28,6 +28,9 @@
#include <list> #include <list>
#include <QXmppVCardIq.h>
#include <QXmppPresence.h>
#include "../global.h" #include "../global.h"
#include "archive.h" #include "archive.h"
@ -62,12 +65,12 @@ public:
void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId); void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
void requestHistory(int count, const QString& before); void requestHistory(int count, const QString& before);
void requestFromEmpty(int count, const QString& before); void requestFromEmpty(int count, const QString& before);
bool hasAvatar() const; QString avatarPath(const QString& resource = "") const;
bool isAvatarAutoGenerated() const; bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
QString avatarHash() const; virtual bool setAvatar(const QByteArray& data, const QString& resource = "");
QString avatarPath() const; virtual bool setAutoGeneratedAvatar(const QString& resource = "");
void setAvatar(const QByteArray& data); virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
void setAutoGeneratedAvatar(); virtual void handlePresence(const QXmppPresence& pres) = 0;
signals: signals:
void nameChanged(const QString& name); void nameChanged(const QString& name);
@ -75,6 +78,7 @@ signals:
void historyResponse(const std::list<Shared::Message>& messages); void historyResponse(const std::list<Shared::Message>& messages);
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
void avatarChanged(Shared::Avatar, const QString& path); void avatarChanged(Shared::Avatar, const QString& path);
void requestVCard(const QString& jid);
public: public:
const QString jid; const QString jid;

View File

@ -34,6 +34,15 @@ Models::Participant::Participant(const QMap<QString, QVariant>& data, Models::It
if (itr != data.end()) { if (itr != data.end()) {
setRole(itr.value().toUInt()); 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() Models::Participant::~Participant()
@ -42,7 +51,7 @@ Models::Participant::~Participant()
int Models::Participant::columnCount() const int Models::Participant::columnCount() const
{ {
return 6; return 8;
} }
QVariant Models::Participant::data(int column) const QVariant Models::Participant::data(int column) const
@ -52,6 +61,10 @@ QVariant Models::Participant::data(int column) const
return static_cast<uint8_t>(affiliation); return static_cast<uint8_t>(affiliation);
case 5: case 5:
return static_cast<uint8_t>(role); return static_cast<uint8_t>(role);
case 6:
return static_cast<quint8>(getAvatarState());
case 7:
return getAvatarPath();
default: default:
return AbstractParticipant::data(column); return AbstractParticipant::data(column);
} }
@ -63,6 +76,10 @@ void Models::Participant::update(const QString& key, const QVariant& value)
setAffiliation(value.toUInt()); setAffiliation(value.toUInt());
} else if (key == "role") { } else if (key == "role") {
setRole(value.toUInt()); setRole(value.toUInt());
} else if (key == "avatarState") {
setAvatarState(value.toUInt());
} else if (key == "avatarPath") {
setAvatarPath(value.toString());
} else { } else {
AbstractParticipant::update(key, value); 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; 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<quint8>(Shared::Avatar::valid)) {
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
setAvatarState(state);
} else {
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room participant" << name << ", skipping";
}
}

View File

@ -42,9 +42,19 @@ public:
void setRole(Shared::Role p_role); void setRole(Shared::Role p_role);
void setRole(unsigned int 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: private:
Shared::Affiliation affiliation; Shared::Affiliation affiliation;
Shared::Role role; Shared::Role role;
Shared::Avatar avatarState;
QString avatarPath;
}; };
} }

View File

@ -66,6 +66,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
case Qt::DisplayRole: case Qt::DisplayRole:
{ {
if (index.column() != 0) { if (index.column() != 0) {
result = "";
break; break;
} }
switch (item->type) { switch (item->type) {
@ -139,11 +140,20 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
} }
break; break;
case Item::participant: { case Item::participant: {
quint8 col = index.column();
Participant* p = static_cast<Participant*>(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) { if (index.column() != 0) {
break; break;
} }
Participant* p = static_cast<Participant*>(item);
result = p->getStatusIcon(false);
} }
break; break;
default: default:

View File

@ -213,7 +213,6 @@ void Squawk::addContact(const QString& account, const QString& jid, const QStrin
settings.beginGroup(account); settings.beginGroup(account);
if (settings.value("expanded", false).toBool()) { if (settings.value("expanded", false).toBool()) {
QModelIndex ind = rosterModel.getAccountIndex(account); QModelIndex ind = rosterModel.getAccountIndex(account);
qDebug() << "expanding account " << ind.data();
m_ui->roster->expand(ind); m_ui->roster->expand(ind);
} }
settings.endGroup(); settings.endGroup();

View File

@ -23,7 +23,16 @@
#include <QRegularExpression> #include <QRegularExpression>
#include "message.h" #include "message.h"
const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])((?:https?|ftp)://\\S+)"); //finds all hypertext references which are not wrapped in a or img tags const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
"(?:https?|ftp):\\/\\/"
"\\w+"
"(?:"
"[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]?"
"(?:"
"\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]+\\)"
")?"
")*"
")");
const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))"); const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent): Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):