forked from blue/squawk
receiving avatars, generating missing avatars, storing state of avatars, global color palette
This commit is contained in:
parent
c678a790e5
commit
64e33b6139
@ -4,7 +4,9 @@ project(squawkCORE)
|
|||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
find_package(Qt5Core CONFIG REQUIRED)
|
||||||
|
find_package(Qt5Gui CONFIG REQUIRED)
|
||||||
find_package(Qt5Network CONFIG REQUIRED)
|
find_package(Qt5Network CONFIG REQUIRED)
|
||||||
|
find_package(Qt5Xml CONFIG REQUIRED)
|
||||||
|
|
||||||
set(squawkCORE_SRC
|
set(squawkCORE_SRC
|
||||||
squawk.cpp
|
squawk.cpp
|
||||||
@ -30,5 +32,7 @@ endif()
|
|||||||
# Use the Widgets module from Qt 5.
|
# Use the Widgets module from Qt 5.
|
||||||
target_link_libraries(squawkCORE Qt5::Core)
|
target_link_libraries(squawkCORE Qt5::Core)
|
||||||
target_link_libraries(squawkCORE Qt5::Network)
|
target_link_libraries(squawkCORE Qt5::Network)
|
||||||
|
target_link_libraries(squawkCORE Qt5::Gui)
|
||||||
|
target_link_libraries(squawkCORE Qt5::Xml)
|
||||||
target_link_libraries(squawkCORE qxmpp)
|
target_link_libraries(squawkCORE qxmpp)
|
||||||
target_link_libraries(squawkCORE lmdb)
|
target_link_libraries(squawkCORE lmdb)
|
||||||
|
159
core/account.cpp
159
core/account.cpp
@ -48,37 +48,39 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
config.setPassword(p_password);
|
config.setPassword(p_password);
|
||||||
config.setAutoAcceptSubscriptions(true);
|
config.setAutoAcceptSubscriptions(true);
|
||||||
|
|
||||||
QObject::connect(&client, SIGNAL(connected()), this, SLOT(onClientConnected()));
|
QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
|
||||||
QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
|
QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
|
||||||
QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&)));
|
QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
|
||||||
QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&)));
|
QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
|
||||||
QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error)));
|
QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
|
||||||
|
|
||||||
QXmppRosterManager& rm = client.rosterManager();
|
QXmppRosterManager& rm = client.rosterManager();
|
||||||
|
|
||||||
QObject::connect(&rm, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived()));
|
QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
|
||||||
QObject::connect(&rm, SIGNAL(itemAdded(const QString&)), this, SLOT(onRosterItemAdded(const QString&)));
|
QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
|
||||||
QObject::connect(&rm, SIGNAL(itemRemoved(const QString&)), this, SLOT(onRosterItemRemoved(const QString&)));
|
QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
|
||||||
QObject::connect(&rm, SIGNAL(itemChanged(const QString&)), this, SLOT(onRosterItemChanged(const QString&)));
|
QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
|
||||||
//QObject::connect(&rm, SIGNAL(presenceChanged(const QString&, const QString&)), this, SLOT(onRosterPresenceChanged(const QString&, const QString&)));
|
//QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
|
||||||
|
|
||||||
client.addExtension(cm);
|
client.addExtension(cm);
|
||||||
|
|
||||||
QObject::connect(cm, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onCarbonMessageReceived(const QXmppMessage&)));
|
QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived);
|
||||||
QObject::connect(cm, SIGNAL(messageSent(const QXmppMessage&)), this, SLOT(onCarbonMessageSent(const QXmppMessage&)));
|
QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent);
|
||||||
|
|
||||||
client.addExtension(am);
|
client.addExtension(am);
|
||||||
|
|
||||||
QObject::connect(am, SIGNAL(logMessage(QXmppLogger::MessageType, const QString&)), this, SLOT(onMamLog(QXmppLogger::MessageType, const QString)));
|
QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog);
|
||||||
QObject::connect(am, SIGNAL(archivedMessageReceived(const QString&, const QXmppMessage&)), this, SLOT(onMamMessageReceived(const QString&, const QXmppMessage&)));
|
QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived);
|
||||||
QObject::connect(am, SIGNAL(resultsRecieved(const QString&, const QXmppResultSetReply&, bool)),
|
QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived);
|
||||||
this, SLOT(onMamResultsReceived(const QString&, const QXmppResultSetReply&, bool)));
|
|
||||||
|
|
||||||
client.addExtension(mm);
|
client.addExtension(mm);
|
||||||
QObject::connect(mm, SIGNAL(roomAdded(QXmppMucRoom*)), this, SLOT(onMucRoomAdded(QXmppMucRoom*)));
|
QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded);
|
||||||
|
|
||||||
client.addExtension(bm);
|
client.addExtension(bm);
|
||||||
QObject::connect(bm, SIGNAL(bookmarksReceived(const QXmppBookmarkSet&)), this, SLOT(bookmarksReceived(const QXmppBookmarkSet&)));
|
QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
|
||||||
|
|
||||||
|
QXmppVCardManager& vm = client.vCardManager();
|
||||||
|
QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
Account::~Account()
|
Account::~Account()
|
||||||
@ -279,6 +281,19 @@ void Core::Account::addedAccount(const QString& jid)
|
|||||||
{"name", re.name()},
|
{"name", re.name()},
|
||||||
{"state", state}
|
{"state", state}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (contact->hasAvatar()) {
|
||||||
|
if (contact->isAvatarAutoGenerated()) {
|
||||||
|
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::valid));
|
||||||
|
} else {
|
||||||
|
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::autocreated));
|
||||||
|
}
|
||||||
|
cData.insert("avatarPath", contact->avatarPath());
|
||||||
|
} else {
|
||||||
|
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::empty));
|
||||||
|
client.vCardManager().requestVCard(jid);
|
||||||
|
pendingVCardRequests.insert(jid);
|
||||||
|
}
|
||||||
int grCount = 0;
|
int grCount = 0;
|
||||||
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
|
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
|
||||||
const QString& groupName = *itr;
|
const QString& groupName = *itr;
|
||||||
@ -296,36 +311,32 @@ void Core::Account::addedAccount(const QString& jid)
|
|||||||
|
|
||||||
void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
|
void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
|
||||||
{
|
{
|
||||||
|
QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory);
|
||||||
QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&, const QDateTime&)), this, SLOT(onContactNeedHistory(const QString&, const QString&, const QDateTime&)));
|
QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
|
||||||
QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&)));
|
QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
|
||||||
QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&)));
|
QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Account::handleNewContact(Core::Contact* contact)
|
void Core::Account::handleNewContact(Core::Contact* contact)
|
||||||
{
|
{
|
||||||
handleNewRosterItem(contact);
|
handleNewRosterItem(contact);
|
||||||
QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&)));
|
QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded);
|
||||||
QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&)));
|
QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved);
|
||||||
QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)),
|
QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged);
|
||||||
this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Account::handleNewConference(Core::Conference* contact)
|
void Core::Account::handleNewConference(Core::Conference* contact)
|
||||||
{
|
{
|
||||||
handleNewRosterItem(contact);
|
handleNewRosterItem(contact);
|
||||||
QObject::connect(contact, SIGNAL(nickChanged(const QString&)), this, SLOT(onMucNickNameChanged(const QString&)));
|
QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged);
|
||||||
QObject::connect(contact, SIGNAL(subjectChanged(const QString&)), this, SLOT(onMucSubjectChanged(const QString&)));
|
QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged);
|
||||||
QObject::connect(contact, SIGNAL(joinedChanged(bool)), this, SLOT(onMucJoinedChanged(bool)));
|
QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged);
|
||||||
QObject::connect(contact, SIGNAL(autoJoinChanged(bool)), this, SLOT(onMucAutoJoinChanged(bool)));
|
QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged);
|
||||||
QObject::connect(contact, SIGNAL(addParticipant(const QString&, const QMap<QString, QVariant>&)),
|
QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant);
|
||||||
this, SLOT(onMucAddParticipant(const QString&, const QMap<QString, QVariant>&)));
|
QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant);
|
||||||
QObject::connect(contact, SIGNAL(changeParticipant(const QString&, const QMap<QString, QVariant>&)),
|
QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
|
||||||
this, SLOT(onMucChangeParticipant(const QString&, const QMap<QString, QVariant>&)));
|
|
||||||
QObject::connect(contact, SIGNAL(removeParticipant(const QString&)), this, SLOT(onMucRemoveParticipant(const QString&)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Core::Account::onPresenceReceived(const QXmppPresence& presence)
|
void Core::Account::onPresenceReceived(const QXmppPresence& presence)
|
||||||
{
|
{
|
||||||
QString id = presence.from();
|
QString id = presence.from();
|
||||||
@ -341,11 +352,45 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
|
|||||||
} else {
|
} else {
|
||||||
qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
|
qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
||||||
|
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
|
||||||
|
if (itr != contacts.end()) {
|
||||||
|
Contact* cnt = itr->second;
|
||||||
|
switch (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 (!cnt->hasAvatar() || (cnt->hasAvatar() && !cnt->isAvatarAutoGenerated())) {
|
||||||
|
cnt->setAutoGeneratedAvatar();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||||
|
if (cnt->hasAvatar()) {
|
||||||
|
if (cnt->isAvatarAutoGenerated()) {
|
||||||
|
client.vCardManager().requestVCard(jid);
|
||||||
|
pendingVCardRequests.insert(jid);
|
||||||
|
} else {
|
||||||
|
if (cnt->avatarHash() != presence.photoHash()) {
|
||||||
|
client.vCardManager().requestVCard(jid);
|
||||||
|
pendingVCardRequests.insert(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.vCardManager().requestVCard(jid);
|
||||||
|
pendingVCardRequests.insert(jid);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (presence.type()) {
|
switch (presence.type()) {
|
||||||
case QXmppPresence::Error:
|
case QXmppPresence::Error:
|
||||||
qDebug() << "An error reported by presence from " << id;
|
qDebug() << "An error reported by presence from" << id << presence.error().text();
|
||||||
break;
|
break;
|
||||||
case QXmppPresence::Available:{
|
case QXmppPresence::Available:{
|
||||||
QDateTime lastInteraction = presence.lastUserInteraction();
|
QDateTime lastInteraction = presence.lastUserInteraction();
|
||||||
@ -1211,3 +1256,43 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
|
|||||||
rm.renameItem(jid, newName);
|
rm.renameItem(jid, newName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Account::onVCardReceived(const QXmppVCardIq& card)
|
||||||
|
{
|
||||||
|
QString jid = card.from();
|
||||||
|
pendingVCardRequests.erase(jid);
|
||||||
|
RosterItem* item = 0;
|
||||||
|
|
||||||
|
std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
|
||||||
|
if (contItr == contacts.end()) {
|
||||||
|
std::map<QString, Conference*>::const_iterator confItr = conferences.find(jid);
|
||||||
|
if (confItr == conferences.end()) {
|
||||||
|
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
item = confItr->second;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item = contItr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ava = card.photo();
|
||||||
|
if (ava.size() > 0) {
|
||||||
|
item->setAvatar(ava);
|
||||||
|
} else {
|
||||||
|
item->setAutoGeneratedAvatar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
|
||||||
|
{
|
||||||
|
RosterItem* item = static_cast<RosterItem*>(sender());
|
||||||
|
QMap<QString, QVariant> cData({
|
||||||
|
{"avatarType", static_cast<uint>(type)}
|
||||||
|
});
|
||||||
|
if (type != Shared::Avatar::empty) {
|
||||||
|
cData.insert("avatarPath", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit changeContact(item->jid, cData);
|
||||||
|
}
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
#include <QXmppClient.h>
|
#include <QXmppClient.h>
|
||||||
#include <QXmppBookmarkManager.h>
|
#include <QXmppBookmarkManager.h>
|
||||||
#include <QXmppBookmarkSet.h>
|
#include <QXmppBookmarkSet.h>
|
||||||
|
#include <QXmppVCardIq.h>
|
||||||
|
#include <QXmppVCardManager.h>
|
||||||
#include "../global.h"
|
#include "../global.h"
|
||||||
#include "contact.h"
|
#include "contact.h"
|
||||||
#include "conference.h"
|
#include "conference.h"
|
||||||
@ -119,6 +121,7 @@ private:
|
|||||||
|
|
||||||
std::map<QString, QString> queuedContacts;
|
std::map<QString, QString> queuedContacts;
|
||||||
std::set<QString> outOfRosterContacts;
|
std::set<QString> outOfRosterContacts;
|
||||||
|
std::set<QString> pendingVCardRequests;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onClientConnected();
|
void onClientConnected();
|
||||||
@ -157,9 +160,12 @@ private slots:
|
|||||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||||
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
||||||
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
|
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
|
||||||
|
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
||||||
|
|
||||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||||
|
|
||||||
|
void onVCardReceived(const QXmppVCardIq& card);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addedAccount(const QString &bareJid);
|
void addedAccount(const QString &bareJid);
|
||||||
void handleNewContact(Contact* contact);
|
void handleNewContact(Contact* contact);
|
||||||
|
135
core/archive.cpp
135
core/archive.cpp
@ -34,6 +34,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
|
|||||||
order(),
|
order(),
|
||||||
stats(),
|
stats(),
|
||||||
hasAvatar(false),
|
hasAvatar(false),
|
||||||
|
avatarAutoGenerated(false),
|
||||||
avatarHash(),
|
avatarHash(),
|
||||||
avatarType()
|
avatarType()
|
||||||
{
|
{
|
||||||
@ -82,13 +83,36 @@ void Core::Archive::open(const QString& account)
|
|||||||
hasAvatar = false;
|
hasAvatar = false;
|
||||||
}
|
}
|
||||||
if (hasAvatar) {
|
if (hasAvatar) {
|
||||||
avatarHash = getStatStringValue("avatarHash", txn).c_str();
|
try {
|
||||||
|
avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
|
||||||
|
} catch (NotFound e) {
|
||||||
|
avatarAutoGenerated = false;
|
||||||
|
}
|
||||||
|
|
||||||
avatarType = getStatStringValue("avatarType", txn).c_str();
|
avatarType = getStatStringValue("avatarType", txn).c_str();
|
||||||
|
if (avatarAutoGenerated) {
|
||||||
|
avatarHash = "";
|
||||||
} else {
|
} else {
|
||||||
|
avatarHash = getStatStringValue("avatarHash", txn).c_str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avatarAutoGenerated = false;
|
||||||
avatarHash = "";
|
avatarHash = "";
|
||||||
avatarType = "";
|
avatarType = "";
|
||||||
}
|
}
|
||||||
mdb_txn_abort(txn);
|
mdb_txn_abort(txn);
|
||||||
|
|
||||||
|
if (hasAvatar) {
|
||||||
|
QFile ava(path + "/avatar." + avatarType);
|
||||||
|
if (!ava.exists()) {
|
||||||
|
bool success = dropAvatar();
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opened = true;
|
opened = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,6 +601,15 @@ bool Core::Archive::getHasAvatar() const
|
|||||||
return hasAvatar;
|
return hasAvatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Core::Archive::getAutoAvatar() const
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Closed("getAutoAvatar", jid.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarAutoGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
QString Core::Archive::getAvatarHash() const
|
QString Core::Archive::getAvatarHash() const
|
||||||
{
|
{
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
@ -594,3 +627,103 @@ QString Core::Archive::getAvatarType() const
|
|||||||
|
|
||||||
return avatarType;
|
return avatarType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Core::Archive::dropAvatar()
|
||||||
|
{
|
||||||
|
MDB_txn *txn;
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Closed("setAvatar", jid.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() == 0) {
|
||||||
|
if (!hasAvatar) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return dropAvatar();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const char* cep;
|
||||||
|
mdb_env_get_path(environment, &cep);
|
||||||
|
QString currentPath(cep);
|
||||||
|
bool needToRemoveOld = false;
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||||
|
hash.addData(data);
|
||||||
|
QString newHash(hash.result());
|
||||||
|
if (hasAvatar) {
|
||||||
|
if (!generated && !avatarAutoGenerated && avatarHash == newHash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QFile oldAvatar(currentPath + "/avatar." + avatarType);
|
||||||
|
if (oldAvatar.exists()) {
|
||||||
|
if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) {
|
||||||
|
needToRemoveOld = true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QMimeDatabase db;
|
||||||
|
QMimeType type = db.mimeTypeForData(data);
|
||||||
|
QString ext = type.preferredSuffix();
|
||||||
|
QFile newAvatar(currentPath + "/avatar." + 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
oldAvatar.remove();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
#define CORE_ARCHIVE_H
|
#define CORE_ARCHIVE_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QMimeType>
|
||||||
|
|
||||||
#include "../global.h"
|
#include "../global.h"
|
||||||
#include <lmdb.h>
|
#include <lmdb.h>
|
||||||
#include "../exception.h"
|
#include "../exception.h"
|
||||||
@ -50,8 +54,10 @@ public:
|
|||||||
bool isFromTheBeginning();
|
bool isFromTheBeginning();
|
||||||
void setFromTheBeginning(bool is);
|
void setFromTheBeginning(bool is);
|
||||||
bool getHasAvatar() const;
|
bool getHasAvatar() const;
|
||||||
|
bool getAutoAvatar() const;
|
||||||
QString getAvatarHash() const;
|
QString getAvatarHash() const;
|
||||||
QString getAvatarType() const;
|
QString getAvatarType() const;
|
||||||
|
bool setAvatar(const QByteArray& data, bool generated = false);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const QString jid;
|
const QString jid;
|
||||||
@ -135,6 +141,7 @@ private:
|
|||||||
MDB_dbi order;
|
MDB_dbi order;
|
||||||
MDB_dbi stats;
|
MDB_dbi stats;
|
||||||
bool hasAvatar;
|
bool hasAvatar;
|
||||||
|
bool avatarAutoGenerated;
|
||||||
QString avatarHash;
|
QString avatarHash;
|
||||||
QString avatarType;
|
QString avatarType;
|
||||||
|
|
||||||
@ -145,6 +152,7 @@ private:
|
|||||||
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);
|
||||||
void printOrder();
|
void printOrder();
|
||||||
void printKeys();
|
void printKeys();
|
||||||
|
bool dropAvatar();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -338,9 +338,57 @@ QString Core::RosterItem::avatarHash() const
|
|||||||
return archive->getAvatarHash();
|
return archive->getAvatarHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Core::RosterItem::isAvatarAutoGenerated() const
|
||||||
|
{
|
||||||
|
return archive->getAutoAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
QString Core::RosterItem::avatarPath() const
|
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 + "/avatar." + archive->getAvatarType();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Core::RosterItem::hasAvatar() const
|
||||||
|
{
|
||||||
|
return archive->getHasAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::RosterItem::setAvatar(const QByteArray& data)
|
||||||
|
{
|
||||||
|
if (archive->setAvatar(data, false)) {
|
||||||
|
if (archive->getHasAvatar()) {
|
||||||
|
emit avatarChanged(Shared::Avatar::empty, "");
|
||||||
|
} else {
|
||||||
|
emit avatarChanged(Shared::Avatar::valid, avatarPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::RosterItem::setAutoGeneratedAvatar()
|
||||||
|
{
|
||||||
|
QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
QPainter painter(&image);
|
||||||
|
quint8 colorIndex = rand() % Shared::colorPalette.size();
|
||||||
|
const QColor& bg = Shared::colorPalette[colorIndex];
|
||||||
|
painter.fillRect(image.rect(), bg);
|
||||||
|
QFont f;
|
||||||
|
f.setBold(true);
|
||||||
|
f.setPixelSize(72);
|
||||||
|
painter.setFont(f);
|
||||||
|
if (bg.lightnessF() > 0.5) {
|
||||||
|
painter.setPen(Qt::black);
|
||||||
|
} else {
|
||||||
|
painter.setPen(Qt::white);
|
||||||
|
}
|
||||||
|
painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, jid.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
@ -60,14 +63,18 @@ public:
|
|||||||
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;
|
bool hasAvatar() const;
|
||||||
|
bool isAvatarAutoGenerated() const;
|
||||||
QString avatarHash() const;
|
QString avatarHash() const;
|
||||||
QString avatarPath() const;
|
QString avatarPath() const;
|
||||||
|
void setAvatar(const QByteArray& data);
|
||||||
|
void setAutoGeneratedAvatar();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged(const QString& name);
|
void nameChanged(const QString& name);
|
||||||
void subscriptionStateChanged(Shared::SubscriptionState state);
|
void subscriptionStateChanged(Shared::SubscriptionState state);
|
||||||
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);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const QString jid;
|
const QString jid;
|
||||||
|
46
global.h
46
global.h
@ -24,6 +24,7 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
#include <QColor>
|
||||||
|
|
||||||
namespace Shared {
|
namespace Shared {
|
||||||
|
|
||||||
@ -69,6 +70,12 @@ enum class Role {
|
|||||||
moderator
|
moderator
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Avatar {
|
||||||
|
empty,
|
||||||
|
autocreated,
|
||||||
|
valid
|
||||||
|
};
|
||||||
|
|
||||||
static const Availability availabilityHighest = offline;
|
static const Availability availabilityHighest = offline;
|
||||||
static const Availability availabilityLowest = online;
|
static const Availability availabilityLowest = online;
|
||||||
|
|
||||||
@ -102,6 +109,45 @@ static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "
|
|||||||
static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
|
static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
|
||||||
QString generateUUID();
|
QString generateUUID();
|
||||||
|
|
||||||
|
static const std::vector<QColor> colorPalette = {
|
||||||
|
QColor(244, 27, 63),
|
||||||
|
QColor(21, 104, 156),
|
||||||
|
QColor(38, 156, 98),
|
||||||
|
QColor(247, 103, 101),
|
||||||
|
QColor(121, 37, 117),
|
||||||
|
QColor(242, 202, 33),
|
||||||
|
QColor(168, 22, 63),
|
||||||
|
QColor(35, 100, 52),
|
||||||
|
QColor(52, 161, 152),
|
||||||
|
QColor(239, 53, 111),
|
||||||
|
QColor(237, 234, 36),
|
||||||
|
QColor(153, 148, 194),
|
||||||
|
QColor(211, 102, 151),
|
||||||
|
QColor(194, 63, 118),
|
||||||
|
QColor(249, 149, 51),
|
||||||
|
QColor(244, 206, 109),
|
||||||
|
QColor(121, 105, 153),
|
||||||
|
QColor(244, 199, 30),
|
||||||
|
QColor(28, 112, 28),
|
||||||
|
QColor(172, 18, 20),
|
||||||
|
QColor(25, 66, 110),
|
||||||
|
QColor(25, 149, 104),
|
||||||
|
QColor(214, 148, 0),
|
||||||
|
QColor(203, 47, 57),
|
||||||
|
QColor(4, 54, 84),
|
||||||
|
QColor(116, 161, 97),
|
||||||
|
QColor(50, 68, 52),
|
||||||
|
QColor(237, 179, 20),
|
||||||
|
QColor(69, 114, 147),
|
||||||
|
QColor(242, 212, 31),
|
||||||
|
QColor(248, 19, 20),
|
||||||
|
QColor(84, 102, 84),
|
||||||
|
QColor(25, 53, 122),
|
||||||
|
QColor(91, 91, 109),
|
||||||
|
QColor(17, 17, 80),
|
||||||
|
QColor(54, 54, 94)
|
||||||
|
};
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
|
Loading…
Reference in New Issue
Block a user