starting to merge inoreader into greader

This commit is contained in:
Martin Rotter 2021-08-05 14:46:39 +02:00
parent 5971287174
commit 8118c3155b
25 changed files with 637 additions and 149 deletions

View file

@ -0,0 +1,13 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/reusable/resizablestackedwidget.h"
ResizableStackedWidget::ResizableStackedWidget(QWidget* parent) : QStackedWidget(parent) {}
QSize ResizableStackedWidget::sizeHint() const {
return currentWidget()->sizeHint();
}
QSize ResizableStackedWidget::minimumSizeHint() const {
return currentWidget()->minimumSizeHint();
}

View file

@ -0,0 +1,16 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef RESIZABLESTACKEDWIDGET_H
#define RESIZABLESTACKEDWIDGET_H
#include <QStackedWidget>
class ResizableStackedWidget : public QStackedWidget {
public:
explicit ResizableStackedWidget(QWidget* parent = nullptr);
virtual QSize sizeHint() const;
virtual QSize minimumSizeHint() const;
};
#endif // RESIZABLESTACKEDWIDGET_H

View file

@ -65,6 +65,7 @@ HEADERS += core/feeddownloader.h \
gui/notifications/singlenotificationeditor.h \
gui/reusable/baselineedit.h \
gui/reusable/progressbarwithtext.h \
gui/reusable/resizablestackedwidget.h \
gui/settings/settingsnotifications.h \
gui/toolbars/basetoolbar.h \
gui/reusable/comboboxwithstatus.h \
@ -251,6 +252,7 @@ SOURCES += core/feeddownloader.cpp \
gui/notifications/singlenotificationeditor.cpp \
gui/reusable/baselineedit.cpp \
gui/reusable/progressbarwithtext.cpp \
gui/reusable/resizablestackedwidget.cpp \
gui/settings/settingsnotifications.cpp \
gui/toolbars/basetoolbar.cpp \
gui/reusable/comboboxwithstatus.cpp \

View file

@ -299,8 +299,8 @@ QString OAuth2Service::redirectUrl() const {
return m_redirectionHandler->listenAddressPort();
}
void OAuth2Service::setRedirectUrl(const QString& redirect_url) {
m_redirectionHandler->setListenAddressPort(redirect_url);
void OAuth2Service::setRedirectUrl(const QString& redirect_url, bool start_handler) {
m_redirectionHandler->setListenAddressPort(redirect_url, start_handler);
}
QString OAuth2Service::refreshToken() const {

View file

@ -57,7 +57,7 @@ class OAuth2Service : public QObject {
void setRefreshToken(const QString& refresh_token);
QString redirectUrl() const;
void setRedirectUrl(const QString& redirect_url);
void setRedirectUrl(const QString& redirect_url, bool start_handler);
QString clientId() const;
void setClientId(const QString& client_id);

View file

@ -29,7 +29,7 @@ bool OAuthHttpHandler::isListening() const {
return m_httpServer.isListening();
}
void OAuthHttpHandler::setListenAddressPort(const QString& full_uri) {
void OAuthHttpHandler::setListenAddressPort(const QString& full_uri, bool start_handler) {
QUrl url = QUrl::fromUserInput(full_uri);
QHostAddress listen_address;
quint16 listen_port = quint16(url.port(80));
@ -41,7 +41,9 @@ void OAuthHttpHandler::setListenAddressPort(const QString& full_uri) {
listen_address = QHostAddress(url.host());
}
if (listen_address == m_listenAddress && listen_port == m_listenPort && m_httpServer.isListening()) {
if (listen_address == m_listenAddress &&
listen_port == m_listenPort &&
start_handler == m_httpServer.isListening()) {
// NOTE: We do not need to change listener's settings or re-start it.
return;
}
@ -51,6 +53,16 @@ void OAuthHttpHandler::setListenAddressPort(const QString& full_uri) {
stop();
}
m_listenAddress = listen_address;
m_listenPort = listen_port;
m_listenAddressPort = full_uri;
if (!start_handler) {
qDebugNN << LOGSEC_OAUTH
<< "User does not want handler to be running.";
return;
}
if (!m_httpServer.listen(listen_address, listen_port)) {
qCriticalNN << LOGSEC_OAUTH
<< "OAuth redirect handler FAILED TO START TO LISTEN on address"
@ -61,10 +73,6 @@ void OAuthHttpHandler::setListenAddressPort(const QString& full_uri) {
<< QUOTE_W_SPACE_DOT(m_httpServer.errorString());
}
else {
m_listenAddress = listen_address;
m_listenPort = listen_port;
m_listenAddressPort = full_uri;
qDebugNN << LOGSEC_OAUTH
<< "OAuth redirect handler IS LISTENING on address"
<< QUOTE_W_SPACE(m_listenAddress.toString())

View file

@ -30,7 +30,7 @@ class OAuthHttpHandler : public QObject {
QString listenAddressPort() const;
// Sets full URL string, for example "http://localhost:123456".
void setListenAddressPort(const QString& full_uri);
void setListenAddressPort(const QString& full_uri, bool start_handler);
signals:
void authRejected(const QString& error_description, const QString& state);

View file

@ -127,7 +127,8 @@ void GmailNetworkFactory::initializeOauth() {
m_oauth2->setRedirectUrl(QString(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(GMAIL_OAUTH_REDIRECT_URI_PORT));
QString::number(GMAIL_OAUTH_REDIRECT_URI_PORT),
true);
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &GmailNetworkFactory::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &GmailNetworkFactory::onAuthFailed);

View file

@ -71,7 +71,7 @@ void GmailServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
m_network->oauth()->setClientId(data["client_id"].toString());
m_network->oauth()->setClientSecret(data["client_secret"].toString());
m_network->oauth()->setRefreshToken(data["refresh_token"].toString());
m_network->oauth()->setRedirectUrl(data["redirect_uri"].toString());
m_network->oauth()->setRedirectUrl(data["redirect_uri"].toString(), true);
}
QList<Message> GmailServiceRoot::obtainNewMessages(Feed* feed,

View file

@ -29,7 +29,8 @@ void FormEditGmailAccount::apply() {
account<GmailServiceRoot>()->network()->oauth()->logout(false);
account<GmailServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
account<GmailServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
account<GmailServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text());
account<GmailServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text(),
true);
account<GmailServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<GmailServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());

View file

@ -53,10 +53,10 @@ GmailAccountDetails::GmailAccountDetails(QWidget* parent)
}
void GmailAccountDetails::testSetup(const QNetworkProxy& custom_proxy) {
m_oauth->logout();
m_oauth->logout(true);
m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text());
m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text(), true);
m_lastProxy = custom_proxy;
m_oauth->login();

View file

@ -4,9 +4,10 @@
#define GREADER_DEFAULT_BATCH_SIZE 100
// URLs.
#define GREADER_URL_REEDAH "https://www.reedah.com"
#define GREADER_URL_TOR "https://theoldreader.com"
#define GREADER_URL_BAZQUX "https://bazqux.com"
#define GREADER_URL_REEDAH "https://www.reedah.com"
#define GREADER_URL_TOR "https://theoldreader.com"
#define GREADER_URL_BAZQUX "https://bazqux.com"
#define GREADER_URL_INOREADER "https://www.inoreader.com"
// States.
#define GREADER_API_STATE_READING_LIST "state/com.google/reading-list"
@ -41,6 +42,15 @@
#define TOR_SPONSORED_STREAM_ID "tor/sponsored"
#define TOR_ITEM_CONTENTS_BATCH 9999
#define INO_HEADER_APPID "AppId"
#define INO_HEADER_APPKEY "AppKey"
#define INO_OAUTH_REDIRECT_URI_PORT 14488
#define INO_OAUTH_SCOPE "read write"
#define INO_OAUTH_TOKEN_URL "https://www.inoreader.com/oauth2/token"
#define INO_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth"
#define INO_REG_API_URL "https://www.inoreader.com/developers/register-app"
// FreshRSS.
#define FRESHRSS_BASE_URL_PATH "api/greader.php/"

View file

@ -2,9 +2,9 @@
#include "services/greader/greaderentrypoint.h"
#include "database/databasequeries.h"
#include "definitions/definitions.h"
#include "miscellaneous/application.h"
#include "database/databasequeries.h"
#include "miscellaneous/iconfactory.h"
#include "services/greader/definitions.h"
#include "services/greader/greaderserviceroot.h"
@ -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, Reedah, ...");
QSL(" Inoreader, FreshRSS, Bazqux, TheOldReader, Reedah, ...");
}
QString GreaderEntryPoint::author() const {

View file

@ -3,10 +3,12 @@
#include "services/greader/greadernetwork.h"
#include "3rd-party/boolinq/boolinq.h"
#include "database/databasequeries.h"
#include "exceptions/applicationexception.h"
#include "exceptions/networkexception.h"
#include "miscellaneous/application.h"
#include "network-web/networkfactory.h"
#include "network-web/oauth2service.h"
#include "network-web/webfactory.h"
#include "services/abstract/category.h"
#include "services/abstract/label.h"
@ -18,9 +20,12 @@
#include <QJsonObject>
GreaderNetwork::GreaderNetwork(QObject* parent)
: QObject(parent), m_service(GreaderServiceRoot::Service::FreshRss), m_username(QString()), m_password(QString()),
m_baseUrl(QString()), m_batchSize(GREADER_DEFAULT_BATCH_SIZE), m_downloadOnlyUnreadMessages(false),
m_prefetchedMessages({}), m_performGlobalFetching(false), m_intelligentSynchronization(false) {
: QObject(parent), m_root(nullptr), m_service(GreaderServiceRoot::Service::FreshRss), m_username(QString()),
m_password(QString()), m_baseUrl(QString()), m_batchSize(GREADER_DEFAULT_BATCH_SIZE), m_downloadOnlyUnreadMessages(false),
m_prefetchedMessages({}), m_performGlobalFetching(false), m_intelligentSynchronization(false),
m_oauth2(new OAuth2Service(INO_OAUTH_AUTH_URL, INO_OAUTH_TOKEN_URL,
{}, {}, INO_OAUTH_SCOPE, this)) {
initializeOauth();
clearCredentials();
}
@ -819,27 +824,11 @@ void GreaderNetwork::setBaseUrl(const QString& base_url) {
m_baseUrl = base_url;
}
QString GreaderNetwork::serviceToString(GreaderServiceRoot::Service service) {
switch (service) {
case GreaderServiceRoot::Service::FreshRss:
return QSL("FreshRSS");
case GreaderServiceRoot::Service::Bazqux:
return QSL("Bazqux");
case GreaderServiceRoot::Service::Reedah:
return QSL("Reedah");
case GreaderServiceRoot::Service::TheOldReader:
return QSL("The Old Reader");
default:
return tr("Other services");
}
}
QPair<QByteArray, QByteArray> GreaderNetwork::authHeader() const {
return { QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), QSL("GoogleLogin auth=%1").arg(m_authAuth).toLocal8Bit() };
QPair<QByteArray, QByteArray> header = { QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
QSL("GoogleLogin auth=%1").arg(m_authAuth).toLocal8Bit() };
return header;
}
bool GreaderNetwork::ensureLogin(const QNetworkProxy& proxy, QNetworkReply::NetworkError* output) {
@ -1053,6 +1042,69 @@ QString GreaderNetwork::generateFullUrl(GreaderNetwork::Operations operation) co
}
}
void GreaderNetwork::onTokensError(const QString& error, const QString& error_description) {
Q_UNUSED(error)
qApp->showGuiMessage(Notification::Event::GeneralEvent,
tr("Inoreader: authentication error"),
tr("Click this to login again. Error is: '%1'").arg(error_description),
QSystemTrayIcon::MessageIcon::Critical,
{}, {},
[this]() {
m_oauth2->setAccessToken(QString());
m_oauth2->setRefreshToken(QString());
m_oauth2->login();
});
}
void GreaderNetwork::onAuthFailed() {
qApp->showGuiMessage(Notification::Event::GeneralEvent,
tr("Inoreader: authorization denied"),
tr("Click this to login again."),
QSystemTrayIcon::MessageIcon::Critical,
{}, {},
[this]() {
m_oauth2->login();
});
}
void GreaderNetwork::initializeOauth() {
#if defined(INOREADER_OFFICIAL_SUPPORT)
m_oauth2->setClientSecretId(TextFactory::decrypt(INOREADER_CLIENT_ID, OAUTH_DECRYPTION_KEY));
m_oauth2->setClientSecretSecret(TextFactory::decrypt(INOREADER_CLIENT_SECRET, OAUTH_DECRYPTION_KEY));
#endif
m_oauth2->setRedirectUrl(QString(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(INO_OAUTH_REDIRECT_URI_PORT),
false);
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &GreaderNetwork::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &GreaderNetwork::onAuthFailed);
connect(m_oauth2, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, QString refresh_token, int expires_in) {
Q_UNUSED(expires_in)
Q_UNUSED(access_token)
if (m_root != nullptr && m_root->accountId() > 0 && !refresh_token.isEmpty()) {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_root->accountId());
}
});
}
OAuth2Service* GreaderNetwork::oauth() const {
return m_oauth2;
}
void GreaderNetwork::setOauth(OAuth2Service* oauth) {
m_oauth2 = oauth;
}
void GreaderNetwork::setRoot(GreaderServiceRoot* root) {
m_root = root;
}
bool GreaderNetwork::intelligentSynchronization() const {
return m_intelligentSynchronization;
}

View file

@ -9,6 +9,8 @@
#include "services/abstract/feed.h"
#include "services/greader/greaderserviceroot.h"
class OAuth2Service;
class GreaderNetwork : public QObject {
Q_OBJECT
@ -87,14 +89,21 @@ class GreaderNetwork : public QObject {
void clearCredentials();
static QString serviceToString(GreaderServiceRoot::Service service);
bool downloadOnlyUnreadMessages() const;
void setDownloadOnlyUnreadMessages(bool download_only_unread);
bool intelligentSynchronization() const;
void setIntelligentSynchronization(bool intelligent_synchronization);
void setRoot(GreaderServiceRoot* root);
OAuth2Service* oauth() const;
void setOauth(OAuth2Service* oauth);
private slots:
void onTokensError(const QString& error, const QString& error_description);
void onAuthFailed();
private:
QPair<QByteArray, QByteArray> authHeader() const;
@ -110,8 +119,10 @@ class GreaderNetwork : public QObject {
RootItem* decodeTagsSubscriptions(const QString& categories, const QString& feeds, bool obtain_icons, const QNetworkProxy& proxy);
QString sanitizedBaseUrl() const;
QString generateFullUrl(Operations operation) const;
void initializeOauth();
private:
GreaderServiceRoot* m_root;
GreaderServiceRoot::Service m_service;
QString m_username;
QString m_password;
@ -124,6 +135,7 @@ class GreaderNetwork : public QObject {
QList<Message> m_prefetchedMessages;
bool m_performGlobalFetching;
bool m_intelligentSynchronization;
OAuth2Service* m_oauth2;
};
#endif // GREADERNETWORK_H

View file

@ -9,6 +9,7 @@
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/mutex.h"
#include "miscellaneous/textfactory.h"
#include "network-web/oauth2service.h"
#include "services/abstract/importantnode.h"
#include "services/abstract/recyclebin.h"
#include "services/greader/greaderentrypoint.h"
@ -18,6 +19,7 @@
GreaderServiceRoot::GreaderServiceRoot(RootItem* parent)
: ServiceRoot(parent), m_network(new GreaderNetwork(this)) {
setIcon(GreaderEntryPoint().icon());
m_network->setRoot(this);
}
bool GreaderServiceRoot::isSyncable() const {
@ -46,6 +48,13 @@ QVariantHash GreaderServiceRoot::customDatabaseData() const {
data["download_only_unread"] = m_network->downloadOnlyUnreadMessages();
data["intelligent_synchronization"] = m_network->intelligentSynchronization();
if (m_network->service() == Service::Inoreader) {
data["client_id"] = m_network->oauth()->clientId();
data["client_secret"] = m_network->oauth()->clientSecret();
data["refresh_token"] = m_network->oauth()->refreshToken();
data["redirect_uri"] = m_network->oauth()->redirectUrl();
}
return data;
}
@ -57,6 +66,13 @@ void GreaderServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
m_network->setBatchSize(data["batch_size"].toInt());
m_network->setDownloadOnlyUnreadMessages(data["download_only_unread"].toBool());
m_network->setIntelligentSynchronization(data["intelligent_synchronization"].toBool());
if (m_network->service() == Service::Inoreader) {
m_network->oauth()->setClientId(data["client_id"].toString());
m_network->oauth()->setClientSecret(data["client_secret"].toString());
m_network->oauth()->setRefreshToken(data["refresh_token"].toString());
m_network->oauth()->setRedirectUrl(data["redirect_uri"].toString(), true);
}
}
void GreaderServiceRoot::aboutToBeginFeedFetching(const QList<Feed*>& feeds,
@ -70,6 +86,28 @@ void GreaderServiceRoot::aboutToBeginFeedFetching(const QList<Feed*>& feeds,
}
}
QString GreaderServiceRoot::serviceToString(Service service) {
switch (service) {
case Service::FreshRss:
return QSL("FreshRSS");
case Service::Bazqux:
return QSL("Bazqux");
case Service::Reedah:
return QSL("Reedah");
case Service::TheOldReader:
return QSL("The Old Reader");
case Service::Inoreader:
return QSL("Inoreader");
default:
return tr("Other services");
}
}
QList<Message> GreaderServiceRoot::obtainNewMessages(Feed* feed,
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
const QHash<QString, QStringList>& tagged_messages) {
@ -109,7 +147,17 @@ void GreaderServiceRoot::start(bool freshly_activated) {
updateTitleIcon();
if (getSubTreeFeeds().isEmpty()) {
syncIn();
if (m_network->service() == Service::Inoreader) {
m_network->oauth()->login([this]() {
syncIn();
});
}
else {
syncIn();
}
}
else if (m_network->service() == Service::Inoreader) {
m_network->oauth()->login();
}
}
@ -199,7 +247,7 @@ ServiceRoot::LabelOperation GreaderServiceRoot::supportedLabelOperations() const
void GreaderServiceRoot::updateTitleIcon() {
setTitle(QString("%1 (%2)").arg(TextFactory::extractUsernameFromEmail(m_network->username()),
m_network->serviceToString(m_network->service())));
GreaderServiceRoot::serviceToString(m_network->service())));
switch (m_network->service()) {
case Service::TheOldReader:
@ -218,6 +266,10 @@ void GreaderServiceRoot::updateTitleIcon() {
setIcon(qApp->icons()->miscIcon(QSL("reedah")));
break;
case Service::Inoreader:
setIcon(qApp->icons()->miscIcon(QSL("inoreader")));
break;
default:
setIcon(GreaderEntryPoint().icon());
break;

View file

@ -17,6 +17,7 @@ class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
TheOldReader = 2,
Bazqux = 4,
Reedah = 8,
Inoreader = 16,
Other = 1024
};
@ -41,6 +42,8 @@ class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
GreaderNetwork* network() const;
static QString serviceToString(Service service);
protected:
virtual RootItem* obtainNewTreeForSyncIn() const;

View file

@ -5,6 +5,7 @@
#include "gui/guiutilities.h"
#include "miscellaneous/iconfactory.h"
#include "network-web/networkfactory.h"
#include "network-web/oauth2service.h"
#include "services/greader/definitions.h"
#include "services/greader/greadernetwork.h"
#include "services/greader/greaderserviceroot.h"
@ -23,20 +24,31 @@ FormEditGreaderAccount::FormEditGreaderAccount(QWidget* parent)
void FormEditGreaderAccount::apply() {
FormAccountDetails::apply();
account<GreaderServiceRoot>()->network()->setBaseUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
account<GreaderServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<GreaderServiceRoot>()->network()->setPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
account<GreaderServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
account<GreaderServiceRoot>()->network()->setDownloadOnlyUnreadMessages(m_details->m_ui.m_cbDownloadOnlyUnreadMessages->isChecked());
account<GreaderServiceRoot>()->network()->setService(m_details->service());
account<GreaderServiceRoot>()->network()->setIntelligentSynchronization(m_details->m_ui.m_cbNewAlgorithm->isChecked());
GreaderServiceRoot* existing_root = account<GreaderServiceRoot>();
account<GreaderServiceRoot>()->saveAccountDataToDatabase();
existing_root->network()->setBaseUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
existing_root->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
existing_root->network()->setPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
existing_root->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
existing_root->network()->setDownloadOnlyUnreadMessages(m_details->m_ui.m_cbDownloadOnlyUnreadMessages->isChecked());
existing_root->network()->setService(m_details->service());
existing_root->network()->setIntelligentSynchronization(m_details->m_ui.m_cbNewAlgorithm->isChecked());
existing_root->network()->oauth()->logout(true);
if (existing_root->network()->service() == GreaderServiceRoot::Service::Inoreader) {
existing_root->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
existing_root->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
existing_root->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text(),
true);
}
existing_root->saveAccountDataToDatabase();
accept();
if (!m_creatingNew) {
account<GreaderServiceRoot>()->completelyRemoveAllData();
account<GreaderServiceRoot>()->start(true);
existing_root->completelyRemoveAllData();
existing_root->start(true);
}
}
@ -46,7 +58,15 @@ void FormEditGreaderAccount::loadAccountData() {
GreaderServiceRoot* existing_root = account<GreaderServiceRoot>();
setWindowIcon(existing_root->icon());
m_details->setService(existing_root->network()->service());
m_details->m_oauth = existing_root->network()->oauth();
m_details->hookNetwork();
m_details->m_ui.m_txtAppId->lineEdit()->setText(m_details->m_oauth->clientId());
m_details->m_ui.m_txtAppKey->lineEdit()->setText(m_details->m_oauth->clientSecret());
m_details->m_ui.m_txtRedirectUrl->lineEdit()->setText(m_details->m_oauth->redirectUrl());
m_details->m_ui.m_txtUsername->lineEdit()->setText(existing_root->network()->username());
m_details->m_ui.m_txtPassword->lineEdit()->setText(existing_root->network()->password());
m_details->m_ui.m_txtUrl->lineEdit()->setText(existing_root->network()->baseUrl());

View file

@ -5,21 +5,26 @@
#include "definitions/definitions.h"
#include "exceptions/applicationexception.h"
#include "gui/guiutilities.h"
#include "miscellaneous/application.h"
#include "miscellaneous/systemfactory.h"
#include "network-web/oauth2service.h"
#include "network-web/webfactory.h"
#include "services/greader/definitions.h"
#include "services/greader/greadernetwork.h"
#include <QVariantHash>
GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent) {
GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent),
m_oauth(nullptr), m_lastProxy({}) {
m_ui.setupUi(this);
for (auto serv : { GreaderServiceRoot::Service::Bazqux,
GreaderServiceRoot::Service::FreshRss,
GreaderServiceRoot::Service::Inoreader,
GreaderServiceRoot::Service::Reedah,
GreaderServiceRoot::Service::TheOldReader,
GreaderServiceRoot::Service::Other }) {
m_ui.m_cmbService->addItem(GreaderNetwork::serviceToString(serv), QVariant::fromValue(serv));
m_ui.m_cmbService->addItem(GreaderServiceRoot::serviceToString(serv), QVariant::fromValue(serv));
}
m_ui.m_lblTestResult->label()->setWordWrap(true);
@ -34,19 +39,37 @@ GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent)
"articles faster, but if your feed contains more articles "
"than specified limit, then some older articles might not be "
"fetched at all."));
GuiUtilities::setLabelAsNotice(*m_ui.m_lblLimitMessages, true);
m_ui.m_lblNewAlgorithm->setText(tr("If you select intelligent synchronization, then only not-yet-fetched "
"or updated articles are downloaded. Network usage is greatly reduced and "
"overall synchronization speed is greatly improved."));
GuiUtilities::setLabelAsNotice(*m_ui.m_lblNewAlgorithm, false);
#if defined(INOREADER_OFFICIAL_SUPPORT)
m_ui.m_lblInfo->setText(tr("There are some preconfigured OAuth tokens so you do not have to fill in your "
"client ID/secret, but it is strongly recommended to obtain your "
"own as preconfigured tokens have limited global usage quota. If you wish "
"to use preconfigured tokens, simply leave all above fields to their default values even "
"if they are empty."));
#else
m_ui.m_lblInfo->setText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."));
#endif
GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true);
connect(m_ui.m_checkShowPassword, &QCheckBox::toggled, this, &GreaderAccountDetails::displayPassword);
connect(m_ui.m_txtPassword->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onPasswordChanged);
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onUsernameChanged);
connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onUrlChanged);
connect(m_ui.m_cmbService, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &GreaderAccountDetails::fillPredefinedUrl);
connect(m_ui.m_cbNewAlgorithm, &QCheckBox::toggled, m_ui.m_spinLimitMessages, &MessageCountSpinBox::setDisabled);
connect(m_ui.m_txtAppId->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::checkOAuthValue);
connect(m_ui.m_txtAppKey->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::checkOAuthValue);
connect(m_ui.m_txtRedirectUrl->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::checkOAuthValue);
connect(m_ui.m_btnRegisterApi, &QPushButton::clicked, this, &GreaderAccountDetails::registerApi);
setTabOrder(m_ui.m_cmbService, m_ui.m_txtUrl->lineEdit());
setTabOrder(m_ui.m_txtUrl->lineEdit(), m_ui.m_cbDownloadOnlyUnreadMessages);
@ -55,12 +78,83 @@ GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent)
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_txtUsername->lineEdit());
setTabOrder(m_ui.m_txtUsername->lineEdit(), m_ui.m_txtPassword->lineEdit());
setTabOrder(m_ui.m_txtPassword->lineEdit(), m_ui.m_checkShowPassword);
setTabOrder(m_ui.m_checkShowPassword, m_ui.m_btnTestSetup);
setTabOrder(m_ui.m_checkShowPassword, m_ui.m_txtAppId);
setTabOrder(m_ui.m_txtAppId, m_ui.m_txtAppKey);
setTabOrder(m_ui.m_txtAppKey, m_ui.m_txtRedirectUrl);
setTabOrder(m_ui.m_txtRedirectUrl, m_ui.m_btnRegisterApi);
setTabOrder(m_ui.m_btnRegisterApi, m_ui.m_btnTestSetup);
onPasswordChanged();
onUsernameChanged();
onUrlChanged();
displayPassword(false);
emit m_ui.m_txtAppId->lineEdit()->textChanged(m_ui.m_txtAppId->lineEdit()->text());
emit m_ui.m_txtAppKey->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
emit m_ui.m_txtRedirectUrl->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
}
void GreaderAccountDetails::onAuthFailed() {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("You did not grant access."),
tr("There was error during testing."));
}
void GreaderAccountDetails::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 GreaderAccountDetails::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."));
try {
GreaderNetwork fac;
fac.setOauth(m_oauth);
auto resp = fac.userInfo(m_lastProxy);
m_ui.m_txtUsername->lineEdit()->setText(resp["userEmail"].toString());
}
catch (const ApplicationException& ex) {
qCriticalNN << LOGSEC_GREADER
<< "Failed to obtain profile with error:"
<< QUOTE_W_SPACE_DOT(ex.message());
}
}
void GreaderAccountDetails::hookNetwork() {
if (m_oauth != nullptr) {
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &GreaderAccountDetails::onAuthGranted);
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &GreaderAccountDetails::onAuthError);
connect(m_oauth, &OAuth2Service::authFailed, this, &GreaderAccountDetails::onAuthFailed);
}
}
void GreaderAccountDetails::registerApi() {
qApp->web()->openUrlInExternalBrowser(INO_REG_API_URL);
}
void GreaderAccountDetails::checkOAuthValue(const QString& value) {
auto* line_edit = qobject_cast<LineEditWithStatus*>(sender()->parent());
if (line_edit != nullptr) {
if (value.isEmpty()) {
#if defined(INOREADER_OFFICIAL_SUPPORT)
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Preconfigured client ID/secret will be used."));
#else
line_edit->setStatus(WidgetWithStatus::StatusType::Error, tr("Empty value is entered."));
#endif
}
else {
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some value is entered."));
}
}
}
GreaderServiceRoot::Service GreaderAccountDetails::service() const {
@ -76,25 +170,38 @@ void GreaderAccountDetails::displayPassword(bool display) {
}
void GreaderAccountDetails::performTest(const QNetworkProxy& custom_proxy) {
GreaderNetwork factory;
m_lastProxy = custom_proxy;
factory.setUsername(m_ui.m_txtUsername->lineEdit()->text());
factory.setPassword(m_ui.m_txtPassword->lineEdit()->text());
factory.setBaseUrl(m_ui.m_txtUrl->lineEdit()->text());
factory.setService(service());
factory.clearCredentials();
auto result = factory.clientLogin(custom_proxy);
if (result != QNetworkReply::NetworkError::NoError) {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("Network error: '%1'.").arg(NetworkFactory::networkErrorText(result)),
tr("Network error, have you entered correct Nextcloud endpoint and password?"));
if (service() == GreaderServiceRoot::Service::Inoreader) {
if (m_oauth != nullptr) {
m_oauth->logout(true);
m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text());
m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text(), true);
m_oauth->login();
}
}
else {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
tr("You are good to go!"),
tr("Yeah."));
GreaderNetwork factory;
factory.setUsername(m_ui.m_txtUsername->lineEdit()->text());
factory.setPassword(m_ui.m_txtPassword->lineEdit()->text());
factory.setBaseUrl(m_ui.m_txtUrl->lineEdit()->text());
factory.setService(service());
factory.clearCredentials();
auto result = factory.clientLogin(custom_proxy);
if (result != QNetworkReply::NetworkError::NoError) {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("Network error: '%1'.").arg(NetworkFactory::networkErrorText(result)),
tr("Network error, have you entered correct Nextcloud endpoint and password?"));
}
else {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
tr("You are good to go!"),
tr("Yeah."));
}
}
}
@ -145,9 +252,17 @@ void GreaderAccountDetails::fillPredefinedUrl() {
m_ui.m_txtUrl->lineEdit()->setText(QSL(GREADER_URL_TOR));
break;
case GreaderServiceRoot::Service::Inoreader:
m_ui.m_txtUrl->lineEdit()->setText(QSL(GREADER_URL_INOREADER));
break;
default:
m_ui.m_txtUrl->lineEdit()->clear();
m_ui.m_txtUrl->setFocus();
break;
}
// Show OAuth settings for Inoreader and classic for other services.
m_ui.m_stackedAuth->setCurrentIndex(service() == GreaderServiceRoot::Service::Inoreader ? 1 : 0);
m_ui.m_txtUrl->setDisabled(service() == GreaderServiceRoot::Service::Inoreader);
}

View file

@ -11,6 +11,8 @@
#include <QNetworkProxy>
class OAuth2Service;
class GreaderAccountDetails : public QWidget {
Q_OBJECT
@ -29,9 +31,25 @@ class GreaderAccountDetails : public QWidget {
void onPasswordChanged();
void onUrlChanged();
void fillPredefinedUrl();
void checkOAuthValue(const QString& value);
void registerApi();
void onAuthFailed();
void onAuthError(const QString& error, const QString& detailed_description);
void onAuthGranted();
private:
void hookNetwork();
private:
Ui::GreaderAccountDetails m_ui;
// Testing OAuth service. This object is not ever copied
// to new living account instance, instead only its properties
// like tokens are copied.
// If editing existing account, then the pointer points
// directly to existing OAuth from the account.
OAuth2Service* m_oauth;
QNetworkProxy m_lastProxy;
};
#endif // GREADERACCOUNTDETAILS_H

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>430</width>
<height>390</height>
<height>437</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
@ -34,6 +34,27 @@
<item row="2" column="1">
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbDownloadOnlyUnreadMessages">
<property name="text">
<string>Download unread articles only</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbNewAlgorithm">
<property name="text">
<string>Intelligent synchronization algorithm</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="m_lblNewAlgorithm">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -59,54 +80,211 @@
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="QGroupBox" name="m_gbAuthentication">
<property name="toolTip">
<string>Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported.</string>
<widget class="ResizableStackedWidget" name="m_stackedAuth">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Authentication</string>
<property name="currentIndex">
<number>0</number>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Username</string>
</property>
<property name="buddy">
<cstring>m_txtUsername</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Password</string>
</property>
<property name="buddy">
<cstring>m_txtPassword</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LineEditWithStatus" name="m_txtPassword" native="true"/>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtUsername" native="true"/>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="m_checkShowPassword">
<property name="text">
<string>Show password</string>
</property>
</widget>
</item>
</layout>
<widget class="QWidget" name="m_authClassic">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="m_gbAuthentication">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported.</string>
</property>
<property name="title">
<string>Authentication</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Username</string>
</property>
<property name="buddy">
<cstring>m_txtUsername</cstring>
</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="label_5">
<property name="text">
<string>Password</string>
</property>
<property name="buddy">
<cstring>m_txtPassword</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LineEditWithStatus" name="m_txtPassword" native="true"/>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="m_checkShowPassword">
<property name="text">
<string>Show password</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="m_authOauth">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>OAuth 2.0 settings</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="m_lblUsername_2">
<property name="text">
<string>App ID</string>
</property>
<property name="buddy">
<cstring>m_txtAppId</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtAppId" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="m_lblUsername_3">
<property name="text">
<string>App key</string>
</property>
<property name="buddy">
<cstring>m_txtAppKey</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LineEditWithStatus" name="m_txtAppKey" native="true"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="m_lblUsername_4">
<property name="text">
<string>Redirect URL</string>
</property>
<property name="buddy">
<cstring>m_txtRedirectUrl</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="LineEditWithStatus" name="m_txtRedirectUrl" native="true"/>
</item>
<item row="4" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="m_btnRegisterApi">
<property name="text">
<string>Get my own App ID</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="m_lblInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="11" column="0" colspan="2">
@ -146,27 +324,6 @@
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbDownloadOnlyUnreadMessages">
<property name="text">
<string>Download unread articles only</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbNewAlgorithm">
<property name="text">
<string>Intelligent synchronization algorithm</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="m_lblNewAlgorithm">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
@ -187,6 +344,12 @@
<extends>QSpinBox</extends>
<header>messagecountspinbox.h</header>
</customwidget>
<customwidget>
<class>ResizableStackedWidget</class>
<extends>QStackedWidget</extends>
<header>resizablestackedwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>m_cmbService</tabstop>

View file

@ -30,7 +30,8 @@ void FormEditInoreaderAccount::apply() {
account<InoreaderServiceRoot>()->network()->oauth()->logout(false);
account<InoreaderServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text(),
true);
account<InoreaderServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());

View file

@ -58,10 +58,10 @@ void InoreaderAccountDetails::testSetup(const QNetworkProxy& custom_proxy) {
m_lastProxy = custom_proxy;
if (m_oauth != nullptr) {
m_oauth->logout();
m_oauth->logout(true);
m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text());
m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text(), true);
m_oauth->login();
}
}

View file

@ -61,7 +61,8 @@ void InoreaderNetworkFactory::initializeOauth() {
m_oauth2->setRedirectUrl(QString(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(INOREADER_OAUTH_REDIRECT_URI_PORT));
QString::number(INOREADER_OAUTH_REDIRECT_URI_PORT),
true);
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &InoreaderNetworkFactory::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &InoreaderNetworkFactory::onAuthFailed);

View file

@ -53,7 +53,7 @@ void InoreaderServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
m_network->oauth()->setClientId(data["client_id"].toString());
m_network->oauth()->setClientSecret(data["client_secret"].toString());
m_network->oauth()->setRefreshToken(data["refresh_token"].toString());
m_network->oauth()->setRedirectUrl(data["redirect_uri"].toString());
m_network->oauth()->setRedirectUrl(data["redirect_uri"].toString(), true);
}
QList<Message> InoreaderServiceRoot::obtainNewMessages(Feed* feed,