From 82d8c0340cf3d97cf598814c7ffe79da88114896 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 19 Mar 2024 10:53:44 +0100 Subject: [PATCH] feedly --- CMakeLists.txt | 1 + src/librssguard-standard/plugin.json | 2 +- src/librssguard/CMakeLists.txt | 12 - .../gui/reusable/messagecountspinbox.h | 2 +- .../gui/reusable/networkproxydetails.h | 2 +- src/librssguard/miscellaneous/feedreader.cpp | 3 - src/librssguard/services/feedly/definitions.h | 35 - .../services/feedly/feedlyentrypoint.cpp | 44 - .../services/feedly/feedlyentrypoint.h | 19 - .../services/feedly/feedlynetwork.cpp | 821 ------------------ .../services/feedly/feedlynetwork.h | 110 --- .../services/feedly/feedlyserviceroot.cpp | 249 ------ .../services/feedly/feedlyserviceroot.h | 48 - .../feedly/gui/feedlyaccountdetails.cpp | 183 ---- .../feedly/gui/feedlyaccountdetails.h | 51 -- .../feedly/gui/feedlyaccountdetails.ui | 159 ---- .../feedly/gui/formeditfeedlyaccount.cpp | 73 -- .../feedly/gui/formeditfeedlyaccount.h | 30 - 18 files changed, 4 insertions(+), 1840 deletions(-) delete mode 100644 src/librssguard/services/feedly/definitions.h delete mode 100644 src/librssguard/services/feedly/feedlyentrypoint.cpp delete mode 100644 src/librssguard/services/feedly/feedlyentrypoint.h delete mode 100644 src/librssguard/services/feedly/feedlynetwork.cpp delete mode 100644 src/librssguard/services/feedly/feedlynetwork.h delete mode 100644 src/librssguard/services/feedly/feedlyserviceroot.cpp delete mode 100644 src/librssguard/services/feedly/feedlyserviceroot.h delete mode 100644 src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp delete mode 100644 src/librssguard/services/feedly/gui/feedlyaccountdetails.h delete mode 100644 src/librssguard/services/feedly/gui/feedlyaccountdetails.ui delete mode 100644 src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp delete mode 100644 src/librssguard/services/feedly/gui/formeditfeedlyaccount.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 66a9ea983..be7b35df6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,7 @@ add_subdirectory(localization) # Plugins. add_subdirectory(src/librssguard-standard) +add_subdirectory(src/librssguard-feedly) # GUI executable. add_subdirectory(src/rssguard) diff --git a/src/librssguard-standard/plugin.json b/src/librssguard-standard/plugin.json index 3274befe7..5af39ee16 100644 --- a/src/librssguard-standard/plugin.json +++ b/src/librssguard-standard/plugin.json @@ -1,5 +1,5 @@ { - "name": "Standard RSS/ATOM/JSON plugin", + "name": "Standard RSS/ATOM/JSON", "author": "Martin Rotter", "website": "https://github.com/martinrotter/rssguard" } \ No newline at end of file diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 4a15dd54a..d834e8b49 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -328,17 +328,6 @@ set(SOURCES services/abstract/serviceroot.h services/abstract/unreadnode.cpp services/abstract/unreadnode.h - services/feedly/definitions.h - services/feedly/feedlyentrypoint.cpp - services/feedly/feedlyentrypoint.h - services/feedly/feedlynetwork.cpp - services/feedly/feedlynetwork.h - services/feedly/feedlyserviceroot.cpp - services/feedly/feedlyserviceroot.h - services/feedly/gui/feedlyaccountdetails.cpp - services/feedly/gui/feedlyaccountdetails.h - services/feedly/gui/formeditfeedlyaccount.cpp - services/feedly/gui/formeditfeedlyaccount.h services/gmail/definitions.h services/gmail/gmailentrypoint.cpp services/gmail/gmailentrypoint.h @@ -465,7 +454,6 @@ set(UI_FILES services/abstract/gui/formaddeditprobe.ui services/abstract/gui/formcategorydetails.ui services/abstract/gui/formfeeddetails.ui - services/feedly/gui/feedlyaccountdetails.ui services/gmail/gui/emailpreviewer.ui services/gmail/gui/formaddeditemail.ui services/gmail/gui/gmailaccountdetails.ui diff --git a/src/librssguard/gui/reusable/messagecountspinbox.h b/src/librssguard/gui/reusable/messagecountspinbox.h index 98a438749..034640f00 100644 --- a/src/librssguard/gui/reusable/messagecountspinbox.h +++ b/src/librssguard/gui/reusable/messagecountspinbox.h @@ -5,7 +5,7 @@ #include -class MessageCountSpinBox : public QSpinBox { +class RSSGUARD_DLLSPEC MessageCountSpinBox : public QSpinBox { Q_OBJECT public: diff --git a/src/librssguard/gui/reusable/networkproxydetails.h b/src/librssguard/gui/reusable/networkproxydetails.h index b57e57b39..b0f342444 100644 --- a/src/librssguard/gui/reusable/networkproxydetails.h +++ b/src/librssguard/gui/reusable/networkproxydetails.h @@ -10,7 +10,7 @@ namespace Ui { class NetworkProxyDetails; } -class NetworkProxyDetails : public QWidget { +class RSSGUARD_DLLSPEC NetworkProxyDetails : public QWidget { Q_OBJECT public: diff --git a/src/librssguard/miscellaneous/feedreader.cpp b/src/librssguard/miscellaneous/feedreader.cpp index d481861d8..be235c33f 100644 --- a/src/librssguard/miscellaneous/feedreader.cpp +++ b/src/librssguard/miscellaneous/feedreader.cpp @@ -16,7 +16,6 @@ #include "miscellaneous/settings.h" #include "services/abstract/cacheforserviceroot.h" #include "services/abstract/serviceroot.h" -#include "services/feedly/feedlyentrypoint.h" #include "services/gmail/gmailentrypoint.h" #include "services/greader/greaderentrypoint.h" #include "services/owncloud/owncloudserviceentrypoint.h" @@ -69,8 +68,6 @@ FeedReader::~FeedReader() { QList FeedReader::feedServices() { if (m_feedServices.isEmpty()) { - // NOTE: All installed services create their entry points here. - m_feedServices.append(new FeedlyEntryPoint()); m_feedServices.append(new GmailEntryPoint()); m_feedServices.append(new GreaderEntryPoint()); m_feedServices.append(new OwnCloudServiceEntryPoint()); diff --git a/src/librssguard/services/feedly/definitions.h b/src/librssguard/services/feedly/definitions.h deleted file mode 100644 index ea6c7ae7f..000000000 --- a/src/librssguard/services/feedly/definitions.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef FEEDLY_DEFINITIONS_H -#define FEEDLY_DEFINITIONS_H - -#define FEEDLY_DEFAULT_BATCH_SIZE 100 -#define FEEDLY_MAX_BATCH_SIZE 500 -#define FEEDLY_MAX_TOTAL_SIZE 5000 -#define FEEDLY_UNTAG_BATCH_SIZE 100 - -#define FEEDLY_GENERATE_DAT "https://feedly.com/v3/auth/dev" - -#define FEEDLY_API_REDIRECT_URI_PORT 14466 -#define FEEDLY_API_SCOPE "https://cloud.feedly.com/subscriptions" - -// #define FEEDLY_API_URL_BASE "https://sandbox7.feedly.com/v3/" -#define FEEDLY_API_URL_BASE "https://cloud.feedly.com/v3/" - -#define FEEDLY_API_SYSTEM_TAG_READ "global.read" -#define FEEDLY_API_SYSTEM_TAG_SAVED "global.saved" - -#define FEEDLY_MARKERS_READ "markAsRead" -#define FEEDLY_MARKERS_UNREAD "keepUnread" -#define FEEDLY_MARKERS_IMPORTANT "markAsSaved" -#define FEEDLY_MARKERS_UNIMPORTANT "markAsUnsaved" - -#define FEEDLY_API_URL_AUTH "auth/auth" -#define FEEDLY_API_URL_TOKEN "auth/token" -#define FEEDLY_API_URL_PROFILE "profile" -#define FEEDLY_API_URL_COLLETIONS "collections" -#define FEEDLY_API_URL_TAGS "tags" -#define FEEDLY_API_URL_STREAM_CONTENTS "streams/contents?streamId=%1" -#define FEEDLY_API_URL_STREAM_IDS "streams/%1/ids" -#define FEEDLY_API_URL_MARKERS "markers" -#define FEEDLY_API_URL_ENTRIES "entries/.mget" - -#endif // FEEDLY_DEFINITIONS_H diff --git a/src/librssguard/services/feedly/feedlyentrypoint.cpp b/src/librssguard/services/feedly/feedlyentrypoint.cpp deleted file mode 100644 index cbd479ed7..000000000 --- a/src/librssguard/services/feedly/feedlyentrypoint.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "services/feedly/feedlyentrypoint.h" - -#include "database/databasequeries.h" -#include "definitions/definitions.h" -#include "miscellaneous/application.h" -#include "miscellaneous/iconfactory.h" -#include "services/feedly/feedlyserviceroot.h" -#include "services/feedly/gui/formeditfeedlyaccount.h" - -ServiceRoot* FeedlyEntryPoint::createNewRoot() const { - FormEditFeedlyAccount form_acc(qApp->mainFormWidget()); - - return form_acc.addEditAccount(); -} - -QList FeedlyEntryPoint::initializeSubtree() const { - QSqlDatabase database = qApp->database()->driver()->connection(QSL("FeedlyEntryPoint")); - - return DatabaseQueries::getAccounts(database, code()); -} - -QString FeedlyEntryPoint::name() const { - return QSL("Feedly"); -} - -QString FeedlyEntryPoint::code() const { - return QSL(SERVICE_CODE_FEEDLY); -} - -QString FeedlyEntryPoint::description() const { - return QObject::tr("Keep up with the topics and trends you care about, without the overwhelm.\n\n" - "Feedly is a secure space where you can privately organize and research the " - "topics and trends that matter to you."); -} - -QString FeedlyEntryPoint::author() const { - return QSL(APP_AUTHOR); -} - -QIcon FeedlyEntryPoint::icon() const { - return qApp->icons()->miscIcon(QSL("feedly")); -} diff --git a/src/librssguard/services/feedly/feedlyentrypoint.h b/src/librssguard/services/feedly/feedlyentrypoint.h deleted file mode 100644 index 392b43774..000000000 --- a/src/librssguard/services/feedly/feedlyentrypoint.h +++ /dev/null @@ -1,19 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef FEEDLYENTRYPOINT_H -#define FEEDLYENTRYPOINT_H - -#include "services/abstract/serviceentrypoint.h" - -class FeedlyEntryPoint : public ServiceEntryPoint { - public: - virtual ServiceRoot* createNewRoot() const; - virtual QList initializeSubtree() const; - virtual QString name() const; - virtual QString code() const; - virtual QString description() const; - virtual QString author() const; - virtual QIcon icon() const; -}; - -#endif // FEEDLYENTRYPOINT_H diff --git a/src/librssguard/services/feedly/feedlynetwork.cpp b/src/librssguard/services/feedly/feedlynetwork.cpp deleted file mode 100644 index 0c5d83f4b..000000000 --- a/src/librssguard/services/feedly/feedlynetwork.cpp +++ /dev/null @@ -1,821 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "services/feedly/feedlynetwork.h" - -#include "3rd-party/boolinq/boolinq.h" -#include "database/databasequeries.h" -#include "exceptions/networkexception.h" -#include "miscellaneous/application.h" -#include "miscellaneous/settings.h" -#include "network-web/networkfactory.h" -#include "network-web/webfactory.h" -#include "services/abstract/category.h" -#include "services/abstract/label.h" -#include "services/abstract/labelsnode.h" -#include "services/feedly/definitions.h" -#include "services/feedly/feedlyserviceroot.h" - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -#include "network-web/oauth2service.h" -#endif - -#include -#include -#include - -FeedlyNetwork::FeedlyNetwork(QObject* parent) - : QObject(parent), m_service(nullptr), -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_oauth(new OAuth2Service(QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_AUTH), - QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_TOKEN), - TextFactory::decrypt(QSL(FEEDLY_CLIENT_ID), OAUTH_DECRYPTION_KEY), - TextFactory::decrypt(QSL(FEEDLY_CLIENT_SECRET), OAUTH_DECRYPTION_KEY), - QSL(FEEDLY_API_SCOPE), - this)), -#endif - m_username(QString()), m_developerAccessToken(QString()), m_batchSize(FEEDLY_DEFAULT_BATCH_SIZE), - m_downloadOnlyUnreadMessages(false), m_intelligentSynchronization(true) { - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_oauth->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(FEEDLY_API_REDIRECT_URI_PORT), true); - - connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyNetwork::onTokensError); - connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyNetwork::onAuthFailed); - connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &FeedlyNetwork::onTokensRetrieved); -#endif -} - -QList FeedlyNetwork::messages(const QString& stream_id, - const QHash& stated_messages) { - if (!m_intelligentSynchronization) { - return streamContents(stream_id); - } - - // 1. Get unread IDs for a feed. - // 2. Get read IDs for a feed. - // 3. Download messages/contents for missing or changed IDs. - QStringList remote_all_ids_list, remote_unread_ids_list; - - remote_unread_ids_list = streamIds(stream_id, true, batchSize()); - - if (!downloadOnlyUnreadMessages()) { - remote_all_ids_list = streamIds(stream_id, false, batchSize()); - } - - // 1. - auto local_unread_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Unread); - QSet local_unread_ids = FROM_LIST_TO_SET(QSet, local_unread_ids_list); - QSet remote_unread_ids = FROM_LIST_TO_SET(QSet, remote_unread_ids_list); - - // 2. - auto local_read_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Read); - QSet local_read_ids = FROM_LIST_TO_SET(QSet, local_read_ids_list); - QSet remote_read_ids = FROM_LIST_TO_SET(QSet, remote_all_ids_list) - remote_unread_ids; - - // 3. - QSet to_download; - - // Undownloaded unread articles. - to_download += remote_unread_ids - local_unread_ids; - - // Undownloaded read articles. - if (!m_downloadOnlyUnreadMessages) { - to_download += remote_read_ids - local_read_ids; - } - - // Read articles newly marked as unread in service. - auto moved_read = local_read_ids.intersect(remote_unread_ids); - - to_download += moved_read; - - // Unread articles newly marked as read in service. - if (!m_downloadOnlyUnreadMessages) { - auto moved_unread = local_unread_ids.intersect(remote_read_ids); - - to_download += moved_unread; - } - - qDebugNN << LOGSEC_FEEDLY << "Will download" << QUOTE_W_SPACE(to_download.size()) << "articles."; - - if (to_download.isEmpty()) { - return {}; - } - else { - return entries(QStringList(to_download.values())); - } -} - -void FeedlyNetwork::untagEntries(const QString& tag_id, const QStringList& msg_custom_ids) { - if (msg_custom_ids.isEmpty()) { - return; - } - - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot untag entries, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::TagEntries) + QSL("/%1/").arg(QString(QUrl::toPercentEncoding(tag_id))); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - int i = 0; - - do { - auto msg_batch = msg_custom_ids.mid(i, FEEDLY_UNTAG_BATCH_SIZE); - - i += FEEDLY_UNTAG_BATCH_SIZE; - - auto ids = boolinq::from(msg_batch) - .select([](const QString& msg_id) { - return QString(QUrl::toPercentEncoding(msg_id)); - }) - .toStdList(); - QString final_url = target_url + FROM_STD_LIST(QStringList, ids).join(','); - auto result = NetworkFactory::performNetworkOperation(final_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::DeleteOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - } - while (i < msg_custom_ids.size()); -} - -void FeedlyNetwork::tagEntries(const QString& tag_id, const QStringList& msg_custom_ids) { - if (msg_custom_ids.isEmpty()) { - return; - } - - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot tag entries, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::TagEntries) + QSL("/%1").arg(QString(QUrl::toPercentEncoding(tag_id))); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - QByteArray input_data; - QJsonObject input; - - input[QSL("entryIds")] = QJsonArray::fromStringList(msg_custom_ids); - input_data = QJsonDocument(input).toJson(QJsonDocument::JsonFormat::Compact); - - auto result = - NetworkFactory::performNetworkOperation(target_url, - timeout, - input_data, - output, - QNetworkAccessManager::Operation::PutOperation, - {bearerHeader(bear), {HTTP_HEADERS_CONTENT_TYPE, "application/json"}}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } -} - -void FeedlyNetwork::markers(const QString& action, const QStringList& msg_custom_ids) { - if (msg_custom_ids.isEmpty()) { - return; - } - - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot mark entries, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::Markers); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - - for (int i = 0; i < msg_custom_ids.size(); i += 500) { - QJsonObject input; - - input[QSL("action")] = action; - input[QSL("type")] = QSL("entries"); - input[QSL("entryIds")] = QJsonArray::fromStringList(msg_custom_ids.mid(i, 500)); - - QByteArray input_data = QJsonDocument(input).toJson(QJsonDocument::JsonFormat::Compact); - auto result = - NetworkFactory::performNetworkOperation(target_url, - timeout, - input_data, - output, - QNetworkAccessManager::Operation::PostOperation, - {bearerHeader(bear), {HTTP_HEADERS_CONTENT_TYPE, "application/json"}}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - } -} - -QList FeedlyNetwork::entries(const QStringList& ids) { - const QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QList msgs; - int next_message = 0; - QString continuation; - const QString target_url = fullUrl(Service::Entries); - const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - - do { - QJsonArray json; - - for (int window = next_message + 1000; next_message < window && next_message < ids.size(); next_message++) { - json.append(QJsonValue(ids.at(next_message))); - } - - QByteArray output; - auto result = - NetworkFactory::performNetworkOperation(target_url, - timeout, - QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), - output, - QNetworkAccessManager::Operation::PostOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - msgs += decodeStreamContents(output, false, continuation); - } - while (next_message < ids.size()); - - return msgs; -} - -QList FeedlyNetwork::streamContents(const QString& stream_id) { - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - QString continuation; - QList messages; - - // We download in batches. - do { - QString target_url = fullUrl(Service::StreamContents).arg(QString(QUrl::toPercentEncoding(stream_id))); - - if (m_downloadOnlyUnreadMessages) { - target_url += QSL("&unreadOnly=true"); - } - - if (!continuation.isEmpty()) { - target_url += QSL("&continuation=%1").arg(continuation); - } - - if (m_batchSize > 0) { - target_url += QSL("&count=%1").arg(QString::number(m_batchSize)); - } - else { - // User wants to download all messages. Make sure we use large batches - // to limit network requests. - target_url += QSL("&count=%1").arg(QString::number(FEEDLY_MAX_BATCH_SIZE)); - } - - auto result = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::GetOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - messages += decodeStreamContents(output, true, continuation); - } - while (!continuation.isEmpty() && (m_batchSize <= 0 || messages.size() < m_batchSize) && - messages.size() <= FEEDLY_MAX_TOTAL_SIZE); - - return messages; -} - -QStringList FeedlyNetwork::streamIds(const QString& stream_id, bool unread_only, int batch_size) { - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain stream IDs, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - QString continuation; - QStringList messages; - - // We download in batches. - do { - QString target_url = fullUrl(Service::StreamIds).arg(QString(QUrl::toPercentEncoding(stream_id))); - - if (batch_size > 0) { - target_url += QSL("?count=%1").arg(QString::number(batch_size)); - } - else { - // User wants to download all messages. Make sure we use large batches - // to limit network requests. - target_url += QSL("?count=%1").arg(QString::number(10000)); - } - - if (unread_only) { - target_url += QSL("&unreadOnly=true"); - } - - if (!continuation.isEmpty()) { - target_url += QSL("&continuation=%1").arg(continuation); - } - - auto result = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::GetOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - messages += decodeStreamIds(output, continuation); - } - while (!continuation.isEmpty() && (batch_size <= 0 || messages.size() < batch_size)); - - return messages; -} - -QStringList FeedlyNetwork::decodeStreamIds(const QByteArray& stream_ids, QString& continuation) const { - QStringList messages; - QJsonDocument json = QJsonDocument::fromJson(stream_ids); - - continuation = json.object()[QSL("continuation")].toString(); - - for (const QJsonValue& id_val : json.object()[QSL("ids")].toArray()) { - messages << id_val.toString(); - } - - return messages; -} - -QList FeedlyNetwork::decodeStreamContents(const QByteArray& stream_contents, - bool nested_items, - QString& continuation) const { - QList messages; - QJsonDocument json = QJsonDocument::fromJson(stream_contents); - auto active_labels = m_service->labelsNode() != nullptr ? m_service->labelsNode()->labels() : QList(); - - continuation = json.object()[QSL("continuation")].toString(); - - auto items = nested_items ? json.object()[QSL("items")].toArray() : json.array(); - - for (const QJsonValue& entry : std::as_const(items)) { - const QJsonObject& entry_obj = entry.toObject(); - Message message; - - message.m_feedId = entry_obj[QSL("origin")].toObject()[QSL("streamId")].toString(); - message.m_title = entry_obj[QSL("title")].toString(); - message.m_author = entry_obj[QSL("author")].toString(); - message.m_contents = entry_obj[QSL("content")].toObject()[QSL("content")].toString(); - message.m_rawContents = QJsonDocument(entry_obj).toJson(QJsonDocument::JsonFormat::Compact); - - if (message.m_contents.isEmpty()) { - message.m_contents = entry_obj[QSL("summary")].toObject()[QSL("content")].toString(); - } - - message.m_createdFromFeed = true; - message.m_created = - QDateTime::fromMSecsSinceEpoch(entry_obj[QSL("published")].toVariant().toLongLong(), Qt::TimeSpec::UTC); - message.m_customId = entry_obj[QSL("id")].toString(); - message.m_isRead = !entry_obj[QSL("unread")].toBool(); - message.m_url = entry_obj[QSL("canonicalUrl")].toString(); - - if (message.m_url.isEmpty()) { - auto canonical_arr = entry_obj[QSL("canonical")].toArray(); - - if (!canonical_arr.isEmpty()) { - message.m_url = canonical_arr.first().toObject()[QSL("href")].toString(); - } - else { - auto alternate_arr = entry_obj[QSL("alternate")].toArray(); - - if (!alternate_arr.isEmpty()) { - message.m_url = alternate_arr.first().toObject()[QSL("href")].toString(); - } - } - } - - auto enclosures = entry_obj[QSL("enclosure")].toArray(); - - for (const QJsonValue& enc : std::as_const(enclosures)) { - const QJsonObject& enc_obj = enc.toObject(); - const QString& enc_href = enc_obj[QSL("href")].toString(); - - if (!boolinq::from(message.m_enclosures).any([enc_href](const Enclosure& existing_enclosure) { - return existing_enclosure.m_url == enc_href; - })) { - message.m_enclosures.append(Enclosure(enc_href, enc_obj[QSL("type")].toString())); - } - } - - auto tags = entry_obj[QSL("tags")].toArray(); - - for (const QJsonValue& tag : std::as_const(tags)) { - const QJsonObject& tag_obj = tag.toObject(); - const QString& tag_id = tag_obj[QSL("id")].toString(); - - if (tag_id.endsWith(FEEDLY_API_SYSTEM_TAG_SAVED)) { - message.m_isImportant = true; - } - else if (tag_id.endsWith(FEEDLY_API_SYSTEM_TAG_READ)) { - // NOTE: We don't do anything with "global read" tag. - } - else { - Label* label = boolinq::from(active_labels.begin(), active_labels.end()).firstOrDefault([tag_id](Label* lbl) { - return lbl->customId() == tag_id; - }); - - if (label != nullptr) { - message.m_assignedLabels.append(label); - } - else { - qCriticalNN << LOGSEC_FEEDLY << "Failed to find live Label object for tag" << QUOTE_W_SPACE_DOT(tag_id); - } - } - } - - messages.append(message); - } - - return messages; -} - -RootItem* FeedlyNetwork::collections(bool obtain_icons) { - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::Collections); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - auto result = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::GetOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - return decodeCollections(output, obtain_icons, m_service->networkProxy(), timeout); -} - -RootItem* FeedlyNetwork::decodeCollections(const QByteArray& json, - bool obtain_icons, - const QNetworkProxy& proxy, - int timeout) const { - QJsonDocument doc = QJsonDocument::fromJson(json); - auto* parent = new RootItem(); - QList used_feeds; - auto coll = doc.array(); - - for (const QJsonValue& cat : std::as_const(coll)) { - QJsonObject cat_obj = cat.toObject(); - auto* category = new Category(parent); - - category->setTitle(cat_obj[QSL("label")].toString()); - category->setCustomId(cat_obj[QSL("id")].toString()); - - auto feeds = cat[QSL("feeds")].toArray(); - - for (const QJsonValue& fee : std::as_const(feeds)) { - QJsonObject fee_obj = fee.toObject(); - - if (used_feeds.contains(fee_obj[QSL("id")].toString())) { - qWarningNN << LOGSEC_FEEDLY << "Feed" << QUOTE_W_SPACE(fee_obj[QSL("id")].toString()) - << "is already decoded and cannot be placed under several categories."; - continue; - } - - auto* feed = new Feed(category); - - feed->setSource(fee_obj[QSL("website")].toString()); - feed->setTitle(fee_obj[QSL("title")].toString()); - feed->setDescription(qApp->web()->stripTags(fee_obj[QSL("description")].toString())); - feed->setCustomId(fee_obj[QSL("id")].toString()); - - if (feed->title().isEmpty()) { - feed->setTitle(feed->description()); - } - - if (feed->title().isEmpty()) { - feed->setTitle(feed->source()); - } - - if (feed->title().isEmpty()) { - feed->setTitle(feed->customId()); - qWarningNN << LOGSEC_FEEDLY - << "Some feed does not have nor title, neither description. Using its ID for its title."; - } - - if (obtain_icons) { - QPixmap icon; - auto result = NetworkFactory::downloadIcon({{fee_obj[QSL("iconUrl")].toString(), true}, - {fee_obj[QSL("website")].toString(), false}, - {fee_obj[QSL("logo")].toString(), true}}, - timeout, - icon, - {}, - proxy); - - if (result == QNetworkReply::NetworkError::NoError && !icon.isNull()) { - feed->setIcon(icon); - } - } - - used_feeds.append(feed->customId()); - category->appendChild(feed); - } - - if (category->childCount() == 0) { - delete category; - } - else { - parent->appendChild(category); - } - } - - return parent; -} - -QVariantHash FeedlyNetwork::profile(const QNetworkProxy& network_proxy) { - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain profile information, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::Profile); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - - // This method uses proxy via parameter, - // not via "m_service" field. - auto result = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::GetOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - network_proxy); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - return QJsonDocument::fromJson(output).object().toVariantHash(); -} - -QList FeedlyNetwork::tags() { - QString bear = bearer(); - - if (bear.isEmpty()) { - qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain tags, because bearer is empty."; - throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError); - } - - QString target_url = fullUrl(Service::Tags); - int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - QByteArray output; - auto result = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - output, - QNetworkAccessManager::Operation::GetOperation, - {bearerHeader(bear)}, - false, - {}, - {}, - m_service->networkProxy()); - - if (result.m_networkError != QNetworkReply::NetworkError::NoError) { - throw NetworkException(result.m_networkError, output); - } - - QJsonDocument json = QJsonDocument::fromJson(output); - QList lbls; - auto tags = json.array(); - - for (const QJsonValue& tag : std::as_const(tags)) { - const QJsonObject& tag_obj = tag.toObject(); - QString name_id = tag_obj[QSL("id")].toString(); - - if (name_id.endsWith(FEEDLY_API_SYSTEM_TAG_READ) || name_id.endsWith(FEEDLY_API_SYSTEM_TAG_SAVED)) { - continue; - } - - QString plain_name = tag_obj[QSL("label")].toString(); - auto* new_lbl = new Label(plain_name, TextFactory::generateColorFromText(name_id)); - - new_lbl->setCustomId(name_id); - lbls.append(new_lbl); - } - - return lbls; -} - -QString FeedlyNetwork::username() const { - return m_username; -} - -void FeedlyNetwork::setUsername(const QString& username) { - m_username = username; -} - -QString FeedlyNetwork::developerAccessToken() const { - return m_developerAccessToken; -} - -void FeedlyNetwork::setDeveloperAccessToken(const QString& dev_acc_token) { - m_developerAccessToken = dev_acc_token; -} - -int FeedlyNetwork::batchSize() const { - return m_batchSize; -} - -void FeedlyNetwork::setBatchSize(int batch_size) { - m_batchSize = batch_size; -} - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - -void FeedlyNetwork::onTokensError(const QString& error, const QString& error_description) { - Q_UNUSED(error) - - qApp->showGuiMessage(Notification::Event::LoginFailure, - {tr("Feedly: authentication error"), - tr("Click this to login again. Error is: '%1'").arg(error_description), - QSystemTrayIcon::MessageIcon::Critical}, - {}, - {tr("Login"), [this]() { - m_oauth->setAccessToken(QString()); - m_oauth->setRefreshToken(QString()); - - // m_oauth->logout(false); - m_oauth->login(); - }}); -} - -void FeedlyNetwork::onAuthFailed() { - qApp->showGuiMessage(Notification::Event::LoginFailure, - {tr("Feedly: authorization denied"), - tr("Click this to login again."), - QSystemTrayIcon::MessageIcon::Critical}, - {}, - {tr("Login"), [this]() { - // m_oauth->logout(false); - m_oauth->login(); - }}); -} - -void FeedlyNetwork::onTokensRetrieved(const QString& access_token, const QString& refresh_token, int expires_in) { - Q_UNUSED(expires_in) - Q_UNUSED(access_token) - - if (m_service != nullptr && !refresh_token.isEmpty()) { - QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); - - DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId()); - } -} - -OAuth2Service* FeedlyNetwork::oauth() const { - return m_oauth; -} - -void FeedlyNetwork::setOauth(OAuth2Service* oauth) { - m_oauth = oauth; -} - -#endif - -QString FeedlyNetwork::fullUrl(FeedlyNetwork::Service service) const { - switch (service) { - case Service::Profile: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_PROFILE); - - case Service::Collections: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_COLLETIONS); - - case Service::Tags: - case Service::TagEntries: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_TAGS); - - case Service::StreamContents: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_STREAM_CONTENTS); - - case Service::StreamIds: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_STREAM_IDS); - - case Service::Entries: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_ENTRIES); - - case Service::Markers: - return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_MARKERS); - - default: - return QSL(FEEDLY_API_URL_BASE); - } -} - -QString FeedlyNetwork::bearer() const { -#if defined(FEEDLY_OFFICIAL_SUPPORT) - if (m_developerAccessToken.simplified().isEmpty()) { - return m_oauth->bearer().toLocal8Bit(); - } -#endif - - return QSL("Bearer %1").arg(m_developerAccessToken); -} - -QPair FeedlyNetwork::bearerHeader(const QString& bearer) const { - return {QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit()}; -} - -void FeedlyNetwork::setIntelligentSynchronization(bool intelligent_sync) { - m_intelligentSynchronization = intelligent_sync; -} - -bool FeedlyNetwork::intelligentSynchronization() const { - return m_intelligentSynchronization; -} - -bool FeedlyNetwork::downloadOnlyUnreadMessages() const { - return m_downloadOnlyUnreadMessages; -} - -void FeedlyNetwork::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) { - m_downloadOnlyUnreadMessages = download_only_unread_messages; -} - -void FeedlyNetwork::setService(FeedlyServiceRoot* service) { - m_service = service; -} diff --git a/src/librssguard/services/feedly/feedlynetwork.h b/src/librssguard/services/feedly/feedlynetwork.h deleted file mode 100644 index edc1fb614..000000000 --- a/src/librssguard/services/feedly/feedlynetwork.h +++ /dev/null @@ -1,110 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef FEEDLYNETWORK_H -#define FEEDLYNETWORK_H - -#include "network-web/networkfactory.h" -#include "services/abstract/feed.h" -#include "services/abstract/serviceroot.h" - -#include - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -class OAuth2Service; -#endif - -class FeedlyServiceRoot; - -class FeedlyNetwork : public QObject { - Q_OBJECT - - public: - explicit FeedlyNetwork(QObject* parent = nullptr); - - QList messages(const QString& stream_id, - const QHash& stated_messages); - - // API operations. - void untagEntries(const QString& tag_id, const QStringList& msg_custom_ids); - void tagEntries(const QString& tag_id, const QStringList& msg_custom_ids); - void markers(const QString& action, const QStringList& msg_custom_ids); - QList entries(const QStringList& ids); - QList streamContents(const QString& stream_id); - QStringList streamIds(const QString& stream_id, bool unread_only, int batch_size); - QVariantHash profile(const QNetworkProxy& network_proxy); - QList tags(); - RootItem* collections(bool obtain_icons); - - // Getters and setters. - QString username() const; - void setUsername(const QString& username); - - QString developerAccessToken() const; - void setDeveloperAccessToken(const QString& dev_acc_token); - - bool downloadOnlyUnreadMessages() const; - void setDownloadOnlyUnreadMessages(bool download_only_unread_messages); - - bool intelligentSynchronization() const; - void setIntelligentSynchronization(bool intelligent_sync); - - int batchSize() const; - void setBatchSize(int batch_size); - - void setService(FeedlyServiceRoot* service); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - OAuth2Service* oauth() const; - void setOauth(OAuth2Service* oauth); - - private slots: - void onTokensError(const QString& error, const QString& error_description); - void onAuthFailed(); - void onTokensRetrieved(const QString& access_token, const QString& refresh_token, int expires_in); -#endif - - private: - enum class Service { - Profile, - Collections, - Tags, - StreamContents, - Markers, - TagEntries, - StreamIds, - Entries - }; - - QString fullUrl(Service service) const; - QString bearer() const; - QStringList decodeStreamIds(const QByteArray& stream_ids, QString& continuation) const; - QList decodeStreamContents(const QByteArray& stream_contents, - bool nested_items, - QString& continuation) const; - RootItem* decodeCollections(const QByteArray& json, - bool obtain_icons, - const QNetworkProxy& proxy, - int timeout = 0) const; - QPair bearerHeader(const QString& bearer) const; - - private: - FeedlyServiceRoot* m_service; - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - OAuth2Service* m_oauth; -#endif - - QString m_username; - QString m_developerAccessToken; - - // Only download N newest messages per feed. - int m_batchSize; - - // Only download unread messages. - bool m_downloadOnlyUnreadMessages; - - // Better synchronization algorithm. - bool m_intelligentSynchronization; -}; - -#endif // FEEDLYNETWORK_H diff --git a/src/librssguard/services/feedly/feedlyserviceroot.cpp b/src/librssguard/services/feedly/feedlyserviceroot.cpp deleted file mode 100644 index 3b725cbb2..000000000 --- a/src/librssguard/services/feedly/feedlyserviceroot.cpp +++ /dev/null @@ -1,249 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "services/feedly/feedlyserviceroot.h" - -#include "database/databasequeries.h" -#include "definitions/definitions.h" -#include "exceptions/applicationexception.h" -#include "exceptions/feedfetchexception.h" -#include "exceptions/networkexception.h" -#include "miscellaneous/application.h" -#include "miscellaneous/textfactory.h" -#include "services/abstract/labelsnode.h" -#include "services/feedly/definitions.h" -#include "services/feedly/feedlyentrypoint.h" -#include "services/feedly/feedlynetwork.h" -#include "services/feedly/gui/formeditfeedlyaccount.h" - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -#include "network-web/oauth2service.h" -#endif - -FeedlyServiceRoot::FeedlyServiceRoot(RootItem* parent) : ServiceRoot(parent), m_network(new FeedlyNetwork(this)) { - setIcon(FeedlyEntryPoint().icon()); - m_network->setService(this); -} - -bool FeedlyServiceRoot::isSyncable() const { - return true; -} - -bool FeedlyServiceRoot::canBeEdited() const { - return true; -} - -FormAccountDetails* FeedlyServiceRoot::accountSetupDialog() const { - return new FormEditFeedlyAccount(qApp->mainFormWidget()); -} - -void FeedlyServiceRoot::editItems(const QList& items) { - if (items.first()->kind() == RootItem::Kind::ServiceRoot) { - QScopedPointer p(qobject_cast(accountSetupDialog())); - - p->addEditAccount(this); - return; - } - - ServiceRoot::editItems(items); -} - -QVariantHash FeedlyServiceRoot::customDatabaseData() const { - QVariantHash data = ServiceRoot::customDatabaseData(); - - data[QSL("username")] = m_network->username(); - data[QSL("dat")] = m_network->developerAccessToken(); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - data[QSL("refresh_token")] = m_network->oauth()->refreshToken(); -#endif - - data[QSL("batch_size")] = m_network->batchSize(); - data[QSL("download_only_unread")] = m_network->downloadOnlyUnreadMessages(); - data[QSL("intelligent_synchronization")] = m_network->intelligentSynchronization(); - - return data; -} - -void FeedlyServiceRoot::setCustomDatabaseData(const QVariantHash& data) { - ServiceRoot::setCustomDatabaseData(data); - - m_network->setUsername(data[QSL("username")].toString()); - m_network->setDeveloperAccessToken(data[QSL("dat")].toString()); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_network->oauth()->setRefreshToken(data[QSL("refresh_token")].toString()); -#endif - - m_network->setBatchSize(data[QSL("batch_size")].toInt()); - m_network->setDownloadOnlyUnreadMessages(data[QSL("download_only_unread")].toBool()); - m_network->setIntelligentSynchronization(data[QSL("intelligent_synchronization")].toBool()); -} - -QList FeedlyServiceRoot::obtainNewMessages(Feed* feed, - const QHash& - stated_messages, - const QHash& tagged_messages) { - Q_UNUSED(tagged_messages) - - try { - return m_network->messages(feed->customId(), stated_messages); - } - catch (const ApplicationException& ex) { - throw FeedFetchException(Feed::Status::NetworkError, ex.message()); - } -} - -void FeedlyServiceRoot::start(bool freshly_activated) { - if (!freshly_activated) { - DatabaseQueries::loadRootFromDatabase(this); - loadCacheFromFile(); - } - - updateTitle(); - - if (getSubTreeFeeds().isEmpty()) { -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_network->oauth()->login([this]() { - syncIn(); - }); -#else - syncIn(); -#endif - } - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - else { - m_network->oauth()->login(); - } -#endif -} - -QString FeedlyServiceRoot::code() const { - return FeedlyEntryPoint().code(); -} - -void FeedlyServiceRoot::saveAllCachedData(bool ignore_errors) { - auto msg_cache = takeMessageCache(); - QMapIterator i(msg_cache.m_cachedStatesRead); - - // Save the actual data read/unread. - while (i.hasNext()) { - i.next(); - auto key = i.key(); - QStringList ids = i.value(); - - if (!ids.isEmpty()) { - try { - network()->markers(key == RootItem::ReadStatus::Read ? QSL(FEEDLY_MARKERS_READ) : QSL(FEEDLY_MARKERS_UNREAD), - ids); - } - catch (const NetworkException& net_ex) { - qCriticalNN << LOGSEC_FEEDLY - << "Failed to synchronize read/unread state with error:" << QUOTE_W_SPACE(net_ex.message()) - << "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError()); - - if (!ignore_errors) { - addMessageStatesToCache(ids, key); - } - } - } - } - - QMapIterator> j(msg_cache.m_cachedStatesImportant); - - // Save the actual data important/not important. - while (j.hasNext()) { - j.next(); - auto key = j.key(); - QList messages = j.value(); - - if (!messages.isEmpty()) { - QStringList ids = customIDsOfMessages(messages); - - try { - network()->markers(key == RootItem::Importance::Important ? FEEDLY_MARKERS_IMPORTANT - : FEEDLY_MARKERS_UNIMPORTANT, - ids); - } - catch (const NetworkException& net_ex) { - qCriticalNN << LOGSEC_FEEDLY << "Failed to synchronize important/unimportant state with error:" - << QUOTE_W_SPACE(net_ex.message()) << "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError()); - - if (!ignore_errors) { - addMessageStatesToCache(messages, key); - } - } - } - } - - QMapIterator k(msg_cache.m_cachedLabelAssignments); - - // Assign label for these messages. - while (k.hasNext()) { - k.next(); - auto label_custom_id = k.key(); - QStringList messages = k.value(); - - if (!messages.isEmpty()) { - try { - network()->tagEntries(label_custom_id, messages); - } - catch (const NetworkException& net_ex) { - qCriticalNN << LOGSEC_FEEDLY - << "Failed to synchronize tag assignments with error:" << QUOTE_W_SPACE(net_ex.message()) - << "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError()); - - if (!ignore_errors) { - addLabelsAssignmentsToCache(messages, label_custom_id, true); - } - } - } - } - - QMapIterator l(msg_cache.m_cachedLabelDeassignments); - - // Remove label from these messages. - while (l.hasNext()) { - l.next(); - auto label_custom_id = l.key(); - QStringList messages = l.value(); - - if (!messages.isEmpty()) { - try { - network()->untagEntries(label_custom_id, messages); - } - catch (const NetworkException& net_ex) { - qCriticalNN << LOGSEC_FEEDLY - << "Failed to synchronize tag DEassignments with error:" << QUOTE_W_SPACE(net_ex.message()) - << "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError()); - - if (!ignore_errors) { - addLabelsAssignmentsToCache(messages, label_custom_id, false); - } - } - } - } -} - -ServiceRoot::LabelOperation FeedlyServiceRoot::supportedLabelOperations() const { - return ServiceRoot::LabelOperation::Synchronised; -} - -void FeedlyServiceRoot::updateTitle() { - setTitle(QSL("%1 (Feedly)").arg(TextFactory::extractUsernameFromEmail(m_network->username()))); -} - -RootItem* FeedlyServiceRoot::obtainNewTreeForSyncIn() const { - auto tree = m_network->collections(true); - auto* lblroot = new LabelsNode(tree); - auto labels = m_network->tags(); - - lblroot->setChildItems(labels); - tree->appendChild(lblroot); - - return tree; -} - -bool FeedlyServiceRoot::wantsBaggedIdsOfExistingMessages() const { - return m_network->intelligentSynchronization(); -} diff --git a/src/librssguard/services/feedly/feedlyserviceroot.h b/src/librssguard/services/feedly/feedlyserviceroot.h deleted file mode 100644 index 833a7feca..000000000 --- a/src/librssguard/services/feedly/feedlyserviceroot.h +++ /dev/null @@ -1,48 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef FEEDLYSERVICEROOT_H -#define FEEDLYSERVICEROOT_H - -#include "services/abstract/cacheforserviceroot.h" -#include "services/abstract/serviceroot.h" - -class FeedlyNetwork; - -class FeedlyServiceRoot : public ServiceRoot, public CacheForServiceRoot { - Q_OBJECT - - public: - explicit FeedlyServiceRoot(RootItem* parent = nullptr); - - virtual bool isSyncable() const; - virtual bool canBeEdited() const; - virtual void editItems(const QList& items); - virtual FormAccountDetails* accountSetupDialog() const; - virtual void start(bool freshly_activated); - virtual QString code() const; - virtual void saveAllCachedData(bool ignore_errors); - virtual LabelOperation supportedLabelOperations() const; - virtual QVariantHash customDatabaseData() const; - virtual void setCustomDatabaseData(const QVariantHash& data); - virtual bool wantsBaggedIdsOfExistingMessages() const; - virtual QList obtainNewMessages(Feed* feed, - const QHash& stated_messages, - const QHash& tagged_messages); - - FeedlyNetwork* network() const; - - protected: - virtual RootItem* obtainNewTreeForSyncIn() const; - - private: - void updateTitle(); - - private: - FeedlyNetwork* m_network; -}; - -inline FeedlyNetwork* FeedlyServiceRoot::network() const { - return m_network; -} - -#endif // FEEDLYSERVICEROOT_H diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp b/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp deleted file mode 100644 index ca9e7fcf4..000000000 --- a/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp +++ /dev/null @@ -1,183 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "services/feedly/gui/feedlyaccountdetails.h" - -#include "definitions/definitions.h" -#include "exceptions/networkexception.h" -#include "miscellaneous/application.h" -#include "network-web/webfactory.h" -#include "services/feedly/definitions.h" -#include "services/feedly/feedlynetwork.h" - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -#include "network-web/oauth2service.h" -#endif - -#include - -FeedlyAccountDetails::FeedlyAccountDetails(QWidget* parent) : QWidget(parent), m_lastProxy({}) { -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_oauth = nullptr; -#endif - - m_ui.setupUi(this); - - m_ui.m_lblTestResult->label()->setWordWrap(true); - m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your account")); - m_ui.m_txtDeveloperAccessToken->lineEdit()->setPlaceholderText(tr("Developer access token")); - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information, - tr("No test done yet."), - tr("Here, results of connection test are shown.")); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_ui.m_lblInfo->setHelpText(tr("Your %1 build has official Feedly support. You do not have to use \"developer access " - "token\". You can therefore leave corresponding field empty.") - .arg(QSL(APP_NAME)), - false); -#else - m_ui.m_lblInfo->setHelpText(tr("Your %1 does not offer official Feedly support, thus you must " - "authorize via special authorization code called \"developer access token\". " - "These tokens are usually valid only for 1 month and allow only 250 API calls " - "each day.") - .arg(QSL(APP_NAME)), - true); -#endif - - m_ui.m_lblLimitMessagesInfo->setHelpText(tr("Beware of downloading too many articles, because " - "Feedly permanently caches ALL articles of the feed, so you might " - "end up with thousands of articles which you will never read anyway."), - true); - - m_ui.m_lblNewAlgorithm->setHelpText(tr("If you select intelligent synchronization, then only not-yet-fetched " - "or updated articles are downloaded. Network usage is greatly reduced and " - "overall synchronization speed is greatly improved, but " - "first feed fetching could be slow anyway if your feed contains " - "huge number of articles."), - false); - - connect(m_ui.m_btnGetToken, &QPushButton::clicked, this, &FeedlyAccountDetails::getDeveloperAccessToken); - connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &FeedlyAccountDetails::onUsernameChanged); - connect(m_ui.m_txtDeveloperAccessToken->lineEdit(), - &BaseLineEdit::textChanged, - this, - &FeedlyAccountDetails::onDeveloperAccessTokenChanged); - - setTabOrder(m_ui.m_txtUsername->lineEdit(), m_ui.m_btnGetToken); - setTabOrder(m_ui.m_btnGetToken, m_ui.m_txtDeveloperAccessToken->lineEdit()); - setTabOrder(m_ui.m_txtDeveloperAccessToken->lineEdit(), m_ui.m_checkDownloadOnlyUnreadMessages); - setTabOrder(m_ui.m_checkDownloadOnlyUnreadMessages, m_ui.m_cbNewAlgorithm); - setTabOrder(m_ui.m_cbNewAlgorithm, m_ui.m_spinLimitMessages); - setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup); - - onDeveloperAccessTokenChanged(); - onUsernameChanged(); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - hookNetwork(); -#endif -} - -void FeedlyAccountDetails::getDeveloperAccessToken() { - qApp->web()->openUrlInExternalBrowser(QSL(FEEDLY_GENERATE_DAT)); -} - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - -void FeedlyAccountDetails::hookNetwork() { - connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &FeedlyAccountDetails::onAuthGranted); - connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyAccountDetails::onAuthError); - connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyAccountDetails::onAuthFailed); -} - -void FeedlyAccountDetails::onAuthFailed() { - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error, - tr("You did not grant access."), - tr("There was error during testing.")); -} - -void FeedlyAccountDetails::onAuthError(const QString& error, const QString& detailed_description) { - Q_UNUSED(error) - - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error, - tr("There is error. %1").arg(detailed_description), - tr("There was error during testing.")); -} - -void FeedlyAccountDetails::onAuthGranted() { - FeedlyNetwork factory; - - factory.setOauth(m_oauth); - - try { - auto prof = factory.profile(m_lastProxy); - - m_ui.m_txtUsername->lineEdit()->setText(prof[QSL("email")].toString()); - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, - tr("Tested successfully. You may be prompted to login once more."), - tr("Your access was approved.")); - } - catch (const ApplicationException& ex) { - qCriticalNN << LOGSEC_FEEDLY << "Failed to obtain profile with error:" << QUOTE_W_SPACE_DOT(ex.message()); - } -} - -#endif - -void FeedlyAccountDetails::performTest(const QNetworkProxy& custom_proxy) { - m_lastProxy = custom_proxy; - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_oauth->logout(false); - - if (m_ui.m_txtDeveloperAccessToken->lineEdit()->text().simplified().isEmpty()) { - m_oauth->login(); - return; - } -#endif - - FeedlyNetwork factory; - - factory.setDeveloperAccessToken(m_ui.m_txtDeveloperAccessToken->lineEdit()->text()); - - try { - auto prof = factory.profile(custom_proxy); - - m_ui.m_txtUsername->lineEdit()->setText(prof[QSL("email")].toString()); - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, - tr("Login was successful."), - tr("Access granted.")); - } - catch (const NetworkException& ex) { - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error, - tr("Error: '%1'").arg(ex.message()), - tr("Some problems.")); - } -} - -void FeedlyAccountDetails::onUsernameChanged() { - const QString username = m_ui.m_txtUsername->lineEdit()->text(); - - if (username.isEmpty()) { - m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("Username cannot be empty.")); - } - else { - m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Username is okay.")); - } -} - -void FeedlyAccountDetails::onDeveloperAccessTokenChanged() { - const QString token = m_ui.m_txtDeveloperAccessToken->lineEdit()->text(); - - if (token.isEmpty()) { -#if defined(FEEDLY_OFFICIAL_SUPPORT) - WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Ok; -#else - WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Error; -#endif - - m_ui.m_txtDeveloperAccessToken->setStatus(stat, tr("Access token is empty.")); - } - else { - m_ui.m_txtDeveloperAccessToken->setStatus(WidgetWithStatus::StatusType::Ok, tr("Access token is okay.")); - } -} diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.h b/src/librssguard/services/feedly/gui/feedlyaccountdetails.h deleted file mode 100644 index 7ffafe610..000000000 --- a/src/librssguard/services/feedly/gui/feedlyaccountdetails.h +++ /dev/null @@ -1,51 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef FEEDLYACCOUNTDETAILS_H -#define FEEDLYACCOUNTDETAILS_H - -#include "services/feedly/feedlyserviceroot.h" - -#include "ui_feedlyaccountdetails.h" - -#include -#include - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -class OAuth2Service; -#endif - -class FeedlyAccountDetails : public QWidget { - Q_OBJECT - - friend class FormEditFeedlyAccount; - - public: - explicit FeedlyAccountDetails(QWidget* parent = nullptr); - - private slots: - void getDeveloperAccessToken(); - void performTest(const QNetworkProxy& custom_proxy); - void onUsernameChanged(); - void onDeveloperAccessTokenChanged(); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - private slots: - void onAuthFailed(); - void onAuthError(const QString& error, const QString& detailed_description); - void onAuthGranted(); - - private: - void hookNetwork(); -#endif - - private: - Ui::FeedlyAccountDetails m_ui; - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - OAuth2Service* m_oauth; -#endif - - QNetworkProxy m_lastProxy; -}; - -#endif // FEEDLYACCOUNTDETAILS_H diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui b/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui deleted file mode 100644 index 45bdac98a..000000000 --- a/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui +++ /dev/null @@ -1,159 +0,0 @@ - - - FeedlyAccountDetails - - - - 0 - 0 - 421 - 321 - - - - - - - Username - - - - - - - - - - Developer access token - - - - - - - - - Get token - - - - - - - - - - - - - - - Download unread articles only - - - - - - - Intelligent synchronization algorithm - - - - - - - - - - - - Only download newest X articles per feed - - - m_spinLimitMessages - - - - - - - - 140 - 16777215 - - - - - - - - - - - - - Qt::Vertical - - - - 400 - 86 - - - - - - - - - - &Login - - - - - - - Qt::RightToLeft - - - - - - - - - - LabelWithStatus - QWidget -
labelwithstatus.h
- 1 -
- - LineEditWithStatus - QWidget -
lineeditwithstatus.h
- 1 -
- - MessageCountSpinBox - QSpinBox -
messagecountspinbox.h
-
- - HelpSpoiler - QWidget -
helpspoiler.h
- 1 -
-
- - m_btnGetToken - m_checkDownloadOnlyUnreadMessages - m_cbNewAlgorithm - m_spinLimitMessages - m_btnTestSetup - - - -
diff --git a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp deleted file mode 100644 index 194a33fb1..000000000 --- a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "services/feedly/gui/formeditfeedlyaccount.h" - -#include "miscellaneous/iconfactory.h" -#include "services/feedly/feedlynetwork.h" -#include "services/feedly/feedlyserviceroot.h" -#include "services/feedly/gui/feedlyaccountdetails.h" - -#if defined(FEEDLY_OFFICIAL_SUPPORT) -#include "network-web/oauth2service.h" -#endif - -FormEditFeedlyAccount::FormEditFeedlyAccount(QWidget* parent) - : FormAccountDetails(qApp->icons()->miscIcon(QSL("feedly")), parent), m_details(new FeedlyAccountDetails(this)) { - insertCustomTab(m_details, tr("Service setup"), 0); - activateTab(0); - - connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditFeedlyAccount::performTest); - m_details->m_ui.m_txtUsername->setFocus(); -} - -void FormEditFeedlyAccount::apply() { - FormAccountDetails::apply(); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - account()->network()->oauth()->logout(false); -#endif - - bool using_another_acc = - m_details->m_ui.m_txtUsername->lineEdit()->text() != account()->network()->username(); - - account()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text()); - account() - ->network() - ->setDownloadOnlyUnreadMessages(m_details->m_ui.m_checkDownloadOnlyUnreadMessages->isChecked()); - account()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value()); - account()->network()->setDeveloperAccessToken(m_details->m_ui.m_txtDeveloperAccessToken->lineEdit() - ->text()); - account()->network()->setIntelligentSynchronization(m_details->m_ui.m_cbNewAlgorithm->isChecked()); - - account()->saveAccountDataToDatabase(); - accept(); - - if (!m_creatingNew) { - if (using_another_acc) { - account()->completelyRemoveAllData(); - } - - account()->start(true); - } -} - -void FormEditFeedlyAccount::loadAccountData() { - FormAccountDetails::loadAccountData(); - -#if defined(FEEDLY_OFFICIAL_SUPPORT) - m_details->m_oauth = account()->network()->oauth(); - m_details->hookNetwork(); -#endif - - m_details->m_ui.m_txtUsername->lineEdit()->setText(account()->network()->username()); - m_details->m_ui.m_txtDeveloperAccessToken->lineEdit() - ->setText(account()->network()->developerAccessToken()); - m_details->m_ui.m_checkDownloadOnlyUnreadMessages - ->setChecked(account()->network()->downloadOnlyUnreadMessages()); - m_details->m_ui.m_spinLimitMessages->setValue(account()->network()->batchSize()); - m_details->m_ui.m_cbNewAlgorithm->setChecked(account()->network()->intelligentSynchronization()); -} - -void FormEditFeedlyAccount::performTest() { - m_details->performTest(m_proxyDetails->proxy()); -} diff --git a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h deleted file mode 100644 index f9a0e892f..000000000 --- a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h +++ /dev/null @@ -1,30 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef FORMEDITFEEDLYACCOUNT_H -#define FORMEDITFEEDLYACCOUNT_H - -#include "services/abstract/gui/formaccountdetails.h" - -class FeedlyAccountDetails; -class FeedlyServiceRoot; - -class FormEditFeedlyAccount : public FormAccountDetails { - Q_OBJECT - - public: - explicit FormEditFeedlyAccount(QWidget* parent = nullptr); - - protected slots: - virtual void apply(); - - protected: - virtual void loadAccountData(); - - private slots: - void performTest(); - - private: - FeedlyAccountDetails* m_details; -}; - -#endif // FORMEDITFEEDLYACCOUNT_H