remove inoreader plugin as it is now fully replaced by greader plugin
This commit is contained in:
parent
b068fafe42
commit
eaffa942f8
@ -183,12 +183,6 @@ HEADERS += core/feeddownloader.h \
|
||||
services/greader/greaderserviceroot.h \
|
||||
services/greader/gui/formeditgreaderaccount.h \
|
||||
services/greader/gui/greaderaccountdetails.h \
|
||||
services/inoreader/definitions.h \
|
||||
services/inoreader/gui/formeditinoreaderaccount.h \
|
||||
services/inoreader/gui/inoreaderaccountdetails.h \
|
||||
services/inoreader/inoreaderentrypoint.h \
|
||||
services/inoreader/inoreadernetworkfactory.h \
|
||||
services/inoreader/inoreaderserviceroot.h \
|
||||
services/owncloud/definitions.h \
|
||||
services/owncloud/gui/formeditowncloudaccount.h \
|
||||
services/owncloud/gui/owncloudaccountdetails.h \
|
||||
@ -364,11 +358,6 @@ SOURCES += core/feeddownloader.cpp \
|
||||
services/greader/greaderserviceroot.cpp \
|
||||
services/greader/gui/formeditgreaderaccount.cpp \
|
||||
services/greader/gui/greaderaccountdetails.cpp \
|
||||
services/inoreader/gui/formeditinoreaderaccount.cpp \
|
||||
services/inoreader/gui/inoreaderaccountdetails.cpp \
|
||||
services/inoreader/inoreaderentrypoint.cpp \
|
||||
services/inoreader/inoreadernetworkfactory.cpp \
|
||||
services/inoreader/inoreaderserviceroot.cpp \
|
||||
services/owncloud/gui/formeditowncloudaccount.cpp \
|
||||
services/owncloud/gui/owncloudaccountdetails.cpp \
|
||||
services/owncloud/owncloudfeed.cpp \
|
||||
@ -438,7 +427,6 @@ FORMS += gui/dialogs/formabout.ui \
|
||||
services/gmail/gui/formdownloadattachment.ui \
|
||||
services/gmail/gui/gmailaccountdetails.ui \
|
||||
services/greader/gui/greaderaccountdetails.ui \
|
||||
services/inoreader/gui/inoreaderaccountdetails.ui \
|
||||
services/owncloud/gui/owncloudaccountdetails.ui \
|
||||
services/standard/gui/formstandardimportexport.ui \
|
||||
services/standard/gui/standardfeeddetails.ui \
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "services/feedly/feedlyentrypoint.h"
|
||||
#include "services/gmail/gmailentrypoint.h"
|
||||
#include "services/greader/greaderentrypoint.h"
|
||||
#include "services/inoreader/inoreaderentrypoint.h"
|
||||
#include "services/owncloud/owncloudserviceentrypoint.h"
|
||||
#include "services/standard/standardserviceentrypoint.h"
|
||||
#include "services/tt-rss/ttrssserviceentrypoint.h"
|
||||
@ -61,7 +60,6 @@ QList<ServiceEntryPoint*> FeedReader::feedServices() {
|
||||
m_feedServices.append(new FeedlyEntryPoint());
|
||||
m_feedServices.append(new GmailEntryPoint());
|
||||
m_feedServices.append(new GreaderEntryPoint());
|
||||
m_feedServices.append(new InoreaderEntryPoint());
|
||||
m_feedServices.append(new OwnCloudServiceEntryPoint());
|
||||
m_feedServices.append(new StandardServiceEntryPoint());
|
||||
m_feedServices.append(new TtRssServiceEntryPoint());
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "network-web/oauthhttphandler.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/inoreader/definitions.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QInputDialog>
|
||||
|
@ -1,34 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef INOREADER_DEFINITIONS_H
|
||||
#define INOREADER_DEFINITIONS_H
|
||||
|
||||
#define INOREADER_OAUTH_REDIRECT_URI_PORT 14488
|
||||
#define INOREADER_OAUTH_SCOPE "read write"
|
||||
#define INOREADER_OAUTH_TOKEN_URL "https://www.inoreader.com/oauth2/token"
|
||||
#define INOREADER_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth"
|
||||
#define INOREADER_REG_API_URL "https://www.inoreader.com/developers/register-app"
|
||||
|
||||
#define INOREADER_REFRESH_TOKEN_KEY "refresh_token"
|
||||
#define INOREADER_ACCESS_TOKEN_KEY "access_token"
|
||||
|
||||
#define INOREADER_DEFAULT_BATCH_SIZE 100
|
||||
#define INOREADER_MAX_BATCH_SIZE 999
|
||||
#define INOREADER_MIN_BATCH_SIZE 20
|
||||
#define INOREADER_API_EDIT_TAG_BATCH 50
|
||||
|
||||
#define INOREADER_STATE_READING_LIST "state/com.google/reading-list"
|
||||
#define INOREADER_STATE_READ "state/com.google/read"
|
||||
#define INOREADER_STATE_IMPORTANT "state/com.google/starred"
|
||||
|
||||
#define INOREADER_FULL_STATE_READING_LIST "user/-/state/com.google/reading-list"
|
||||
#define INOREADER_FULL_STATE_READ "user/-/state/com.google/read"
|
||||
#define INOREADER_FULL_STATE_IMPORTANT "user/-/state/com.google/starred"
|
||||
|
||||
#define INOREADER_API_FEED_CONTENTS "https://www.inoreader.com/reader/api/0/stream/contents"
|
||||
#define INOREADER_API_LIST_LABELS "https://www.inoreader.com/reader/api/0/tag/list?types=1"
|
||||
#define INOREADER_API_LIST_FEEDS "https://www.inoreader.com/reader/api/0/subscription/list"
|
||||
#define INOREADER_API_EDIT_TAG "https://www.inoreader.com/reader/api/0/edit-tag"
|
||||
#define INOREADER_API_USER_INFO "https://www.inoreader.com/reader/api/0/user-info"
|
||||
|
||||
#endif // INOREADER_DEFINITIONS_H
|
@ -1,67 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/inoreader/gui/formeditinoreaderaccount.h"
|
||||
|
||||
#include "gui/guiutilities.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/inoreader/definitions.h"
|
||||
#include "services/inoreader/gui/inoreaderaccountdetails.h"
|
||||
#include "services/inoreader/inoreadernetworkfactory.h"
|
||||
#include "services/inoreader/inoreaderserviceroot.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
FormEditInoreaderAccount::FormEditInoreaderAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("inoreader")), parent), m_details(new InoreaderAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Server setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditInoreaderAccount::testSetup);
|
||||
|
||||
m_details->m_ui.m_txtUsername->setFocus();
|
||||
}
|
||||
|
||||
void FormEditInoreaderAccount::apply() {
|
||||
FormAccountDetails::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(),
|
||||
true);
|
||||
|
||||
account<InoreaderServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<InoreaderServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<InoreaderServiceRoot>()->network()->setDownloadOnlyUnreadMessages(m_details->m_ui.m_cbDownloadOnlyUnreadMessages->isChecked());
|
||||
|
||||
account<InoreaderServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew) {
|
||||
account<InoreaderServiceRoot>()->completelyRemoveAllData();
|
||||
account<InoreaderServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditInoreaderAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
m_details->m_oauth = account<InoreaderServiceRoot>()->network()->oauth();
|
||||
m_details->hookNetwork();
|
||||
|
||||
// Setup the GUI.
|
||||
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(account<InoreaderServiceRoot>()->network()->username());
|
||||
m_details->m_ui.m_spinLimitMessages->setValue(account<InoreaderServiceRoot>()->network()->batchSize());
|
||||
m_details->m_ui.m_cbDownloadOnlyUnreadMessages->setChecked(account<InoreaderServiceRoot>()->network()->downloadOnlyUnreadMessages());
|
||||
}
|
||||
|
||||
void FormEditInoreaderAccount::testSetup() {
|
||||
m_details->testSetup(m_proxyDetails->proxy());
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITINOREADERACCOUNT_H
|
||||
#define FORMEDITINOREADERACCOUNT_H
|
||||
|
||||
#include "services/abstract/gui/formaccountdetails.h"
|
||||
|
||||
class InoreaderServiceRoot;
|
||||
class InoreaderAccountDetails;
|
||||
|
||||
class FormEditInoreaderAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditInoreaderAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private slots:
|
||||
void testSetup();
|
||||
|
||||
private:
|
||||
InoreaderAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITINOREADERACCOUNT_H
|
@ -1,139 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/inoreader/gui/inoreaderaccountdetails.h"
|
||||
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "gui/guiutilities.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/inoreader/definitions.h"
|
||||
#include "services/inoreader/inoreadernetworkfactory.h"
|
||||
|
||||
InoreaderAccountDetails::InoreaderAccountDetails(QWidget* parent)
|
||||
: QWidget(parent), m_oauth(nullptr), m_lastProxy({}) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true);
|
||||
|
||||
#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 it preconfigured tokens have limited global usage quota. If you wish "
|
||||
"to use preconfigured tokens, simply leave those fields empty and make sure "
|
||||
"to leave default value of redirect URL."));
|
||||
#else
|
||||
m_ui.m_lblInfo->setText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."));
|
||||
#endif
|
||||
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
|
||||
tr("Not tested yet."),
|
||||
tr("Not tested yet."));
|
||||
m_ui.m_lblTestResult->label()->setWordWrap(true);
|
||||
m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("User-visible username"));
|
||||
|
||||
setTabOrder(m_ui.m_txtUsername->lineEdit(), 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_cbDownloadOnlyUnreadMessages);
|
||||
setTabOrder(m_ui.m_cbDownloadOnlyUnreadMessages, m_ui.m_spinLimitMessages);
|
||||
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup);
|
||||
|
||||
connect(m_ui.m_txtAppId->lineEdit(), &BaseLineEdit::textChanged, this, &InoreaderAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtAppKey->lineEdit(), &BaseLineEdit::textChanged, this, &InoreaderAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtRedirectUrl->lineEdit(), &BaseLineEdit::textChanged, this, &InoreaderAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &InoreaderAccountDetails::checkUsername);
|
||||
connect(m_ui.m_btnRegisterApi, &QPushButton::clicked, this, &InoreaderAccountDetails::registerApi);
|
||||
|
||||
emit m_ui.m_txtUsername->lineEdit()->textChanged(m_ui.m_txtUsername->lineEdit()->text());
|
||||
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());
|
||||
|
||||
hookNetwork();
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::testSetup(const QNetworkProxy& custom_proxy) {
|
||||
m_lastProxy = custom_proxy;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::checkUsername(const QString& username) {
|
||||
if (username.isEmpty()) {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("No username entered."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some username entered."));
|
||||
}
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::onAuthFailed() {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("You did not grant access."),
|
||||
tr("There was error during testing."));
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::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 InoreaderAccountDetails::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 {
|
||||
InoreaderNetworkFactory 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_INOREADER
|
||||
<< "Failed to obtain profile with error:"
|
||||
<< QUOTE_W_SPACE_DOT(ex.message());
|
||||
}
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::hookNetwork() {
|
||||
if (m_oauth != nullptr) {
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &InoreaderAccountDetails::onAuthGranted);
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &InoreaderAccountDetails::onAuthError);
|
||||
connect(m_oauth, &OAuth2Service::authFailed, this, &InoreaderAccountDetails::onAuthFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::registerApi() {
|
||||
qApp->web()->openUrlInExternalBrowser(INOREADER_REG_API_URL);
|
||||
}
|
||||
|
||||
void InoreaderAccountDetails::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."));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef INOREADERACCOUNTDETAILS_H
|
||||
#define INOREADERACCOUNTDETAILS_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui_inoreaderaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
|
||||
class OAuth2Service;
|
||||
|
||||
class InoreaderAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditInoreaderAccount;
|
||||
|
||||
public:
|
||||
explicit InoreaderAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void registerApi();
|
||||
void testSetup(const QNetworkProxy& custom_proxy);;
|
||||
void checkOAuthValue(const QString& value);
|
||||
void checkUsername(const QString& username);
|
||||
void onAuthFailed();
|
||||
void onAuthError(const QString& error, const QString& detailed_description);
|
||||
void onAuthGranted();
|
||||
|
||||
private:
|
||||
void hookNetwork();
|
||||
|
||||
private:
|
||||
Ui::InoreaderAccountDetails 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 // INOREADERACCOUNTDETAILS_H
|
@ -1,216 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>InoreaderAccountDetails</class>
|
||||
<widget class="QWidget" name="InoreaderAccountDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>427</width>
|
||||
<height>265</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<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" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>OAuth 2.0 settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<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">
|
||||
<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>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Only download newest X articles per feed</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_spinLimitMessages</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MessageCountSpinBox" name="m_spinLimitMessages">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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="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>&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>
|
||||
<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>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_cbDownloadOnlyUnreadMessages">
|
||||
<property name="text">
|
||||
<string>Download unread articles only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LabelWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>labelwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LineEditWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>lineeditwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageCountSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>messagecountspinbox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,46 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/inoreader/inoreaderentrypoint.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "database/databasequeries.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/inoreader/definitions.h"
|
||||
#include "services/inoreader/gui/formeditinoreaderaccount.h"
|
||||
#include "services/inoreader/inoreadernetworkfactory.h"
|
||||
#include "services/inoreader/inoreaderserviceroot.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
ServiceRoot* InoreaderEntryPoint::createNewRoot() const {
|
||||
FormEditInoreaderAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<InoreaderServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> InoreaderEntryPoint::initializeSubtree() const {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("InoreaderEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<InoreaderServiceRoot>(database, code());
|
||||
}
|
||||
|
||||
QString InoreaderEntryPoint::name() const {
|
||||
return QSL("Inoreader");
|
||||
}
|
||||
|
||||
QString InoreaderEntryPoint::code() const {
|
||||
return SERVICE_CODE_INOREADER;
|
||||
}
|
||||
|
||||
QString InoreaderEntryPoint::description() const {
|
||||
return QObject::tr("This is integration of Inoreader.");
|
||||
}
|
||||
|
||||
QString InoreaderEntryPoint::author() const {
|
||||
return APP_AUTHOR;
|
||||
}
|
||||
|
||||
QIcon InoreaderEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("inoreader"));
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef INOREADERENTRYPOINT_H
|
||||
#define INOREADERENTRYPOINT_H
|
||||
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
|
||||
class InoreaderEntryPoint : 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 // INOREADERENTRYPOINT_H
|
@ -1,504 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/inoreader/inoreadernetworkfactory.h"
|
||||
|
||||
#include "3rd-party/boolinq/boolinq.h"
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "gui/dialogs/formmain.h"
|
||||
#include "gui/tabwidget.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "network-web/silentnetworkaccessmanager.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/abstract/category.h"
|
||||
#include "services/abstract/labelsnode.h"
|
||||
#include "services/inoreader/definitions.h"
|
||||
#include "services/inoreader/inoreaderserviceroot.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
||||
InoreaderNetworkFactory::InoreaderNetworkFactory(QObject* parent) : QObject(parent),
|
||||
m_service(nullptr), m_username(QString()), m_downloadOnlyUnreadMessages(false), m_batchSize(INOREADER_DEFAULT_BATCH_SIZE),
|
||||
m_oauth2(new OAuth2Service(INOREADER_OAUTH_AUTH_URL, INOREADER_OAUTH_TOKEN_URL,
|
||||
{}, {}, INOREADER_OAUTH_SCOPE, this)) {
|
||||
initializeOauth();
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::setService(InoreaderServiceRoot* service) {
|
||||
m_service = service;
|
||||
}
|
||||
|
||||
OAuth2Service* InoreaderNetworkFactory::oauth() const {
|
||||
return m_oauth2;
|
||||
}
|
||||
|
||||
QString InoreaderNetworkFactory::username() const {
|
||||
return m_username;
|
||||
}
|
||||
|
||||
int InoreaderNetworkFactory::batchSize() const {
|
||||
return m_batchSize;
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::setBatchSize(int batch_size) {
|
||||
m_batchSize = batch_size;
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::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(INOREADER_OAUTH_REDIRECT_URI_PORT),
|
||||
true);
|
||||
|
||||
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &InoreaderNetworkFactory::onTokensError);
|
||||
connect(m_oauth2, &OAuth2Service::authFailed, this, &InoreaderNetworkFactory::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_service != nullptr && m_service->accountId() > 0 && !refresh_token.isEmpty()) {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool InoreaderNetworkFactory::downloadOnlyUnreadMessages() const {
|
||||
return m_downloadOnlyUnreadMessages;
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::setDownloadOnlyUnreadMessages(bool download_only_unread) {
|
||||
m_downloadOnlyUnreadMessages = download_only_unread;
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::setUsername(const QString& username) {
|
||||
m_username = username;
|
||||
}
|
||||
|
||||
RootItem* InoreaderNetworkFactory::feedsCategories(bool obtain_icons) {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray output_labels;
|
||||
auto result_labels = NetworkFactory::performNetworkOperation(INOREADER_API_LIST_LABELS,
|
||||
qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(),
|
||||
{},
|
||||
output_labels,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{ { QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
bearer.toLocal8Bit() } },
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result_labels.first != QNetworkReply::NetworkError::NoError) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray output_feeds;
|
||||
auto result_feeds = NetworkFactory::performNetworkOperation(INOREADER_API_LIST_FEEDS,
|
||||
qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(),
|
||||
{},
|
||||
output_feeds,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{ { QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
bearer.toLocal8Bit() } },
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result_feeds.first != QNetworkReply::NetworkError::NoError) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decodeFeedCategoriesData(output_labels, output_feeds, obtain_icons);
|
||||
}
|
||||
|
||||
QVariantHash InoreaderNetworkFactory::userInfo(const QNetworkProxy& custom_proxy) {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("not logged in"));
|
||||
}
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
auto res = NetworkFactory::performNetworkOperation(INOREADER_API_USER_INFO,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{ { QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
bearer.toLocal8Bit() } },
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (res.first != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(res.first);
|
||||
}
|
||||
|
||||
return QJsonDocument::fromJson(output).object().toVariantHash();
|
||||
}
|
||||
|
||||
QList<RootItem*> InoreaderNetworkFactory::getLabels() {
|
||||
QList<RootItem*> lbls;
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
return lbls;
|
||||
}
|
||||
|
||||
QByteArray output;
|
||||
auto result = NetworkFactory::performNetworkOperation(INOREADER_API_LIST_LABELS,
|
||||
qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(),
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{ { QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
bearer.toLocal8Bit() } },
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
QJsonDocument json_lbls = QJsonDocument::fromJson(output);
|
||||
auto json_tags = json_lbls.object()["tags"].toArray();
|
||||
|
||||
for (const QJsonValue& lbl_val : qAsConst(json_tags)) {
|
||||
QJsonObject lbl_obj = lbl_val.toObject();
|
||||
|
||||
if (lbl_obj["type"] == QL1S("tag")) {
|
||||
QString name_id = lbl_obj["id"].toString();
|
||||
QString plain_name = QRegularExpression(".+\\/([^\\/]+)").match(name_id).captured(1);
|
||||
auto* new_lbl = new Label(plain_name, TextFactory::generateColorFromText(name_id));
|
||||
|
||||
new_lbl->setCustomId(name_id);
|
||||
lbls.append(new_lbl);
|
||||
}
|
||||
}
|
||||
|
||||
return lbls;
|
||||
}
|
||||
|
||||
QList<Message> InoreaderNetworkFactory::messages(ServiceRoot* root, const QString& stream_id, Feed::Status& error) {
|
||||
QString target_url = INOREADER_API_FEED_CONTENTS;
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_INOREADER
|
||||
<< "Cannot download messages for"
|
||||
<< QUOTE_NO_SPACE(stream_id)
|
||||
<< ", bearer is empty.";
|
||||
error = Feed::Status::AuthError;
|
||||
return QList<Message>();
|
||||
}
|
||||
|
||||
target_url += QSL("/") + QUrl::toPercentEncoding(stream_id) + QString("?n=%1").arg(batchSize() <= 0
|
||||
? INOREADER_MAX_BATCH_SIZE
|
||||
: batchSize());
|
||||
|
||||
if (downloadOnlyUnreadMessages()) {
|
||||
target_url += QSL("&xt=%1").arg(INOREADER_FULL_STATE_READ);
|
||||
}
|
||||
|
||||
QByteArray output_msgs;
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(),
|
||||
{},
|
||||
output_msgs,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{ { QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
bearer.toLocal8Bit() } },
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.first != QNetworkReply::NetworkError::NoError) {
|
||||
qCriticalNN << LOGSEC_INOREADER
|
||||
<< "Cannot download messages for "
|
||||
<< QUOTE_NO_SPACE(stream_id)
|
||||
<< ", network error:"
|
||||
<< QUOTE_W_SPACE_DOT(result.first);
|
||||
error = Feed::Status::NetworkError;
|
||||
return QList<Message>();
|
||||
}
|
||||
else {
|
||||
error = Feed::Status::Normal;
|
||||
return decodeMessages(root, output_msgs, stream_id);
|
||||
}
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError InoreaderNetworkFactory::editLabels(const QString& state, bool assign,
|
||||
const QStringList& msg_custom_ids) {
|
||||
QString target_url = INOREADER_API_EDIT_TAG;
|
||||
|
||||
if (assign) {
|
||||
target_url += QString("?a=") + state + "&";
|
||||
}
|
||||
else {
|
||||
target_url += QString("?r=") + state + "&";
|
||||
}
|
||||
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
return QNetworkReply::NetworkError::AuthenticationRequiredError;
|
||||
}
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
m_oauth2->bearer().toLocal8Bit()));
|
||||
|
||||
QStringList trimmed_ids;
|
||||
|
||||
for (const QString& id : msg_custom_ids) {
|
||||
trimmed_ids.append(QString("i=") + id);
|
||||
}
|
||||
|
||||
QStringList working_subset; working_subset.reserve(std::min(INOREADER_API_EDIT_TAG_BATCH, trimmed_ids.size()));
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
|
||||
// Now, we perform messages update in batches (max XX messages per batch).
|
||||
while (!trimmed_ids.isEmpty()) {
|
||||
// We take XX IDs.
|
||||
for (int i = 0; i < INOREADER_API_EDIT_TAG_BATCH && !trimmed_ids.isEmpty(); i++) {
|
||||
working_subset.append(trimmed_ids.takeFirst());
|
||||
}
|
||||
|
||||
QString batch_final_url = target_url + working_subset.join(QL1C('&'));
|
||||
|
||||
// We send this batch.
|
||||
QByteArray output;
|
||||
auto result = NetworkFactory::performNetworkOperation(batch_final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.first != QNetworkReply::NetworkError::NoError) {
|
||||
return result.first;
|
||||
}
|
||||
|
||||
// Cleanup for next batch.
|
||||
working_subset.clear();
|
||||
}
|
||||
|
||||
return QNetworkReply::NetworkError::NoError;
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError InoreaderNetworkFactory::markMessagesRead(RootItem::ReadStatus status, const QStringList& msg_custom_ids) {
|
||||
return editLabels(INOREADER_FULL_STATE_READ, status == RootItem::ReadStatus::Read, msg_custom_ids);
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError InoreaderNetworkFactory::markMessagesStarred(RootItem::Importance importance, const QStringList& msg_custom_ids) {
|
||||
return editLabels(INOREADER_FULL_STATE_IMPORTANT, importance == RootItem::Importance::Important, msg_custom_ids);
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::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 InoreaderNetworkFactory::onAuthFailed() {
|
||||
qApp->showGuiMessage(Notification::Event::GeneralEvent,
|
||||
tr("Inoreader: authorization denied"),
|
||||
tr("Click this to login again."),
|
||||
QSystemTrayIcon::MessageIcon::Critical,
|
||||
{}, {},
|
||||
[this]() {
|
||||
m_oauth2->login();
|
||||
});
|
||||
}
|
||||
|
||||
QList<Message> InoreaderNetworkFactory::decodeMessages(ServiceRoot* root, const QString& messages_json_data, const QString& stream_id) {
|
||||
QList<Message> messages;
|
||||
QJsonArray json = QJsonDocument::fromJson(messages_json_data.toUtf8()).object()["items"].toArray();
|
||||
auto active_labels = root->labelsNode() != nullptr ? root->labelsNode()->labels() : QList<Label*>();
|
||||
|
||||
messages.reserve(json.count());
|
||||
|
||||
for (const QJsonValue& obj : json) {
|
||||
auto message_obj = obj.toObject();
|
||||
Message message;
|
||||
|
||||
message.m_title = qApp->web()->unescapeHtml(message_obj["title"].toString());
|
||||
message.m_author = qApp->web()->unescapeHtml(message_obj["author"].toString());
|
||||
message.m_created = QDateTime::fromSecsSinceEpoch(message_obj["published"].toInt(), Qt::UTC);
|
||||
message.m_createdFromFeed = true;
|
||||
message.m_customId = message_obj["id"].toString();
|
||||
|
||||
auto alternates = message_obj["alternate"].toArray();
|
||||
auto enclosures = message_obj["enclosure"].toArray();
|
||||
auto categories = message_obj["categories"].toArray();
|
||||
|
||||
for (const QJsonValue& alt : alternates) {
|
||||
auto alt_obj = alt.toObject();
|
||||
QString mime = alt_obj["type"].toString();
|
||||
QString href = alt_obj["href"].toString();
|
||||
|
||||
if (mime == QL1S("text/html")) {
|
||||
message.m_url = href;
|
||||
}
|
||||
else {
|
||||
message.m_enclosures.append(Enclosure(href, mime));
|
||||
}
|
||||
}
|
||||
|
||||
for (const QJsonValue& enc : enclosures) {
|
||||
auto enc_obj = enc.toObject();
|
||||
QString mime = enc_obj["type"].toString();
|
||||
QString href = enc_obj["href"].toString();
|
||||
|
||||
message.m_enclosures.append(Enclosure(href, mime));
|
||||
}
|
||||
|
||||
for (const QJsonValue& cat : categories) {
|
||||
QString category = cat.toString();
|
||||
|
||||
if (category.contains(INOREADER_STATE_READ)) {
|
||||
message.m_isRead = !category.contains(INOREADER_STATE_READING_LIST);
|
||||
}
|
||||
else if (category.contains(INOREADER_STATE_IMPORTANT)) {
|
||||
message.m_isImportant = category.contains(INOREADER_STATE_IMPORTANT);
|
||||
}
|
||||
else if (category.contains(QSL("label"))) {
|
||||
Label* label = boolinq::from(active_labels.begin(), active_labels.end()).firstOrDefault([category](Label* lbl) {
|
||||
return lbl->customId() == category;
|
||||
});
|
||||
|
||||
if (label != nullptr) {
|
||||
// We found live Label object for our assigned label.
|
||||
message.m_assignedLabels.append(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.m_contents = message_obj["summary"].toObject()["content"].toString();
|
||||
message.m_rawContents = QJsonDocument(message_obj).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
message.m_feedId = stream_id;
|
||||
|
||||
messages.append(message);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
RootItem* InoreaderNetworkFactory::decodeFeedCategoriesData(const QString& categories, const QString& feeds, bool obtain_icons) {
|
||||
auto* parent = new RootItem();
|
||||
QJsonArray json = QJsonDocument::fromJson(categories.toUtf8()).object()["tags"].toArray();
|
||||
QMap<QString, RootItem*> cats;
|
||||
|
||||
cats.insert(QString(), parent);
|
||||
|
||||
for (const QJsonValue& obj : json) {
|
||||
auto label = obj.toObject();
|
||||
|
||||
if (label["type"].toString() == QL1S("folder")) {
|
||||
QString label_id = label["id"].toString();
|
||||
|
||||
// We have label (not "state").
|
||||
auto* category = new Category();
|
||||
|
||||
category->setDescription(label["htmlUrl"].toString());
|
||||
category->setTitle(label_id.mid(label_id.lastIndexOf(QL1C('/')) + 1));
|
||||
category->setCustomId(label_id);
|
||||
|
||||
cats.insert(category->customId(), category);
|
||||
parent->appendChild(category);
|
||||
}
|
||||
}
|
||||
|
||||
json = QJsonDocument::fromJson(feeds.toUtf8()).object()["subscriptions"].toArray();
|
||||
|
||||
for (const QJsonValue& obj : qAsConst(json)) {
|
||||
auto subscription = obj.toObject();
|
||||
QString id = subscription["id"].toString();
|
||||
QString title = subscription["title"].toString();
|
||||
QString url = subscription["htmlUrl"].toString();
|
||||
QString parent_label;
|
||||
QJsonArray assigned_categories = subscription["categories"].toArray();
|
||||
|
||||
for (const QJsonValue& cat : assigned_categories) {
|
||||
QString potential_id = cat.toObject()["id"].toString();
|
||||
|
||||
if (potential_id.contains(QSL("/label/"))) {
|
||||
parent_label = potential_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We have label (not "state").
|
||||
auto* feed = new Feed();
|
||||
|
||||
feed->setDescription(url);
|
||||
feed->setSource(url);
|
||||
feed->setTitle(title);
|
||||
feed->setCustomId(id);
|
||||
|
||||
if (obtain_icons) {
|
||||
QString icon_url = subscription["iconUrl"].toString();
|
||||
|
||||
if (!icon_url.isEmpty()) {
|
||||
QByteArray icon_data;
|
||||
|
||||
if (NetworkFactory::performNetworkOperation(icon_url, DOWNLOAD_TIMEOUT,
|
||||
QByteArray(), icon_data,
|
||||
QNetworkAccessManager::GetOperation).first == QNetworkReply::NoError) {
|
||||
// Icon downloaded, set it up.
|
||||
QPixmap icon_pixmap;
|
||||
|
||||
icon_pixmap.loadFromData(icon_data);
|
||||
feed->setIcon(QIcon(icon_pixmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cats.contains(parent_label)) {
|
||||
cats[parent_label]->appendChild(feed);
|
||||
}
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
void InoreaderNetworkFactory::setOauth(OAuth2Service* oauth) {
|
||||
m_oauth2 = oauth;
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef INOREADERNETWORKFACTORY_H
|
||||
#define INOREADERNETWORKFACTORY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/message.h"
|
||||
|
||||
#include "services/abstract/feed.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
class RootItem;
|
||||
class InoreaderServiceRoot;
|
||||
class OAuth2Service;
|
||||
|
||||
class InoreaderNetworkFactory : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InoreaderNetworkFactory(QObject* parent = nullptr);
|
||||
|
||||
void setService(InoreaderServiceRoot* service);
|
||||
|
||||
OAuth2Service* oauth() const;
|
||||
void setOauth(OAuth2Service* oauth);
|
||||
|
||||
QString username() const;
|
||||
void setUsername(const QString& username);
|
||||
|
||||
// Gets/sets the amount of messages to obtain during single feed update.
|
||||
int batchSize() const;
|
||||
void setBatchSize(int batch_size);
|
||||
|
||||
// Returns tree of feeds/categories.
|
||||
// Top-level root of the tree is not needed here.
|
||||
// Returned items do not have primary IDs assigned.
|
||||
RootItem* feedsCategories(bool obtain_icons);
|
||||
|
||||
QVariantHash userInfo(const QNetworkProxy& custom_proxy);
|
||||
QList<RootItem*> getLabels();
|
||||
QList<Message> messages(ServiceRoot* root, const QString& stream_id, Feed::Status& error);
|
||||
QNetworkReply::NetworkError editLabels(const QString& state, bool assign, const QStringList& msg_custom_ids);
|
||||
QNetworkReply::NetworkError markMessagesRead(RootItem::ReadStatus status, const QStringList& msg_custom_ids);
|
||||
QNetworkReply::NetworkError markMessagesStarred(RootItem::Importance importance, const QStringList& msg_custom_ids);
|
||||
|
||||
bool downloadOnlyUnreadMessages() const;
|
||||
void setDownloadOnlyUnreadMessages(bool download_only_unread);
|
||||
|
||||
private slots:
|
||||
void onTokensError(const QString& error, const QString& error_description);
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
QList<Message> decodeMessages(ServiceRoot* root, const QString& messages_json_data, const QString& stream_id);
|
||||
RootItem* decodeFeedCategoriesData(const QString& categories, const QString& feeds, bool obtain_icons);
|
||||
|
||||
void initializeOauth();
|
||||
|
||||
private:
|
||||
InoreaderServiceRoot* m_service;
|
||||
QString m_username;
|
||||
bool m_downloadOnlyUnreadMessages;
|
||||
int m_batchSize;
|
||||
OAuth2Service* m_oauth2;
|
||||
};
|
||||
|
||||
#endif // INOREADERNETWORKFACTORY_H
|
@ -1,211 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/inoreader/inoreaderserviceroot.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "exceptions/feedfetchexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/abstract/importantnode.h"
|
||||
#include "services/abstract/labelsnode.h"
|
||||
#include "services/abstract/recyclebin.h"
|
||||
#include "services/inoreader/gui/formeditinoreaderaccount.h"
|
||||
#include "services/inoreader/inoreaderentrypoint.h"
|
||||
#include "services/inoreader/inoreadernetworkfactory.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
InoreaderServiceRoot::InoreaderServiceRoot(RootItem* parent)
|
||||
: ServiceRoot(parent), m_network(new InoreaderNetworkFactory(this)) {
|
||||
m_network->setService(this);
|
||||
setIcon(InoreaderEntryPoint().icon());
|
||||
}
|
||||
|
||||
InoreaderServiceRoot::~InoreaderServiceRoot() = default;
|
||||
|
||||
void InoreaderServiceRoot::updateTitle() {
|
||||
setTitle(TextFactory::extractUsernameFromEmail(m_network->username()) + QSL(" (Inoreader)"));
|
||||
}
|
||||
|
||||
ServiceRoot::LabelOperation InoreaderServiceRoot::supportedLabelOperations() const {
|
||||
return ServiceRoot::LabelOperation(0);
|
||||
}
|
||||
|
||||
QVariantHash InoreaderServiceRoot::customDatabaseData() const {
|
||||
QVariantHash data;
|
||||
|
||||
data["username"] = m_network->username();
|
||||
data["download_only_unread"] = m_network->downloadOnlyUnreadMessages();
|
||||
data["batch_size"] = m_network->batchSize();
|
||||
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;
|
||||
}
|
||||
|
||||
void InoreaderServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
m_network->setUsername(data["username"].toString());
|
||||
m_network->setBatchSize(data["batch_size"].toInt());
|
||||
m_network->setDownloadOnlyUnreadMessages(data["download_only_unread"].toBool());
|
||||
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);
|
||||
}
|
||||
|
||||
QList<Message> InoreaderServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(stated_messages)
|
||||
Q_UNUSED(tagged_messages)
|
||||
|
||||
Feed::Status error = Feed::Status::Normal;
|
||||
QList<Message> messages = network()->messages(this, feed->customId(), error);
|
||||
|
||||
if (error != Feed::Status::NewMessages && error != Feed::Status::Normal) {
|
||||
throw FeedFetchException(error);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
bool InoreaderServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InoreaderServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InoreaderServiceRoot::editViaGui() {
|
||||
FormEditInoreaderAccount form_pointer(qApp->mainFormWidget());
|
||||
|
||||
form_pointer.addEditAccount(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InoreaderServiceRoot::supportsFeedAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InoreaderServiceRoot::supportsCategoryAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void InoreaderServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
|
||||
loadCacheFromFile();
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
m_network->oauth()->login([this]() {
|
||||
syncIn();
|
||||
});
|
||||
}
|
||||
else {
|
||||
m_network->oauth()->login();
|
||||
}
|
||||
}
|
||||
|
||||
QString InoreaderServiceRoot::code() const {
|
||||
return InoreaderEntryPoint().code();
|
||||
}
|
||||
|
||||
QString InoreaderServiceRoot::additionalTooltip() const {
|
||||
return tr("Authentication status: %1\n"
|
||||
"Login tokens expiration: %2").arg(network()->oauth()->isFullyLoggedIn() ? tr("logged-in") : tr("NOT logged-in"),
|
||||
network()->oauth()->tokensExpireIn().isValid() ?
|
||||
network()->oauth()->tokensExpireIn().toString() : QSL("-"));
|
||||
}
|
||||
|
||||
RootItem* InoreaderServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
auto tree = m_network->feedsCategories(true);
|
||||
|
||||
if (tree != nullptr) {
|
||||
auto* lblroot = new LabelsNode(tree);
|
||||
auto labels = m_network->getLabels();
|
||||
|
||||
lblroot->setChildItems(labels);
|
||||
tree->appendChild(lblroot);
|
||||
|
||||
return tree;
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void InoreaderServiceRoot::saveAllCachedData(bool ignore_errors) {
|
||||
auto msg_cache = takeMessageCache();
|
||||
QMapIterator<RootItem::ReadStatus, QStringList> i(msg_cache.m_cachedStatesRead);
|
||||
|
||||
// Save the actual data read/unread.
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
auto key = i.key();
|
||||
QStringList ids = i.value();
|
||||
|
||||
if (!ids.isEmpty()) {
|
||||
if (network()->markMessagesRead(key, ids) != QNetworkReply::NetworkError::NoError && !ignore_errors) {
|
||||
addMessageStatesToCache(ids, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<RootItem::Importance, QList<Message>> j(msg_cache.m_cachedStatesImportant);
|
||||
|
||||
// Save the actual data important/not important.
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
auto key = j.key();
|
||||
QList<Message> messages = j.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
QStringList custom_ids;
|
||||
|
||||
for (const Message& msg : messages) {
|
||||
custom_ids.append(msg.m_customId);
|
||||
}
|
||||
|
||||
if (network()->markMessagesStarred(key, custom_ids) != QNetworkReply::NetworkError::NoError && !ignore_errors) {
|
||||
addMessageStatesToCache(messages, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<QString, QStringList> k(msg_cache.m_cachedLabelAssignments);
|
||||
|
||||
// Assign label for these messages.
|
||||
while (k.hasNext()) {
|
||||
k.next();
|
||||
auto label_custom_id = k.key();
|
||||
QStringList messages = k.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
if (network()->editLabels(label_custom_id, true, messages) != QNetworkReply::NetworkError::NoError && !ignore_errors) {
|
||||
addLabelsAssignmentsToCache(messages, label_custom_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<QString, QStringList> l(msg_cache.m_cachedLabelDeassignments);
|
||||
|
||||
// Remove label from these messages.
|
||||
while (l.hasNext()) {
|
||||
l.next();
|
||||
auto label_custom_id = l.key();
|
||||
QStringList messages = l.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
if (network()->editLabels(label_custom_id, false, messages) != QNetworkReply::NetworkError::NoError && !ignore_errors) {
|
||||
addLabelsAssignmentsToCache(messages, label_custom_id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef INOREADERSERVICEROOT_H
|
||||
#define INOREADERSERVICEROOT_H
|
||||
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
|
||||
class InoreaderNetworkFactory;
|
||||
|
||||
class InoreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InoreaderServiceRoot(RootItem* parent = nullptr);
|
||||
virtual ~InoreaderServiceRoot();
|
||||
|
||||
InoreaderNetworkFactory* network() const;
|
||||
|
||||
virtual LabelOperation supportedLabelOperations() const;
|
||||
virtual bool isSyncable() const;
|
||||
virtual bool canBeEdited() const;
|
||||
virtual bool editViaGui();
|
||||
virtual bool supportsFeedAdding() const;
|
||||
virtual bool supportsCategoryAdding() const;
|
||||
virtual void start(bool freshly_activated);
|
||||
virtual QString code() const;
|
||||
virtual QString additionalTooltip() const;
|
||||
virtual void saveAllCachedData(bool ignore_errors);
|
||||
virtual QVariantHash customDatabaseData() const;
|
||||
virtual void setCustomDatabaseData(const QVariantHash& data);
|
||||
virtual QList<Message> obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages);
|
||||
|
||||
protected:
|
||||
virtual RootItem* obtainNewTreeForSyncIn() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
|
||||
private:
|
||||
InoreaderNetworkFactory* m_network;
|
||||
};
|
||||
|
||||
inline InoreaderNetworkFactory* InoreaderServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
#endif // INOREADERSERVICEROOT_H
|
Loading…
x
Reference in New Issue
Block a user