muc participant avatars
This commit is contained in:
parent
efc90e18c3
commit
55703c2007
@ -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<uint>(Shared::Avatar::valid));
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
|
||||
}
|
||||
cData.insert("avatarPath", contact->avatarPath());
|
||||
cData.insert("avatarPath", contact->avatarPath() + "." + info.type);
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(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<QString, Contact*>::const_iterator itr = contacts.find(jid);
|
||||
if (itr != contacts.end()) {
|
||||
item = itr->second;
|
||||
} 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;
|
||||
}
|
||||
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<uint>(Shared::Avatar::valid));
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
|
||||
}
|
||||
cData.insert("avatarPath", conf->avatarPath());
|
||||
cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(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<QString, Contact*>::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<QString, QVariant> cd = {
|
||||
{"avatarState", static_cast<quint8>(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();
|
||||
|
@ -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<QString, QVariant>& data);
|
||||
void connectionStateChanged(int);
|
||||
|
232
core/archive.cpp
232
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;
|
||||
}
|
||||
|
@ -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<Shared::Message> 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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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<QString, QVariant> 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<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();
|
||||
}
|
||||
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<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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "rosteritem.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,9 @@
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppPresence.h>
|
||||
|
||||
#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<Shared::Message>& 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;
|
||||
|
@ -34,6 +34,15 @@ Models::Participant::Participant(const QMap<QString, QVariant>& 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<uint8_t>(affiliation);
|
||||
case 5:
|
||||
return static_cast<uint8_t>(role);
|
||||
case 6:
|
||||
return static_cast<quint8>(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<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";
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,19 @@ public:
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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<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) {
|
||||
break;
|
||||
}
|
||||
Participant* p = static_cast<Participant*>(item);
|
||||
result = p->getStatusIcon(false);
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -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();
|
||||
|
@ -23,7 +23,16 @@
|
||||
#include <QRegularExpression>
|
||||
#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))");
|
||||
|
||||
Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
|
||||
|
Loading…
Reference in New Issue
Block a user