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 000000000..4c40aca34
Binary files /dev/null and b/resources/graphics/misc/feedly.png differ
diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc
index d2fe9c3a0..22722de10 100755
--- a/resources/rssguard.qrc
+++ b/resources/rssguard.qrc
@@ -17,6 +17,7 @@
graphics/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
+
+ 1
+
+
+ LabelWithStatus
+ QWidget
+
+ 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 {