Some initial code for #105.

This commit is contained in:
Martin Rotter 2021-02-08 12:26:30 +01:00
parent da8de799b7
commit 0c56897bca
24 changed files with 936 additions and 4 deletions

View File

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

View File

@ -30,7 +30,7 @@
<url type="donation">https://martinrotter.github.io/donate/</url>
<content_rating type="oars-1.1" />
<releases>
<release version="3.8.4" date="2021-02-05"/>
<release version="3.8.4" date="2021-02-08"/>
</releases>
<content_rating type="oars-1.0">
<content_attribute id="violence-cartoon">none</content_attribute>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -17,6 +17,7 @@
<file>graphics/misc/adblock.png</file>
<file>graphics/misc/adblock-disabled.png</file>
<file>graphics/misc/bazqux.png</file>
<file>graphics/misc/feedly.png</file>
<file>graphics/misc/freshrss.png</file>
<file>graphics/misc/gmail.png</file>
<file>graphics/misc/google.png</file>

@ -1 +1 @@
Subproject commit 9c10723bfbaf6cb85107d6ee16e0324e9e487749
Subproject commit 47f4125753452eff8800dbd6600c5a05540b15d9

View File

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

View File

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

View File

@ -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<ServiceEntryPoint*> 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());

View File

@ -10,7 +10,8 @@
#include <QTcpSocket>
#include <QUrlQuery>
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));
}

View File

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

View File

@ -0,0 +1,47 @@
// For license of this file, see <project-root-folder>/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<FeedlyServiceRoot>();
}
QList<ServiceRoot*> 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"));
}

View File

@ -0,0 +1,19 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef FEEDLYENTRYPOINT_H
#define FEEDLYENTRYPOINT_H
#include "services/abstract/serviceentrypoint.h"
class FeedlyEntryPoint : public ServiceEntryPoint {
public:
virtual ServiceRoot* createNewRoot() const;
virtual QList<ServiceRoot*> 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

View File

@ -0,0 +1,35 @@
// For license of this file, see <project-root-folder>/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<FeedlyServiceRoot*>(getParentServiceRoot());
}
QList<Message> FeedlyFeed::obtainNewMessages(bool* error_during_obtaining) {
return {};
/*
Feed::Status error = Feed::Status::Normal;
QList<Message> 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;*/
}

View File

@ -0,0 +1,19 @@
// For license of this file, see <project-root-folder>/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<Message> obtainNewMessages(bool* error_during_obtaining);
};
#endif // FEEDLYFEED_H

View File

@ -0,0 +1,123 @@
// For license of this file, see <project-root-folder>/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 <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
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;
}

View File

@ -0,0 +1,56 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef FEEDLYNETWORK_H
#define FEEDLYNETWORK_H
#include <QObject>
#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

View File

@ -0,0 +1,111 @@
// For license of this file, see <project-root-folder>/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<RootItem*>() << 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<Category>(database, accountId());
Assignment feeds = DatabaseQueries::getFeeds<FeedlyFeed>(database, qApp->feedReader()->messageFilters(), accountId());
auto labels = DatabaseQueries::getLabels(database, accountId());
performInitialAssembly(categories, feeds, labels);
}

View File

@ -0,0 +1,45 @@
// For license of this file, see <project-root-folder>/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

View File

@ -0,0 +1,153 @@
// For license of this file, see <project-root-folder>/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<void (QSpinBox::*)(int)>(&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."));
}
}

View File

@ -0,0 +1,50 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef FEEDLYACCOUNTDETAILS_H
#define FEEDLYACCOUNTDETAILS_H
#include <QWidget>
#include "ui_feedlyaccountdetails.h"
#include "services/feedly/feedlyserviceroot.h"
#include <QNetworkProxy>
#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

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FeedlyAccountDetails</class>
<widget class="QWidget" name="FeedlyAccountDetails">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>421</width>
<height>235</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="m_lblUsername">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtUsername" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="m_lblUsername_2">
<property name="text">
<string>Developer access token</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="m_btnGetToken">
<property name="text">
<string>Get token</string>
</property>
</widget>
</item>
<item>
<widget class="LineEditWithStatus" name="m_txtDeveloperAccessToken" native="true"/>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="m_lblInfo">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="1">
<widget class="QSpinBox" name="m_spinLimitMessages">
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string> message(s)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Only download newest X messages per feed</string>
</property>
<property name="buddy">
<cstring>m_spinLimitMessages</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>400</width>
<height>86</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="m_btnTestSetup">
<property name="text">
<string>&amp;Login</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LabelWithStatus" name="m_lblTestResult" native="true">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEditWithStatus</class>
<extends>QWidget</extends>
<header>lineeditwithstatus.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LabelWithStatus</class>
<extends>QWidget</extends>
<header>labelwithstatus.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,66 @@
// For license of this file, see <project-root-folder>/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<FeedlyServiceRoot>();
#if defined (FEEDLY_OFFICIAL_SUPPORT)
// We copy credentials from testing OAuth to live OAuth.
account<FeedlyServiceRoot>()->network()->oauth()->setAccessToken(m_details->m_oauth->accessToken());
account<FeedlyServiceRoot>()->network()->oauth()->setRefreshToken(m_details->m_oauth->refreshToken());
account<FeedlyServiceRoot>()->network()->oauth()->setTokensExpireIn(m_details->m_oauth->tokensExpireIn());
#endif
account<FeedlyServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<FeedlyServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
account<FeedlyServiceRoot>()->network()->setDeveloperAccessToken(m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()->text());
account<FeedlyServiceRoot>()->saveAccountDataToDatabase(!editing_account);
accept();
if (editing_account) {
account<FeedlyServiceRoot>()->completelyRemoveAllData();
account<FeedlyServiceRoot>()->syncIn();
}
}
void FormEditFeedlyAccount::setEditableAccount(ServiceRoot* editable_account) {
FormAccountDetails::setEditableAccount(editable_account);
FeedlyServiceRoot* existing_root = account<FeedlyServiceRoot>();
#if defined (FEEDLY_OFFICIAL_SUPPORT)
m_details->m_oauth = account<FeedlyServiceRoot>()->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());
}

View File

@ -0,0 +1,30 @@
// For license of this file, see <project-root-folder>/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

View File

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