vCard #29

Manually merged
blue merged 20 commits from vCard into master 2019-11-08 07:47:38 +00:00
88 changed files with 5193 additions and 532 deletions

View File

@ -2,13 +2,14 @@ cmake_minimum_required(VERSION 3.0)
project(squawk)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
include(GNUInstallDirs)
include_directories(.)
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5LinguistTools)

View File

@ -3,8 +3,10 @@ project(squawkCORE)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
set(squawkCORE_SRC
squawk.cpp
@ -15,6 +17,7 @@ set(squawkCORE_SRC
conference.cpp
storage.cpp
networkaccess.cpp
adapterFuctions.cpp
)
# Tell CMake to create the helloworld executable
@ -30,5 +33,7 @@ endif()
# Use the Widgets module from Qt 5.
target_link_libraries(squawkCORE Qt5::Core)
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 lmdb)

View File

@ -36,49 +36,99 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
am(new QXmppMamManager()),
mm(new QXmppMucManager()),
bm(new QXmppBookmarkManager()),
rm(client.findExtension<QXmppRosterManager>()),
vm(client.findExtension<QXmppVCardManager>()),
contacts(),
conferences(),
maxReconnectTimes(0),
reconnectTimes(0),
queuedContacts(),
outOfRosterContacts()
outOfRosterContacts(),
avatarHash(),
avatarType(),
ownVCardRequestInProgress(false)
{
config.setUser(p_login);
config.setDomain(p_server);
config.setPassword(p_password);
config.setAutoAcceptSubscriptions(true);
QObject::connect(&client, SIGNAL(connected()), this, SLOT(onClientConnected()));
QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&)));
QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&)));
QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error)));
QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
QXmppRosterManager& rm = client.rosterManager();
QObject::connect(&rm, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived()));
QObject::connect(&rm, SIGNAL(itemAdded(const QString&)), this, SLOT(onRosterItemAdded(const QString&)));
QObject::connect(&rm, SIGNAL(itemRemoved(const QString&)), this, SLOT(onRosterItemRemoved(const QString&)));
QObject::connect(&rm, SIGNAL(itemChanged(const QString&)), this, SLOT(onRosterItemChanged(const QString&)));
//QObject::connect(&rm, SIGNAL(presenceChanged(const QString&, const QString&)), this, SLOT(onRosterPresenceChanged(const QString&, const QString&)));
QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
QObject::connect(rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
QObject::connect(rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
QObject::connect(rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
//QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
client.addExtension(cm);
QObject::connect(cm, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onCarbonMessageReceived(const QXmppMessage&)));
QObject::connect(cm, SIGNAL(messageSent(const QXmppMessage&)), this, SLOT(onCarbonMessageSent(const QXmppMessage&)));
QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived);
QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent);
client.addExtension(am);
QObject::connect(am, SIGNAL(logMessage(QXmppLogger::MessageType, const QString&)), this, SLOT(onMamLog(QXmppLogger::MessageType, const QString)));
QObject::connect(am, SIGNAL(archivedMessageReceived(const QString&, const QXmppMessage&)), this, SLOT(onMamMessageReceived(const QString&, const QXmppMessage&)));
QObject::connect(am, SIGNAL(resultsRecieved(const QString&, const QXmppResultSetReply&, bool)),
this, SLOT(onMamResultsReceived(const QString&, const QXmppResultSetReply&, bool)));
QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog);
QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived);
QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived);
client.addExtension(mm);
QObject::connect(mm, SIGNAL(roomAdded(QXmppMucRoom*)), this, SLOT(onMucRoomAdded(QXmppMucRoom*)));
QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded);
client.addExtension(bm);
QObject::connect(bm, SIGNAL(bookmarksReceived(const QXmppBookmarkSet&)), this, SLOT(bookmarksReceived(const QXmppBookmarkSet&)));
QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
//QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name;
QDir dir(path);
if (!dir.exists()) {
bool res = dir.mkpath(path);
if (!res) {
qDebug() << "Couldn't create a cache directory for account" << name;
throw 22;
}
}
QFile* avatar = new QFile(path + "/avatar.png");
QString type = "png";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.jpg");
type = "jpg";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.jpeg");
type = "jpeg";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.gif");
type = "gif";
}
}
}
if (avatar->exists()) {
if (avatar->open(QFile::ReadOnly)) {
QCryptographicHash sha1(QCryptographicHash::Sha1);
sha1.addData(avatar);
avatarHash = sha1.result();
avatarType = type;
}
}
if (avatarType.size() != 0) {
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
presence.setPhotoHash(avatarHash.toUtf8());
} else {
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
}
}
Account::~Account()
@ -189,8 +239,10 @@ QString Core::Account::getServer() const
void Core::Account::onRosterReceived()
{
QXmppRosterManager& rm = client.rosterManager();
QStringList bj = rm.getRosterBareJids();
vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
ownVCardRequestInProgress = true;
QStringList bj = rm->getRosterBareJids();
for (int i = 0; i < bj.size(); ++i) {
const QString& jid = bj[i];
addedAccount(jid);
@ -210,8 +262,7 @@ void Core::Account::onRosterItemAdded(const QString& bareJid)
addedAccount(bareJid);
std::map<QString, QString>::const_iterator itr = queuedContacts.find(bareJid);
if (itr != queuedContacts.end()) {
QXmppRosterManager& rm = client.rosterManager();
rm.subscribe(bareJid, itr->second);
rm->subscribe(bareJid, itr->second);
queuedContacts.erase(itr);
}
}
@ -224,8 +275,7 @@ void Core::Account::onRosterItemChanged(const QString& bareJid)
return;
}
Contact* contact = itr->second;
QXmppRosterManager& rm = client.rosterManager();
QXmppRosterIq::Item re = rm.getRosterEntry(bareJid);
QXmppRosterIq::Item re = rm->getRosterEntry(bareJid);
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
@ -254,9 +304,8 @@ void Core::Account::onRosterItemRemoved(const QString& bareJid)
void Core::Account::addedAccount(const QString& jid)
{
QXmppRosterManager& rm = client.rosterManager();
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
QXmppRosterIq::Item re = rm.getRosterEntry(jid);
QXmppRosterIq::Item re = rm->getRosterEntry(jid);
Contact* contact;
bool newContact = false;
if (itr == contacts.end()) {
@ -279,6 +328,19 @@ void Core::Account::addedAccount(const QString& jid)
{"name", re.name()},
{"state", state}
});
if (contact->hasAvatar()) {
if (!contact->isAvatarAutoGenerated()) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
}
cData.insert("avatarPath", contact->avatarPath());
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", "");
requestVCard(jid);
}
int grCount = 0;
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
const QString& groupName = *itr;
@ -296,39 +358,35 @@ void Core::Account::addedAccount(const QString& jid)
void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
{
QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&, const QDateTime&)), this, SLOT(onContactNeedHistory(const QString&, const QString&, const QDateTime&)));
QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&)));
QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&)));
QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory);
QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
}
void Core::Account::handleNewContact(Core::Contact* contact)
{
handleNewRosterItem(contact);
QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&)));
QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&)));
QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)),
this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState)));
QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded);
QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved);
QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged);
}
void Core::Account::handleNewConference(Core::Conference* contact)
{
handleNewRosterItem(contact);
QObject::connect(contact, SIGNAL(nickChanged(const QString&)), this, SLOT(onMucNickNameChanged(const QString&)));
QObject::connect(contact, SIGNAL(subjectChanged(const QString&)), this, SLOT(onMucSubjectChanged(const QString&)));
QObject::connect(contact, SIGNAL(joinedChanged(bool)), this, SLOT(onMucJoinedChanged(bool)));
QObject::connect(contact, SIGNAL(autoJoinChanged(bool)), this, SLOT(onMucAutoJoinChanged(bool)));
QObject::connect(contact, SIGNAL(addParticipant(const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onMucAddParticipant(const QString&, const QMap<QString, QVariant>&)));
QObject::connect(contact, SIGNAL(changeParticipant(const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onMucChangeParticipant(const QString&, const QMap<QString, QVariant>&)));
QObject::connect(contact, SIGNAL(removeParticipant(const QString&)), this, SLOT(onMucRemoveParticipant(const QString&)));
QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged);
QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged);
QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged);
QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged);
QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant);
QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant);
QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
}
void Core::Account::onPresenceReceived(const QXmppPresence& presence)
void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
{
QString id = presence.from();
QString id = p_presence.from();
QStringList comps = id.split("/");
QString jid = comps.front();
QString resource = comps.back();
@ -337,25 +395,75 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
if (jid == myJid) {
if (resource == getResource()) {
emit availabilityChanged(presence.availableStatusType());
emit availabilityChanged(p_presence.availableStatusType());
} else {
qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
if (!ownVCardRequestInProgress) {
switch (p_presence.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
if (avatarType.size() > 0) {
vm->requestClientVCard();
ownVCardRequestInProgress = true;
}
break;
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
if (avatarHash != p_presence.photoHash()) {
vm->requestClientVCard();
ownVCardRequestInProgress = true;
}
break;
}
}
}
} 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 (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 (!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()) {
requestVCard(jid);
} else {
if (cnt->avatarHash() != p_presence.photoHash()) {
requestVCard(jid);
}
}
} else {
requestVCard(jid);
}
break;
}
}
}
}
switch (presence.type()) {
switch (p_presence.type()) {
case QXmppPresence::Error:
qDebug() << "An error reported by presence from " << id;
qDebug() << "An error reported by presence from" << id << p_presence.error().text();
break;
case QXmppPresence::Available:{
QDateTime lastInteraction = presence.lastUserInteraction();
QDateTime lastInteraction = p_presence.lastUserInteraction();
if (!lastInteraction.isValid()) {
lastInteraction = QDateTime::currentDateTime();
}
emit addPresence(jid, resource, {
{"lastActivity", lastInteraction},
{"availability", presence.availableStatusType()}, //TODO check and handle invisible
{"status", presence.statusText()}
{"availability", p_presence.availableStatusType()}, //TODO check and handle invisible
{"status", p_presence.statusText()}
});
}
break;
@ -380,7 +488,7 @@ void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QStrin
{
//not used for now;
qDebug() << "presence changed for " << bareJid << " resource " << resource;
const QXmppPresence& presence = client.rosterManager().getPresence(bareJid, resource);
const QXmppPresence& presence = rm->getPresence(bareJid, resource);
}
void Core::Account::setLogin(const QString& p_login)
@ -940,8 +1048,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
void Core::Account::subscribeToContact(const QString& jid, const QString& reason)
{
if (state == Shared::connected) {
QXmppRosterManager& rm = client.rosterManager();
rm.subscribe(jid, reason);
rm->subscribe(jid, reason);
} else {
qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping";
}
@ -950,8 +1057,7 @@ void Core::Account::subscribeToContact(const QString& jid, const QString& reason
void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason)
{
if (state == Shared::connected) {
QXmppRosterManager& rm = client.rosterManager();
rm.unsubscribe(jid, reason);
rm->unsubscribe(jid, reason);
} else {
qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping";
}
@ -965,8 +1071,7 @@ void Core::Account::removeContactRequest(const QString& jid)
outOfRosterContacts.erase(itr);
onRosterItemRemoved(jid);
} else {
QXmppRosterManager& rm = client.rosterManager();
rm.removeItem(jid);
rm->removeItem(jid);
}
} else {
qDebug() << "An attempt to remove contact " << jid << " from account " << name << " but the account is not in the connected state, skipping";
@ -982,8 +1087,7 @@ void Core::Account::addContactRequest(const QString& jid, const QString& name, c
qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping";
} else {
queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here;
QXmppRosterManager& rm = client.rosterManager();
rm.addItem(jid, name, groups);
rm->addItem(jid, name, groups);
}
} else {
qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is not in the connected state, skipping";
@ -1160,8 +1264,7 @@ void Core::Account::addContactToGroupRequest(const QString& jid, const QString&
if (itr == contacts.end()) {
qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping";
} else {
QXmppRosterManager& rm = client.rosterManager();
QXmppRosterIq::Item item = rm.getRosterEntry(jid);
QXmppRosterIq::Item item = rm->getRosterEntry(jid);
QSet<QString> groups = item.groups();
if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib
groups.insert(groupName);
@ -1183,8 +1286,7 @@ void Core::Account::removeContactFromGroupRequest(const QString& jid, const QStr
if (itr == contacts.end()) {
qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping";
} else {
QXmppRosterManager& rm = client.rosterManager();
QXmppRosterIq::Item item = rm.getRosterEntry(jid);
QXmppRosterIq::Item item = rm->getRosterEntry(jid);
QSet<QString> groups = item.groups();
QSet<QString>::const_iterator gItr = groups.find(groupName);
if (gItr != groups.end()) {
@ -1207,7 +1309,245 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
if (itr == contacts.end()) {
qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping";
} else {
QXmppRosterManager& rm = client.rosterManager();
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()) {
if (jid == getLogin() + "@" + getServer()) {
onOwnVCardReceived(card);
} else {
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 {
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);
}
void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
{
QByteArray ava = card.photo();
bool avaChanged = false;
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
if (ava.size() > 0) {
QCryptographicHash sha1(QCryptographicHash::Sha1);
sha1.addData(ava);
QString newHash(sha1.result());
QMimeDatabase db;
QMimeType newType = db.mimeTypeForData(ava);
if (avatarType.size() > 0) {
if (avatarHash != newHash) {
QString oldPath = path + "avatar." + avatarType;
QFile oldAvatar(oldPath);
bool oldToRemove = false;
if (oldAvatar.exists()) {
if (oldAvatar.rename(oldPath + ".bak")) {
oldToRemove = true;
} else {
qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
}
}
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(ava);
newAvatar.close();
avatarHash = newHash;
avatarType = newType.preferredSuffix();
avaChanged = true;
} else {
qDebug() << "Received new avatar for account" << name << "but can't save it";
if (oldToRemove) {
qDebug() << "rolling back to the old avatar";
if (!oldAvatar.rename(oldPath)) {
qDebug() << "Couldn't roll back to the old avatar in account" << name;
}
}
}
}
} else {
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(ava);
newAvatar.close();
avatarHash = newHash;
avatarType = newType.preferredSuffix();
avaChanged = true;
} else {
qDebug() << "Received new avatar for account" << name << "but can't save it";
}
}
} else {
if (avatarType.size() > 0) {
QFile oldAvatar(path + "avatar." + avatarType);
if (!oldAvatar.remove()) {
qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
} else {
avatarType = "";
avatarHash = "";
avaChanged = true;
}
}
}
if (avaChanged) {
QMap<QString, QVariant> change;
if (avatarType.size() > 0) {
presence.setPhotoHash(avatarHash.toUtf8());
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
change.insert("avatarPath", path + "avatar." + avatarType);
} else {
presence.setPhotoHash("");
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
change.insert("avatarPath", "");
}
client.setClientPresence(presence);
emit changed(change);
}
ownVCardRequestInProgress = false;
Shared::VCard vCard;
initializeVCard(vCard, card);
if (avatarType.size() > 0) {
vCard.setAvatarType(Shared::Avatar::valid);
vCard.setAvatarPath(path + "avatar." + avatarType);
} else {
vCard.setAvatarType(Shared::Avatar::empty);
}
emit receivedVCard(getLogin() + "@" + getServer(), vCard);
}
QString Core::Account::getAvatarPath() const
{
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
}
void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
{
RosterItem* item = static_cast<RosterItem*>(sender());
QMap<QString, QVariant> cData({
{"avatarState", static_cast<uint>(type)},
{"avatarPath", path}
});
emit changeContact(item->jid, cData);
}
void Core::Account::requestVCard(const QString& jid)
{
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
if (jid == getLogin() + "@" + getServer()) {
if (!ownVCardRequestInProgress) {
vm->requestClientVCard();
ownVCardRequestInProgress = true;
}
} else {
vm->requestVCard(jid);
pendingVCardRequests.insert(jid);
}
}
}
void Core::Account::uploadVCard(const Shared::VCard& card)
{
QXmppVCardIq iq;
initializeQXmppVCard(iq, card);
bool avatarChanged = false;
if (card.getAvatarType() == Shared::Avatar::empty) {
if (avatarType.size() > 0) {
avatarChanged = true;
}
} else {
QString newPath = card.getAvatarPath();
QString oldPath = getAvatarPath();
QByteArray data;
QString type;
if (newPath != oldPath) {
QFile avatar(newPath);
if (!avatar.open(QFile::ReadOnly)) {
qDebug() << "An attempt to upload new vCard to account" << name
<< "but it wasn't possible to read file" << newPath
<< "which was supposed to be new avatar, uploading old avatar";
if (avatarType.size() > 0) {
QFile oA(oldPath);
if (!oA.open(QFile::ReadOnly)) {
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
avatarChanged = true;
} else {
data = oA.readAll();
}
}
} else {
data = avatar.readAll();
avatarChanged = true;
}
} else {
if (avatarType.size() > 0) {
QFile oA(oldPath);
if (!oA.open(QFile::ReadOnly)) {
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
avatarChanged = true;
} else {
data = oA.readAll();
}
}
}
if (data.size() > 0) {
QMimeDatabase db;
type = db.mimeTypeForData(data).name();
iq.setPhoto(data);
iq.setPhotoType(type);
}
}
vm->setClientVCard(iq);
onOwnVCardReceived(iq);
}

View File

@ -19,7 +19,13 @@
#ifndef CORE_ACCOUNT_H
#define CORE_ACCOUNT_H
#include <QtCore/QObject>
#include <QObject>
#include <QCryptographicHash>
#include <QFile>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QDir>
#include <map>
#include <set>
@ -30,6 +36,8 @@
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#include "../global.h"
#include "contact.h"
#include "conference.h"
@ -54,6 +62,7 @@ public:
QString getServer() const;
QString getPassword() const;
QString getResource() const;
QString getAvatarPath() const;
Shared::Availability getAvailability() const;
void setName(const QString& p_name);
@ -78,8 +87,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);
signals:
void changed(const QMap<QString, QVariant>& data);
void connectionStateChanged(int);
void availabilityChanged(int);
void addGroup(const QString& name);
@ -99,6 +111,7 @@ signals:
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& jid, const QString& nickName);
void receivedVCard(const QString& jid, const Shared::VCard& card);
private:
QString name;
@ -112,6 +125,8 @@ private:
QXmppMamManager* am;
QXmppMucManager* mm;
QXmppBookmarkManager* bm;
QXmppRosterManager* rm;
QXmppVCardManager* vm;
std::map<QString, Contact*> contacts;
std::map<QString, Conference*> conferences;
unsigned int maxReconnectTimes;
@ -119,6 +134,11 @@ private:
std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts;
std::set<QString> pendingVCardRequests;
QString avatarHash;
QString avatarType;
bool ownVCardRequestInProgress;
private slots:
void onClientConnected();
@ -157,8 +177,12 @@ private slots:
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list);
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 onVCardReceived(const QXmppVCardIq& card);
void onOwnVCardReceived(const QXmppVCardIq& card);
private:
void addedAccount(const QString &bareJid);
@ -178,6 +202,8 @@ private:
};
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
}

275
core/adapterFuctions.cpp Normal file
View File

@ -0,0 +1,275 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_ADAPTER_FUNCTIONS_H
#define CORE_ADAPTER_FUNCTIONS_H
#include "account.h"
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
{
vCard.setFullName(card.fullName());
vCard.setFirstName(card.firstName());
vCard.setMiddleName(card.middleName());
vCard.setLastName(card.lastName());
vCard.setBirthday(card.birthday());
vCard.setNickName(card.nickName());
vCard.setDescription(card.description());
vCard.setUrl(card.url());
QXmppVCardOrganization org = card.organization();
vCard.setOrgName(org.organization());
vCard.setOrgRole(org.role());
vCard.setOrgUnit(org.unit());
vCard.setOrgTitle(org.title());
QList<QXmppVCardEmail> emails = card.emails();
std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
for (const QXmppVCardEmail& em : emails) {
QString addr = em.address();
if (addr.size() != 0) {
QXmppVCardEmail::Type et = em.type();
bool prefered = false;
bool accounted = false;
if (et & QXmppVCardEmail::Preferred) {
prefered = true;
}
if (et & QXmppVCardEmail::Home) {
myEmails.emplace_back(addr, Shared::VCard::Email::home, prefered);
accounted = true;
}
if (et & QXmppVCardEmail::Work) {
myEmails.emplace_back(addr, Shared::VCard::Email::work, prefered);
accounted = true;
}
if (!accounted) {
myEmails.emplace_back(addr, Shared::VCard::Email::none, prefered);
}
}
}
QList<QXmppVCardPhone> phones = card.phones();
std::deque<Shared::VCard::Phone>& myPhones = vCard.getPhones();
for (const QXmppVCardPhone& ph : phones) {
QString num = ph.number();
if (num.size() != 0) {
QXmppVCardPhone::Type pt = ph.type();
bool prefered = false;
bool accounted = false;
if (pt & QXmppVCardPhone::Preferred) {
prefered = true;
}
bool home = false;
bool work = false;
if (pt & QXmppVCardPhone::Home) {
home = true;
}
if (pt & QXmppVCardPhone::Work) {
work = true;
}
if (pt & QXmppVCardPhone::Fax) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Voice) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Pager) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Cell) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Video) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Modem) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (!accounted) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
}
}
}
}
}
void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
iq.setFullName(card.getFullName());
iq.setFirstName(card.getFirstName());
iq.setMiddleName(card.getMiddleName());
iq.setLastName(card.getLastName());
iq.setNickName(card.getNickName());
iq.setBirthday(card.getBirthday());
iq.setDescription(card.getDescription());
iq.setUrl(card.getUrl());
QXmppVCardOrganization org;
org.setOrganization(card.getOrgName());
org.setUnit(card.getOrgUnit());
org.setRole(card.getOrgRole());
org.setTitle(card.getOrgTitle());
iq.setOrganization(org);
const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
QList<QXmppVCardEmail> emails;
for (const Shared::VCard::Email& mEm : myEmails) {
QXmppVCardEmail em;
QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
if (mEm.prefered) {
t = t | QXmppVCardEmail::Preferred;
}
if (mEm.role == Shared::VCard::Email::home) {
t = t | QXmppVCardEmail::Home;
} else if (mEm.role == Shared::VCard::Email::work) {
t = t | QXmppVCardEmail::Work;
}
em.setType(t);
em.setAddress(mEm.address);
emails.push_back(em);
}
std::map<QString, QXmppVCardPhone> phones;
QList<QXmppVCardPhone> phs;
const std::deque<Shared::VCard::Phone>& myPhones = card.getPhones();
for (const Shared::VCard::Phone& mPh : myPhones) {
std::map<QString, QXmppVCardPhone>::iterator itr = phones.find(mPh.number);
if (itr == phones.end()) {
itr = phones.emplace(mPh.number, QXmppVCardPhone()).first;
}
QXmppVCardPhone& phone = itr->second;
phone.setNumber(mPh.number);
switch (mPh.type) {
case Shared::VCard::Phone::fax:
phone.setType(phone.type() | QXmppVCardPhone::Fax);
break;
case Shared::VCard::Phone::pager:
phone.setType(phone.type() | QXmppVCardPhone::Pager);
break;
case Shared::VCard::Phone::voice:
phone.setType(phone.type() | QXmppVCardPhone::Voice);
break;
case Shared::VCard::Phone::cell:
phone.setType(phone.type() | QXmppVCardPhone::Cell);
break;
case Shared::VCard::Phone::video:
phone.setType(phone.type() | QXmppVCardPhone::Video);
break;
case Shared::VCard::Phone::modem:
phone.setType(phone.type() | QXmppVCardPhone::Modem);
break;
case Shared::VCard::Phone::other:
phone.setType(phone.type() | QXmppVCardPhone::PCS); //loss of information, but I don't even know what the heck is this type of phone!
break;
}
switch (mPh.role) {
case Shared::VCard::Phone::home:
phone.setType(phone.type() | QXmppVCardPhone::Home);
break;
case Shared::VCard::Phone::work:
phone.setType(phone.type() | QXmppVCardPhone::Work);
break;
default:
break;
}
if (mPh.prefered) {
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
}
}
for (const std::pair<QString, QXmppVCardPhone>& phone : phones) {
phs.push_back(phone.second);
}
iq.setEmails(emails);
iq.setPhones(phs);
}
#endif // CORE_ADAPTER_FUNCTIONS_H

View File

@ -32,7 +32,11 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
environment(),
main(),
order(),
stats()
stats(),
hasAvatar(false),
avatarAutoGenerated(false),
avatarHash(),
avatarType()
{
}
@ -66,7 +70,49 @@ void Core::Archive::open(const QString& account)
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
mdb_txn_commit(txn);
fromTheBeginning = _isFromTheBeginning();
mdb_txn_begin(environment, NULL, 0, &txn);
try {
fromTheBeginning = getStatBoolValue("beginning", txn);
} catch (NotFound e) {
fromTheBeginning = false;
}
try {
hasAvatar = getStatBoolValue("hasAvatar", txn);
} catch (NotFound e) {
hasAvatar = false;
}
if (hasAvatar) {
try {
avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
} catch (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 = "";
}
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;
}
}
@ -396,40 +442,6 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
return res;
}
bool Core::Archive::_isFromTheBeginning()
{
std::string strKey = "beginning";
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = strKey.size();
lmdbKey.mv_data = (char*)strKey.c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
if (rc == MDB_NOTFOUND) {
mdb_txn_abort(txn);
return false;
} else if (rc) {
qDebug() <<"isFromTheBeginning error: " << mdb_strerror(rc);
mdb_txn_abort(txn);
throw NotFound(strKey, jid.toStdString());
} else {
uint8_t value = *(uint8_t*)(lmdbData.mv_data);
bool is;
if (value == 144) {
is = false;
} else if (value == 72) {
is = true;
} else {
qDebug() <<"isFromTheBeginning error: stored value doesn't match any magic number, the answer is most probably wrong";
}
mdb_txn_abort(txn);
return is;
}
}
bool Core::Archive::isFromTheBeginning()
{
if (!opened) {
@ -445,26 +457,15 @@ void Core::Archive::setFromTheBeginning(bool is)
}
if (fromTheBeginning != is) {
fromTheBeginning = is;
const std::string& id = "beginning";
uint8_t value = 144;
if (is) {
value = 72;
}
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = sizeof value;
lmdbData.mv_data = &value;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Couldn't store beginning key into stat database:" << mdb_strerror(rc);
bool success = setStatValue("beginning", is, txn);
if (success != 0) {
mdb_txn_abort(txn);
} else {
mdb_txn_commit(txn);
}
mdb_txn_commit(txn);
}
}
@ -508,3 +509,221 @@ void Core::Archive::printKeys()
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}
bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc;
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
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
} else {
uint8_t value = *(uint8_t*)(lmdbData.mv_data);
bool is;
if (value == 144) {
is = false;
} else if (value == 72) {
is = true;
} else {
qDebug() << "error retrieving boolean stat" << id.c_str() << ": stored value doesn't match any magic number, the answer is most probably wrong";
throw NotFound(id, jid.toStdString());
}
return is;
}
}
std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
{
MDB_cursor* cursor;
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc;
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
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
} else {
std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
return value;
}
}
bool Core::Archive::setStatValue(const std::string& id, bool value, MDB_txn* txn)
{
uint8_t binvalue = 144;
if (value) {
binvalue = 72;
}
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = sizeof binvalue;
lmdbData.mv_data = &binvalue;
int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
return false;
}
return true;
}
bool Core::Archive::setStatValue(const std::string& id, const std::string& value, MDB_txn* txn)
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (