From b72375e8e8d237ef95744700315603eb6b2d39a4 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 17 Oct 2017 12:00:59 +0200 Subject: [PATCH] Added some base file sfor gmail plugin. --- rssguard.pro | 10 +- src/services/gmail/definitions.h | 5 + src/services/gmail/gmailfeed.cpp | 47 ++ src/services/gmail/gmailfeed.h | 37 ++ src/services/gmail/gmailserviceroot.cpp | 224 +++++++++ src/services/gmail/gmailserviceroot.h | 78 +++ .../gmail/network/gmailnetworkfactory.cpp | 462 ++++++++++++++++++ .../gmail/network/gmailnetworkfactory.h | 78 +++ 8 files changed, 939 insertions(+), 2 deletions(-) create mode 100755 src/services/gmail/gmailfeed.cpp create mode 100755 src/services/gmail/gmailfeed.h create mode 100755 src/services/gmail/gmailserviceroot.cpp create mode 100755 src/services/gmail/gmailserviceroot.h create mode 100755 src/services/gmail/network/gmailnetworkfactory.cpp create mode 100755 src/services/gmail/network/gmailnetworkfactory.h diff --git a/rssguard.pro b/rssguard.pro index ee3588bd5..c9f92e49a 100755 --- a/rssguard.pro +++ b/rssguard.pro @@ -501,7 +501,10 @@ equals(USE_WEBENGINE, true) { src/network-web/oauth2service.h \ src/gui/dialogs/oauthlogin.h \ src/services/gmail/definitions.h \ - src/services/gmail/gmailentrypoint.h + src/services/gmail/gmailentrypoint.h \ + src/services/gmail/gmailserviceroot.h \ + src/services/gmail/gmailfeed.h \ + src/services/gmail/network/gmailnetworkfactory.h SOURCES += src/gui/locationlineedit.cpp \ src/gui/webviewer.cpp \ @@ -517,7 +520,10 @@ equals(USE_WEBENGINE, true) { src/services/inoreader/inoreaderfeed.cpp \ src/network-web/oauth2service.cpp \ src/gui/dialogs/oauthlogin.cpp \ - src/services/gmail/gmailentrypoint.cpp + src/services/gmail/gmailentrypoint.cpp \ + src/services/gmail/gmailserviceroot.cpp \ + src/services/gmail/gmailfeed.cpp \ + src/services/gmail/network/gmailnetworkfactory.cpp # Add AdBlock sources. HEADERS += src/network-web/adblock/adblockaddsubscriptiondialog.h \ diff --git a/src/services/gmail/definitions.h b/src/services/gmail/definitions.h index 97c5e034e..1dbe99c41 100755 --- a/src/services/gmail/definitions.h +++ b/src/services/gmail/definitions.h @@ -19,4 +19,9 @@ #ifndef GMAIL_DEFINITIONS_H #define GMAIL_DEFINITIONS_H +#define GMAIL_OAUTH_AUTH_URL "https://accounts.google.com/o/oauth2/auth" +#define GMAIL_OAUTH_TOKEN_URL "https://accounts.google.com/o/oauth2/token" +#define GMAIL_OAUTH_SCOPE "https://mail.google.com/" +#define GMAIL_DEFAULT_BATCH_SIZE 100 + #endif // GMAIL_DEFINITIONS_H diff --git a/src/services/gmail/gmailfeed.cpp b/src/services/gmail/gmailfeed.cpp new file mode 100755 index 000000000..af065ed19 --- /dev/null +++ b/src/services/gmail/gmailfeed.cpp @@ -0,0 +1,47 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/gmail/gmailfeed.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/gmail/gmailserviceroot.h" +#include "services/gmail/network/gmailnetworkfactory.h" + +GmailFeed::GmailFeed(RootItem* parent) : Feed(parent) {} + +GmailFeed::GmailFeed(const QSqlRecord& record) : Feed(record) {} + +GmailServiceRoot* GmailFeed::serviceRoot() const { + return qobject_cast(getParentServiceRoot()); +} + +QList GmailFeed::obtainNewMessages(bool* error_during_obtaining) { + Feed::Status error; + + // TODO: dodělat + QList messages;/* = serviceRoot()->network()->messages(customId(), error); + + setStatus(error); + + if (error == Feed::Status::NetworkError || error == Feed::Status::AuthError) { + * error_during_obtaining = true; + }*/ + + return messages; +} diff --git a/src/services/gmail/gmailfeed.h b/src/services/gmail/gmailfeed.h new file mode 100755 index 000000000..69006d9dc --- /dev/null +++ b/src/services/gmail/gmailfeed.h @@ -0,0 +1,37 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef GMAILFEED_H +#define GMAILFEED_H + +#include "services/abstract/feed.h" + +class GmailServiceRoot; + +class GmailFeed : public Feed { + public: + explicit GmailFeed(RootItem* parent = nullptr); + explicit GmailFeed(const QSqlRecord& record); + + GmailServiceRoot* serviceRoot() const; + + private: + QList obtainNewMessages(bool* error_during_obtaining); +}; + +#endif // GMAILFEED_H diff --git a/src/services/gmail/gmailserviceroot.cpp b/src/services/gmail/gmailserviceroot.cpp new file mode 100755 index 000000000..68a2e8d22 --- /dev/null +++ b/src/services/gmail/gmailserviceroot.cpp @@ -0,0 +1,224 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// Copyright (C) 2010-2014 by David Rosca +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/gmail/gmailserviceroot.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" +#include "network-web/oauth2service.h" +#include "services/abstract/recyclebin.h" +#include "services/gmail/gmailentrypoint.h" +#include "services/gmail/network/gmailnetworkfactory.h" + +GmailServiceRoot::GmailServiceRoot(GmailNetworkFactory* network, RootItem* parent) : ServiceRoot(parent), + CacheForServiceRoot(), m_serviceMenu(QList()), m_network(network) { + if (network == nullptr) { + m_network = new GmailNetworkFactory(this); + } + else { + m_network->setParent(this); + } + + m_network->setService(this); + setIcon(GmailEntryPoint().icon()); +} + +GmailServiceRoot::~GmailServiceRoot() {} + +void GmailServiceRoot::updateTitle() { + setTitle(m_network->userName() + QSL(" (Gmail)")); +} + +void GmailServiceRoot::loadFromDatabase() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + Assignment categories = DatabaseQueries::getCategories(database, accountId()); + Assignment feeds = DatabaseQueries::getInoreaderFeeds(database, accountId()); + + // All data are now obtained, lets create the hierarchy. + assembleCategories(categories); + assembleFeeds(feeds); + + // As the last item, add recycle bin, which is needed. + appendChild(recycleBin()); + updateCounts(true); +} + +void GmailServiceRoot::saveAccountDataToDatabase() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (accountId() != NO_PARENT_CATEGORY) { + // TODO: dodělat + + /*if (DatabaseQueries::overwriteInoreaderAccount(database, m_network->userName(), + m_network->oauth()->clientId(), + m_network->oauth()->clientSecret(), + m_network->oauth()->redirectUrl(), + m_network->oauth()->refreshToken(), + m_network->batchSize(), + accountId())) { + updateTitle(); + itemChanged(QList() << this); + }*/ + } + else { + bool saved; + int id_to_assign = DatabaseQueries::createAccount(database, code(), &saved); + + if (saved) { + // TODO: dodělat + + /*if (DatabaseQueries::createInoreaderAccount(database, id_to_assign, + m_network->userName(), + m_network->oauth()->clientId(), + m_network->oauth()->clientSecret(), + m_network->oauth()->redirectUrl(), + m_network->oauth()->refreshToken(), + m_network->batchSize())) { + setId(id_to_assign); + setAccountId(id_to_assign); + updateTitle(); + }*/ + } + } +} + +bool GmailServiceRoot::canBeEdited() const { + return true; +} + +bool GmailServiceRoot::editViaGui() { + //FormEditInoreaderAccount form_pointer(qApp->mainFormWidget()); + // TODO: dodělat + //form_pointer.execForEdit(this); + return true; +} + +bool GmailServiceRoot::supportsFeedAdding() const { + return true; +} + +bool GmailServiceRoot::supportsCategoryAdding() const { + return false; +} + +void GmailServiceRoot::start(bool freshly_activated) { + Q_UNUSED(freshly_activated) + + loadFromDatabase(); + loadCacheFromFile(accountId()); + + m_network->oauth()->login(); + + if (childCount() <= 1) { + syncIn(); + } +} + +void GmailServiceRoot::stop() { + saveCacheToFile(accountId()); +} + +QList GmailServiceRoot::serviceMenu() { + if (m_serviceMenu.isEmpty()) { + QAction* act_sync_in = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Sync in"), this); + + connect(act_sync_in, &QAction::triggered, this, &GmailServiceRoot::syncIn); + m_serviceMenu.append(act_sync_in); + } + + return m_serviceMenu; +} + +QString GmailServiceRoot::code() const { + return GmailEntryPoint().code(); +} + +QString GmailServiceRoot::additionalTooltip() const { + return tr("Authentication status: %1\n" + "Login tokens expiration: %2").arg(network()->oauth()->isFullyLoggedIn() ? tr("logged-in") : tr("NOT logged-in"), + network()->oauth()->tokensExpireIn().isValid() ? + network()->oauth()->tokensExpireIn().toString() : QSL("-")); +} + +RootItem* GmailServiceRoot::obtainNewTreeForSyncIn() const { + // TODO: dodělat + return nullptr; + + //return m_network->feedsCategories(true); +} + +void GmailServiceRoot::addNewFeed(const QString& url) { + Q_UNUSED(url) +} + +void GmailServiceRoot::addNewCategory() {} + +void GmailServiceRoot::saveAllCachedData(bool async) { + QPair, QMap>> msgCache = takeMessageCache(); + QMapIterator i(msgCache.first); + + // Save the actual data read/unread. + while (i.hasNext()) { + i.next(); + auto key = i.key(); + QStringList ids = i.value(); + + if (!ids.isEmpty()) { + // TODO: dodělat + //network()->markMessagesRead(key, ids, async); + } + } + + QMapIterator> j(msgCache.second); + + // Save the actual data important/not important. + while (j.hasNext()) { + j.next(); + auto key = j.key(); + + QList messages = j.value(); + + if (!messages.isEmpty()) { + QStringList custom_ids; + + foreach (const Message& msg, messages) { + custom_ids.append(msg.m_customId); + } + + // TODO: dodělat + //network()->markMessagesStarred(key, custom_ids, async); + } + } +} + +bool GmailServiceRoot::canBeDeleted() const { + return true; +} + +bool GmailServiceRoot::deleteViaGui() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (DatabaseQueries::deleteInoreaderAccount(database, accountId())) { + return ServiceRoot::deleteViaGui(); + } + else { + return false; + } +} diff --git a/src/services/gmail/gmailserviceroot.h b/src/services/gmail/gmailserviceroot.h new file mode 100755 index 000000000..99a892508 --- /dev/null +++ b/src/services/gmail/gmailserviceroot.h @@ -0,0 +1,78 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// Copyright (C) 2010-2014 by David Rosca +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef INOREADERSERVICEROOT_H +#define INOREADERSERVICEROOT_H + +#include "services/abstract/cacheforserviceroot.h" +#include "services/abstract/serviceroot.h" + +class GmailNetworkFactory; + +class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot { + Q_OBJECT + + public: + explicit GmailServiceRoot(GmailNetworkFactory* network, RootItem* parent = nullptr); + virtual ~GmailServiceRoot(); + + void saveAccountDataToDatabase(); + + void setNetwork(GmailNetworkFactory* network); + GmailNetworkFactory* network() const; + + bool canBeEdited() const; + bool editViaGui(); + bool canBeDeleted() const; + bool deleteViaGui(); + bool supportsFeedAdding() const; + bool supportsCategoryAdding() const; + void start(bool freshly_activated); + void stop(); + QString code() const; + + QString additionalTooltip() const; + + RootItem* obtainNewTreeForSyncIn() const; + + void saveAllCachedData(bool async = true); + + public slots: + void addNewFeed(const QString& url); + void addNewCategory(); + void updateTitle(); + + private: + void loadFromDatabase(); + QList serviceMenu(); + + private: + QList m_serviceMenu; + GmailNetworkFactory* m_network; +}; + +inline void GmailServiceRoot::setNetwork(GmailNetworkFactory* network) { + m_network = network; +} + +inline GmailNetworkFactory* GmailServiceRoot::network() const { + return m_network; +} + +#endif // INOREADERSERVICEROOT_H diff --git a/src/services/gmail/network/gmailnetworkfactory.cpp b/src/services/gmail/network/gmailnetworkfactory.cpp new file mode 100755 index 000000000..646f5631a --- /dev/null +++ b/src/services/gmail/network/gmailnetworkfactory.cpp @@ -0,0 +1,462 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/gmail/network/gmailnetworkfactory.h" + +#include "definitions/definitions.h" +#include "gui/dialogs/formmain.h" +#include "gui/tabwidget.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "network-web/networkfactory.h" +#include "network-web/oauth2service.h" +#include "network-web/silentnetworkaccessmanager.h" +#include "network-web/webfactory.h" +#include "services/abstract/category.h" +#include "services/gmail/definitions.h" +#include "services/gmail/gmailfeed.h" +#include "services/gmail/gmailserviceroot.h" + +#include +#include +#include +#include +#include + +GmailNetworkFactory::GmailNetworkFactory(QObject* parent) : QObject(parent), + m_service(nullptr), m_username(QString()), m_batchSize(GMAIL_DEFAULT_BATCH_SIZE), + m_oauth2(new OAuth2Service(GMAIL_OAUTH_AUTH_URL, GMAIL_OAUTH_TOKEN_URL, + QString(), QString(), GMAIL_OAUTH_SCOPE)) { + initializeOauth(); +} + +void GmailNetworkFactory::setService(GmailServiceRoot* service) { + m_service = service; +} + +OAuth2Service* GmailNetworkFactory::oauth() const { + return m_oauth2; +} + +QString GmailNetworkFactory::userName() const { + return m_username; +} + +int GmailNetworkFactory::batchSize() const { + return m_batchSize; +} + +void GmailNetworkFactory::setBatchSize(int batch_size) { + m_batchSize = batch_size; +} + +void GmailNetworkFactory::initializeOauth() { + connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &GmailNetworkFactory::onTokensError); + connect(m_oauth2, &OAuth2Service::authFailed, this, &GmailNetworkFactory::onAuthFailed); + connect(m_oauth2, &OAuth2Service::tokensReceived, [this](QString access_token, QString refresh_token, int expires_in) { + Q_UNUSED(expires_in) + + if (m_service != nullptr && !access_token.isEmpty() && !refresh_token.isEmpty()) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + DatabaseQueries::storeNewInoreaderTokens(database, refresh_token, m_service->accountId()); + + qApp->showGuiMessage(tr("Logged in successfully"), + tr("Your login to Inoreader was authorized."), + QSystemTrayIcon::MessageIcon::Information); + } + }); +} + +void GmailNetworkFactory::setUsername(const QString& username) { + m_username = username; +} + +RootItem* GmailNetworkFactory::feedsCategories(bool obtain_icons) { + Downloader downloader; + QEventLoop loop; + QString bearer = m_oauth2->bearer().toLocal8Bit(); + + if (bearer.isEmpty()) { + return nullptr; + } + + downloader.appendRawHeader(QString("Authorization").toLocal8Bit(), bearer.toLocal8Bit()); + + // We need to quit event loop when the download finishes. + connect(&downloader, &Downloader::completed, &loop, &QEventLoop::quit); + + // TODO: dodělat + //downloader.manipulateData(INOREADER_API_LIST_LABELS, QNetworkAccessManager::Operation::GetOperation); + loop.exec(); + + if (downloader.lastOutputError() != QNetworkReply::NetworkError::NoError) { + return nullptr; + } + + QString category_data = downloader.lastOutputData(); + + // TODO: dodělat + //downloader.manipulateData(INOREADER_API_LIST_FEEDS, QNetworkAccessManager::Operation::GetOperation); + loop.exec(); + + if (downloader.lastOutputError() != QNetworkReply::NetworkError::NoError) { + return nullptr; + } + + QString feed_data = downloader.lastOutputData(); + + return decodeFeedCategoriesData(category_data, feed_data, obtain_icons); +} + +QList GmailNetworkFactory::messages(const QString& stream_id, Feed::Status& error) { + Downloader downloader; + QEventLoop loop; + QString target_url;// TODO: dodělat + // = INOREADER_API_FEED_CONTENTS; + QString bearer = m_oauth2->bearer().toLocal8Bit(); + + if (bearer.isEmpty()) { + error = Feed::Status::AuthError; + return QList(); + } + + target_url += QSL("/") + QUrl::toPercentEncoding(stream_id) + QString("?n=%1").arg(batchSize()); + downloader.appendRawHeader(QString("Authorization").toLocal8Bit(), bearer.toLocal8Bit()); + + // We need to quit event loop when the download finishes. + connect(&downloader, &Downloader::completed, &loop, &QEventLoop::quit); + downloader.manipulateData(target_url, QNetworkAccessManager::Operation::GetOperation); + loop.exec(); + + if (downloader.lastOutputError() != QNetworkReply::NetworkError::NoError) { + error = Feed::Status::NetworkError; + return QList(); + } + else { + QString messages_data = downloader.lastOutputData(); + + error = Feed::Status::Normal; + return decodeMessages(messages_data, stream_id); + } +} + +void GmailNetworkFactory::markMessagesRead(RootItem::ReadStatus status, const QStringList& custom_ids, bool async) { + QString target_url;// TODO: dodělat + // = INOREADER_API_EDIT_TAG; + // TODO: dodělat + // + + /* + if (status == RootItem::ReadStatus::Read) { + target_url += QString("?a=user/-/") + INOREADER_STATE_READ + "&"; + } + else { + target_url += QString("?r=user/-/") + INOREADER_STATE_READ + "&"; + }*/ + QString bearer = m_oauth2->bearer().toLocal8Bit(); + + if (bearer.isEmpty()) { + return; + } + + QList> headers; + headers.append(QPair(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), + m_oauth2->bearer().toLocal8Bit())); + + QStringList trimmed_ids; + QRegularExpression regex_short_id(QSL("[0-9a-zA-Z]+$")); + + foreach (const QString& id, custom_ids) { + QString simplified_id = regex_short_id.match(id).captured(); + + trimmed_ids.append(QString("i=") + simplified_id); + } + + QStringList working_subset; + int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); + + working_subset.reserve(trimmed_ids.size() > 200 ? 200 : trimmed_ids.size()); + + // Now, we perform messages update in batches (max 200 messages per batch). + while (!trimmed_ids.isEmpty()) { + // We take 200 IDs. + for (int i = 0; i < 200 && !trimmed_ids.isEmpty(); i++) { + working_subset.append(trimmed_ids.takeFirst()); + } + + QString batch_final_url = target_url + working_subset.join(QL1C('&')); + + // We send this batch. + if (async) { + + NetworkFactory::performAsyncNetworkOperation(batch_final_url, + timeout, + QByteArray(), + QNetworkAccessManager::Operation::GetOperation, + headers); + } + else { + QByteArray output; + + NetworkFactory::performNetworkOperation(batch_final_url, + timeout, + QByteArray(), + output, + QNetworkAccessManager::Operation::GetOperation, + headers); + } + + // Cleanup for next batch. + working_subset.clear(); + } +} + +void GmailNetworkFactory::markMessagesStarred(RootItem::Importance importance, const QStringList& custom_ids, bool async) { + QString target_url; // TODO: dodělat + //= INOREADER_API_EDIT_TAG; + +/* + if (importance == RootItem::Importance::Important) { + target_url += QString("?a=user/-/") + INOREADER_STATE_IMPORTANT + "&"; + } + else { + target_url += QString("?r=user/-/") + INOREADER_STATE_IMPORTANT + "&"; + }*/ + QString bearer = m_oauth2->bearer().toLocal8Bit(); + + if (bearer.isEmpty()) { + return; + } + + QList> headers; + headers.append(QPair(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), + m_oauth2->bearer().toLocal8Bit())); + + QStringList trimmed_ids; + QRegularExpression regex_short_id(QSL("[0-9a-zA-Z]+$")); + + foreach (const QString& id, custom_ids) { + QString simplified_id = regex_short_id.match(id).captured(); + + trimmed_ids.append(QString("i=") + simplified_id); + } + + QStringList working_subset; + int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); + + working_subset.reserve(trimmed_ids.size() > 200 ? 200 : trimmed_ids.size()); + + // Now, we perform messages update in batches (max 200 messages per batch). + while (!trimmed_ids.isEmpty()) { + // We take 200 IDs. + for (int i = 0; i < 200 && !trimmed_ids.isEmpty(); i++) { + working_subset.append(trimmed_ids.takeFirst()); + } + + QString batch_final_url = target_url + working_subset.join(QL1C('&')); + + // We send this batch. + if (async) { + + NetworkFactory::performAsyncNetworkOperation(batch_final_url, + timeout, + QByteArray(), + QNetworkAccessManager::Operation::GetOperation, + headers); + } + else { + QByteArray output; + + NetworkFactory::performNetworkOperation(batch_final_url, + timeout, + QByteArray(), + output, + QNetworkAccessManager::Operation::GetOperation, + headers); + } + + // Cleanup for next batch. + working_subset.clear(); + } +} + +void GmailNetworkFactory::onTokensError(const QString& error, const QString& error_description) { + Q_UNUSED(error) + + qApp->showGuiMessage(tr("Inoreader: authentication error"), + tr("Click this to login again. Error is: '%1'").arg(error_description), + QSystemTrayIcon::Critical, + nullptr, false, + [this]() { + m_oauth2->login(); + }); +} + +void GmailNetworkFactory::onAuthFailed() { + qApp->showGuiMessage(tr("Inoreader: authorization denied"), + tr("Click this to login again."), + QSystemTrayIcon::Critical, + nullptr, false, + [this]() { + m_oauth2->login(); + }); +} + +QList GmailNetworkFactory::decodeMessages(const QString& messages_json_data, const QString& stream_id) { + QList messages; + QJsonArray json = QJsonDocument::fromJson(messages_json_data.toUtf8()).object()["items"].toArray(); + + messages.reserve(json.count()); + + foreach (const QJsonValue& obj, json) { + auto message_obj = obj.toObject(); + Message message; + + message.m_title = message_obj["title"].toString(); + message.m_author = message_obj["author"].toString(); + message.m_created = QDateTime::fromSecsSinceEpoch(message_obj["published"].toInt()); + message.m_createdFromFeed = true; + message.m_customId = message_obj["id"].toString(); + + auto alternates = message_obj["alternate"].toArray(); + auto enclosures = message_obj["enclosure"].toArray(); + auto categories = message_obj["categories"].toArray(); + + foreach (const QJsonValue& alt, alternates) { + auto alt_obj = alt.toObject(); + QString mime = alt_obj["type"].toString(); + QString href = alt_obj["href"].toString(); + + if (mime == QL1S("text/html")) { + message.m_url = href; + } + else { + message.m_enclosures.append(Enclosure(href, mime)); + } + } + + foreach (const QJsonValue& enc, enclosures) { + auto enc_obj = enc.toObject(); + QString mime = enc_obj["type"].toString(); + QString href = enc_obj["href"].toString(); + + message.m_enclosures.append(Enclosure(href, mime)); + } + + foreach (const QJsonValue& cat, categories) { + QString category = cat.toString(); + + // TODO: dodělat + // + + /* + if (category.contains(INOREADER_STATE_READ)) { + message.m_isRead = !category.contains(INOREADER_STATE_READING_LIST); + } + else if (category.contains(INOREADER_STATE_IMPORTANT)) { + message.m_isImportant = category.contains(INOREADER_STATE_IMPORTANT); + }*/ + } + + message.m_contents = message_obj["summary"].toObject()["content"].toString(); + message.m_feedId = stream_id; + + messages.append(message); + } + + return messages; +} + +RootItem* GmailNetworkFactory::decodeFeedCategoriesData(const QString& categories, const QString& feeds, bool obtain_icons) { + RootItem* parent = new RootItem(); + QJsonArray json = QJsonDocument::fromJson(categories.toUtf8()).object()["tags"].toArray(); + + QMap cats; + cats.insert(QString(), parent); + + foreach (const QJsonValue& obj, json) { + auto label = obj.toObject(); + QString label_id = label["id"].toString(); + + if (label_id.contains(QSL("/label/"))) { + // We have label (not "state"). + Category* category = new Category(); + + category->setDescription(label["htmlUrl"].toString()); + category->setTitle(label_id.mid(label_id.lastIndexOf(QL1C('/')) + 1)); + category->setCustomId(label_id); + + cats.insert(category->customId(), category); + + // All categories in ownCloud are top-level. + parent->appendChild(category); + } + } + + json = QJsonDocument::fromJson(feeds.toUtf8()).object()["subscriptions"].toArray(); + + foreach (const QJsonValue& obj, json) { + auto subscription = obj.toObject(); + QString id = subscription["id"].toString(); + QString title = subscription["title"].toString(); + QString url = subscription["htmlUrl"].toString(); + QString parent_label; + QJsonArray categories = subscription["categories"].toArray(); + + foreach (const QJsonValue& cat, categories) { + QString potential_id = cat.toObject()["id"].toString(); + + if (potential_id.contains(QSL("/label/"))) { + parent_label = potential_id; + break; + } + } + + // We have label (not "state"). + GmailFeed* feed = new GmailFeed(); + + feed->setDescription(url); + feed->setUrl(url); + feed->setTitle(title); + feed->setCustomId(id); + + if (obtain_icons) { + QString icon_url = subscription["iconUrl"].toString(); + + if (!icon_url.isEmpty()) { + QByteArray icon_data; + + if (NetworkFactory::performNetworkOperation(icon_url, DOWNLOAD_TIMEOUT, + QByteArray(), icon_data, + QNetworkAccessManager::GetOperation).first == QNetworkReply::NoError) { + // Icon downloaded, set it up. + QPixmap icon_pixmap; + + icon_pixmap.loadFromData(icon_data); + feed->setIcon(QIcon(icon_pixmap)); + } + } + } + + if (cats.contains(parent_label)) { + cats[parent_label]->appendChild(feed); + } + } + + return parent; +} diff --git a/src/services/gmail/network/gmailnetworkfactory.h b/src/services/gmail/network/gmailnetworkfactory.h new file mode 100755 index 000000000..b86e31e34 --- /dev/null +++ b/src/services/gmail/network/gmailnetworkfactory.h @@ -0,0 +1,78 @@ +// This file is part of RSS Guard. + +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef INOREADERNETWORKFACTORY_H +#define INOREADERNETWORKFACTORY_H + +#include + +#include "core/message.h" + +#include "services/abstract/feed.h" +#include "services/abstract/rootitem.h" + +#include + +class RootItem; +class GmailServiceRoot; +class OAuth2Service; + +class GmailNetworkFactory : public QObject { + Q_OBJECT + + public: + explicit GmailNetworkFactory(QObject* parent = nullptr); + + void setService(GmailServiceRoot* service); + + OAuth2Service* oauth() const; + + QString userName() const; + void setUsername(const QString& username); + + // Gets/sets the amount of messages to obtain during single feed update. + int batchSize() const; + void setBatchSize(int batch_size); + + // Returns tree of feeds/categories. + // Top-level root of the tree is not needed here. + // Returned items do not have primary IDs assigned. + RootItem* feedsCategories(bool obtain_icons); + + QList messages(const QString& stream_id, Feed::Status& error); + void markMessagesRead(RootItem::ReadStatus status, const QStringList& custom_ids, bool async = true); + void markMessagesStarred(RootItem::Importance importance, const QStringList& custom_ids, bool async = true); + + private slots: + void onTokensError(const QString& error, const QString& error_description); + void onAuthFailed(); + + private: + QList decodeMessages(const QString& messages_json_data, const QString& stream_id); + RootItem* decodeFeedCategoriesData(const QString& categories, const QString& feeds, bool obtain_icons); + + void initializeOauth(); + + private: + GmailServiceRoot* m_service; + QString m_username; + int m_batchSize; + OAuth2Service* m_oauth2; +}; + +#endif // INOREADERNETWORKFACTORY_H