Verified Commit 3ef056e1 authored by Linus Jahn's avatar Linus Jahn

Rewrite VCardManager to QXmpp

parent cb210394
......@@ -41,7 +41,7 @@ kde_enable_exceptions()
# Find packages
find_package(PkgConfig REQUIRED)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Svg Sql QuickControls2)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Svg Sql QuickControls2 Xml)
find_package(KF5Kirigami2 REQUIRED)
pkg_check_modules(QXmpp REQUIRED qxmpp>=0.9)
......@@ -130,6 +130,7 @@ target_link_libraries(${PROJECT_NAME}
Qt5::Quick
Qt5::Svg
Qt5::Network
Qt5::Xml
${__Qt5Widgets_LIBRARIES}
${QXmpp_LIBRARIES}
)
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
......@@ -79,10 +79,6 @@ AvatarFileStorage::AvatarFileStorage(QObject *parent) : QObject(parent)
}
}
AvatarFileStorage::~AvatarFileStorage()
{
}
AvatarFileStorage::AddAvatarResult AvatarFileStorage::addAvatar(const QString &jid,
const QByteArray &avatar)
{
......@@ -90,11 +86,16 @@ AvatarFileStorage::AddAvatarResult AvatarFileStorage::addAvatar(const QString &j
// generate a hexadecimal hash of the raw avatar
result.hash = QString(QCryptographicHash::hash(avatar, QCryptographicHash::Sha1).toHex());
QString oldHash = jidAvatarMap[jid];
// set the new hash and the `hasChanged` tag
if (jidAvatarMap[jid] != result.hash) {
if (oldHash != result.hash) {
jidAvatarMap[jid] = result.hash;
result.hasChanged = true;
emit avatarIdsChanged();
// delete the avatar if it isn't used anymore
cleanUp(oldHash);
}
// abort if the avatar with this hash is already saved
......@@ -126,6 +127,40 @@ AvatarFileStorage::AddAvatarResult AvatarFileStorage::addAvatar(const QString &j
return result;
}
void AvatarFileStorage::clearAvatar(QString &jid)
{
QString oldHash;
if (jidAvatarMap.contains(jid))
oldHash = jidAvatarMap[jid];
// if user had no avatar before, just return
if (oldHash.isEmpty()) {
return;
} else {
jidAvatarMap.remove(jid);
saveAvatarsFile();
cleanUp(oldHash);
emit avatarIdsChanged();
}
}
void AvatarFileStorage::cleanUp(QString &oldHash)
{
if (oldHash.isEmpty())
return;
// check if the same avatar is still used by another account
if (jidAvatarMap.values().contains(oldHash))
return;
// delete the old avatar locally
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
QDir::separator() + QString("avatars");
QDir dir(path);
if (dir.exists(oldHash))
dir.remove(oldHash);
}
QString AvatarFileStorage::getAvatarPath(const QString &hash) const
{
return QStandardPaths::locate(QStandardPaths::CacheLocation, QString("avatars") +
......@@ -154,8 +189,10 @@ bool AvatarFileStorage::hasAvatarHash(const QString& hash) const
void AvatarFileStorage::saveAvatarsFile()
{
QFile file(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
QDir::separator() + QString("avatars") + QDir::separator() + QString("avatar_list.sha1"));
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
QDir::separator() + QString("avatars") + QDir::separator() +
QString("avatar_list.sha1");
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return;
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
......@@ -41,7 +41,6 @@ class AvatarFileStorage : public QObject
public:
AvatarFileStorage(QObject *parent = 0);
~AvatarFileStorage();
struct AddAvatarResult {
/* SHA1 HEX Hash */
......@@ -60,6 +59,16 @@ public:
*/
AddAvatarResult addAvatar(const QString &jid, const QByteArray &avatar);
/**
* Clears the user's avatar
*/
void clearAvatar(QString &jid);
/**
* Deletes the avatar with this hash, if it isn't used anymore
*/
void cleanUp(QString &oldHash);
/**
* Returns the path to the avatar of the JID
*/
......
......@@ -15,7 +15,7 @@ set(KAIDAN_SOURCES
${CURDIR}/PresenceCache.cpp
${CURDIR}/DiscoveryManager.cpp
# ${CURDIR}/UploadHandler.cpp
# ${CURDIR}/VCardManager.cpp
${CURDIR}/VCardManager.cpp
${CURDIR}/LogHandler.cpp
${CURDIR}/StatusBar.cpp
# ${CURDIR}/QtHttpUploader.cpp
......
......@@ -45,6 +45,7 @@
#include "RosterManager.h"
#include "MessageHandler.h"
#include "DiscoveryManager.h"
#include "VCardManager.h"
ClientWorker::ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, QGuiApplication *app,
QObject* parent)
......@@ -53,7 +54,9 @@ ClientWorker::ClientWorker(Caches *caches, Kaidan *kaidan, bool enableLogging, Q
client = new QXmppClient(this);
logger = new LogHandler(client, this);
logger->enableLogging(enableLogging);
rosterManager = new RosterManager(kaidan, client, caches->rosterModel, this);
vCardManager = new VCardManager(client, caches->avatarStorage, this);
rosterManager = new RosterManager(kaidan, client, caches->rosterModel,
caches->avatarStorage, vCardManager, this);
msgHandler = new MessageHandler(kaidan, client, caches->msgModel, this);
discoManager = new DiscoveryManager(client, this);
......@@ -75,6 +78,7 @@ ClientWorker::~ClientWorker()
delete rosterManager;
delete msgHandler;
delete discoManager;
delete vCardManager;
}
void ClientWorker::main()
......
......@@ -52,6 +52,7 @@ class ClientWorker;
class RosterManager;
class MessageHandler;
class DiscoveryManager;
class VCardManager;
class ClientThread : public QThread
{
......@@ -185,6 +186,7 @@ private:
RosterManager *rosterManager;
MessageHandler *msgHandler;
DiscoveryManager *discoManager;
VCardManager *vCardManager;
};
#endif // CLIENTWORKER_H
......@@ -31,15 +31,18 @@
#include "RosterManager.h"
#include "Kaidan.h"
#include "Globals.h"
#include "VCardManager.h"
#include "ClientWorker.h"
// QXmpp
#include <QXmppClient.h>
#include <QXmppUtils.h>
#include <QXmppRosterManager.h>
RosterManager::RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *rosterModel,
RosterManager::RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *model,
AvatarFileStorage *avatarStorage, VCardManager *vCardManager,
QObject *parent)
: QObject(parent), kaidan(kaidan), model(rosterModel), client(client),
manager(client->rosterManager())
: QObject(parent), kaidan(kaidan), client(client), model(model),
avatarStorage(avatarStorage), vCardManager(vCardManager), manager(client->rosterManager())
{
connect(&manager, &QXmppRosterManager::rosterReceived,
this, &RosterManager::populateRoster);
......@@ -48,7 +51,7 @@ RosterManager::RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *r
QXmppRosterIq::Item item = manager.getRosterEntry(jid);
emit model->insertContactRequested(jid, item.name());
// TODO: get vcard/avatar
vCardManager->fetchVCard(jid);
});
connect(&manager, &QXmppRosterManager::itemChanged, [=] (QString jid) {
......@@ -71,6 +74,7 @@ RosterManager::RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *r
manager.refuseSubscription(jid);
});
// user actions
connect(kaidan, &Kaidan::addContact, this, &RosterManager::addContact);
connect(kaidan, &Kaidan::removeContact, this, &RosterManager::removeContact);
......@@ -93,7 +97,8 @@ void RosterManager::populateRoster()
QString name = manager.getRosterEntry(jid).name();
contactList[jid] = name;
// TODO: fetch avatar
if (avatarStorage->getHashOfJid(jid).isEmpty())
vCardManager->fetchVCard(jid);
}
// replace current contacts with new ones from server
......
......@@ -42,6 +42,7 @@
class Kaidan;
class QXmppClient;
class VCardManager;
class RosterManager : public QObject
{
......@@ -49,6 +50,7 @@ class RosterManager : public QObject
public:
RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *rosterModel,
AvatarFileStorage *avatarStorage, VCardManager *vCardManager,
QObject *parent = nullptr);
public slots:
......@@ -61,8 +63,11 @@ private slots:
private:
Kaidan *kaidan;
RosterModel *model;
QXmppClient *client;
RosterModel *model;
AvatarFileStorage *avatarStorage;
VCardManager *vCardManager;
QXmppRosterManager &manager;
QString chatPartner;
};
......
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
......@@ -29,65 +29,50 @@
*/
#include "VCardManager.h"
#include <gloox/vcardupdate.h>
#include "AvatarFileStorage.h"
#include <QXmppClient.h>
#include <QXmppUtils.h>
#include <QXmppVCardIq.h>
VCardManager::VCardManager(gloox::Client *client, AvatarFileStorage *avatarStorage,
RosterModel *rosterModel)
VCardManager::VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent)
: QObject(parent), client(client), manager(client->vCardManager()), avatarStorage(avatars)
{
this->client = client;
this->vCardManager = new gloox::VCardManager(client);
this->avatarStorage = avatarStorage;
this->rosterModel = rosterModel;
client->registerPresenceHandler(this);
client->registerConnectionListener(this);
}
connect(&manager, &QXmppVCardManager::vCardReceived, this, &VCardManager::handleVCard);
connect(client, &QXmppClient::presenceReceived, this, &VCardManager::handlePresence);
VCardManager::~VCardManager()
{
delete vCardManager;
// Currently we're not requesting the own VCard on every connection because it is probably
// way too resource intensive on mobile connections with many reconnects.
// Actually we would need to request our own avatar, calculate the hash of it and publish
// that in our presence.
//
// XEP-0084: User Avatar - probably best option (as long as the servers support XEP-0398:
// User Avatar to vCard-Based Avatars Conversion)
}
void VCardManager::fetchVCard(QString jid)
{
vCardManager->fetchVCard(gloox::JID(jid.toStdString()), this);
client->vCardManager().requestVCard(jid);
}
void VCardManager::handleVCard(const gloox::JID& jid, const gloox::VCard* vcard)
void VCardManager::handleVCard(const QXmppVCardIq &iq)
{
QByteArray avatarBytes = QByteArray(vcard->photo().binval.c_str(),
vcard->photo().binval.length());
if (!avatarBytes.isEmpty())
avatarStorage->addAvatar(QString::fromStdString(jid.bare()), avatarBytes);
if (!iq.photo().isEmpty())
avatarStorage->addAvatar(QXmppUtils::jidToBareJid(iq.from()), iq.photo());
}
void VCardManager::handleVCardResult(VCardContext context, const gloox::JID &jid, gloox::StanzaError stanzaError)
void VCardManager::handlePresence(const QXmppPresence &presence)
{
}
if (presence.vCardUpdateType() == QXmppPresence::VCardUpdateValidPhoto) {
QString hash = avatarStorage->getHashOfJid(QXmppUtils::jidToBareJid(presence.from()));
QString newHash = presence.photoHash();
void VCardManager::handlePresence(const gloox::Presence& presence)
{
const gloox::VCardUpdate *vcupdate = presence.findExtension<gloox::VCardUpdate>
(gloox::ExtVCardUpdate);
// check if hash differs and we need to refetch the avatar
if (hash != newHash)
manager.requestVCard(QXmppUtils::jidToBareJid(presence.from()));
// if their photo hash differs from what we have saved locally, refetch the vCard
if (vcupdate && !vcupdate->hash().empty() &&
avatarStorage->getHashOfJid(QString::fromStdString(
presence.from().bare())).toStdString() != vcupdate->hash()) {
fetchVCard(QString::fromStdString(presence.from().bare()));
} else if (presence.vCardUpdateType() == QXmppPresence::VCardUpdateNoPhoto) {
QString bareJid = QXmppUtils::jidToBareJid(presence.from());
avatarStorage->clearAvatar(bareJid);
}
}
void VCardManager::onConnect()
{
vCardManager->fetchVCard(client->jid().bare(), this);
}
void VCardManager::onDisconnect(gloox::ConnectionError error)
{
}
bool VCardManager::onTLSConnect(const gloox::CertInfo &info)
{
return true;
// ignore VCardUpdateNone (protocol unsupported) and VCardUpdateNotReady
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan is free software: you can redistribute it and/or modify
......@@ -31,37 +31,37 @@
#ifndef VCARDMANAGER_H
#define VCARDMANAGER_H
// gloox
#include <gloox/client.h>
#include <gloox/vcardhandler.h>
#include <gloox/vcardmanager.h>
#include <gloox/presencehandler.h>
#include <gloox/connectionlistener.h>
// Kaidan
#include "AvatarFileStorage.h"
#include "RosterModel.h"
#include <QObject>
#include <QXmppVCardManager.h>
#include <QXmppPresence.h>
class VCardManager : public gloox::VCardHandler, public gloox::PresenceHandler,
public gloox::ConnectionListener
class AvatarFileStorage;
class QXmppClient;
class VCardManager : public QObject
{
public:
VCardManager(gloox::Client *client, AvatarFileStorage *avatarStorage, RosterModel *rosterModel);
~VCardManager();
VCardManager(QXmppClient *client, AvatarFileStorage *avatars, QObject *parent = nullptr);
/**
* Will request the VCard from the server
*/
void fetchVCard(QString jid);
virtual void handleVCard(const gloox::JID &jid, const gloox::VCard *vcard);
virtual void handleVCardResult(VCardContext context, const gloox::JID &jid,
gloox::StanzaError stanzaError = gloox::StanzaErrorUndefined);
virtual void handlePresence(const gloox::Presence &presence);
virtual void onConnect();
virtual void onDisconnect(gloox::ConnectionError error);
virtual bool onTLSConnect(const gloox::CertInfo &info);
/**
* Handles incoming VCards and processes them (save avatar, etc.)
*/
void handleVCard(const QXmppVCardIq &iq);
/**
* Handles incoming presences and checks if the avatar needs to be refreshed
*/
void handlePresence(const QXmppPresence &presence);
private:
gloox::Client *client;
gloox::VCardManager *vCardManager;
QXmppClient *client;
QXmppVCardManager &manager;
AvatarFileStorage *avatarStorage;
RosterModel *rosterModel;
};
#endif // VCARDMANAGER_H
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment