From 64df91a89113760be13cf0503fafdb5d82918fd1 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Thu, 30 Jul 2020 08:08:45 +0200 Subject: [PATCH] gmail plugin can send/reply to messages --- src/librssguard/3rd-party/boolinq/boolinq.h | 17 +-- src/librssguard/definitions/definitions.h | 20 ++++ src/librssguard/librssguard.pro | 3 + .../miscellaneous/databasequeries.cpp | 22 ++++ .../miscellaneous/databasequeries.h | 1 + .../services/gmail/gmailserviceroot.cpp | 6 +- .../gmail/gui/emailrecipientcontrol.cpp | 4 + .../services/gmail/gui/formaddeditemail.cpp | 31 +++-- .../services/gmail/gui/formaddeditemail.h | 2 + .../gmail/network/gmailnetworkfactory.cpp | 108 +++--------------- .../gmail/network/gmailnetworkfactory.h | 5 +- .../network/owncloudnetworkfactory.cpp | 2 +- 12 files changed, 103 insertions(+), 118 deletions(-) diff --git a/src/librssguard/3rd-party/boolinq/boolinq.h b/src/librssguard/3rd-party/boolinq/boolinq.h index 0d52c1167..c1c25696d 100755 --- a/src/librssguard/3rd-party/boolinq/boolinq.h +++ b/src/librssguard/3rd-party/boolinq/boolinq.h @@ -64,7 +64,7 @@ namespace boolinq { void for_each(std::function apply) const { - return for_each_i([apply](T value, int index) { return apply(value); }); + return for_each_i([apply](T value, int) { return apply(value); }); } Linq, int>, T> where_i(std::function filter) const @@ -87,7 +87,7 @@ namespace boolinq { Linq, int>, T> where(std::function filter) const { - return where_i([filter](T value, int index) { return filter(value); }); + return where_i([filter](T value, int) { return filter(value); }); } Linq, int>, T> take(int count) const @@ -296,13 +296,14 @@ namespace boolinq { Linq &linqCopy = std::get<1>(tuple); std::unordered_set<_TKey> &set = std::get<2>(tuple); - _TKey key = apply(linq.next()); - if (set.insert(key).second) { - return std::make_pair(key, linqCopy.where([apply, key](T v){ - return apply(v) == key; - })); + while (true) { + _TKey key = apply(linq.next()); + if (set.insert(key).second) { + return std::make_pair(key, linqCopy.where([apply, key](T v){ + return apply(v) == key; + })); + } } - throw LinqEndException(); } ); } diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 504f16c17..26d960562 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -141,6 +141,26 @@ #define APP_NO_THEME "" #define APP_THEME_SUFFIX ".png" +#ifndef qDebugNN +#define qDebugNN qDebug().noquote().nospace() +#endif + +#ifndef qWarningNN +#define qWarningNN qWarning().noquote().nospace() +#endif + +#ifndef qCriticalNN +#define qCriticalNN qCritical().noquote().nospace() +#endif + +#ifndef qFatalNN +#define qFatalNN qFatal().noquote().nospace() +#endif + +#ifndef qInfoNN +#define qInfoNN qInfo().noquote().nospace() +#endif + #ifndef QSL // Thin macro wrapper for literal strings. diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index 80c25a86b..f1713b6cb 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -417,6 +417,9 @@ else { SOURCES += $$files(3rd-party/mimesis/*.cpp, false) HEADERS += $$files(3rd-party/mimesis/*.hpp, false) +# Add boolinq. +HEADERS += $$files(3rd-party/boolinq/*.h, false) + INCLUDEPATH += $$PWD/. \ $$PWD/gui \ $$PWD/gui/dialogs \ diff --git a/src/librssguard/miscellaneous/databasequeries.cpp b/src/librssguard/miscellaneous/databasequeries.cpp index 01132c82e..2b8c7d83e 100755 --- a/src/librssguard/miscellaneous/databasequeries.cpp +++ b/src/librssguard/miscellaneous/databasequeries.cpp @@ -1720,6 +1720,28 @@ bool DatabaseQueries::createTtRssAccount(const QSqlDatabase& db, int id_to_assig } } +QStringList DatabaseQueries::getAllRecipients(const QSqlDatabase& db, int account_id) { + QSqlQuery query(db); + QStringList rec; + + query.prepare(QSL("SELECT DISTINCT author " + "FROM Messages " + "WHERE account_id = :account_id AND author IS NOT NULL AND author != '' " + "ORDER BY lower(author) ASC;")); + query.bindValue(QSL(":account_id"), account_id); + + if (query.exec()) { + while (query.next()) { + rec.append(query.value(0).toString()); + } + } + else { + qWarningNN << "Query for all recipients failed: '" << query.lastError().text() << "'."; + } + + return rec; +} + QList DatabaseQueries::getGmailAccounts(const QSqlDatabase& db, bool* ok) { QSqlQuery query(db); QList roots; diff --git a/src/librssguard/miscellaneous/databasequeries.h b/src/librssguard/miscellaneous/databasequeries.h index 1f0aea30d..28afa1dd2 100644 --- a/src/librssguard/miscellaneous/databasequeries.h +++ b/src/librssguard/miscellaneous/databasequeries.h @@ -145,6 +145,7 @@ class DatabaseQueries { bool force_server_side_feed_update, bool download_only_unread_messages); // Gmail account. + static QStringList getAllRecipients(const QSqlDatabase& db, int account_id); static bool deleteGmailAccount(const QSqlDatabase& db, int account_id); static QList getGmailAccounts(const QSqlDatabase& db, bool* ok = nullptr); static bool overwriteGmailAccount(const QSqlDatabase& db, const QString& username, const QString& app_id, diff --git a/src/librssguard/services/gmail/gmailserviceroot.cpp b/src/librssguard/services/gmail/gmailserviceroot.cpp index 64eebcbd1..6fc84ba19 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.cpp +++ b/src/librssguard/services/gmail/gmailserviceroot.cpp @@ -135,10 +135,10 @@ bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const { QList GmailServiceRoot::contextMenuMessagesList(const QList& messages) { if (messages.size() == 1) { - if (m_actionReply == nullptr) { - m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply"), this); + m_replyToMessage = messages.at(0); - m_replyToMessage = messages.at(0); + if (m_actionReply == nullptr) { + m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply to this message"), this); connect(m_actionReply, &QAction::triggered, this, &GmailServiceRoot::replyToEmail); } diff --git a/src/librssguard/services/gmail/gui/emailrecipientcontrol.cpp b/src/librssguard/services/gmail/gui/emailrecipientcontrol.cpp index a7c1fcab3..026f7b25a 100644 --- a/src/librssguard/services/gmail/gui/emailrecipientcontrol.cpp +++ b/src/librssguard/services/gmail/gui/emailrecipientcontrol.cpp @@ -61,5 +61,9 @@ void EmailRecipientControl::setPossibleRecipients(const QStringList& rec) { QCompleter* cmpl = new QCompleter(rec, m_txtRecipient); + cmpl->setFilterMode(Qt::MatchFlag::MatchContains); + cmpl->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + cmpl->setCompletionMode(QCompleter::CompletionMode::UnfilteredPopupCompletion); + m_txtRecipient->setCompleter(cmpl); } diff --git a/src/librssguard/services/gmail/gui/formaddeditemail.cpp b/src/librssguard/services/gmail/gui/formaddeditemail.cpp index d180fe3fe..0065d9d4e 100644 --- a/src/librssguard/services/gmail/gui/formaddeditemail.cpp +++ b/src/librssguard/services/gmail/gui/formaddeditemail.cpp @@ -7,13 +7,18 @@ #include "gui/guiutilities.h" #include "gui/messagebox.h" #include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" #include "miscellaneous/iconfactory.h" #include "services/gmail/gmailserviceroot.h" #include "services/gmail/gui/emailrecipientcontrol.h" #include "services/gmail/network/gmailnetworkfactory.h" +#include + +#include + FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) - : QDialog(parent), m_root(root), m_originalMessage(nullptr) { + : QDialog(parent), m_root(root), m_originalMessage(nullptr), m_possibleRecipients({}) { m_ui.setupUi(this); GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("mail-message-new"))); @@ -33,6 +38,14 @@ FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) &QPushButton::clicked, this, &FormAddEditEmail::onOkClicked); + + QSqlDatabase db = qApp->database()->connection(metaObject()->className()); + + m_possibleRecipients = DatabaseQueries::getAllRecipients(db, m_root->accountId()); + + for (auto* rec: recipientControls()) { + rec->setPossibleRecipients(m_possibleRecipients); + } } void FormAddEditEmail::execForAdd() { @@ -44,7 +57,7 @@ void FormAddEditEmail::execForReply(Message* original_message) { m_originalMessage = original_message; addRecipientRow(m_originalMessage->m_author); - m_ui.m_txtSubject->setText(QSL("Re:%1").arg(m_originalMessage->m_title)); + m_ui.m_txtSubject->setText(QSL("Re: %1").arg(m_originalMessage->m_title)); exec(); } @@ -129,18 +142,14 @@ void FormAddEditEmail::addRecipientRow(const QString& recipient) { connect(mail_rec, &EmailRecipientControl::removalRequested, this, &FormAddEditEmail::removeRecipientRow); - try { - QStringList rec = m_root->network()->getAllRecipients(); - - mail_rec->setPossibleRecipients(rec); - } - catch (const ApplicationException& ex) { - qWarning("Failed to get recipients: '%s'.", qPrintable(ex.message())); - } - + mail_rec->setPossibleRecipients(m_possibleRecipients); m_ui.m_layout->insertRow(m_ui.m_layout->count() - 5, mail_rec); } +void FormAddEditEmail::closeEvent(QCloseEvent* event) { + // event->ignore(); +} + QList FormAddEditEmail::recipientControls() const { QList list; diff --git a/src/librssguard/services/gmail/gui/formaddeditemail.h b/src/librssguard/services/gmail/gui/formaddeditemail.h index 0ce4f4b82..bc641c631 100644 --- a/src/librssguard/services/gmail/gui/formaddeditemail.h +++ b/src/librssguard/services/gmail/gui/formaddeditemail.h @@ -31,6 +31,7 @@ class FormAddEditEmail : public QDialog { void addRecipientRow(const QString& recipient = QString()); private: + void closeEvent(QCloseEvent* event); QList recipientControls() const; private: @@ -39,6 +40,7 @@ class FormAddEditEmail : public QDialog { Ui::FormAddEditEmail m_ui; QList m_recipientControls; Message* m_originalMessage; + QStringList m_possibleRecipients; }; #endif // FORMADDEDITEMAIL_H diff --git a/src/librssguard/services/gmail/network/gmailnetworkfactory.cpp b/src/librssguard/services/gmail/network/gmailnetworkfactory.cpp index d1c917515..aac217702 100644 --- a/src/librssguard/services/gmail/network/gmailnetworkfactory.cpp +++ b/src/librssguard/services/gmail/network/gmailnetworkfactory.cpp @@ -56,7 +56,7 @@ QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, Message* reply_to_m QString bearer = m_oauth2->bearer().toLocal8Bit(); if (bearer.isEmpty()) { - throw ApplicationException(tr("you aren't logged in")); + //throw ApplicationException(tr("you aren't logged in")); } if (reply_to_message != nullptr) { @@ -72,8 +72,8 @@ QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, Message* reply_to_m }*/ if (metadata.contains(QSL("Message-ID"))) { - msg["References"] = metadata.value(QSL("Message-ID")).toString().toStdString(); - msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toString().toStdString(); + msg["References"] = metadata.value(QSL("Message-ID")).toStdString(); + msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toStdString(); } } @@ -433,93 +433,7 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, return true; } -QStringList GmailNetworkFactory::getAllRecipients() { - QString bearer = m_oauth2->bearer().toLocal8Bit(); - - if (bearer.isEmpty()) { - throw ApplicationException(tr("not logged-in")); - } - - QStringList recipients; - QList> headers; - - headers.append(QPair(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), - m_oauth2->bearer().toLocal8Bit())); - headers.append(QPair(QString(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(), - QString(GMAIL_CONTENT_TYPE_JSON).toLocal8Bit())); - - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray msg_list_data; - - // TODO: Cyklicky!! - auto list_res = NetworkFactory::performNetworkOperation(GMAIL_API_MSGS_LIST, - timeout, - QByteArray(), - msg_list_data, - QNetworkAccessManager::Operation::GetOperation, - headers); - - if (list_res.first != QNetworkReply::NetworkError::NoError) { - throw ApplicationException(tr("comm error when asking for recipients")); - } - - QJsonDocument json_list = QJsonDocument::fromJson(msg_list_data); - QStringList message_ids; - - for (const auto& msg_nod : json_list.object()["messages"].toArray()) { - message_ids.append(msg_nod.toObject()["id"].toString()); - } - - auto* multi = new QHttpMultiPart(); - - multi->setContentType(QHttpMultiPart::ContentType::MixedType); - - for (const QString& msg : message_ids) { - QHttpPart part; - - part.setRawHeader(HTTP_HEADERS_CONTENT_TYPE, GMAIL_CONTENT_TYPE_HTTP); - QString full_msg_endpoint = QString("GET /gmail/v1/users/me/messages/%1?metadataHeaders=From&metadataHeaders=To&format=metadata\r\n").arg(msg); - - part.setBody(full_msg_endpoint.toUtf8()); - multi->append(part); - } - - QList output; - - headers.removeLast(); - - NetworkResult res = NetworkFactory::performNetworkOperation(GMAIL_API_BATCH, - timeout, - multi, - output, - QNetworkAccessManager::Operation::PostOperation, - headers); - - if (res.first == QNetworkReply::NetworkError::NoError) { - // We parse each part of HTTP response (it contains HTTP headers and payload with msg full data). - for (const HttpResponse& part : output) { - QJsonObject msg_doc = QJsonDocument::fromJson(part.body().toUtf8()).object(); - auto headers = msg_doc["payload"].toObject()["headers"].toArray(); - - if (headers.size() >= 2) { - for (const auto& head : headers) { - auto val = head.toObject()["value"].toString(); - - if (!recipients.contains(val)) { - recipients.append(val); - } - } - } - } - - return recipients; - } - else { - throw ApplicationException(tr("comm error when asking for recipients")); - } -} - -QVariantMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) { +QMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) { QString bearer = m_oauth2->bearer(); if (bearer.isEmpty()) { @@ -543,7 +457,19 @@ QVariantMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QNetworkAccessManager::Operation::GetOperation, headers); - if (res.first == QNetworkReply::NetworkError::NoError) {} + if (res.first == QNetworkReply::NetworkError::NoError) { + QJsonDocument doc = QJsonDocument::fromJson(output); + QMap result; + auto headers = doc.object()["payload"].toObject()["headers"].toArray(); + + for (const auto& header : headers) { + QJsonObject obj_header = header.toObject(); + + result.insert(obj_header["name"].toString(), obj_header["value"].toString()); + } + + return result; + } else { throw ApplicationException(tr("failed to get metadata")); } diff --git a/src/librssguard/services/gmail/network/gmailnetworkfactory.h b/src/librssguard/services/gmail/network/gmailnetworkfactory.h index a8568ace9..82726ebd9 100644 --- a/src/librssguard/services/gmail/network/gmailnetworkfactory.h +++ b/src/librssguard/services/gmail/network/gmailnetworkfactory.h @@ -38,9 +38,6 @@ class GmailNetworkFactory : public QObject { // Sends e-mail, returns its ID. QString sendEmail(Mimesis::Message msg, Message* reply_to_message = nullptr); - // Returns all possible recipients. - QStringList getAllRecipients(); - Downloader* downloadAttachment(const QString& msg_id, const QString& attachment_id); QList messages(const QString& stream_id, Feed::Status& error); @@ -53,7 +50,7 @@ class GmailNetworkFactory : public QObject { private: bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id); - QVariantMap getMessageMetadata(const QString& msg_id, const QStringList& metadata); + QMap getMessageMetadata(const QString& msg_id, const QStringList& metadata); bool obtainAndDecodeFullMessages(const QList& lite_messages, const QString& feed_id, QList& full_messages); QList decodeLiteMessages(const QString& messages_json_data, const QString& stream_id, QString& next_page_token); diff --git a/src/librssguard/services/owncloud/network/owncloudnetworkfactory.cpp b/src/librssguard/services/owncloud/network/owncloudnetworkfactory.cpp index 6a1cd3068..eaf927f26 100644 --- a/src/librssguard/services/owncloud/network/owncloudnetworkfactory.cpp +++ b/src/librssguard/services/owncloud/network/owncloudnetworkfactory.cpp @@ -120,7 +120,7 @@ OwnCloudStatusResponse OwnCloudNetworkFactory::status() { headers); OwnCloudStatusResponse status_response(QString::fromUtf8(result_raw)); - qDebug().noquote().nospace() << "Raw status data is:" << result_raw; + qDebugNN << "Raw status data is:" << result_raw; if (network_reply.first != QNetworkReply::NoError) { qWarning("Nextcloud: Obtaining status info failed with error %d.", network_reply.first);