gmail plugin can send/reply to messages

This commit is contained in:
Martin Rotter 2020-07-30 08:08:45 +02:00
parent a3d142034f
commit 64df91a891
12 changed files with 103 additions and 118 deletions

View File

@ -64,7 +64,7 @@ namespace boolinq {
void for_each(std::function<void(T)> 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<std::tuple<Linq<S, T>, int>, T> where_i(std::function<bool(T, int)> filter) const
@ -87,7 +87,7 @@ namespace boolinq {
Linq<std::tuple<Linq<S, T>, int>, T> where(std::function<bool(T)> filter) const
{
return where_i([filter](T value, int index) { return filter(value); });
return where_i([filter](T value, int) { return filter(value); });
}
Linq<std::tuple<Linq<S, T>, int>, T> take(int count) const
@ -296,13 +296,14 @@ namespace boolinq {
Linq<S, T> &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();
}
);
}

View File

@ -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.

View File

@ -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 \

View File

@ -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<ServiceRoot*> DatabaseQueries::getGmailAccounts(const QSqlDatabase& db, bool* ok) {
QSqlQuery query(db);
QList<ServiceRoot*> roots;

View File

@ -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<ServiceRoot*> getGmailAccounts(const QSqlDatabase& db, bool* ok = nullptr);
static bool overwriteGmailAccount(const QSqlDatabase& db, const QString& username, const QString& app_id,

View File

@ -135,10 +135,10 @@ bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
QList<QAction*> GmailServiceRoot::contextMenuMessagesList(const QList<Message>& 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);
}

View File

@ -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);
}

View File

@ -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 <QtConcurrent/QtConcurrentRun>
#include <QCloseEvent>
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<EmailRecipientControl*> FormAddEditEmail::recipientControls() const {
QList<EmailRecipientControl*> list;

View File

@ -31,6 +31,7 @@ class FormAddEditEmail : public QDialog {
void addRecipientRow(const QString& recipient = QString());
private:
void closeEvent(QCloseEvent* event);
QList<EmailRecipientControl*> recipientControls() const;
private:
@ -39,6 +40,7 @@ class FormAddEditEmail : public QDialog {
Ui::FormAddEditEmail m_ui;
QList<EmailRecipientControl*> m_recipientControls;
Message* m_originalMessage;
QStringList m_possibleRecipients;
};
#endif // FORMADDEDITEMAIL_H

View File

@ -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<QPair<QByteArray, QByteArray>> headers;
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
m_oauth2->bearer().toLocal8Bit()));
headers.append(QPair<QByteArray, QByteArray>(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<HttpResponse> 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<QString, QString> 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<QString, QString> 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"));
}

View File

@ -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<Message> 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<QString, QString> getMessageMetadata(const QString& msg_id, const QStringList& metadata);
bool obtainAndDecodeFullMessages(const QList<Message>& lite_messages, const QString& feed_id, QList<Message>& full_messages);
QList<Message> decodeLiteMessages(const QString& messages_json_data, const QString& stream_id, QString& next_page_token);

View File

@ -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);