From 0c56897bca5da42756a5fe81daab056fa4ec86b6 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Mon, 8 Feb 2021 12:26:30 +0100 Subject: [PATCH] Some initial code for #105. --- pri/vars.pri | 14 ++ .../desktop/com.github.rssguard.appdata.xml | 2 +- resources/graphics/misc/feedly.png | Bin 0 -> 2398 bytes resources/rssguard.qrc | 1 + resources/scripts/7za | 2 +- src/librssguard/definitions/definitions.h | 1 + src/librssguard/librssguard.pro | 14 ++ src/librssguard/miscellaneous/feedreader.cpp | 2 + .../network-web/oauthhttphandler.cpp | 3 +- src/librssguard/services/feedly/definitions.h | 14 ++ .../services/feedly/feedlyentrypoint.cpp | 47 ++++++ .../services/feedly/feedlyentrypoint.h | 19 +++ .../services/feedly/feedlyfeed.cpp | 35 ++++ src/librssguard/services/feedly/feedlyfeed.h | 19 +++ .../services/feedly/feedlynetwork.cpp | 123 ++++++++++++++ .../services/feedly/feedlynetwork.h | 56 +++++++ .../services/feedly/feedlyserviceroot.cpp | 111 +++++++++++++ .../services/feedly/feedlyserviceroot.h | 45 ++++++ .../feedly/gui/feedlyaccountdetails.cpp | 153 ++++++++++++++++++ .../feedly/gui/feedlyaccountdetails.h | 50 ++++++ .../feedly/gui/feedlyaccountdetails.ui | 131 +++++++++++++++ .../feedly/gui/formeditfeedlyaccount.cpp | 66 ++++++++ .../feedly/gui/formeditfeedlyaccount.h | 30 ++++ .../services/greader/greaderentrypoint.cpp | 2 +- 24 files changed, 936 insertions(+), 4 deletions(-) create mode 100755 resources/graphics/misc/feedly.png create mode 100755 src/librssguard/services/feedly/definitions.h create mode 100755 src/librssguard/services/feedly/feedlyentrypoint.cpp create mode 100755 src/librssguard/services/feedly/feedlyentrypoint.h create mode 100755 src/librssguard/services/feedly/feedlyfeed.cpp create mode 100755 src/librssguard/services/feedly/feedlyfeed.h create mode 100755 src/librssguard/services/feedly/feedlynetwork.cpp create mode 100755 src/librssguard/services/feedly/feedlynetwork.h create mode 100755 src/librssguard/services/feedly/feedlyserviceroot.cpp create mode 100755 src/librssguard/services/feedly/feedlyserviceroot.h create mode 100755 src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp create mode 100755 src/librssguard/services/feedly/gui/feedlyaccountdetails.h create mode 100755 src/librssguard/services/feedly/gui/feedlyaccountdetails.ui create mode 100755 src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp create mode 100755 src/librssguard/services/feedly/gui/formeditfeedlyaccount.h diff --git a/pri/vars.pri b/pri/vars.pri index 2855f7c46..309d4f3ba 100644 --- a/pri/vars.pri +++ b/pri/vars.pri @@ -34,3 +34,17 @@ isEmpty(USE_WEBENGINE) { ##message($$MSG_PREFIX: WebEngine component is probably NOT installed, disabling it.) } } + +isEmpty(FEEDLY_CLIENT_ID)|isEmpty(FEEDLY_CLIENT_SECRET) { + FEEDLY_OFFICIAL_SUPPORT = false + + message($$MSG_PREFIX: Feedly client ID/secret variables are not set. Disabling official support.) +} +else { + FEEDLY_OFFICIAL_SUPPORT = true + DEFINES *= FEEDLY_OFFICIAL_SUPPORT + DEFINES *= FEEDLY_CLIENT_ID='"\\\"$$FEEDLY_CLIENT_ID\\\""' + DEFINES *= FEEDLY_CLIENT_SECRET='"\\\"$$FEEDLY_CLIENT_SECRET\\\""' + + message($$MSG_PREFIX: Enabling official Feedly support.) +} diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 937f5e74d..5b0599196 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -30,7 +30,7 @@ https://martinrotter.github.io/donate/ - + none diff --git a/resources/graphics/misc/feedly.png b/resources/graphics/misc/feedly.png new file mode 100755 index 0000000000000000000000000000000000000000..4c40aca34ac877283f43e26ab594256ad4bb577d GIT binary patch literal 2398 zcmV-k38D6hP)#Q5qrDvfl`dI zg8iiU0d%1nfq+EtJ(?El3shoEv?yqzMwi47CIUfYLNO3R+U~YXp;d`QjRBNew%xY0 zyL<1QGw1kWW_EA4-MusS-q`~Fle;^+cV^D~{&VJ>=Q-z$aL)1nENz`VS7nRn1K^_1 z;$Or$r~wKH&UP(;oaejDBu2*%ENXeo-suZ~mC^Pqz{}CL1QxAYiVGk4(4v$R{dT~| zfjfbr&QbG{XE8xV7QC_)Zsr_0i`+;C8~A5FeMBvTg($ z;fH}Cz%cu~H$vLz_iaE4*vulBptCwLXNUIzZ=QWswCw{oMcbRn*o3SMNCQ7t9$_t6 z6ce&ApdI*Fnng09(*|?`KGvd`&`AS22_GwS6FOx;7Vxn$HK8Q~M8mer7Cu&HCZskX zaKTK8&N(53Knv9pyc8ir!dN5|6H*!AaCCcJXrXX8p@af(6Sd>RQpm*c0)fTyx_=6Q zvMwT#xaJ%kHzBbBLI|p+#*ZsoxvX!E5I{AkxjXiLXZUdS$WT${ed}CPc!|Jw0f7s+ z@BLrG;<)?34m_dJQem5CryVsRbqy%%qFCZ};alVG8rwcRdgS0xPp;b!oJ|C8T!3$V zc2u@-?TYmPbn7yAjophtprl+>6H*yqofDqa_|~}1yT3mC^x=I&{rMi>xF9iny#e00 z1_5k(`v!!7&+ogJJRT~1#FAWl+a@G7z=6<0F=?vq3uD`dpE^7`G*IaCeQQ#|-(T6n zH7nL*T!1IT;*AUV=!y+E2VdTQA998x(e<}wf?9U{MM?G(4#+Fd?H+$_c>lx;LrZhr zsqcIvycTM9A0hFr&wH1w!U0bn8lkL9Noqk@L}0%wO0SC0;r zbura5zYsixZHtx~;{yJ2{O{zHmlVE{qotbA%&9Yi98YN9b{JkDa1OpW_C5ai@24s2 zQqtI`2ig0|Pao}~H>zrR_jqn`Fy1F#*)>-ZvyO)Pw-bru1rxo~EsZE&AfR@AC z*5J4E@XI^tFZ2?)wg-h%nQ*o?;GDr5Ymy;0SK>Bdt^tkk4*{Rz%?BSVkqM0kyp`Z% zH8mkN;H?NBYe5qv?ZAuH;m?m6JShZX;hw4!YBb^3G&GEw+4;Ei@HT>9w}oromIl6$ zT2P}J)Bz}{T+;Zz9mOWx82x<{pkny@SS0vmg+8XuGywOn+rp13-@pOBGcyAu8A=l} z-Uw_50u5Xae1*`6rlGN z%-Su1E2Dolt$*<|x}(w_{8a;$=va*w3gZH<9vI}ob=%NVMd!#=G3$B>^hJLxSf&z+ zpUYU|EP_=Iw_>TYw zi9%sZSS9uWA0~wf&bgUJFmM)$;DNz!z8T<038UU1uIh0ZJVr_rVg=4p(nTtL7Xx9) z+^9Doyn)l7bwe8_cv5k!euA4vwnkl~#JC`;9l;s!{e`6j)*#3<9bOO~&TQ;jHo-ba zmoBkueE*CIS}3HDD4}NHAAfZh=NEdJj?&?3P{TNb6dgCADLg>j3(Qr#<62-RDS8Jj z6i4ePxN_MC`1Goep+q>@`I~?Jo+l3N=KMl0r_2;Zm1jp~3l_(n`|hC@)XA${rvjiv z@G!pt(1r<0NG44+yt4=_4kZ-ld1WTeGtU|s+F`1uvzrL2ntQErh^so^2n zEb5F2LWqquTc7Zqky6OS^;8PQ3I7y#tiFLt-z8_H(ke=a2m3GO_SHAcZi=dqCLL}W z9#YJs3n9!@Fs(nhd~oc+!EGD0R1=e?ny4nksr2sg=NeM!a8Z=QA%$SlOmWM|-Rv2E zo{}ykZ3x?fhZF_?C?QSF*7c=JSC2h<)lW7qFZNCN)+BBd;#B&Uk*!SBPs{{C6Sd>q zyk{%B#`n{kJRNQu9v1Ef&rsk3)(=$Z%k}7@E|>$y{gV-kVQ>{VUC0;{u9$TfUAu5graphics/misc/adblock.png graphics/misc/adblock-disabled.png graphics/misc/bazqux.png + graphics/misc/feedly.png graphics/misc/freshrss.png graphics/misc/gmail.png graphics/misc/google.png diff --git a/resources/scripts/7za b/resources/scripts/7za index 9c10723bf..47f412575 160000 --- a/resources/scripts/7za +++ b/resources/scripts/7za @@ -1 +1 @@ -Subproject commit 9c10723bfbaf6cb85107d6ee16e0324e9e487749 +Subproject commit 47f4125753452eff8800dbd6600c5a05540b15d9 diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 6cb52d1d1..a03f9b229 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -13,6 +13,7 @@ #define SERVICE_CODE_TT_RSS "tt-rss" #define SERVICE_CODE_OWNCLOUD "owncloud" #define SERVICE_CODE_GREADER "greader" +#define SERVICE_CODE_FEEDLY "feedly" #define SERVICE_CODE_INOREADER "inoreader" #define SERVICE_CODE_GMAIL "gmail" diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index 5c6359180..e69b18a50 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -143,6 +143,13 @@ HEADERS += core/feeddownloader.h \ services/abstract/rootitem.h \ services/abstract/serviceentrypoint.h \ services/abstract/serviceroot.h \ + services/feedly/definitions.h \ + services/feedly/feedlyentrypoint.h \ + services/feedly/feedlyfeed.h \ + services/feedly/feedlynetwork.h \ + services/feedly/feedlyserviceroot.h \ + services/feedly/gui/feedlyaccountdetails.h \ + services/feedly/gui/formeditfeedlyaccount.h \ services/gmail/definitions.h \ services/gmail/gmailentrypoint.h \ services/gmail/gmailfeed.h \ @@ -312,6 +319,12 @@ SOURCES += core/feeddownloader.cpp \ services/abstract/recyclebin.cpp \ services/abstract/rootitem.cpp \ services/abstract/serviceroot.cpp \ + services/feedly/feedlyentrypoint.cpp \ + services/feedly/feedlyfeed.cpp \ + services/feedly/feedlynetwork.cpp \ + services/feedly/feedlyserviceroot.cpp \ + services/feedly/gui/feedlyaccountdetails.cpp \ + services/feedly/gui/formeditfeedlyaccount.cpp \ services/gmail/gmailentrypoint.cpp \ services/gmail/gmailfeed.cpp \ services/gmail/gmailserviceroot.cpp \ @@ -398,6 +411,7 @@ FORMS += gui/dialogs/formabout.ui \ services/abstract/gui/formaccountdetails.ui \ services/abstract/gui/formfeeddetails.ui \ services/abstract/gui/authenticationdetails.ui \ + services/feedly/gui/feedlyaccountdetails.ui \ services/gmail/gui/gmailaccountdetails.ui \ services/greader/gui/greaderaccountdetails.ui \ services/inoreader/gui/inoreaderaccountdetails.ui \ diff --git a/src/librssguard/miscellaneous/feedreader.cpp b/src/librssguard/miscellaneous/feedreader.cpp index 4e184eb58..6e969b6d5 100644 --- a/src/librssguard/miscellaneous/feedreader.cpp +++ b/src/librssguard/miscellaneous/feedreader.cpp @@ -14,6 +14,7 @@ #include "miscellaneous/mutex.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/inoreader/inoreaderentrypoint.h" @@ -55,6 +56,7 @@ 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 InoreaderEntryPoint()); diff --git a/src/librssguard/network-web/oauthhttphandler.cpp b/src/librssguard/network-web/oauthhttphandler.cpp index 686777147..1810ae131 100644 --- a/src/librssguard/network-web/oauthhttphandler.cpp +++ b/src/librssguard/network-web/oauthhttphandler.cpp @@ -10,7 +10,8 @@ #include #include -OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent) : QObject(parent), m_successText(success_text) { +OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent) + : QObject(parent), m_successText(success_text) { connect(&m_httpServer, &QTcpServer::newConnection, this, &OAuthHttpHandler::clientConnected); setListenAddressPort(QString(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(OAUTH_REDIRECT_URI_PORT)); } diff --git a/src/librssguard/services/feedly/definitions.h b/src/librssguard/services/feedly/definitions.h new file mode 100755 index 000000000..5d9cca062 --- /dev/null +++ b/src/librssguard/services/feedly/definitions.h @@ -0,0 +1,14 @@ +#ifndef FEEDLY_DEFINITIONS_H +#define FEEDLY_DEFINITIONS_H + +#define FEEDLY_UNLIMITED_BATCH_SIZE -1 +#define FEEDLY_GENERATE_DAT "https://feedly.com/v3/auth/dev" + +#define FEEDLY_API_REDIRECT_URI_PORT 8080 +#define FEEDLY_API_SCOPE "https://cloud.feedly.com/subscriptions" + +#define FEEDLY_API_URL_BASE "https://cloud.feedly.com/v3/" +#define FEEDLY_API_URL_AUTH "auth/auth" +#define FEEDLY_API_URL_TOKEN "auth/token" + +#endif // FEEDLY_DEFINITIONS_H diff --git a/src/librssguard/services/feedly/feedlyentrypoint.cpp b/src/librssguard/services/feedly/feedlyentrypoint.cpp new file mode 100755 index 000000000..95ff211eb --- /dev/null +++ b/src/librssguard/services/feedly/feedlyentrypoint.cpp @@ -0,0 +1,47 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyentrypoint.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" +#include "services/feedly/definitions.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()->connection(QSL("FeedlyEntryPoint")); + + return {}; + + //return DatabaseQueries::getGreaderAccounts(database); +} + +QString FeedlyEntryPoint::name() const { + return QSL("Feedly (WIP)"); +} + +QString FeedlyEntryPoint::code() const { + return SERVICE_CODE_FEEDLY; +} + +QString FeedlyEntryPoint::description() const { + return QObject::tr("Keep up with the topics and trends you care about, without the overwhelm. " + "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 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 new file mode 100755 index 000000000..392b43774 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyentrypoint.h @@ -0,0 +1,19 @@ +// 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/feedlyfeed.cpp b/src/librssguard/services/feedly/feedlyfeed.cpp new file mode 100755 index 000000000..1e2fd3d4e --- /dev/null +++ b/src/librssguard/services/feedly/feedlyfeed.cpp @@ -0,0 +1,35 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyfeed.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/feedly/feedlynetwork.h" +#include "services/feedly/feedlyserviceroot.h" + +FeedlyFeed::FeedlyFeed(RootItem* parent) : Feed(parent) {} + +FeedlyFeed::FeedlyFeed(const QSqlRecord& record) : Feed(record) {} + +FeedlyServiceRoot* FeedlyFeed::serviceRoot() const { + return qobject_cast(getParentServiceRoot()); +} + +QList FeedlyFeed::obtainNewMessages(bool* error_during_obtaining) { + return {}; + + /* + Feed::Status error = Feed::Status::Normal; + QList messages = serviceRoot()->network()->streamContents(getParentServiceRoot(), + customId(), + error, + getParentServiceRoot()->networkProxy()); + + setStatus(error); + + if (error == Feed::Status::NetworkError || error == Feed::Status::AuthError) { + * error_during_obtaining = true; + } + + return messages;*/ +} diff --git a/src/librssguard/services/feedly/feedlyfeed.h b/src/librssguard/services/feedly/feedlyfeed.h new file mode 100755 index 000000000..aaf756295 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyfeed.h @@ -0,0 +1,19 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYFEED_H +#define FEEDLYFEED_H + +#include "services/abstract/feed.h" + +class FeedlyServiceRoot; + +class FeedlyFeed : public Feed { + public: + explicit FeedlyFeed(RootItem* parent = nullptr); + explicit FeedlyFeed(const QSqlRecord& record); + + FeedlyServiceRoot* serviceRoot() const; + QList obtainNewMessages(bool* error_during_obtaining); +}; + +#endif // FEEDLYFEED_H diff --git a/src/librssguard/services/feedly/feedlynetwork.cpp b/src/librssguard/services/feedly/feedlynetwork.cpp new file mode 100755 index 000000000..d6b587228 --- /dev/null +++ b/src/librssguard/services/feedly/feedlynetwork.cpp @@ -0,0 +1,123 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlynetwork.h" + +#include "3rd-party/boolinq/boolinq.h" +#include "miscellaneous/application.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/feedlyfeed.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) + FEEDLY_API_URL_AUTH, + QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_TOKEN, + "dontknow", + "dontknow", + FEEDLY_API_SCOPE, this)), +#endif + m_username(QString()), + m_developerAccessToken(QString()), m_batchSize(FEEDLY_UNLIMITED_BATCH_SIZE) { + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth->setRedirectUrl(QString(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(FEEDLY_API_REDIRECT_URI_PORT)); + + connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyNetwork::onTokensError); + connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyNetwork::onAuthFailed); + connect(m_oauth, &OAuth2Service::tokensReceived, this, &FeedlyNetwork::onTokensReceived); +#endif +} + +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(tr("Feedly: authentication error"), + tr("Click this to login again. Error is: '%1'").arg(error_description), + QSystemTrayIcon::MessageIcon::Critical, + nullptr, false, + [this]() { + m_oauth->setAccessToken({}); + m_oauth->setRefreshToken({}); + m_oauth->login(); + }); +} + +void FeedlyNetwork::onAuthFailed() { + qApp->showGuiMessage(tr("Feedly: authorization denied"), + tr("Click this to login again."), + QSystemTrayIcon::MessageIcon::Critical, + nullptr, false, + [this]() { + m_oauth->login(); + }); +} + +void FeedlyNetwork::onTokensReceived(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()->connection(metaObject()->className()); + + //DatabaseQueries::storeNewInoreaderTokens(database, refresh_token, m_service->accountId()); + + qApp->showGuiMessage(tr("Logged in successfully"), + tr("Your login to Feedly was authorized."), + QSystemTrayIcon::MessageIcon::Information); + } +} + +OAuth2Service* FeedlyNetwork::oauth() const { + return m_oauth; +} + +void FeedlyNetwork::setOauth(OAuth2Service* oauth) { + m_oauth = oauth; +} + +#endif + +void FeedlyNetwork::setService(FeedlyServiceRoot* service) { + m_service = service; +} diff --git a/src/librssguard/services/feedly/feedlynetwork.h b/src/librssguard/services/feedly/feedlynetwork.h new file mode 100755 index 000000000..635fd24a6 --- /dev/null +++ b/src/librssguard/services/feedly/feedlynetwork.h @@ -0,0 +1,56 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYNETWORK_H +#define FEEDLYNETWORK_H + +#include + +#include "network-web/networkfactory.h" +#include "services/abstract/feed.h" + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +class OAuth2Service; +#endif + +class FeedlyServiceRoot; + +class FeedlyNetwork : public QObject { + Q_OBJECT + + public: + explicit FeedlyNetwork(QObject* parent = nullptr); + + QString username() const; + void setUsername(const QString& username); + + QString developerAccessToken() const; + void setDeveloperAccessToken(const QString& dev_acc_token); + + 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 onTokensReceived(const QString& access_token, const QString& refresh_token, int expires_in); +#endif + + private: + FeedlyServiceRoot* m_service; + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + OAuth2Service* m_oauth; +#endif + + QString m_username; + QString m_developerAccessToken; + int m_batchSize; +}; + +#endif // FEEDLYNETWORK_H diff --git a/src/librssguard/services/feedly/feedlyserviceroot.cpp b/src/librssguard/services/feedly/feedlyserviceroot.cpp new file mode 100755 index 000000000..d72b8795e --- /dev/null +++ b/src/librssguard/services/feedly/feedlyserviceroot.cpp @@ -0,0 +1,111 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyserviceroot.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/mutex.h" +#include "miscellaneous/textfactory.h" +#include "services/abstract/importantnode.h" +#include "services/abstract/recyclebin.h" +#include "services/feedly/feedlyentrypoint.h" +#include "services/feedly/feedlyfeed.h" +#include "services/feedly/feedlynetwork.h" +#include "services/feedly/gui/formeditfeedlyaccount.h" + +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; +} + +bool FeedlyServiceRoot::canBeDeleted() const { + return true; +} + +bool FeedlyServiceRoot::editViaGui() { + FormEditFeedlyAccount form_pointer(qApp->mainFormWidget()); + + form_pointer.addEditAccount(this); + return true; +} + +bool FeedlyServiceRoot::deleteViaGui() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + + if (DatabaseQueries::deleteGreaderAccount(database, accountId())) { + return ServiceRoot::deleteViaGui(); + } + else { + return false; + } +} + +void FeedlyServiceRoot::start(bool freshly_activated) { + Q_UNUSED(freshly_activated) + loadFromDatabase(); + loadCacheFromFile(); + + if (childCount() <= 3) { + syncIn(); + } +} + +QString FeedlyServiceRoot::code() const { + return FeedlyServiceRoot().code(); +} + +void FeedlyServiceRoot::saveAllCachedData(bool ignore_errors) { + auto msg_cache = takeMessageCache(); +} + +void FeedlyServiceRoot::updateTitle() { + setTitle(QString("%1 (Feedly)").arg(TextFactory::extractUsernameFromEmail(m_network->username()))); +} + +void FeedlyServiceRoot::saveAccountDataToDatabase(bool creating_new) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + + /* + if (!creating_new) { + if (DatabaseQueries::overwriteGreaderAccount(database, m_network->username(), + m_network->password(), m_network->service(), + m_network->baseUrl(), m_network->batchSize(), + accountId())) { + updateTitle(); + itemChanged(QList() << this); + } + } + else { + if (DatabaseQueries::createGreaderAccount(database, accountId(), m_network->username(), + m_network->password(), m_network->service(), + m_network->baseUrl(), m_network->batchSize())) { + updateTitle(); + } + }*/ +} + +RootItem* FeedlyServiceRoot::obtainNewTreeForSyncIn() const { + return nullptr; + + //return m_network->categoriesFeedsLabelsTree(true, networkProxy()); +} + +void FeedlyServiceRoot::loadFromDatabase() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + Assignment categories = DatabaseQueries::getCategories(database, accountId()); + Assignment feeds = DatabaseQueries::getFeeds(database, qApp->feedReader()->messageFilters(), accountId()); + auto labels = DatabaseQueries::getLabels(database, accountId()); + + performInitialAssembly(categories, feeds, labels); +} diff --git a/src/librssguard/services/feedly/feedlyserviceroot.h b/src/librssguard/services/feedly/feedlyserviceroot.h new file mode 100755 index 000000000..789628799 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyserviceroot.h @@ -0,0 +1,45 @@ +// 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 bool canBeDeleted() const; + virtual bool editViaGui(); + virtual bool deleteViaGui(); + virtual void start(bool freshly_activated); + virtual QString code() const; + virtual void saveAllCachedData(bool ignore_errors); + + FeedlyNetwork* network() const; + + void updateTitle(); + void saveAccountDataToDatabase(bool creating_new); + + protected: + virtual RootItem* obtainNewTreeForSyncIn() const; + + private: + void loadFromDatabase(); + + 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 new file mode 100755 index 000000000..99bc426a1 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp @@ -0,0 +1,153 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/gui/feedlyaccountdetails.h" + +#include "definitions/definitions.h" +#include "gui/guiutilities.h" +#include "miscellaneous/application.h" +#include "miscellaneous/systemfactory.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 + +FeedlyAccountDetails::FeedlyAccountDetails(QWidget* parent) : QWidget(parent) { +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth = new OAuth2Service(QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_AUTH, + QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_TOKEN, + "dontknow", + "dontknow", + FEEDLY_API_SCOPE, this), +#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->setText(tr("Your %1 build has official Feedly support. You do not have to use \"developer acess " + "token\". You can therefore leave corresponding field empty.").arg(APP_NAME)); +#else + m_ui.m_lblInfo->setText(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(APP_NAME)); +#endif + + connect(m_ui.m_spinLimitMessages, static_cast(&QSpinBox::valueChanged), this, [=](int value) { + if (value <= 0) { + m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("= unlimited")); + } + else { + m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("messages")); + } + }); + + GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true); + + 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_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(FEEDLY_GENERATE_DAT); +} + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + +void FeedlyAccountDetails::hookNetwork() { + connect(m_oauth, &OAuth2Service::tokensReceived, 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() { + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, + tr("Tested successfully. You may be prompted to login once more."), + tr("Your access was approved.")); +} + +#endif + +void FeedlyAccountDetails::performTest(const QNetworkProxy& custom_proxy) { +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth->logout(); + + if (m_oauth->login()) { + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, + tr("You are already logged in."), + tr("Access granted.")); + } +#else + FeedlyNetwork factory; + + factory.setUsername(m_ui.m_txtUsername->lineEdit()->text()); + factory.setDeveloperAccessToken(m_ui.m_txtDeveloperAccessToken->lineEdit()->text()); + + // TODO: todo +#endif +} + +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 new file mode 100755 index 000000000..faa6e7c42 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.h @@ -0,0 +1,50 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYACCOUNTDETAILS_H +#define FEEDLYACCOUNTDETAILS_H + +#include + +#include "ui_feedlyaccountdetails.h" + +#include "services/feedly/feedlyserviceroot.h" + +#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 +}; + +#endif // FEEDLYACCOUNTDETAILS_H diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui b/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui new file mode 100755 index 000000000..04fb07ef4 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui @@ -0,0 +1,131 @@ + + + FeedlyAccountDetails + + + + 0 + 0 + 421 + 235 + + + + + + + Username + + + + + + + + + + Developer access token + + + + + + + + + Get token + + + + + + + + + + + + Qt::AlignCenter + + + true + + + + + + + + + + 140 + 16777215 + + + + message(s) + + + + + + + Only download newest X messages per feed + + + m_spinLimitMessages + + + + + + + + + Qt::Vertical + + + + 400 + 86 + + + + + + + + + + &Login + + + + + + + Qt::RightToLeft + + + + + + + + + + LineEditWithStatus + QWidget +
lineeditwithstatus.h
+ 1 +
+ + LabelWithStatus + QWidget +
labelwithstatus.h
+ 1 +
+
+ + +
diff --git a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp new file mode 100755 index 000000000..05e17cc6d --- /dev/null +++ b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp @@ -0,0 +1,66 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/gui/formeditfeedlyaccount.h" + +#include "gui/guiutilities.h" +#include "miscellaneous/iconfactory.h" +#include "network-web/networkfactory.h" +#include "services/feedly/definitions.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("google")), 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() { + bool editing_account = !applyInternal(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + // We copy credentials from testing OAuth to live OAuth. + account()->network()->oauth()->setAccessToken(m_details->m_oauth->accessToken()); + account()->network()->oauth()->setRefreshToken(m_details->m_oauth->refreshToken()); + account()->network()->oauth()->setTokensExpireIn(m_details->m_oauth->tokensExpireIn()); +#endif + + account()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text()); + account()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value()); + account()->network()->setDeveloperAccessToken(m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()->text()); + + account()->saveAccountDataToDatabase(!editing_account); + accept(); + + if (editing_account) { + account()->completelyRemoveAllData(); + account()->syncIn(); + } +} + +void FormEditFeedlyAccount::setEditableAccount(ServiceRoot* editable_account) { + FormAccountDetails::setEditableAccount(editable_account); + + FeedlyServiceRoot* existing_root = account(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_details->m_oauth = account()->network()->oauth(); + m_details->hookNetwork(); +#endif + + m_details->m_ui.m_txtUsername->lineEdit()->setText(existing_root->network()->username()); + m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()->setText(existing_root->network()->developerAccessToken()); + m_details->m_ui.m_spinLimitMessages->setValue(existing_root->network()->batchSize()); +} + +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 new file mode 100755 index 000000000..4d86a091f --- /dev/null +++ b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h @@ -0,0 +1,30 @@ +// 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 setEditableAccount(ServiceRoot* editable_account); + + private slots: + void performTest(); + + private: + FeedlyAccountDetails* m_details; +}; + +#endif // FORMEDITFEEDLYACCOUNT_H diff --git a/src/librssguard/services/greader/greaderentrypoint.cpp b/src/librssguard/services/greader/greaderentrypoint.cpp index a9871ad7a..496313af6 100755 --- a/src/librssguard/services/greader/greaderentrypoint.cpp +++ b/src/librssguard/services/greader/greaderentrypoint.cpp @@ -32,7 +32,7 @@ QString GreaderEntryPoint::code() const { QString GreaderEntryPoint::description() const { return QObject::tr("Google Reader API is used by many online RSS readers. This is here to support") + - QSL(" FreshRSS, Bazqux, TheOldReader."); + QSL(" FreshRSS, Bazqux, TheOldReader, Reedah, ..."); } QString GreaderEntryPoint::author() const {