diff --git a/src/Kaidan.h b/src/Kaidan.h index 0e3ba54..4d5fd21 100644 --- a/src/Kaidan.h +++ b/src/Kaidan.h @@ -1,436 +1,441 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 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 * 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef KAIDAN_H #define KAIDAN_H // Qt #include #include #include // Kaidan #include "ClientWorker.h" #include "Enums.h" #include "Globals.h" class QGuiApplication; class Database; class QXmppClient; class QmlUtils; using namespace Enums; /** * @class Kaidan Kaidan's Back-End Class * * @brief This class will initiate the complete back-end, including the @see Database * connection, viewing models (@see MessageModel, @see RosterModel), etc. * * This class will run in the main thread, the XMPP connection and the database managers * run in other threads. */ class Kaidan : public QObject { Q_OBJECT Q_PROPERTY(QmlUtils* utils READ getUtils CONSTANT) Q_PROPERTY(RosterModel* rosterModel READ getRosterModel CONSTANT) Q_PROPERTY(MessageModel* messageModel READ getMessageModel CONSTANT) Q_PROPERTY(AvatarFileStorage* avatarStorage READ getAvatarStorage NOTIFY avatarStorageChanged) Q_PROPERTY(PresenceCache* presenceCache READ getPresenceCache CONSTANT) Q_PROPERTY(TransferCache* transferCache READ getTransferCache CONSTANT) Q_PROPERTY(QSettings* settings READ getSettings CONSTANT) Q_PROPERTY(quint8 connectionState READ getConnectionState NOTIFY connectionStateChanged) Q_PROPERTY(quint8 disconnReason READ getDisconnReason NOTIFY disconnReasonChanged) Q_PROPERTY(QString jid READ getJid WRITE setJid NOTIFY jidChanged) Q_PROPERTY(QString jidResource READ getJidResource WRITE setJidResource NOTIFY jidResourceChanged) Q_PROPERTY(QString password READ getPassword WRITE setPassword NOTIFY passwordChanged) Q_PROPERTY(bool uploadServiceFound READ getUploadServiceFound NOTIFY uploadServiceFoundChanged) public: Kaidan(QGuiApplication *app, bool enableLogging = true, QObject *parent = nullptr); ~Kaidan(); /** * Start connection (called from QML when ready) */ Q_INVOKABLE void start(); /** * Connect to the XMPP server * * If you haven't set a username and password, they are used from the * last successful login (the settings file). */ Q_INVOKABLE void mainConnect(); /** * Disconnect from XMPP server * * This will disconnect the client from the server. When disconnected, * the connectionStateChanged signal will be emitted. * * @param openLogInPage If true, the newCredentialsNeeded signal will be * emitted. */ Q_INVOKABLE void mainDisconnect(bool openLogInPage = false); /** * Returns the current ConnectionState */ Q_INVOKABLE quint8 getConnectionState() const { return (quint8) connectionState; } /** * Returns the last disconnection reason */ Q_INVOKABLE quint8 getDisconnReason() const; /** * Set own JID used for connection * * To really change the JID of the current connection, you'll need to * reconnect. */ void setJid(const QString &jid); /** * Get the current JID */ QString getJid() const { return creds.jid; } /** * Set a optional custom JID resource (device name) */ void setJidResource(const QString &jidResource); /** * Get the JID resoruce */ QString getJidResource() const { return creds.jidResource; } /** * Set the password for next connection */ void setPassword(const QString &password); /** * Get the currently used password */ QString getPassword() const { return creds.password; } RosterModel* getRosterModel() const { return m_caches->rosterModel; } MessageModel* getMessageModel() const { return m_caches->msgModel; } AvatarFileStorage* getAvatarStorage() const { return m_caches->avatarStorage; } PresenceCache* getPresenceCache() const { return m_caches->presCache; } TransferCache* getTransferCache() const { return m_caches->transferCache; } QSettings* getSettings() const { return m_caches->settings; } QmlUtils* getUtils() const { return m_utils; } /** * Adds XMPP URI to open as soon as possible */ void addOpenUri(const QString &uri); /** * Connects to the server by the parsed credentials (bare JID and password) * from a given XMPP URI (e.g. from scanning a QR code) * like "xmpp:user@example.org?login;password=abc" */ Q_INVOKABLE void loginByUri(const QString &uri); /** * Returns whether an HTTP File Upload service has been found */ bool getUploadServiceFound() const { return uploadServiceFound; } static Kaidan *instance(); signals: void avatarStorageChanged(); /** * Emitted, when the client's connection state has changed (e.g. when * successfully connected or when disconnected) */ void connectionStateChanged(); /** * Emitted, when the client failed to connect and gives the reason in * a DisconnectionReason enumatrion. */ void disconnReasonChanged(); /** * Emitted when the JID was changed */ void jidChanged(); /** * Emitted when the JID resouce (device name) has changed */ void jidResourceChanged(); /** * Emitted when the used password for logging in has changed */ void passwordChanged(); /** * Emitted when there are no (correct) credentials and new are needed * * The client will be in disconnected state, when this is emitted. */ void newCredentialsNeeded(); /** * Emitted when log in worked with new credentials * * The client will be in connected state, when this is emitted. */ void logInWorked(); /** * Show passive notification */ void passiveNotificationRequested(QString text); /** * Emitted, whan a subscription request was received */ void subscriptionRequestReceived(QString from, QString msg); /** * Incoming subscription request was accepted or declined by the user */ void subscriptionRequestAnswered(QString jid, bool accepted); /** * Request vCard of any JID * * Is required when the avatar (or other information) of a JID are * requested and the JID is not in the roster. */ void vCardRequested(QString jid); /** * XMPP URI received * * Is called when Kaidan was used to open an XMPP URI (i.e. 'xmpp:kaidan@muc.kaidan.im?join') */ void xmppUriReceived(QString uri); /** * The upload progress of a file upload has changed */ void uploadProgressMade(QString msgId, quint64 sent, quint64 total); /** * An HTTP File Upload service was discovered */ void uploadServiceFoundChanged(); /** * Send a text message to any JID * * Currently only contacts are displayed on the RosterPage (there is no * way to view a list of all chats -> for contacts and non-contacts), so * you should only send messages to JIDs from your roster, otherwise you * won't be able to see the message history. */ void sendMessage(QString jid, QString message, bool isSpoiler, QString spoilerHint); /** * Correct the last message * * To get/check the last message id, use `kaidan.messageModel.lastMessageId(jid)` */ void correctMessage(QString toJid, QString msgId, QString message); /** * Upload and send file */ void sendFile(QString jid, QString filePath, QString message); /** * Add a contact to your roster * * @param nick A simple nick name for the new contact, which should be * used to display in the roster. */ void addContact(QString jid, QString nick, QString msg); /** * Remove a contact from your roster * * Only the JID is needed. */ void removeContact(QString jid); + /** + * Change a contact's name + */ + void renameContact(const QString &jid, const QString &newContactName); + /** * Downloads an attached media file of a message * * @param msgId The message * @param url the media url from the message */ void downloadMedia(QString msgId, QString url); /** * Changes the user's password on the server * * @param newPassword The new password */ void changePassword(const QString &newPassword); /** * Emitted, when changing the password has succeeded. */ void passwordChangeSucceeded(); /** * Emitted, when changing the password has failed. */ void passwordChangeFailed(); /** * Emitted, when a contact was muted/unmuted. */ void notificationsMutedChanged(const QString& jid); public slots: /** * Set current connection state */ void setConnectionState(QXmppClient::State state); /** * Sets the disconnection error/reason */ void setDisconnReason(Enums::DisconnectionReason reason); /** * Receives messages from another instance of the application */ void receiveMessage(quint32, const QByteArray &msg) { // currently we only send XMPP URIs addOpenUri(msg); } /** * Enables HTTP File Upload to be used (will be called from UploadManager) */ void setUploadServiceFound(bool enabled) { uploadServiceFound = enabled; emit uploadServiceFoundChanged(); } /** * Returns whether notifications are enabled for the given contact. */ bool notificationsMuted(const QString& jid); /** * Sets the notifications to muted/unmuted. * * @param muted true if notifications should be muted. * @param jid contains the current chatpartner's jid. */ void setNotificationsMuted(const QString& jid, bool muted); private: void connectDatabases(); /** * Notifies if no login URI was found */ void notifyLoginUriNotFound(); QmlUtils *m_utils; Database *m_database; QThread *m_dbThrd; MessageDb *m_msgDb; RosterDb *m_rosterDb; QThread *m_cltThrd; ClientWorker::Caches *m_caches; ClientWorker *m_client; ClientWorker::Credentials creds; QString openUriCache; bool uploadServiceFound = false; ConnectionState connectionState = ConnectionState::StateDisconnected; DisconnReason disconnReason = DisconnReason::ConnNoError; static Kaidan *s_instance; }; #endif diff --git a/src/RosterManager.cpp b/src/RosterManager.cpp index ad2804c..6b9f23e 100644 --- a/src/RosterManager.cpp +++ b/src/RosterManager.cpp @@ -1,204 +1,217 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 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 * 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #include "RosterManager.h" // Kaidan #include "ClientWorker.h" #include "Globals.h" #include "Kaidan.h" #include "VCardManager.h" // QXmpp #include #include #include RosterManager::RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *model, AvatarFileStorage *avatarStorage, VCardManager *vCardManager, QObject *parent) : QObject(parent), kaidan(kaidan), client(client), model(model), avatarStorage(avatarStorage), vCardManager(vCardManager), manager(client->rosterManager()) { connect(&manager, &QXmppRosterManager::rosterReceived, this, &RosterManager::populateRoster); connect(&manager, &QXmppRosterManager::itemAdded, this, [this, vCardManager, model] (const QString &jid) { emit model->addItemRequested(RosterItem(manager.getRosterEntry(jid))); vCardManager->fetchVCard(jid); }); connect(&manager, &QXmppRosterManager::itemChanged, - this, [this, model] (QString jid) { - emit model->updateItemRequested(m_chatPartner, - [this, &jid] (RosterItem &item) { + this, [this, model] (const QString &jid) { + emit model->updateItemRequested(m_chatPartner, [=] (RosterItem &item) { item.setName(manager.getRosterEntry(jid).name()); }); }); connect(&manager, &QXmppRosterManager::itemRemoved, model, &RosterModel::removeItemRequested); connect(&manager, &QXmppRosterManager::subscriptionReceived, this, [kaidan] (const QString &jid) { // emit signal to ask user emit kaidan->subscriptionRequestReceived(jid, QString()); }); connect(kaidan, &Kaidan::subscriptionRequestAnswered, this, [=] (QString jid, bool accepted) { if (accepted) { manager.acceptSubscription(jid); // do not send a subscription request if both users have already subscribed // each others presence if (manager.getRosterEntry(jid).subscriptionType() != QXmppRosterIq::Item::Both) manager.subscribe(jid); } else { manager.refuseSubscription(jid); } }); // update local copy of chat partner connect(kaidan->getMessageModel(), &MessageModel::chatPartnerChanged, this, [=] (const QString &jid) { m_chatPartner = jid; } ); // user actions connect(kaidan, &Kaidan::addContact, this, &RosterManager::addContact); connect(kaidan, &Kaidan::removeContact, this, &RosterManager::removeContact); + connect(kaidan, &Kaidan::renameContact, this, &RosterManager::renameContact); connect(kaidan, &Kaidan::sendMessage, this, &RosterManager::handleSendMessage); connect(client, &QXmppClient::messageReceived, this, &RosterManager::handleMessage); } void RosterManager::populateRoster() { qDebug() << "[client] [RosterManager] Populating roster"; // create a new list of contacts QHash items; const QStringList bareJids = manager.getRosterBareJids(); for (const auto &jid : bareJids) { items[jid] = RosterItem(manager.getRosterEntry(jid)); if (avatarStorage->getHashOfJid(jid).isEmpty()) vCardManager->fetchVCard(jid); } // replace current contacts with new ones from server emit model->replaceItemsRequested(items); } void RosterManager::addContact(const QString &jid, const QString &name, const QString &msg) { if (client->state() == QXmppClient::ConnectedState) { manager.addItem(jid, name); manager.subscribe(jid, msg); } else { emit kaidan->passiveNotificationRequested( tr("Could not add contact, as a result of not being connected.") ); qWarning() << "[client] [RosterManager] Could not add contact, as a result of " "not being connected."; } } void RosterManager::removeContact(const QString &jid) { if (client->state() == QXmppClient::ConnectedState) { manager.unsubscribe(jid); manager.removeItem(jid); } else { emit kaidan->passiveNotificationRequested( tr("Could not remove contact, as a result of not being connected.") ); qWarning() << "[client] [RosterManager] Could not remove contact, as a result of " "not being connected."; } } +void RosterManager::renameContact(const QString &jid, const QString &newContactName) +{ + if (client->state() == QXmppClient::ConnectedState) { + manager.renameItem(jid, newContactName); + } else { + emit kaidan->passiveNotificationRequested( + tr("Could not rename contact, as a result of not being connected.") + ); + qWarning() << "[client] [RosterManager] Could not rename contact, as a result of " + "not being connected."; + } +} + void RosterManager::handleSendMessage(const QString &jid, const QString &message, bool isSpoiler, const QString &spoilerHint) { if (client->state() == QXmppClient::ConnectedState) { // update roster item const QString lastMessage = isSpoiler ? spoilerHint.isEmpty() ? tr("Spoiler") : spoilerHint : message; // sorting order in contact list const QDateTime dateTime = QDateTime::currentDateTimeUtc(); emit model->updateItemRequested(jid, [=] (RosterItem &item) { item.setLastMessage(lastMessage); item.setLastExchanged(dateTime); }); } } void RosterManager::handleMessage(const QXmppMessage &msg) { if (msg.body().isEmpty() || msg.type() == QXmppMessage::Error) return; // msg.from() can be our JID, if it's a carbon/forward from another client QString fromJid = QXmppUtils::jidToBareJid(msg.from()); bool sentByMe = fromJid == client->configuration().jidBare(); QString contactJid = sentByMe ? QXmppUtils::jidToBareJid(msg.to()) : fromJid; // update last exchanged datetime (sorting order in contact list) const QDateTime dateTime = QDateTime::currentDateTimeUtc(); // update unread message counter, if chat is not active if (sentByMe) { // if we sent a message (with another device), reset counter emit model->updateItemRequested(contactJid, [dateTime] (RosterItem &item) { item.setLastExchanged(dateTime); item.setUnreadMessages(0); }); } else if (m_chatPartner != contactJid) { emit model->updateItemRequested(contactJid, [dateTime] (RosterItem &item) { item.setLastExchanged(dateTime); item.setUnreadMessages(item.unreadMessages() + 1); }); } } diff --git a/src/RosterManager.h b/src/RosterManager.h index 2e9849d..8679e57 100644 --- a/src/RosterManager.h +++ b/src/RosterManager.h @@ -1,76 +1,77 @@ /* * Kaidan - A user-friendly XMPP client for every device! * * Copyright (C) 2016-2019 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 * 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. * * In addition, as a special exception, the author of Kaidan gives * permission to link the code of its release with the OpenSSL * project's "OpenSSL" library (or with modified versions of it that * use the same license as the "OpenSSL" library), and distribute the * linked executables. You must obey the GNU General Public License in * all respects for all of the code used other than "OpenSSL". If you * modify this file, you may extend this exception to your version of * the file, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Kaidan 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 Kaidan. If not, see . */ #ifndef ROSTERMANAGER_H #define ROSTERMANAGER_H // Qt #include // Kaidan class AvatarFileStorage; class Kaidan; class RosterModel; class VCardManager; // QXmpp class QXmppClient; class QXmppMessage; class QXmppRosterManager; class RosterManager : public QObject { Q_OBJECT public: RosterManager(Kaidan *kaidan, QXmppClient *client, RosterModel *rosterModel, AvatarFileStorage *avatarStorage, VCardManager *vCardManager, QObject *parent = nullptr); public slots: void addContact(const QString &jid, const QString &name, const QString &msg); void removeContact(const QString &jid); + void renameContact(const QString &jid, const QString &newContactName); void handleSendMessage(const QString &jid, const QString &message, bool isSpoiler = false, const QString &spoilerHint = QString()); private slots: void populateRoster(); void handleMessage(const QXmppMessage &msg); private: Kaidan *kaidan; QXmppClient *client; RosterModel *model; AvatarFileStorage *avatarStorage; VCardManager *vCardManager; QXmppRosterManager &manager; QString m_chatPartner; }; #endif // ROSTERMANAGER_H