nextcloud
This commit is contained in:
parent
b9cb1471a5
commit
e7a388bb26
@ -327,6 +327,8 @@ add_subdirectory(src/librssguard-standard)
|
||||
add_subdirectory(src/librssguard-feedly)
|
||||
add_subdirectory(src/librssguard-gmail)
|
||||
add_subdirectory(src/librssguard-greader)
|
||||
add_subdirectory(src/librssguard-ttrss)
|
||||
add_subdirectory(src/librssguard-nextcloud)
|
||||
|
||||
# GUI executable.
|
||||
add_subdirectory(src/rssguard)
|
||||
|
@ -324,54 +324,6 @@ set(SOURCES
|
||||
services/abstract/serviceroot.h
|
||||
services/abstract/unreadnode.cpp
|
||||
services/abstract/unreadnode.h
|
||||
services/owncloud/definitions.h
|
||||
services/owncloud/gui/formeditowncloudaccount.cpp
|
||||
services/owncloud/gui/formeditowncloudaccount.h
|
||||
services/owncloud/gui/owncloudaccountdetails.cpp
|
||||
services/owncloud/gui/owncloudaccountdetails.h
|
||||
services/owncloud/owncloudfeed.cpp
|
||||
services/owncloud/owncloudfeed.h
|
||||
services/owncloud/owncloudnetworkfactory.cpp
|
||||
services/owncloud/owncloudnetworkfactory.h
|
||||
services/owncloud/owncloudserviceentrypoint.cpp
|
||||
services/owncloud/owncloudserviceentrypoint.h
|
||||
services/owncloud/owncloudserviceroot.cpp
|
||||
services/owncloud/owncloudserviceroot.h
|
||||
services/reddit/definitions.h
|
||||
services/reddit/gui/formeditredditaccount.cpp
|
||||
services/reddit/gui/formeditredditaccount.h
|
||||
services/reddit/gui/redditaccountdetails.cpp
|
||||
services/reddit/gui/redditaccountdetails.h
|
||||
services/reddit/redditcategory.cpp
|
||||
services/reddit/redditcategory.h
|
||||
services/reddit/redditentrypoint.cpp
|
||||
services/reddit/redditentrypoint.h
|
||||
services/reddit/redditnetworkfactory.cpp
|
||||
services/reddit/redditnetworkfactory.h
|
||||
services/reddit/redditserviceroot.cpp
|
||||
services/reddit/redditserviceroot.h
|
||||
services/reddit/redditsubscription.cpp
|
||||
services/reddit/redditsubscription.h
|
||||
services/tt-rss/definitions.h
|
||||
services/tt-rss/gui/formeditttrssaccount.cpp
|
||||
services/tt-rss/gui/formeditttrssaccount.h
|
||||
services/tt-rss/gui/formttrssfeeddetails.cpp
|
||||
services/tt-rss/gui/formttrssfeeddetails.h
|
||||
services/tt-rss/gui/formttrssnote.cpp
|
||||
services/tt-rss/gui/formttrssnote.h
|
||||
services/tt-rss/gui/ttrssaccountdetails.cpp
|
||||
services/tt-rss/gui/ttrssaccountdetails.h
|
||||
services/tt-rss/gui/ttrssfeeddetails.cpp
|
||||
services/tt-rss/gui/ttrssfeeddetails.h
|
||||
services/tt-rss/ttrssfeed.cpp
|
||||
services/tt-rss/ttrssfeed.h
|
||||
services/tt-rss/ttrssnetworkfactory.cpp
|
||||
services/tt-rss/ttrssnetworkfactory.h
|
||||
services/tt-rss/ttrssnotetopublish.h
|
||||
services/tt-rss/ttrssserviceentrypoint.cpp
|
||||
services/tt-rss/ttrssserviceentrypoint.h
|
||||
services/tt-rss/ttrssserviceroot.cpp
|
||||
services/tt-rss/ttrssserviceroot.h
|
||||
)
|
||||
|
||||
set(UI_FILES
|
||||
@ -415,11 +367,6 @@ set(UI_FILES
|
||||
services/abstract/gui/formaddeditprobe.ui
|
||||
services/abstract/gui/formcategorydetails.ui
|
||||
services/abstract/gui/formfeeddetails.ui
|
||||
services/owncloud/gui/owncloudaccountdetails.ui
|
||||
services/reddit/gui/redditaccountdetails.ui
|
||||
services/tt-rss/gui/formttrssnote.ui
|
||||
services/tt-rss/gui/ttrssaccountdetails.ui
|
||||
services/tt-rss/gui/ttrssfeeddetails.ui
|
||||
)
|
||||
|
||||
if(ENABLE_MEDIAPLAYER)
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#define SERVICE_CODE_STD_RSS "std-rss"
|
||||
#define SERVICE_CODE_TT_RSS "tt-rss"
|
||||
#define SERVICE_CODE_OWNCLOUD "owncloud"
|
||||
#define SERVICE_CODE_NEXTCLOUD "nextcloud"
|
||||
#define SERVICE_CODE_GREADER "greader"
|
||||
#define SERVICE_CODE_FEEDLY "feedly"
|
||||
#define SERVICE_CODE_INOREADER "inoreader"
|
||||
|
@ -15,10 +15,8 @@
|
||||
#include "miscellaneous/pluginfactory.h"
|
||||
#include "miscellaneous/settings.h"
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
#include "services/owncloud/owncloudserviceentrypoint.h"
|
||||
#include "services/reddit/redditentrypoint.h"
|
||||
#include "services/tt-rss/ttrssserviceentrypoint.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
@ -66,14 +64,6 @@ FeedReader::~FeedReader() {
|
||||
|
||||
QList<ServiceEntryPoint*> FeedReader::feedServices() {
|
||||
if (m_feedServices.isEmpty()) {
|
||||
m_feedServices.append(new OwnCloudServiceEntryPoint());
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
m_feedServices.append(new RedditEntryPoint());
|
||||
#endif
|
||||
|
||||
m_feedServices.append(new TtRssServiceEntryPoint());
|
||||
|
||||
PluginFactory plugin_loader;
|
||||
|
||||
// Add dynamically loaded plugins.
|
||||
|
@ -1,13 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUD_DEFINITIONS_H
|
||||
#define OWNCLOUD_DEFINITIONS_H
|
||||
|
||||
#define OWNCLOUD_CONTENT_TYPE_JSON "application/json; charset=utf-8"
|
||||
#define OWNCLOUD_API_VERSION "1.2"
|
||||
#define OWNCLOUD_API_PATH "index.php/apps/news/api/v1-2/"
|
||||
#define OWNCLOUD_MIN_VERSION "6.0.5"
|
||||
#define OWNCLOUD_UNLIMITED_BATCH_SIZE -1
|
||||
#define OWNCLOUD_DEFAULT_BATCH_SIZE 100
|
||||
|
||||
#endif // OWNCLOUD_DEFINITIONS_H
|
@ -1,61 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/gui/formeditowncloudaccount.h"
|
||||
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/owncloud/gui/owncloudaccountdetails.h"
|
||||
#include "services/owncloud/owncloudnetworkfactory.h"
|
||||
#include "services/owncloud/owncloudserviceroot.h"
|
||||
|
||||
FormEditOwnCloudAccount::FormEditOwnCloudAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("nextcloud")), parent), m_details(new OwnCloudAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Server setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditOwnCloudAccount::performTest);
|
||||
|
||||
m_details->m_ui.m_txtUrl->setFocus();
|
||||
}
|
||||
|
||||
void FormEditOwnCloudAccount::apply() {
|
||||
FormAccountDetails::apply();
|
||||
|
||||
bool using_another_acc =
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->text() != account<OwnCloudServiceRoot>()->network()->authUsername() ||
|
||||
m_details->m_ui.m_txtUrl->lineEdit()->text() != account<OwnCloudServiceRoot>()->network()->url();
|
||||
|
||||
account<OwnCloudServiceRoot>()->network()->setUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
|
||||
account<OwnCloudServiceRoot>()->network()->setAuthUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<OwnCloudServiceRoot>()->network()->setAuthPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
|
||||
account<OwnCloudServiceRoot>()->network()->setForceServerSideUpdate(m_details->m_ui.m_checkServerSideUpdate
|
||||
->isChecked());
|
||||
account<OwnCloudServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<OwnCloudServiceRoot>()
|
||||
->network()
|
||||
->setDownloadOnlyUnreadMessages(m_details->m_ui.m_checkDownloadOnlyUnreadMessages->isChecked());
|
||||
|
||||
account<OwnCloudServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew && using_another_acc) {
|
||||
account<OwnCloudServiceRoot>()->completelyRemoveAllData();
|
||||
account<OwnCloudServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditOwnCloudAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
OwnCloudServiceRoot* existing_root = account<OwnCloudServiceRoot>();
|
||||
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->setText(existing_root->network()->authUsername());
|
||||
m_details->m_ui.m_txtPassword->lineEdit()->setText(existing_root->network()->authPassword());
|
||||
m_details->m_ui.m_txtUrl->lineEdit()->setText(existing_root->network()->url());
|
||||
m_details->m_ui.m_checkDownloadOnlyUnreadMessages->setChecked(existing_root->network()->downloadOnlyUnreadMessages());
|
||||
m_details->m_ui.m_checkServerSideUpdate->setChecked(existing_root->network()->forceServerSideUpdate());
|
||||
m_details->m_ui.m_spinLimitMessages->setValue(existing_root->network()->batchSize());
|
||||
}
|
||||
|
||||
void FormEditOwnCloudAccount::performTest() {
|
||||
m_details->performTest(m_proxyDetails->proxy());
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITOWNCLOUDACCOUNT_H
|
||||
#define FORMEDITOWNCLOUDACCOUNT_H
|
||||
|
||||
#include "services/abstract/gui/formaccountdetails.h"
|
||||
|
||||
class OwnCloudAccountDetails;
|
||||
class OwnCloudServiceRoot;
|
||||
|
||||
class FormEditOwnCloudAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditOwnCloudAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private slots:
|
||||
void performTest();
|
||||
|
||||
private:
|
||||
OwnCloudAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITOWNCLOUDACCOUNT_H
|
@ -1,122 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/gui/owncloudaccountdetails.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/systemfactory.h"
|
||||
#include "services/owncloud/definitions.h"
|
||||
#include "services/owncloud/owncloudnetworkfactory.h"
|
||||
|
||||
OwnCloudAccountDetails::OwnCloudAccountDetails(QWidget* parent) : QWidget(parent) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.m_lblTestResult->label()->setWordWrap(true);
|
||||
m_ui.m_lblServerSideUpdateInformation
|
||||
->setHelpText(tr("Leaving this option on causes that updates "
|
||||
"of feeds will be probably much slower and may time-out often."),
|
||||
true);
|
||||
m_ui.m_txtPassword->lineEdit()->setPlaceholderText(tr("Password for your Nextcloud account"));
|
||||
m_ui.m_txtPassword->lineEdit()->setPasswordMode(true);
|
||||
m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your Nextcloud account"));
|
||||
m_ui.m_txtUrl->lineEdit()->setPlaceholderText(tr("URL of your Nextcloud server, without any API path"));
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
|
||||
tr("No test done yet."),
|
||||
tr("Here, results of connection test are shown."));
|
||||
|
||||
connect(m_ui.m_spinLimitMessages,
|
||||
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
this,
|
||||
[=](int value) {
|
||||
if (value <= 0) {
|
||||
m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("= unlimited"));
|
||||
}
|
||||
else {
|
||||
m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("articles"));
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_ui.m_txtPassword->lineEdit(), &BaseLineEdit::textChanged, this, &OwnCloudAccountDetails::onPasswordChanged);
|
||||
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &OwnCloudAccountDetails::onUsernameChanged);
|
||||
connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &OwnCloudAccountDetails::onUrlChanged);
|
||||
|
||||
setTabOrder(m_ui.m_txtUrl->lineEdit(), m_ui.m_checkDownloadOnlyUnreadMessages);
|
||||
setTabOrder(m_ui.m_checkDownloadOnlyUnreadMessages, m_ui.m_spinLimitMessages);
|
||||
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_checkServerSideUpdate);
|
||||
setTabOrder(m_ui.m_checkServerSideUpdate, 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_btnTestSetup);
|
||||
|
||||
onPasswordChanged();
|
||||
onUsernameChanged();
|
||||
onUrlChanged();
|
||||
}
|
||||
|
||||
void OwnCloudAccountDetails::performTest(const QNetworkProxy& custom_proxy) {
|
||||
OwnCloudNetworkFactory factory;
|
||||
|
||||
factory.setAuthUsername(m_ui.m_txtUsername->lineEdit()->text());
|
||||
factory.setAuthPassword(m_ui.m_txtPassword->lineEdit()->text());
|
||||
factory.setUrl(m_ui.m_txtUrl->lineEdit()->text());
|
||||
factory.setForceServerSideUpdate(m_ui.m_checkServerSideUpdate->isChecked());
|
||||
|
||||
OwnCloudStatusResponse result = factory.status(custom_proxy);
|
||||
|
||||
if (result.networkError() != QNetworkReply::NetworkError::NoError) {
|
||||
m_ui.m_lblTestResult
|
||||
->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Network error: '%1'.").arg(NetworkFactory::networkErrorText(result.networkError())),
|
||||
tr("Network error, have you entered correct Nextcloud endpoint and password?"));
|
||||
}
|
||||
else if (result.isLoaded()) {
|
||||
if (!SystemFactory::isVersionEqualOrNewer(result.version(), QSL(OWNCLOUD_MIN_VERSION))) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Installed version: %1, required at least: %2.")
|
||||
.arg(result.version(), QSL(OWNCLOUD_MIN_VERSION)),
|
||||
tr("Selected Nextcloud News server is running unsupported version."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
|
||||
tr("Installed version: %1, required at least: %2.")
|
||||
.arg(result.version(), QSL(OWNCLOUD_MIN_VERSION)),
|
||||
tr("Nextcloud News server is okay."));
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Unspecified error, did you enter correct URL?"),
|
||||
tr("Unspecified error, did you enter correct URL?"));
|
||||
}
|
||||
}
|
||||
|
||||
void OwnCloudAccountDetails::onUsernameChanged() {
|
||||
const QString username = m_ui.m_txtUsername->lineEdit()->text();
|
||||
|
||||
if (username.isEmpty()) {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("Username cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Username is okay."));
|
||||
}
|
||||
}
|
||||
|
||||
void OwnCloudAccountDetails::onPasswordChanged() {
|
||||
const QString password = m_ui.m_txtPassword->lineEdit()->text();
|
||||
|
||||
if (password.isEmpty()) {
|
||||
m_ui.m_txtPassword->setStatus(WidgetWithStatus::StatusType::Error, tr("Password cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtPassword->setStatus(WidgetWithStatus::StatusType::Ok, tr("Password is okay."));
|
||||
}
|
||||
}
|
||||
|
||||
void OwnCloudAccountDetails::onUrlChanged() {
|
||||
const QString url = m_ui.m_txtUrl->lineEdit()->text();
|
||||
|
||||
if (url.isEmpty()) {
|
||||
m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Error, tr("URL cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Ok, tr("URL is okay."));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUDACCOUNTDETAILS_H
|
||||
#define OWNCLOUDACCOUNTDETAILS_H
|
||||
|
||||
#include "ui_owncloudaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
#include <QWidget>
|
||||
|
||||
class OwnCloudAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditOwnCloudAccount;
|
||||
|
||||
public:
|
||||
explicit OwnCloudAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void performTest(const QNetworkProxy& custom_proxy);
|
||||
void onUsernameChanged();
|
||||
void onPasswordChanged();
|
||||
void onUrlChanged();
|
||||
|
||||
private:
|
||||
Ui::OwnCloudAccountDetails m_ui;
|
||||
};
|
||||
|
||||
#endif // OWNCLOUDACCOUNTDETAILS_H
|
@ -1,201 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OwnCloudAccountDetails</class>
|
||||
<widget class="QWidget" name="OwnCloudAccountDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>433</width>
|
||||
<height>363</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="m_lblTitle">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtUrl</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_checkDownloadOnlyUnreadMessages">
|
||||
<property name="text">
|
||||
<string>Download unread articles only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<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">
|
||||
<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">
|
||||
<widget class="HelpSpoiler" name="m_lblServerSideUpdateInformation" native="true"/>
|
||||
</item>
|
||||
<item row="6" 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>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_btnTestSetup">
|
||||
<property name="text">
|
||||
<string>&Test setup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LabelWithStatus" name="m_lblTestResult" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>409</width>
|
||||
<height>35</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_checkServerSideUpdate">
|
||||
<property name="text">
|
||||
<string>Force execution of server-side feeds update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEditWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>lineeditwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LabelWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>labelwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageCountSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>messagecountspinbox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>HelpSpoiler</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>helpspoiler.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>m_checkDownloadOnlyUnreadMessages</tabstop>
|
||||
<tabstop>m_checkServerSideUpdate</tabstop>
|
||||
<tabstop>m_spinLimitMessages</tabstop>
|
||||
<tabstop>m_btnTestSetup</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,35 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/owncloudfeed.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "services/owncloud/owncloudnetworkfactory.h"
|
||||
#include "services/owncloud/owncloudserviceroot.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
OwnCloudFeed::OwnCloudFeed(RootItem* parent) : Feed(parent) {}
|
||||
|
||||
bool OwnCloudFeed::canBeDeleted() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OwnCloudFeed::deleteItem() {
|
||||
if (serviceRoot()->network()->deleteFeed(customId(), getParentServiceRoot()->networkProxy()) && removeItself()) {
|
||||
serviceRoot()->requestItemRemoval(this);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OwnCloudFeed::removeItself() {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
return DatabaseQueries::deleteFeed(database, this, serviceRoot()->accountId());
|
||||
}
|
||||
|
||||
OwnCloudServiceRoot* OwnCloudFeed::serviceRoot() const {
|
||||
return qobject_cast<OwnCloudServiceRoot*>(getParentServiceRoot());
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUDFEED_H
|
||||
#define OWNCLOUDFEED_H
|
||||
|
||||
#include "services/abstract/feed.h"
|
||||
|
||||
class OwnCloudServiceRoot;
|
||||
|
||||
class OwnCloudFeed : public Feed {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OwnCloudFeed(RootItem* parent = nullptr);
|
||||
|
||||
virtual bool canBeDeleted() const;
|
||||
virtual bool deleteItem();
|
||||
|
||||
private:
|
||||
bool removeItself();
|
||||
OwnCloudServiceRoot* serviceRoot() const;
|
||||
};
|
||||
|
||||
#endif // OWNCLOUDFEED_H
|
@ -1,660 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/owncloudnetworkfactory.h"
|
||||
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/settings.h"
|
||||
#include "miscellaneous/textfactory.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "services/abstract/category.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
#include "services/owncloud/definitions.h"
|
||||
#include "services/owncloud/owncloudfeed.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPixmap>
|
||||
|
||||
OwnCloudNetworkFactory::OwnCloudNetworkFactory()
|
||||
: m_url(QString()), m_fixedUrl(QString()), m_downloadOnlyUnreadMessages(false), m_forceServerSideUpdate(false),
|
||||
m_authUsername(QString()), m_authPassword(QString()), m_batchSize(OWNCLOUD_DEFAULT_BATCH_SIZE),
|
||||
m_urlUser(QString()), m_urlStatus(QString()), m_urlFolders(QString()), m_urlFeeds(QString()),
|
||||
m_urlMessages(QString()), m_urlFeedsUpdate(QString()), m_urlDeleteFeed(QString()), m_urlRenameFeed(QString()) {}
|
||||
|
||||
OwnCloudNetworkFactory::~OwnCloudNetworkFactory() = default;
|
||||
|
||||
QString OwnCloudNetworkFactory::url() const {
|
||||
return m_url;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setUrl(const QString& url) {
|
||||
m_url = url;
|
||||
|
||||
if (url.endsWith('/')) {
|
||||
m_fixedUrl = url;
|
||||
}
|
||||
else {
|
||||
m_fixedUrl = url + '/';
|
||||
}
|
||||
|
||||
// Store endpoints.
|
||||
m_urlUser = m_fixedUrl + OWNCLOUD_API_PATH + "user";
|
||||
m_urlStatus = m_fixedUrl + OWNCLOUD_API_PATH + "status";
|
||||
m_urlFolders = m_fixedUrl + OWNCLOUD_API_PATH + "folders";
|
||||
m_urlFeeds = m_fixedUrl + OWNCLOUD_API_PATH + "feeds";
|
||||
m_urlMessages = m_fixedUrl + OWNCLOUD_API_PATH + "items?id=%1&batchSize=%2&type=%3&getRead=%4";
|
||||
m_urlFeedsUpdate = m_fixedUrl + OWNCLOUD_API_PATH + "feeds/update?userId=%1&feedId=%2";
|
||||
m_urlDeleteFeed = m_fixedUrl + OWNCLOUD_API_PATH + "feeds/%1";
|
||||
m_urlRenameFeed = m_fixedUrl + OWNCLOUD_API_PATH + "feeds/%1/rename";
|
||||
}
|
||||
|
||||
bool OwnCloudNetworkFactory::forceServerSideUpdate() const {
|
||||
return m_forceServerSideUpdate;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setForceServerSideUpdate(bool force_update) {
|
||||
m_forceServerSideUpdate = force_update;
|
||||
}
|
||||
|
||||
QString OwnCloudNetworkFactory::authUsername() const {
|
||||
return m_authUsername;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setAuthUsername(const QString& auth_username) {
|
||||
m_authUsername = auth_username;
|
||||
}
|
||||
|
||||
QString OwnCloudNetworkFactory::authPassword() const {
|
||||
return m_authPassword;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setAuthPassword(const QString& auth_password) {
|
||||
m_authPassword = auth_password;
|
||||
}
|
||||
|
||||
OwnCloudStatusResponse OwnCloudNetworkFactory::status(const QNetworkProxy& custom_proxy) {
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(m_urlStatus,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
OwnCloudStatusResponse status_response(network_reply.m_networkError, QString::fromUtf8(result_raw));
|
||||
|
||||
qDebugNN << LOGSEC_NEXTCLOUD << "Raw status data is:" << QUOTE_W_SPACE_DOT(result_raw);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Obtaining status info failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
return status_response;
|
||||
}
|
||||
|
||||
OwnCloudGetFeedsCategoriesResponse OwnCloudNetworkFactory::feedsCategories(const QNetworkProxy& custom_proxy) {
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(m_urlFolders,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Obtaining of categories failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
return OwnCloudGetFeedsCategoriesResponse(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
QString content_categories = QString::fromUtf8(result_raw);
|
||||
|
||||
// Now, obtain feeds.
|
||||
network_reply = NetworkFactory::performNetworkOperation(m_urlFeeds,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Obtaining of feeds failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
return OwnCloudGetFeedsCategoriesResponse(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
QString content_feeds = QString::fromUtf8(result_raw);
|
||||
|
||||
return OwnCloudGetFeedsCategoriesResponse(network_reply.m_networkError, content_categories, content_feeds);
|
||||
}
|
||||
|
||||
bool OwnCloudNetworkFactory::deleteFeed(const QString& feed_id, const QNetworkProxy& custom_proxy) {
|
||||
QString final_url = m_urlDeleteFeed.arg(feed_id);
|
||||
QByteArray raw_output;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(final_url,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
raw_output,
|
||||
QNetworkAccessManager::Operation::DeleteOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Obtaining of categories failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool OwnCloudNetworkFactory::createFeed(const QString& url, int parent_id, const QNetworkProxy& custom_proxy) {
|
||||
QJsonObject json;
|
||||
|
||||
json[QSL("url")] = url;
|
||||
|
||||
auto nextcloud_version = status(custom_proxy).version();
|
||||
|
||||
if (SystemFactory::isVersionEqualOrNewer(nextcloud_version, QSL("15.1.0"))) {
|
||||
json[QSL("folderId")] = parent_id == 0 ? QJsonValue(QJsonValue::Type::Null) : parent_id;
|
||||
}
|
||||
else {
|
||||
json[QSL("folderId")] = parent_id;
|
||||
}
|
||||
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(m_urlFeeds,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::PostOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Creating of category failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool OwnCloudNetworkFactory::renameFeed(const QString& new_name,
|
||||
const QString& custom_feed_id,
|
||||
const QNetworkProxy& custom_proxy) {
|
||||
QString final_url = m_urlRenameFeed.arg(custom_feed_id);
|
||||
QByteArray result_raw;
|
||||
QJsonObject json;
|
||||
|
||||
json[QSL("feedTitle")] = new_name;
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(final_url,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::PutOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Renaming of feed failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
OwnCloudGetMessagesResponse OwnCloudNetworkFactory::getMessages(int feed_id, const QNetworkProxy& custom_proxy) {
|
||||
if (forceServerSideUpdate()) {
|
||||
triggerFeedUpdate(feed_id, custom_proxy);
|
||||
}
|
||||
|
||||
QString final_url = m_urlMessages.arg(QString::number(feed_id),
|
||||
QString::number(batchSize() <= 0 ? -1 : batchSize()),
|
||||
QString::number(0),
|
||||
m_downloadOnlyUnreadMessages ? QSL("false") : QSL("true"));
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(final_url,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
result_raw,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
OwnCloudGetMessagesResponse msgs_response(network_reply.m_networkError, QString::fromUtf8(result_raw));
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Obtaining messages failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
return msgs_response;
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError OwnCloudNetworkFactory::triggerFeedUpdate(int feed_id, const QNetworkProxy& custom_proxy) {
|
||||
// Now, we can trigger the update.
|
||||
QByteArray raw_output;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
NetworkResult network_reply =
|
||||
NetworkFactory::performNetworkOperation(m_urlFeedsUpdate.arg(authUsername(), QString::number(feed_id)),
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QByteArray(),
|
||||
raw_output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
|
||||
if (network_reply.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Feeds update failed with error"
|
||||
<< QUOTE_W_SPACE_DOT(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
return network_reply.m_networkError;
|
||||
}
|
||||
|
||||
NetworkResult OwnCloudNetworkFactory::markMessagesRead(RootItem::ReadStatus status,
|
||||
const QStringList& custom_ids,
|
||||
const QNetworkProxy& custom_proxy) {
|
||||
QJsonObject json;
|
||||
QJsonArray ids;
|
||||
QString final_url;
|
||||
|
||||
if (status == RootItem::ReadStatus::Read) {
|
||||
final_url = m_fixedUrl + QSL(OWNCLOUD_API_PATH) + QSL("items/read/multiple");
|
||||
}
|
||||
else {
|
||||
final_url = m_fixedUrl + QSL(OWNCLOUD_API_PATH) + QSL("items/unread/multiple");
|
||||
}
|
||||
|
||||
for (const QString& id : custom_ids) {
|
||||
ids.append(QJsonValue(id.toInt()));
|
||||
}
|
||||
|
||||
json[QSL("items")] = ids;
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
QByteArray output;
|
||||
|
||||
return NetworkFactory::performNetworkOperation(final_url,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PutOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
}
|
||||
|
||||
NetworkResult OwnCloudNetworkFactory::markMessagesStarred(RootItem::Importance importance,
|
||||
const QStringList& feed_ids,
|
||||
const QStringList& guid_hashes,
|
||||
const QNetworkProxy& custom_proxy) {
|
||||
QJsonObject json;
|
||||
QJsonArray ids;
|
||||
QString final_url;
|
||||
|
||||
if (importance == RootItem::Importance::Important) {
|
||||
final_url = m_fixedUrl + OWNCLOUD_API_PATH + "items/star/multiple";
|
||||
}
|
||||
else {
|
||||
final_url = m_fixedUrl + OWNCLOUD_API_PATH + "items/unstar/multiple";
|
||||
}
|
||||
|
||||
for (int i = 0; i < feed_ids.size(); i++) {
|
||||
QJsonObject item;
|
||||
|
||||
item[QSL("feedId")] = feed_ids.at(i);
|
||||
item[QSL("guidHash")] = guid_hashes.at(i);
|
||||
ids.append(item);
|
||||
}
|
||||
|
||||
json[QSL("items")] = ids;
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, OWNCLOUD_CONTENT_TYPE_JSON);
|
||||
headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authUsername,
|
||||
m_authPassword);
|
||||
|
||||
QByteArray output;
|
||||
|
||||
return NetworkFactory::performNetworkOperation(final_url,
|
||||
qApp->settings()
|
||||
->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout))
|
||||
.toInt(),
|
||||
QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PutOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy);
|
||||
}
|
||||
|
||||
int OwnCloudNetworkFactory::batchSize() const {
|
||||
return m_batchSize;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setBatchSize(int batch_size) {
|
||||
m_batchSize = batch_size;
|
||||
}
|
||||
|
||||
bool OwnCloudNetworkFactory::downloadOnlyUnreadMessages() const {
|
||||
return m_downloadOnlyUnreadMessages;
|
||||
}
|
||||
|
||||
void OwnCloudNetworkFactory::setDownloadOnlyUnreadMessages(bool dowload_only_unread_messages) {
|
||||
m_downloadOnlyUnreadMessages = dowload_only_unread_messages;
|
||||
}
|
||||
|
||||
OwnCloudResponse::OwnCloudResponse(QNetworkReply::NetworkError response, const QString& raw_content)
|
||||
: m_networkError(response), m_rawContent(QJsonDocument::fromJson(raw_content.toUtf8()).object()),
|
||||
m_emptyString(raw_content.isEmpty()) {}
|
||||
|
||||
OwnCloudResponse::~OwnCloudResponse() = default;
|
||||
|
||||
bool OwnCloudResponse::isLoaded() const {
|
||||
return !m_emptyString && !m_rawContent.isEmpty();
|
||||
}
|
||||
|
||||
QString OwnCloudResponse::toString() const {
|
||||
return QJsonDocument(m_rawContent).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError OwnCloudResponse::networkError() const {
|
||||
return m_networkError;
|
||||
}
|
||||
|
||||
OwnCloudStatusResponse::OwnCloudStatusResponse(QNetworkReply::NetworkError response, const QString& raw_content)
|
||||
: OwnCloudResponse(response, raw_content) {}
|
||||
|
||||
OwnCloudStatusResponse::~OwnCloudStatusResponse() = default;
|
||||
|
||||
QString OwnCloudStatusResponse::version() const {
|
||||
if (isLoaded()) {
|
||||
return m_rawContent[QSL("version")].toString();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
OwnCloudGetFeedsCategoriesResponse::OwnCloudGetFeedsCategoriesResponse(QNetworkReply::NetworkError response,
|
||||
QString raw_categories,
|
||||
QString raw_feeds)
|
||||
: OwnCloudResponse(response), m_contentCategories(std::move(raw_categories)), m_contentFeeds(std::move(raw_feeds)) {}
|
||||
|
||||
OwnCloudGetFeedsCategoriesResponse::~OwnCloudGetFeedsCategoriesResponse() = default;
|
||||
|
||||
RootItem* OwnCloudGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons) const {
|
||||
auto* parent = new RootItem();
|
||||
QMap<QString, RootItem*> cats;
|
||||
|
||||
// Top-level feed have "folderId" set to "0" or JSON "null" value.
|
||||
cats.insert(QSL("0"), parent);
|
||||
|
||||
// Process categories first, then process feeds.
|
||||
auto json_folders = QJsonDocument::fromJson(m_contentCategories.toUtf8()).object()[QSL("folders")].toArray();
|
||||
|
||||
for (const QJsonValue& cat : std::as_const(json_folders)) {
|
||||
QJsonObject item = cat.toObject();
|
||||
auto* category = new Category();
|
||||
|
||||
category->setTitle(item[QSL("name")].toString());
|
||||
category->setCustomId(QString::number(item[QSL("id")].toInt()));
|
||||
cats.insert(category->customId(), category);
|
||||
|
||||
// All categories in Nextcloud are top-level.
|
||||
parent->appendChild(category);
|
||||
}
|
||||
|
||||
// We have categories added, now add all feeds.
|
||||
auto json_feeds = QJsonDocument::fromJson(m_contentFeeds.toUtf8()).object()[QSL("feeds")].toArray();
|
||||
|
||||
for (const QJsonValue& fed : std::as_const(json_feeds)) {
|
||||
QJsonObject item = fed.toObject();
|
||||
auto* feed = new OwnCloudFeed();
|
||||
|
||||
if (obtain_icons) {
|
||||
QString icon_path = item[QSL("faviconLink")].toString();
|
||||
|
||||
if (!icon_path.isEmpty()) {
|
||||
QByteArray icon_data;
|
||||
|
||||
if (NetworkFactory::performNetworkOperation(icon_path,
|
||||
DOWNLOAD_TIMEOUT,
|
||||
QByteArray(),
|
||||
icon_data,
|
||||
QNetworkAccessManager::Operation::GetOperation)
|
||||
.m_networkError == QNetworkReply::NetworkError::NoError) {
|
||||
// Icon downloaded, set it up.
|
||||
QPixmap icon_pixmap;
|
||||
|
||||
icon_pixmap.loadFromData(icon_data);
|
||||
feed->setIcon(QIcon(icon_pixmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feed->setCustomId(QString::number(item[QSL("id")].toInt()));
|
||||
feed->setSource(item[QSL("url")].toString());
|
||||
|
||||
if (feed->source().isEmpty()) {
|
||||
feed->setSource(item[QSL("link")].toString());
|
||||
}
|
||||
|
||||
feed->setTitle(item[QSL("title")].toString());
|
||||
|
||||
if (feed->title().isEmpty()) {
|
||||
if (feed->source().isEmpty()) {
|
||||
// We cannot add feed which has no title and no url to RSS Guard!!!
|
||||
qCriticalNN << LOGSEC_NEXTCLOUD << "Skipping feed with custom ID" << QUOTE_W_SPACE(feed->customId())
|
||||
<< "from adding to RSS Guard because it has no title and url.";
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
feed->setTitle(feed->source());
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Starting with News 15.1.0, top-level feeds do not have parent folder ID 0, but JSON "null".
|
||||
// Luckily, if folder ID is not convertible to int, then default 0 value is returned.
|
||||
cats.value(QString::number(item[QSL("folderId")].toInt(0)))->appendChild(feed);
|
||||
qDebugNN << LOGSEC_NEXTCLOUD << "Custom ID of next fetched processed feed is"
|
||||
<< QUOTE_W_SPACE_DOT(feed->customId());
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
OwnCloudGetMessagesResponse::OwnCloudGetMessagesResponse(QNetworkReply::NetworkError response,
|
||||
const QString& raw_content)
|
||||
: OwnCloudResponse(response, raw_content) {}
|
||||
|
||||
OwnCloudGetMessagesResponse::~OwnCloudGetMessagesResponse() = default;
|
||||
|
||||
QList<Message> OwnCloudGetMessagesResponse::messages() const {
|
||||
QList<Message> msgs;
|
||||
auto json_items = m_rawContent[QSL("items")].toArray();
|
||||
|
||||
for (const QJsonValue& message : std::as_const(json_items)) {
|
||||
QJsonObject message_map = message.toObject();
|
||||
Message msg;
|
||||
|
||||
msg.m_author = message_map[QSL("author")].toString();
|
||||
msg.m_contents = message_map[QSL("body")].toString();
|
||||
msg.m_created = TextFactory::parseDateTime(message_map[QSL("pubDate")].toDouble() * 1000);
|
||||
msg.m_createdFromFeed = true;
|
||||
msg.m_customId = message_map[QSL("id")].toVariant().toString();
|
||||
msg.m_customHash = message_map[QSL("guidHash")].toString();
|
||||
msg.m_rawContents = QJsonDocument(message_map).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
|
||||
// In case body is empty, check for content in mediaDescription if item is available.
|
||||
if (msg.m_contents.isEmpty() && !message_map[QSL("mediaDescription")].isUndefined()) {
|
||||
msg.m_contents = message_map[QSL("mediaDescription")].toString();
|
||||
}
|
||||
|
||||
// Check for mediaThumbnail and append as first enclosure to be viewed in internal viewer.
|
||||
if (!message_map[QSL("mediaThumbnail")].isUndefined()) {
|
||||
Enclosure enclosure;
|
||||
|
||||
enclosure.m_mimeType = QSL("image/jpg");
|
||||
enclosure.m_url = message_map[QSL("mediaThumbnail")].toString();
|
||||
|
||||
msg.m_enclosures.append(enclosure);
|
||||
}
|
||||
|
||||
QString enclosure_link = message_map[QSL("enclosureLink")].toString();
|
||||
|
||||
if (!enclosure_link.isEmpty()) {
|
||||
Enclosure enclosure;
|
||||
|
||||
enclosure.m_mimeType = message_map[QSL("enclosureMime")].toString();
|
||||
enclosure.m_url = enclosure_link;
|
||||
|
||||
if (enclosure.m_mimeType.isEmpty()) {
|
||||
enclosure.m_mimeType = QSL("image/png");
|
||||
}
|
||||
|
||||
if (!message_map[QSL("enclosureMime")].toString().isEmpty() ||
|
||||
!enclosure_link.startsWith(QSL("https://www.youtube.com/v/"))) {
|
||||
msg.m_enclosures.append(enclosure);
|
||||
}
|
||||
}
|
||||
|
||||
msg.m_feedId = message_map[QSL("feedId")].toVariant().toString();
|
||||
msg.m_isImportant = message_map[QSL("starred")].toBool();
|
||||
msg.m_isRead = !message_map[QSL("unread")].toBool();
|
||||
msg.m_title = message_map[QSL("title")].toString();
|
||||
msg.m_url = message_map[QSL("url")].toString();
|
||||
|
||||
if (msg.m_title.simplified().isEmpty()) {
|
||||
msg.m_title = message_map[QSL("mediaDescription")].toString();
|
||||
}
|
||||
|
||||
if (msg.m_title.simplified().isEmpty()) {
|
||||
msg.m_title = msg.m_url;
|
||||
}
|
||||
|
||||
msgs.append(msg);
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUDNETWORKFACTORY_H
|
||||
#define OWNCLOUDNETWORKFACTORY_H
|
||||
|
||||
#include "core/message.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QString>
|
||||
|
||||
class OwnCloudResponse {
|
||||
public:
|
||||
explicit OwnCloudResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~OwnCloudResponse();
|
||||
|
||||
bool isLoaded() const;
|
||||
QString toString() const;
|
||||
QNetworkReply::NetworkError networkError() const;
|
||||
|
||||
protected:
|
||||
QNetworkReply::NetworkError m_networkError;
|
||||
QJsonObject m_rawContent;
|
||||
bool m_emptyString;
|
||||
};
|
||||
|
||||
class OwnCloudGetMessagesResponse : public OwnCloudResponse {
|
||||
public:
|
||||
explicit OwnCloudGetMessagesResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~OwnCloudGetMessagesResponse();
|
||||
|
||||
QList<Message> messages() const;
|
||||
};
|
||||
|
||||
class OwnCloudStatusResponse : public OwnCloudResponse {
|
||||
public:
|
||||
explicit OwnCloudStatusResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~OwnCloudStatusResponse();
|
||||
|
||||
QString version() const;
|
||||
};
|
||||
|
||||
class RootItem;
|
||||
|
||||
class OwnCloudGetFeedsCategoriesResponse : public OwnCloudResponse {
|
||||
public:
|
||||
explicit OwnCloudGetFeedsCategoriesResponse(QNetworkReply::NetworkError response,
|
||||
QString raw_categories = QString(),
|
||||
QString raw_feeds = QString());
|
||||
virtual ~OwnCloudGetFeedsCategoriesResponse();
|
||||
|
||||
// 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) const;
|
||||
|
||||
private:
|
||||
QString m_contentCategories;
|
||||
QString m_contentFeeds;
|
||||
};
|
||||
|
||||
class OwnCloudNetworkFactory {
|
||||
public:
|
||||
explicit OwnCloudNetworkFactory();
|
||||
virtual ~OwnCloudNetworkFactory();
|
||||
|
||||
QString url() const;
|
||||
void setUrl(const QString& url);
|
||||
|
||||
bool forceServerSideUpdate() const;
|
||||
void setForceServerSideUpdate(bool force_update);
|
||||
|
||||
QString authUsername() const;
|
||||
void setAuthUsername(const QString& auth_username);
|
||||
|
||||
QString authPassword() const;
|
||||
void setAuthPassword(const QString& auth_password);
|
||||
|
||||
// Gets/sets the amount of messages to obtain during single feed update.
|
||||
int batchSize() const;
|
||||
void setBatchSize(int batch_size);
|
||||
|
||||
bool downloadOnlyUnreadMessages() const;
|
||||
void setDownloadOnlyUnreadMessages(bool dowload_only_unread_messages);
|
||||
|
||||
// Operations.
|
||||
|
||||
// Get version info.
|
||||
OwnCloudStatusResponse status(const QNetworkProxy& custom_proxy);
|
||||
|
||||
// Get feeds & categories (used for sync-in).
|
||||
OwnCloudGetFeedsCategoriesResponse feedsCategories(const QNetworkProxy& custom_proxy);
|
||||
|
||||
// Feed operations.
|
||||
bool deleteFeed(const QString& feed_id, const QNetworkProxy& custom_proxy);
|
||||
bool createFeed(const QString& url, int parent_id, const QNetworkProxy& custom_proxy);
|
||||
bool renameFeed(const QString& new_name, const QString& custom_feed_id, const QNetworkProxy& custom_proxy);
|
||||
|
||||
// Get messages for given feed.
|
||||
OwnCloudGetMessagesResponse getMessages(int feed_id, const QNetworkProxy& custom_proxy);
|
||||
|
||||
// Misc methods.
|
||||
QNetworkReply::NetworkError triggerFeedUpdate(int feed_id, const QNetworkProxy& custom_proxy);
|
||||
|
||||
NetworkResult markMessagesRead(RootItem::ReadStatus status,
|
||||
const QStringList& custom_ids,
|
||||
const QNetworkProxy& custom_proxy);
|
||||
|
||||
NetworkResult markMessagesStarred(RootItem::Importance importance,
|
||||
const QStringList& feed_ids,
|
||||
const QStringList& guid_hashes,
|
||||
const QNetworkProxy& custom_proxy);
|
||||
|
||||
private:
|
||||
QString m_url;
|
||||
QString m_fixedUrl;
|
||||
bool m_downloadOnlyUnreadMessages;
|
||||
bool m_forceServerSideUpdate;
|
||||
QString m_authUsername;
|
||||
QString m_authPassword;
|
||||
int m_batchSize;
|
||||
|
||||
// Endpoints.
|
||||
QString m_urlUser;
|
||||
QString m_urlStatus;
|
||||
QString m_urlFolders;
|
||||
QString m_urlFeeds;
|
||||
QString m_urlMessages;
|
||||
QString m_urlFeedsUpdate;
|
||||
QString m_urlDeleteFeed;
|
||||
QString m_urlRenameFeed;
|
||||
};
|
||||
|
||||
#endif // OWNCLOUDNETWORKFACTORY_H
|
@ -1,45 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/owncloudserviceentrypoint.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/owncloud/definitions.h"
|
||||
#include "services/owncloud/gui/formeditowncloudaccount.h"
|
||||
#include "services/owncloud/owncloudserviceroot.h"
|
||||
|
||||
ServiceRoot* OwnCloudServiceEntryPoint::createNewRoot() const {
|
||||
FormEditOwnCloudAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<OwnCloudServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> OwnCloudServiceEntryPoint::initializeSubtree() const {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("OwnCloudServiceEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<OwnCloudServiceRoot>(database, code());
|
||||
}
|
||||
|
||||
QString OwnCloudServiceEntryPoint::name() const {
|
||||
return QSL("Nextcloud News");
|
||||
}
|
||||
|
||||
QString OwnCloudServiceEntryPoint::code() const {
|
||||
return QSL(SERVICE_CODE_OWNCLOUD);
|
||||
}
|
||||
|
||||
QString OwnCloudServiceEntryPoint::description() const {
|
||||
return QObject::tr("The News app is an RSS/Atom feed aggregator. "
|
||||
"It is part of Nextcloud suite. This plugin implements %1 API.")
|
||||
.arg(QSL(OWNCLOUD_API_VERSION));
|
||||
}
|
||||
|
||||
QString OwnCloudServiceEntryPoint::author() const {
|
||||
return QSL(APP_AUTHOR);
|
||||
}
|
||||
|
||||
QIcon OwnCloudServiceEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("nextcloud"));
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUDSERVICEENTRYPOINT_H
|
||||
#define OWNCLOUDSERVICEENTRYPOINT_H
|
||||
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
|
||||
class OwnCloudServiceEntryPoint : 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 // OWNCLOUDSERVICEENTRYPOINT_H
|
@ -1,176 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/owncloud/owncloudserviceroot.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "exceptions/feedfetchexception.h"
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/textfactory.h"
|
||||
#include "services/owncloud/gui/formeditowncloudaccount.h"
|
||||
#include "services/owncloud/owncloudfeed.h"
|
||||
#include "services/owncloud/owncloudnetworkfactory.h"
|
||||
#include "services/owncloud/owncloudserviceentrypoint.h"
|
||||
|
||||
OwnCloudServiceRoot::OwnCloudServiceRoot(RootItem* parent)
|
||||
: ServiceRoot(parent), m_network(new OwnCloudNetworkFactory()) {
|
||||
setIcon(OwnCloudServiceEntryPoint().icon());
|
||||
}
|
||||
|
||||
OwnCloudServiceRoot::~OwnCloudServiceRoot() {
|
||||
delete m_network;
|
||||
}
|
||||
|
||||
bool OwnCloudServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OwnCloudServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
FormAccountDetails* OwnCloudServiceRoot::accountSetupDialog() const {
|
||||
return new FormEditOwnCloudAccount(qApp->mainFormWidget());
|
||||
}
|
||||
|
||||
void OwnCloudServiceRoot::editItems(const QList<RootItem*>& items) {
|
||||
if (items.first()->kind() == RootItem::Kind::ServiceRoot) {
|
||||
QScopedPointer<FormEditOwnCloudAccount> p(qobject_cast<FormEditOwnCloudAccount*>(accountSetupDialog()));
|
||||
|
||||
p->addEditAccount(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceRoot::editItems(items);
|
||||
}
|
||||
|
||||
bool OwnCloudServiceRoot::supportsFeedAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OwnCloudServiceRoot::supportsCategoryAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void OwnCloudServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadRootFromDatabase<Category, OwnCloudFeed>(this);
|
||||
loadCacheFromFile();
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
syncIn();
|
||||
}
|
||||
}
|
||||
|
||||
QString OwnCloudServiceRoot::code() const {
|
||||
return OwnCloudServiceEntryPoint().code();
|
||||
}
|
||||
|
||||
OwnCloudNetworkFactory* OwnCloudServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
void OwnCloudServiceRoot::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()) {
|
||||
auto res = network()->markMessagesRead(key, ids, networkProxy());
|
||||
|
||||
if (!ignore_errors && res.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
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 feed_ids, guid_hashes;
|
||||
|
||||
for (const Message& msg : messages) {
|
||||
feed_ids.append(msg.m_feedId);
|
||||
guid_hashes.append(msg.m_customHash);
|
||||
}
|
||||
|
||||
auto res = network()->markMessagesStarred(key, feed_ids, guid_hashes, networkProxy());
|
||||
|
||||
if (!ignore_errors && res.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
addMessageStatesToCache(messages, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OwnCloudServiceRoot::updateTitle() {
|
||||
setTitle(m_network->authUsername() + QSL(" (Nextcloud News)"));
|
||||
}
|
||||
|
||||
RootItem* OwnCloudServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
OwnCloudGetFeedsCategoriesResponse feed_cats_response = m_network->feedsCategories(networkProxy());
|
||||
|
||||
if (feed_cats_response.networkError() == QNetworkReply::NetworkError::NoError) {
|
||||
return feed_cats_response.feedsCategories(true);
|
||||
}
|
||||
else {
|
||||
throw NetworkException(feed_cats_response.networkError(),
|
||||
tr("cannot get list of feeds, network error '%1'").arg(feed_cats_response.networkError()));
|
||||
}
|
||||
}
|
||||
|
||||
QVariantHash OwnCloudServiceRoot::customDatabaseData() const {
|
||||
QVariantHash data = ServiceRoot::customDatabaseData();
|
||||
|
||||
data[QSL("auth_username")] = m_network->authUsername();
|
||||
data[QSL("auth_password")] = TextFactory::encrypt(m_network->authPassword());
|
||||
data[QSL("url")] = m_network->url();
|
||||
data[QSL("force_update")] = m_network->forceServerSideUpdate();
|
||||
data[QSL("batch_size")] = m_network->batchSize();
|
||||
data[QSL("download_only_unread")] = m_network->downloadOnlyUnreadMessages();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void OwnCloudServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
ServiceRoot::setCustomDatabaseData(data);
|
||||
|
||||
m_network->setAuthUsername(data[QSL("auth_username")].toString());
|
||||
m_network->setAuthPassword(TextFactory::decrypt(data[QSL("auth_password")].toString()));
|
||||
m_network->setUrl(data[QSL("url")].toString());
|
||||
m_network->setForceServerSideUpdate(data[QSL("force_update")].toBool());
|
||||
m_network->setBatchSize(data[QSL("batch_size")].toInt());
|
||||
m_network->setDownloadOnlyUnreadMessages(data[QSL("download_only_unread")].toBool());
|
||||
}
|
||||
|
||||
QList<Message> OwnCloudServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>&
|
||||
stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(stated_messages)
|
||||
Q_UNUSED(tagged_messages)
|
||||
|
||||
OwnCloudGetMessagesResponse messages = network()->getMessages(feed->customNumericId(), networkProxy());
|
||||
|
||||
if (messages.networkError() != QNetworkReply::NetworkError::NoError) {
|
||||
throw FeedFetchException(Feed::Status::NetworkError);
|
||||
}
|
||||
else {
|
||||
return messages.messages();
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef OWNCLOUDSERVICEROOT_H
|
||||
#define OWNCLOUDSERVICEROOT_H
|
||||
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
class OwnCloudNetworkFactory;
|
||||
class Mutex;
|
||||
|
||||
class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OwnCloudServiceRoot(RootItem* parent = nullptr);
|
||||
virtual ~OwnCloudServiceRoot();
|
||||
|
||||
virtual bool isSyncable() const;
|
||||
virtual bool canBeEdited() const;
|
||||
virtual void editItems(const QList<RootItem*>& items);
|
||||
virtual FormAccountDetails* accountSetupDialog() const;
|
||||
virtual bool supportsFeedAdding() const;
|
||||
virtual bool supportsCategoryAdding() const;
|
||||
virtual void start(bool freshly_activated);
|
||||
virtual QString code() 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);
|
||||
|
||||
OwnCloudNetworkFactory* network() const;
|
||||
|
||||
protected:
|
||||
virtual RootItem* obtainNewTreeForSyncIn() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
|
||||
private:
|
||||
OwnCloudNetworkFactory* m_network;
|
||||
};
|
||||
|
||||
#endif // OWNCLOUDSERVICEROOT_H
|
@ -1,23 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDIT_DEFINITIONS_H
|
||||
#define REDDIT_DEFINITIONS_H
|
||||
|
||||
#define REDDIT_OAUTH_REDIRECT_URI_PORT 14499
|
||||
#define REDDIT_OAUTH_AUTH_URL "https://www.reddit.com/api/v1/authorize"
|
||||
#define REDDIT_OAUTH_TOKEN_URL "https://www.reddit.com/api/v1/access_token"
|
||||
#define REDDIT_OAUTH_SCOPE "identity mysubreddits read"
|
||||
|
||||
#define REDDIT_REG_API_URL "https://www.reddit.com/prefs/apps"
|
||||
|
||||
#define REDDIT_API_GET_PROFILE "https://oauth.reddit.com/api/v1/me"
|
||||
#define REDDIT_API_SUBREDDITS "https://oauth.reddit.com/subreddits/mine/subscriber?limit=%1"
|
||||
#define REDDIT_API_HOT "https://oauth.reddit.com%1hot?limit=%2&count=%3&g=%4"
|
||||
|
||||
#define REDDIT_DEFAULT_BATCH_SIZE 100
|
||||
#define REDDIT_MAX_BATCH_SIZE 999
|
||||
|
||||
#define REDDIT_CONTENT_TYPE_HTTP "application/http"
|
||||
#define REDDIT_CONTENT_TYPE_JSON "application/json"
|
||||
|
||||
#endif // REDDIT_DEFINITIONS_H
|
@ -1,67 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/gui/formeditredditaccount.h"
|
||||
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/reddit/gui/redditaccountdetails.h"
|
||||
#include "services/reddit/redditserviceroot.h"
|
||||
|
||||
FormEditRedditAccount::FormEditRedditAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("reddit")), parent), m_details(new RedditAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Server setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
m_details->m_ui.m_txtUsername->setFocus();
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, [this]() {
|
||||
m_details->testSetup(m_proxyDetails->proxy());
|
||||
});
|
||||
}
|
||||
|
||||
void FormEditRedditAccount::apply() {
|
||||
FormAccountDetails::apply();
|
||||
|
||||
bool using_another_acc =
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->text() != account<RedditServiceRoot>()->network()->username();
|
||||
|
||||
// Make sure that the data copied from GUI are used for brand new login.
|
||||
account<RedditServiceRoot>()->network()->oauth()->logout(false);
|
||||
account<RedditServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
|
||||
account<RedditServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
|
||||
account<RedditServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text(),
|
||||
true);
|
||||
|
||||
account<RedditServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<RedditServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<RedditServiceRoot>()->network()->setDownloadOnlyUnreadMessages(m_details->m_ui.m_cbDownloadOnlyUnreadMessages
|
||||
->isChecked());
|
||||
|
||||
account<RedditServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew) {
|
||||
if (using_another_acc) {
|
||||
account<RedditServiceRoot>()->completelyRemoveAllData();
|
||||
}
|
||||
|
||||
account<RedditServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditRedditAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
m_details->m_oauth = account<RedditServiceRoot>()->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<RedditServiceRoot>()->network()->username());
|
||||
m_details->m_ui.m_spinLimitMessages->setValue(account<RedditServiceRoot>()->network()->batchSize());
|
||||
m_details->m_ui.m_cbDownloadOnlyUnreadMessages
|
||||
->setChecked(account<RedditServiceRoot>()->network()->downloadOnlyUnreadMessages());
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITREDDITACCOUNT_H
|
||||
#define FORMEDITREDDITACCOUNT_H
|
||||
|
||||
#include "services/abstract/gui/formaccountdetails.h"
|
||||
#include "services/reddit/redditnetworkfactory.h"
|
||||
|
||||
class RedditServiceRoot;
|
||||
class RedditAccountDetails;
|
||||
|
||||
class FormEditRedditAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditRedditAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private:
|
||||
RedditAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITREDDITACCOUNT_H
|
@ -1,120 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/gui/redditaccountdetails.h"
|
||||
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/reddit/definitions.h"
|
||||
#include "services/reddit/redditnetworkfactory.h"
|
||||
|
||||
RedditAccountDetails::RedditAccountDetails(QWidget* parent) : QWidget(parent), m_oauth(nullptr), m_lastProxy({}) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.m_lblInfo->setHelpText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."),
|
||||
true);
|
||||
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_spinLimitMessages);
|
||||
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup);
|
||||
|
||||
connect(m_ui.m_txtAppId->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtAppKey->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtRedirectUrl->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
|
||||
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkUsername);
|
||||
connect(m_ui.m_btnRegisterApi, &QPushButton::clicked, this, &RedditAccountDetails::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 RedditAccountDetails::testSetup(const QNetworkProxy& custom_proxy) {
|
||||
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_lastProxy = custom_proxy;
|
||||
m_oauth->login();
|
||||
}
|
||||
|
||||
void RedditAccountDetails::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 RedditAccountDetails::onAuthFailed() {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("You did not grant access."),
|
||||
tr("There was error during testing."));
|
||||
}
|
||||
|
||||
void RedditAccountDetails::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 RedditAccountDetails::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 {
|
||||
RedditNetworkFactory fac;
|
||||
|
||||
fac.setOauth(m_oauth);
|
||||
|
||||
auto resp = fac.me(m_lastProxy);
|
||||
|
||||
m_ui.m_txtUsername->lineEdit()->setText(resp[QSL("name")].toString());
|
||||
}
|
||||
catch (const ApplicationException& ex) {
|
||||
qCriticalNN << LOGSEC_REDDIT << "Failed to obtain profile with error:" << QUOTE_W_SPACE_DOT(ex.message());
|
||||
}
|
||||
}
|
||||
|
||||
void RedditAccountDetails::hookNetwork() {
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &RedditAccountDetails::onAuthGranted);
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &RedditAccountDetails::onAuthError);
|
||||
connect(m_oauth, &OAuth2Service::authFailed, this, &RedditAccountDetails::onAuthFailed);
|
||||
}
|
||||
|
||||
void RedditAccountDetails::registerApi() {
|
||||
qApp->web()->openUrlInExternalBrowser(QSL(REDDIT_REG_API_URL));
|
||||
}
|
||||
|
||||
void RedditAccountDetails::checkOAuthValue(const QString& value) {
|
||||
auto* line_edit = qobject_cast<LineEditWithStatus*>(sender()->parent());
|
||||
|
||||
if (line_edit != nullptr) {
|
||||
if (value.isEmpty()) {
|
||||
#if defined(REDDIT_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,43 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITACCOUNTDETAILS_H
|
||||
#define REDDITACCOUNTDETAILS_H
|
||||
|
||||
#include "ui_redditaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
#include <QWidget>
|
||||
|
||||
class OAuth2Service;
|
||||
|
||||
class RedditAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditRedditAccount;
|
||||
|
||||
public:
|
||||
explicit RedditAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void testSetup(const QNetworkProxy& custom_proxy);
|
||||
|
||||
private slots:
|
||||
void registerApi();
|
||||
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::RedditAccountDetails m_ui;
|
||||
|
||||
// Pointer to live OAuth.
|
||||
OAuth2Service* m_oauth;
|
||||
QNetworkProxy m_lastProxy;
|
||||
};
|
||||
|
||||
#endif // REDDITACCOUNTDETAILS_H
|
@ -1,202 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RedditAccountDetails</class>
|
||||
<widget class="QWidget" name="RedditAccountDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>431</width>
|
||||
<height>259</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>Client 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>Client secret</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 credentials</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="HelpSpoiler" name="m_lblInfo" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<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 row="0" column="1">
|
||||
<widget class="MessageCountSpinBox" name="m_spinLimitMessages">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</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>410</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>LineEditWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>lineeditwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LabelWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>labelwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageCountSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>messagecountspinbox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>HelpSpoiler</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>helpspoiler.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>m_btnRegisterApi</tabstop>
|
||||
<tabstop>m_cbDownloadOnlyUnreadMessages</tabstop>
|
||||
<tabstop>m_spinLimitMessages</tabstop>
|
||||
<tabstop>m_btnTestSetup</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,19 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/redditcategory.h"
|
||||
|
||||
RedditCategory::RedditCategory(Type type, RootItem* parent_item) : Category(parent_item), m_type(type) {
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
RedditCategory::Type RedditCategory::type() const {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
void RedditCategory::updateTitle() {
|
||||
switch (m_type) {
|
||||
case Type::Subscriptions:
|
||||
setTitle(tr("Subscriptions"));
|
||||
break;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITCATEGORY_H
|
||||
#define REDDITCATEGORY_H
|
||||
|
||||
#include "services/abstract/category.h"
|
||||
|
||||
class RedditCategory : public Category {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Type {
|
||||
Subscriptions = 1
|
||||
};
|
||||
|
||||
explicit RedditCategory(Type type = Type::Subscriptions, RootItem* parent_item = nullptr);
|
||||
|
||||
Type type() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
};
|
||||
|
||||
#endif // REDDITCATEGORY_H
|
@ -1,44 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/redditentrypoint.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/reddit/gui/formeditredditaccount.h"
|
||||
#include "services/reddit/redditserviceroot.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
ServiceRoot* RedditEntryPoint::createNewRoot() const {
|
||||
FormEditRedditAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<RedditServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> RedditEntryPoint::initializeSubtree() const {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("RedditEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<RedditServiceRoot>(database, code());
|
||||
}
|
||||
|
||||
QString RedditEntryPoint::name() const {
|
||||
return QSL("Reddit");
|
||||
}
|
||||
|
||||
QString RedditEntryPoint::code() const {
|
||||
return QSL(SERVICE_CODE_REDDIT);
|
||||
}
|
||||
|
||||
QString RedditEntryPoint::description() const {
|
||||
return QObject::tr("Simplistic Reddit client.");
|
||||
}
|
||||
|
||||
QString RedditEntryPoint::author() const {
|
||||
return QSL(APP_AUTHOR);
|
||||
}
|
||||
|
||||
QIcon RedditEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("reddit"));
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITENTRYPOINT_H
|
||||
#define REDDITENTRYPOINT_H
|
||||
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
|
||||
class RedditEntryPoint : 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 // REDDITENTRYPOINT_H
|
@ -1,322 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/redditnetworkfactory.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/settings.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/reddit/definitions.h"
|
||||
#include "services/reddit/redditserviceroot.h"
|
||||
#include "services/reddit/redditsubscription.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
||||
RedditNetworkFactory::RedditNetworkFactory(QObject* parent)
|
||||
: QObject(parent), m_service(nullptr), m_username(QString()), m_batchSize(REDDIT_DEFAULT_BATCH_SIZE),
|
||||
m_downloadOnlyUnreadMessages(false), m_oauth2(new OAuth2Service(QSL(REDDIT_OAUTH_AUTH_URL),
|
||||
QSL(REDDIT_OAUTH_TOKEN_URL),
|
||||
{},
|
||||
{},
|
||||
QSL(REDDIT_OAUTH_SCOPE),
|
||||
this)) {
|
||||
initializeOauth();
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::setService(RedditServiceRoot* service) {
|
||||
m_service = service;
|
||||
}
|
||||
|
||||
OAuth2Service* RedditNetworkFactory::oauth() const {
|
||||
return m_oauth2;
|
||||
}
|
||||
|
||||
QString RedditNetworkFactory::username() const {
|
||||
return m_username;
|
||||
}
|
||||
|
||||
int RedditNetworkFactory::batchSize() const {
|
||||
return m_batchSize;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::setBatchSize(int batch_size) {
|
||||
m_batchSize = batch_size;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::initializeOauth() {
|
||||
m_oauth2->setUseHttpBasicAuthWithClientData(true);
|
||||
m_oauth2->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(REDDIT_OAUTH_REDIRECT_URI_PORT), true);
|
||||
|
||||
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &RedditNetworkFactory::onTokensError);
|
||||
connect(m_oauth2, &OAuth2Service::authFailed, this, &RedditNetworkFactory::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 && !refresh_token.isEmpty()) {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool RedditNetworkFactory::downloadOnlyUnreadMessages() const {
|
||||
return m_downloadOnlyUnreadMessages;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) {
|
||||
m_downloadOnlyUnreadMessages = download_only_unread_messages;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::setOauth(OAuth2Service* oauth) {
|
||||
m_oauth2 = oauth;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::setUsername(const QString& username) {
|
||||
m_username = username;
|
||||
}
|
||||
|
||||
QVariantHash RedditNetworkFactory::me(const QNetworkProxy& custom_proxy) {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("you are not logged in"));
|
||||
}
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers.append(QPair<QByteArray, QByteArray>(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
m_oauth2->bearer().toLocal8Bit()));
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
auto result = NetworkFactory::performNetworkOperation(QSL(REDDIT_API_GET_PROFILE),
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
}
|
||||
else {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
|
||||
return doc.object().toVariantHash();
|
||||
}
|
||||
}
|
||||
|
||||
QList<Feed*> RedditNetworkFactory::subreddits(const QNetworkProxy& custom_proxy) {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("you are not logged in"));
|
||||
}
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers.append(QPair<QByteArray, QByteArray>(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
m_oauth2->bearer().toLocal8Bit()));
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QString after;
|
||||
QList<Feed*> subs;
|
||||
|
||||
do {
|
||||
QByteArray output;
|
||||
QString final_url = QSL(REDDIT_API_SUBREDDITS).arg(QString::number(100));
|
||||
|
||||
if (!after.isEmpty()) {
|
||||
final_url += QSL("&after=%1").arg(after);
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
}
|
||||
else {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
QJsonObject root_doc = doc.object();
|
||||
|
||||
after = root_doc["data"].toObject()["after"].toString();
|
||||
|
||||
for (const QJsonValue& sub_val : root_doc["data"].toObject()["children"].toArray()) {
|
||||
const auto sub_obj = sub_val.toObject()["data"].toObject();
|
||||
|
||||
RedditSubscription* new_sub = new RedditSubscription();
|
||||
|
||||
new_sub->setCustomId(sub_obj["id"].toString());
|
||||
new_sub->setTitle(sub_obj["title"].toString());
|
||||
new_sub->setDescription(sub_obj["public_description"].toString());
|
||||
|
||||
new_sub->setPrefixedName(sub_obj["url"].toString());
|
||||
|
||||
QPixmap icon;
|
||||
QString icon_url = sub_obj["community_icon"].toString();
|
||||
|
||||
if (icon_url.isEmpty()) {
|
||||
icon_url = sub_obj["icon_img"].toString();
|
||||
}
|
||||
|
||||
if (icon_url.contains(QL1S("?"))) {
|
||||
icon_url = icon_url.mid(0, icon_url.indexOf(QL1S("?")));
|
||||
}
|
||||
|
||||
if (!icon_url.isEmpty() &&
|
||||
NetworkFactory::downloadIcon({{icon_url, true}}, timeout, icon, headers, custom_proxy) ==
|
||||
QNetworkReply::NetworkError::NoError) {
|
||||
new_sub->setIcon(icon);
|
||||
}
|
||||
|
||||
subs.append(new_sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!after.isEmpty());
|
||||
|
||||
// posty dle jmena redditu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/new
|
||||
//
|
||||
// komenty pro post dle id postu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/comments/<ID-POSTU>
|
||||
|
||||
return subs;
|
||||
}
|
||||
|
||||
QList<Message> RedditNetworkFactory::hot(const QString& sub_name, const QNetworkProxy& custom_proxy) {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("you are not logged in"));
|
||||
}
|
||||
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers.append(QPair<QByteArray, QByteArray>(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
m_oauth2->bearer().toLocal8Bit()));
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QString after;
|
||||
QList<Message> msgs;
|
||||
|
||||
int desired_count = batchSize();
|
||||
|
||||
do {
|
||||
int next_batch = desired_count <= 0 ? 100 : std::min(100, int(desired_count - msgs.size()));
|
||||
|
||||
QByteArray output;
|
||||
QString final_url =
|
||||
QSL(REDDIT_API_HOT).arg(sub_name, QString::number(next_batch), QString::number(msgs.size()), QSL("GLOBAL"));
|
||||
|
||||
if (!after.isEmpty()) {
|
||||
final_url += QSL("&after=%1").arg(after);
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
}
|
||||
else {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
QJsonObject root_doc = doc.object();
|
||||
|
||||
after = root_doc["data"].toObject()["after"].toString();
|
||||
|
||||
for (const QJsonValue& sub_val : root_doc["data"].toObject()["children"].toArray()) {
|
||||
const auto msg_obj = sub_val.toObject()["data"].toObject();
|
||||
|
||||
Message new_msg;
|
||||
|
||||
new_msg.m_customId = msg_obj["id"].toString();
|
||||
new_msg.m_title = msg_obj["title"].toString();
|
||||
new_msg.m_author = msg_obj["author"].toString();
|
||||
new_msg.m_createdFromFeed = true;
|
||||
new_msg.m_created =
|
||||
QDateTime::fromSecsSinceEpoch(msg_obj["created_utc"].toVariant().toLongLong(), Qt::TimeSpec::UTC);
|
||||
new_msg.m_url = QSL("https://reddit.com") + msg_obj["permalink"].toString();
|
||||
new_msg.m_contents =
|
||||
msg_obj["description_html"]
|
||||
.toString(); // když prazdny, je poustnutej třeba obrazek či odkaz?, viz property "post_hint"?
|
||||
new_msg.m_rawContents = QJsonDocument(msg_obj).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
|
||||
msgs.append(new_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!after.isEmpty() && (desired_count <= 0 || desired_count > msgs.size()));
|
||||
|
||||
// posty dle jmena redditu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/new
|
||||
//
|
||||
// komenty pro post dle id postu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/comments/<ID-POSTU>
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::onTokensError(const QString& error, const QString& error_description) {
|
||||
Q_UNUSED(error)
|
||||
|
||||
qApp->showGuiMessage(Notification::Event::LoginFailure,
|
||||
{tr("Reddit: authentication error"),
|
||||
tr("Click this to login again. Error is: '%1'").arg(error_description),
|
||||
QSystemTrayIcon::MessageIcon::Critical},
|
||||
{},
|
||||
{tr("Login"), [this]() {
|
||||
m_oauth2->setAccessToken(QString());
|
||||
m_oauth2->setRefreshToken(QString());
|
||||
m_oauth2->login();
|
||||
}});
|
||||
}
|
||||
|
||||
void RedditNetworkFactory::onAuthFailed() {
|
||||
qApp->showGuiMessage(Notification::Event::LoginFailure,
|
||||
{tr("Reddit: authorization denied"),
|
||||
tr("Click this to login again."),
|
||||
QSystemTrayIcon::MessageIcon::Critical},
|
||||
{},
|
||||
{tr("Login"), [this]() {
|
||||
m_oauth2->login();
|
||||
}});
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITNETWORKFACTORY_H
|
||||
#define REDDITNETWORKFACTORY_H
|
||||
|
||||
#include "core/message.h"
|
||||
#include "services/abstract/feed.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
class RootItem;
|
||||
class RedditServiceRoot;
|
||||
class OAuth2Service;
|
||||
class Downloader;
|
||||
|
||||
struct Subreddit {};
|
||||
|
||||
class RedditNetworkFactory : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RedditNetworkFactory(QObject* parent = nullptr);
|
||||
|
||||
void setService(RedditServiceRoot* service);
|
||||
|
||||
OAuth2Service* oauth() const;
|
||||
void setOauth(OAuth2Service* oauth);
|
||||
|
||||
QString username() const;
|
||||
void setUsername(const QString& username);
|
||||
|
||||
int batchSize() const;
|
||||
void setBatchSize(int batch_size);
|
||||
|
||||
bool downloadOnlyUnreadMessages() const;
|
||||
void setDownloadOnlyUnreadMessages(bool download_only_unread_messages);
|
||||
|
||||
// API methods.
|
||||
QVariantHash me(const QNetworkProxy& custom_proxy);
|
||||
QList<Feed*> subreddits(const QNetworkProxy& custom_proxy);
|
||||
QList<Message> hot(const QString& sub_name, const QNetworkProxy& custom_proxy);
|
||||
|
||||
private slots:
|
||||
void onTokensError(const QString& error, const QString& error_description);
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
void initializeOauth();
|
||||
|
||||
private:
|
||||
RedditServiceRoot* m_service;
|
||||
QString m_username;
|
||||
int m_batchSize;
|
||||
bool m_downloadOnlyUnreadMessages;
|
||||
OAuth2Service* m_oauth2;
|
||||
};
|
||||
|
||||
#endif // REDDITNETWORKFACTORY_H
|
@ -1,142 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/redditserviceroot.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/reddit/gui/formeditredditaccount.h"
|
||||
#include "services/reddit/redditcategory.h"
|
||||
#include "services/reddit/redditentrypoint.h"
|
||||
#include "services/reddit/redditnetworkfactory.h"
|
||||
#include "services/reddit/redditsubscription.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
|
||||
RedditServiceRoot::RedditServiceRoot(RootItem* parent)
|
||||
: ServiceRoot(parent), m_network(new RedditNetworkFactory(this)) {
|
||||
m_network->setService(this);
|
||||
setIcon(RedditEntryPoint().icon());
|
||||
}
|
||||
|
||||
void RedditServiceRoot::updateTitle() {
|
||||
setTitle(TextFactory::extractUsernameFromEmail(m_network->username()) + QSL(" (Reddit)"));
|
||||
}
|
||||
|
||||
RootItem* RedditServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
auto* root = new RootItem();
|
||||
|
||||
auto feeds = m_network->subreddits(networkProxy());
|
||||
|
||||
for (auto* feed : feeds) {
|
||||
root->appendChild(feed);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
QVariantHash RedditServiceRoot::customDatabaseData() const {
|
||||
QVariantHash data = ServiceRoot::customDatabaseData();
|
||||
|
||||
data[QSL("username")] = m_network->username();
|
||||
data[QSL("batch_size")] = m_network->batchSize();
|
||||
data[QSL("download_only_unread")] = m_network->downloadOnlyUnreadMessages();
|
||||
data[QSL("client_id")] = m_network->oauth()->clientId();
|
||||
data[QSL("client_secret")] = m_network->oauth()->clientSecret();
|
||||
data[QSL("refresh_token")] = m_network->oauth()->refreshToken();
|
||||
data[QSL("redirect_uri")] = m_network->oauth()->redirectUrl();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void RedditServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
ServiceRoot::setCustomDatabaseData(data);
|
||||
|
||||
m_network->setUsername(data[QSL("username")].toString());
|
||||
m_network->setBatchSize(data[QSL("batch_size")].toInt());
|
||||
m_network->setDownloadOnlyUnreadMessages(data[QSL("download_only_unread")].toBool());
|
||||
m_network->oauth()->setClientId(data[QSL("client_id")].toString());
|
||||
m_network->oauth()->setClientSecret(data[QSL("client_secret")].toString());
|
||||
m_network->oauth()->setRefreshToken(data[QSL("refresh_token")].toString());
|
||||
m_network->oauth()->setRedirectUrl(data[QSL("redirect_uri")].toString(), true);
|
||||
}
|
||||
|
||||
QList<Message> RedditServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>&
|
||||
stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(stated_messages)
|
||||
Q_UNUSED(tagged_messages)
|
||||
Q_UNUSED(feed)
|
||||
|
||||
QList<Message> messages = m_network->hot(qobject_cast<RedditSubscription*>(feed)->prefixedName(), networkProxy());
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
bool RedditServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedditServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RedditServiceRoot::editItems(const QList<RootItem*>& items) {
|
||||
if (items.first()->kind() == RootItem::Kind::ServiceRoot) {
|
||||
QScopedPointer<FormEditRedditAccount> p(qobject_cast<FormEditRedditAccount*>(accountSetupDialog()));
|
||||
|
||||
p->addEditAccount(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceRoot::editItems(items);
|
||||
}
|
||||
|
||||
FormAccountDetails* RedditServiceRoot::accountSetupDialog() const {
|
||||
return new FormEditRedditAccount(qApp->mainFormWidget());
|
||||
}
|
||||
|
||||
bool RedditServiceRoot::supportsFeedAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RedditServiceRoot::supportsCategoryAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RedditServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadRootFromDatabase<RedditCategory, RedditSubscription>(this);
|
||||
loadCacheFromFile();
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
m_network->oauth()->login([this]() {
|
||||
syncIn();
|
||||
});
|
||||
}
|
||||
else {
|
||||
m_network->oauth()->login();
|
||||
}
|
||||
}
|
||||
|
||||
QString RedditServiceRoot::code() const {
|
||||
return RedditEntryPoint().code();
|
||||
}
|
||||
|
||||
QString RedditServiceRoot::additionalTooltip() const {
|
||||
return ServiceRoot::additionalTooltip() + QSL("\n") +
|
||||
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("-"));
|
||||
}
|
||||
|
||||
void RedditServiceRoot::saveAllCachedData(bool ignore_errors) {
|
||||
Q_UNUSED(ignore_errors)
|
||||
auto msg_cache = takeMessageCache();
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITSERVICEROOT_H
|
||||
#define REDDITSERVICEROOT_H
|
||||
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
|
||||
class RedditNetworkFactory;
|
||||
|
||||
class RedditServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RedditServiceRoot(RootItem* parent = nullptr);
|
||||
|
||||
void setNetwork(RedditNetworkFactory* network);
|
||||
RedditNetworkFactory* network() const;
|
||||
|
||||
virtual bool isSyncable() const;
|
||||
virtual bool canBeEdited() const;
|
||||
virtual void editItems(const QList<RootItem*>& items);
|
||||
virtual FormAccountDetails* accountSetupDialog() const;
|
||||
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:
|
||||
RedditNetworkFactory* m_network;
|
||||
};
|
||||
|
||||
inline void RedditServiceRoot::setNetwork(RedditNetworkFactory* network) {
|
||||
m_network = network;
|
||||
}
|
||||
|
||||
inline RedditNetworkFactory* RedditServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
#endif // REDDITSERVICEROOT_H
|
@ -1,32 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/reddit/redditsubscription.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "services/reddit/redditserviceroot.h"
|
||||
|
||||
RedditSubscription::RedditSubscription(RootItem* parent) : Feed(parent), m_prefixedName(QString()) {}
|
||||
|
||||
RedditServiceRoot* RedditSubscription::serviceRoot() const {
|
||||
return qobject_cast<RedditServiceRoot*>(getParentServiceRoot());
|
||||
}
|
||||
|
||||
QString RedditSubscription::prefixedName() const {
|
||||
return m_prefixedName;
|
||||
}
|
||||
|
||||
void RedditSubscription::setPrefixedName(const QString& prefixed_name) {
|
||||
m_prefixedName = prefixed_name;
|
||||
}
|
||||
|
||||
QVariantHash RedditSubscription::customDatabaseData() const {
|
||||
QVariantHash data;
|
||||
|
||||
data.insert(QSL("prefixed_name"), prefixedName());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void RedditSubscription::setCustomDatabaseData(const QVariantHash& data) {
|
||||
setPrefixedName(data.value(QSL("prefixed_name")).toString());
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef REDDITSUBSCRIPTION_H
|
||||
#define REDDITSUBSCRIPTION_H
|
||||
|
||||
#include "services/abstract/feed.h"
|
||||
|
||||
class RedditServiceRoot;
|
||||
|
||||
class RedditSubscription : public Feed {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RedditSubscription(RootItem* parent = nullptr);
|
||||
|
||||
RedditServiceRoot* serviceRoot() const;
|
||||
|
||||
QString prefixedName() const;
|
||||
void setPrefixedName(const QString& prefixed_name);
|
||||
|
||||
virtual QVariantHash customDatabaseData() const;
|
||||
virtual void setCustomDatabaseData(const QVariantHash& data);
|
||||
|
||||
private:
|
||||
QString m_prefixedName;
|
||||
};
|
||||
|
||||
#endif // REDDITSUBSCRIPTION_H
|
@ -1,49 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSS_DEFINITIONS_H
|
||||
#define TTRSS_DEFINITIONS_H
|
||||
|
||||
#define TTRSS_MINIMAL_API_LEVEL 9
|
||||
#define TTRSS_CONTENT_TYPE_JSON "application/json; charset=utf-8"
|
||||
|
||||
///
|
||||
/// Errors.
|
||||
///
|
||||
#define TTRSS_NOT_LOGGED_IN "NOT_LOGGED_IN" // Error when user needs to login before making an operation.
|
||||
#define TTRSS_UNKNOWN_METHOD "UNKNOWN_METHOD" // Given "op" is not recognized.
|
||||
#define TTRSS_INCORRECT_USAGE "INCORRECT_USAGE" // Given "op" was used with bad parameters.
|
||||
|
||||
// Limitations
|
||||
#define TTRSS_DEFAULT_MESSAGES 100
|
||||
#define TTRSS_MAX_MESSAGES 200
|
||||
|
||||
// General return status codes.
|
||||
#define TTRSS_API_STATUS_OK 0
|
||||
#define TTRSS_API_STATUS_ERR 1
|
||||
#define TTRSS_CONTENT_NOT_LOADED -1
|
||||
|
||||
// Login.
|
||||
#define TTRSS_API_DISABLED "API_DISABLED" // API is not enabled.
|
||||
#define TTRSS_LOGIN_ERROR "LOGIN_ERROR" // Incorrect password/username.
|
||||
|
||||
// Get feed tree.
|
||||
#define TTRSS_GFT_TYPE_CATEGORY "category"
|
||||
|
||||
// "Published" feed/label.
|
||||
#define TTRSS_PUBLISHED_LABEL_ID -2
|
||||
#define TTRSS_PUBLISHED_FEED_ID 0
|
||||
|
||||
// Subscribe to feed.
|
||||
#define STF_UNKNOWN -1
|
||||
#define STF_EXISTS 0
|
||||
#define STF_INVALID_URL 2
|
||||
#define STF_UNREACHABLE_URL 5
|
||||
#define STF_URL_NO_FEED 3
|
||||
#define STF_URL_MANY_FEEDS 4
|
||||
#define STF_INSERTED 1
|
||||
|
||||
// Unsubscribe from feed.
|
||||
#define UFF_FEED_NOT_FOUND "FEED_NOT_FOUND"
|
||||
#define UFF_OK "OK"
|
||||
|
||||
#endif // TTRSS_DEFINITIONS_H
|
@ -1,69 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/gui/formeditttrssaccount.h"
|
||||
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/tt-rss/gui/ttrssaccountdetails.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
FormEditTtRssAccount::FormEditTtRssAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("tt-rss")), parent), m_details(new TtRssAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Server setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditTtRssAccount::performTest);
|
||||
m_details->m_ui.m_txtUrl->setFocus();
|
||||
}
|
||||
|
||||
void FormEditTtRssAccount::apply() {
|
||||
FormAccountDetails::apply();
|
||||
|
||||
bool using_another_acc =
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->text() != account<TtRssServiceRoot>()->network()->username() ||
|
||||
m_details->m_ui.m_txtUrl->lineEdit()->text() != account<TtRssServiceRoot>()->network()->url();
|
||||
|
||||
account<TtRssServiceRoot>()->network()->logout(m_account->networkProxy());
|
||||
account<TtRssServiceRoot>()->network()->setUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
|
||||
account<TtRssServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<TtRssServiceRoot>()->network()->setPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
|
||||
account<TtRssServiceRoot>()->network()->setAuthIsUsed(m_details->m_ui.m_gbHttpAuthentication->isChecked());
|
||||
account<TtRssServiceRoot>()->network()->setAuthUsername(m_details->m_ui.m_txtHttpUsername->lineEdit()->text());
|
||||
account<TtRssServiceRoot>()->network()->setAuthPassword(m_details->m_ui.m_txtHttpPassword->lineEdit()->text());
|
||||
account<TtRssServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<TtRssServiceRoot>()->network()->setIntelligentSynchronization(m_details->m_ui.m_cbNewAlgorithm->isChecked());
|
||||
account<TtRssServiceRoot>()->network()->setForceServerSideUpdate(m_details->m_ui.m_checkServerSideUpdate
|
||||
->isChecked());
|
||||
account<TtRssServiceRoot>()
|
||||
->network()
|
||||
->setDownloadOnlyUnreadMessages(m_details->m_ui.m_checkDownloadOnlyUnreadMessages->isChecked());
|
||||
|
||||
account<TtRssServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew && using_another_acc) {
|
||||
account<TtRssServiceRoot>()->completelyRemoveAllData();
|
||||
account<TtRssServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditTtRssAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
TtRssServiceRoot* existing_root = account<TtRssServiceRoot>();
|
||||
|
||||
m_details->m_ui.m_gbHttpAuthentication->setChecked(existing_root->network()->authIsUsed());
|
||||
m_details->m_ui.m_txtHttpPassword->lineEdit()->setText(existing_root->network()->authPassword());
|
||||
m_details->m_ui.m_txtHttpUsername->lineEdit()->setText(existing_root->network()->authUsername());
|
||||
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()->url());
|
||||
m_details->m_ui.m_spinLimitMessages->setValue(existing_root->network()->batchSize());
|
||||
m_details->m_ui.m_checkServerSideUpdate->setChecked(existing_root->network()->forceServerSideUpdate());
|
||||
m_details->m_ui.m_checkDownloadOnlyUnreadMessages->setChecked(existing_root->network()->downloadOnlyUnreadMessages());
|
||||
m_details->m_ui.m_cbNewAlgorithm->setChecked(existing_root->network()->intelligentSynchronization());
|
||||
}
|
||||
|
||||
void FormEditTtRssAccount::performTest() {
|
||||
m_details->performTest(m_proxyDetails->proxy());
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITACCOUNT_H
|
||||
#define FORMEDITACCOUNT_H
|
||||
|
||||
#include "services/abstract/gui/formaccountdetails.h"
|
||||
|
||||
class RootItem;
|
||||
class TtRssServiceRoot;
|
||||
class TtRssAccountDetails;
|
||||
|
||||
class FormEditTtRssAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditTtRssAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private slots:
|
||||
void performTest();
|
||||
|
||||
private:
|
||||
TtRssAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITACCOUNT_H
|
@ -1,73 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/gui/formttrssfeeddetails.h"
|
||||
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "services/abstract/gui/authenticationdetails.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/gui/ttrssfeeddetails.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QTimer>
|
||||
|
||||
FormTtRssFeedDetails::FormTtRssFeedDetails(ServiceRoot* service_root,
|
||||
RootItem* parent_to_select,
|
||||
const QString& url,
|
||||
QWidget* parent)
|
||||
: FormFeedDetails(service_root, parent), m_feedDetails(new TtRssFeedDetails(this)),
|
||||
m_authDetails(new AuthenticationDetails(true, this)), m_parentToSelect(parent_to_select), m_urlToProcess(url) {}
|
||||
|
||||
void FormTtRssFeedDetails::apply() {
|
||||
if (!m_creatingNew) {
|
||||
// NOTE: We can only edit base properties, therefore
|
||||
// base method is fine.
|
||||
FormFeedDetails::apply();
|
||||
}
|
||||
else {
|
||||
RootItem* parent = m_feedDetails->ui.m_cmbParentCategory->currentData().value<RootItem*>();
|
||||
auto* root = qobject_cast<TtRssServiceRoot*>(parent->getParentServiceRoot());
|
||||
const int category_id = parent->kind() == RootItem::Kind::ServiceRoot ? 0 : parent->customNumericId();
|
||||
const TtRssSubscribeToFeedResponse response =
|
||||
root->network()->subscribeToFeed(m_feedDetails->ui.m_txtUrl->lineEdit()->text(),
|
||||
category_id,
|
||||
m_serviceRoot->networkProxy(),
|
||||
m_authDetails->authenticationType() ==
|
||||
NetworkFactory::NetworkAuthentication::Basic,
|
||||
m_authDetails->username(),
|
||||
m_authDetails->password());
|
||||
|
||||
if (response.code() == STF_INSERTED) {
|
||||
// Feed was added online.
|
||||
qApp->showGuiMessage(Notification::Event::GeneralEvent,
|
||||
{tr("Feed added"),
|
||||
tr("Feed was added, obtaining new tree of feeds now."),
|
||||
QSystemTrayIcon::MessageIcon::Information});
|
||||
QTimer::singleShot(300, root, &TtRssServiceRoot::syncIn);
|
||||
}
|
||||
else {
|
||||
throw ApplicationException(tr("API returned error code %1").arg(QString::number(response.code())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FormTtRssFeedDetails::loadFeedData() {
|
||||
FormFeedDetails::loadFeedData();
|
||||
|
||||
if (m_creatingNew) {
|
||||
insertCustomTab(m_feedDetails, tr("General"), 0);
|
||||
insertCustomTab(m_authDetails, tr("Network"), 1);
|
||||
activateTab(0);
|
||||
|
||||
m_feedDetails->loadCategories(m_serviceRoot->getSubTreeCategories(), m_serviceRoot, m_parentToSelect);
|
||||
|
||||
if (!m_urlToProcess.isEmpty()) {
|
||||
m_feedDetails->ui.m_txtUrl->lineEdit()->setText(m_urlToProcess);
|
||||
}
|
||||
|
||||
m_feedDetails->ui.m_txtUrl->lineEdit()->selectAll();
|
||||
m_feedDetails->ui.m_txtUrl->setFocus();
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMTTRSSFEEDDETAILS_H
|
||||
#define FORMTTRSSFEEDDETAILS_H
|
||||
|
||||
#include "services/abstract/gui/formfeeddetails.h"
|
||||
|
||||
class TtRssFeed;
|
||||
class TtRssFeedDetails;
|
||||
class AuthenticationDetails;
|
||||
|
||||
class FormTtRssFeedDetails : public FormFeedDetails {
|
||||
public:
|
||||
explicit FormTtRssFeedDetails(ServiceRoot* service_root,
|
||||
RootItem* parent_to_select = nullptr,
|
||||
const QString& url = QString(),
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
private:
|
||||
virtual void loadFeedData();
|
||||
|
||||
private:
|
||||
TtRssFeedDetails* m_feedDetails;
|
||||
AuthenticationDetails* m_authDetails;
|
||||
RootItem* m_parentToSelect;
|
||||
QString m_urlToProcess;
|
||||
};
|
||||
|
||||
#endif // FORMTTRSSFEEDDETAILS_H
|
@ -1,78 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/gui/formttrssnote.h"
|
||||
|
||||
#include "gui/guiutilities.h"
|
||||
#include "gui/messagebox.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
#include "services/tt-rss/ttrssnotetopublish.h"
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
FormTtRssNote::FormTtRssNote(TtRssServiceRoot* root)
|
||||
: QDialog(qApp->mainFormWidget()), m_root(root), m_titleOk(false), m_urlOk(false) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
GuiUtilities::applyDialogProperties(*this,
|
||||
qApp->icons()->fromTheme(QSL("emblem-shared")),
|
||||
tr("Share note to \"Published\" feed"));
|
||||
|
||||
setTabOrder(m_ui.m_txtTitle->lineEdit(), m_ui.m_txtUrl->lineEdit());
|
||||
setTabOrder(m_ui.m_txtUrl->lineEdit(), m_ui.m_txtContent);
|
||||
setTabOrder(m_ui.m_txtContent, m_ui.m_btnBox);
|
||||
|
||||
connect(m_ui.m_txtTitle->lineEdit(), &BaseLineEdit::textChanged, this, &FormTtRssNote::onTitleChanged);
|
||||
connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &FormTtRssNote::onUrlChanged);
|
||||
connect(m_ui.m_btnBox, &QDialogButtonBox::accepted, this, &FormTtRssNote::sendNote);
|
||||
|
||||
emit m_ui.m_txtTitle->lineEdit()->textChanged({});
|
||||
emit m_ui.m_txtUrl->lineEdit()->textChanged({});
|
||||
}
|
||||
|
||||
void FormTtRssNote::sendNote() {
|
||||
TtRssNoteToPublish note;
|
||||
|
||||
note.m_content = m_ui.m_txtContent->toPlainText();
|
||||
note.m_url = m_ui.m_txtUrl->lineEdit()->text();
|
||||
note.m_title = m_ui.m_txtTitle->lineEdit()->text();
|
||||
|
||||
auto res = m_root->network()->shareToPublished(note, m_root->networkProxy());
|
||||
|
||||
if (res.status() == TTRSS_API_STATUS_OK) {
|
||||
accept();
|
||||
}
|
||||
else {
|
||||
MsgBox::show({},
|
||||
QMessageBox::Icon::Critical,
|
||||
tr("Cannot share note"),
|
||||
tr("There was an error, when trying to send your custom note."),
|
||||
{},
|
||||
res.error());
|
||||
}
|
||||
}
|
||||
|
||||
void FormTtRssNote::onTitleChanged(const QString& text) {
|
||||
m_titleOk = !text.simplified().isEmpty();
|
||||
|
||||
m_ui.m_txtTitle->setStatus(m_titleOk ? WidgetWithStatus::StatusType::Ok : WidgetWithStatus::StatusType::Error,
|
||||
tr("Enter non-empty title."));
|
||||
|
||||
updateOkButton();
|
||||
}
|
||||
|
||||
void FormTtRssNote::onUrlChanged(const QString& text) {
|
||||
m_urlOk = text.startsWith(URI_SCHEME_HTTPS) || text.startsWith(URI_SCHEME_HTTP);
|
||||
|
||||
m_ui.m_txtUrl->setStatus(m_urlOk ? WidgetWithStatus::StatusType::Ok : WidgetWithStatus::StatusType::Error,
|
||||
tr("Enter valid URL."));
|
||||
|
||||
updateOkButton();
|
||||
}
|
||||
|
||||
void FormTtRssNote::updateOkButton() {
|
||||
m_ui.m_btnBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(m_urlOk && m_titleOk);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMTTRSSNOTE_H
|
||||
#define FORMTTRSSNOTE_H
|
||||
|
||||
#include "ui_formttrssnote.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class TtRssServiceRoot;
|
||||
|
||||
class FormTtRssNote : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormTtRssNote(TtRssServiceRoot* root);
|
||||
|
||||
private slots:
|
||||
void sendNote();
|
||||
void onTitleChanged(const QString& text);
|
||||
void onUrlChanged(const QString& text);
|
||||
|
||||
private:
|
||||
void updateOkButton();
|
||||
|
||||
private:
|
||||
Ui::FormTtRssNote m_ui;
|
||||
TtRssServiceRoot* m_root;
|
||||
bool m_titleOk;
|
||||
bool m_urlOk;
|
||||
};
|
||||
|
||||
#endif // FORMTTRSSNOTE_H
|
@ -1,105 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FormTtRssNote</class>
|
||||
<widget class="QDialog" name="FormTtRssNote">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>340</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtTitle</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtTitle" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="m_btnBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtUrl</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Content</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtContent</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPlainTextEdit" name="m_txtContent"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEditWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>lineeditwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>m_btnBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>FormTtRssNote</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>295</x>
|
||||
<y>327</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,188 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/gui/ttrssaccountdetails.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
|
||||
TtRssAccountDetails::TtRssAccountDetails(QWidget* parent) : QWidget(parent) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.m_lblTestResult->label()->setWordWrap(true);
|
||||
m_ui.m_lblNewAlgorithm
|
||||
->setHelpText(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, but "
|
||||
"first feed fetching could be slow anyway if your feed contains "
|
||||
"huge number of articles.<br/><br/>"
|
||||
"Also, make sure to install <a href=\"https://www.google.com\">api_newsplus</a> TT-RSS "
|
||||
"plugin to your server instance."),
|
||||
true,
|
||||
true);
|
||||
m_ui.m_lblServerSideUpdateInformation
|
||||
->setHelpText(tr("Leaving this option on causes that updates "
|
||||
"of feeds will be probably much slower and may time-out often."),
|
||||
true);
|
||||
m_ui.m_txtHttpUsername->lineEdit()->setPlaceholderText(tr("HTTP authentication username"));
|
||||
m_ui.m_txtHttpPassword->lineEdit()->setPlaceholderText(tr("HTTP authentication password"));
|
||||
m_ui.m_txtPassword->lineEdit()->setPlaceholderText(tr("Password for your TT-RSS account"));
|
||||
m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your TT-RSS account"));
|
||||
m_ui.m_txtUrl->lineEdit()->setPlaceholderText(tr("URL of your TT-RSS instance WITHOUT trailing \"/api/\" string"));
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
|
||||
tr("No test done yet."),
|
||||
tr("Here, results of connection test are shown."));
|
||||
|
||||
setTabOrder(m_ui.m_txtUrl->lineEdit(), m_ui.m_checkDownloadOnlyUnreadMessages);
|
||||
setTabOrder(m_ui.m_checkDownloadOnlyUnreadMessages, m_ui.m_spinLimitMessages);
|
||||
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_cbNewAlgorithm);
|
||||
setTabOrder(m_ui.m_cbNewAlgorithm, m_ui.m_checkServerSideUpdate);
|
||||
setTabOrder(m_ui.m_checkServerSideUpdate, 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_gbHttpAuthentication);
|
||||
setTabOrder(m_ui.m_gbHttpAuthentication, m_ui.m_txtHttpUsername->lineEdit());
|
||||
setTabOrder(m_ui.m_txtHttpUsername->lineEdit(), m_ui.m_txtHttpPassword->lineEdit());
|
||||
setTabOrder(m_ui.m_txtHttpPassword->lineEdit(), m_ui.m_btnTestSetup);
|
||||
|
||||
m_ui.m_txtHttpPassword->lineEdit()->setPasswordMode(true);
|
||||
m_ui.m_txtPassword->lineEdit()->setPasswordMode(true);
|
||||
|
||||
connect(m_ui.m_txtPassword->lineEdit(), &BaseLineEdit::textChanged, this, &TtRssAccountDetails::onPasswordChanged);
|
||||
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &TtRssAccountDetails::onUsernameChanged);
|
||||
connect(m_ui.m_txtHttpPassword->lineEdit(),
|
||||
&BaseLineEdit::textChanged,
|
||||
this,
|
||||
&TtRssAccountDetails::onHttpPasswordChanged);
|
||||
connect(m_ui.m_txtHttpUsername->lineEdit(),
|
||||
&BaseLineEdit::textChanged,
|
||||
this,
|
||||
&TtRssAccountDetails::onHttpUsernameChanged);
|
||||
connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &TtRssAccountDetails::onUrlChanged);
|
||||
connect(m_ui.m_gbHttpAuthentication, &QGroupBox::toggled, this, &TtRssAccountDetails::onHttpPasswordChanged);
|
||||
connect(m_ui.m_gbHttpAuthentication, &QGroupBox::toggled, this, &TtRssAccountDetails::onHttpUsernameChanged);
|
||||
|
||||
onPasswordChanged();
|
||||
onUsernameChanged();
|
||||
onUrlChanged();
|
||||
onHttpPasswordChanged();
|
||||
onHttpUsernameChanged();
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::performTest(const QNetworkProxy& proxy) {
|
||||
TtRssNetworkFactory factory;
|
||||
|
||||
factory.setUsername(m_ui.m_txtUsername->lineEdit()->text());
|
||||
factory.setPassword(m_ui.m_txtPassword->lineEdit()->text());
|
||||
factory.setUrl(m_ui.m_txtUrl->lineEdit()->text());
|
||||
factory.setAuthIsUsed(m_ui.m_gbHttpAuthentication->isChecked());
|
||||
factory.setAuthUsername(m_ui.m_txtHttpUsername->lineEdit()->text());
|
||||
factory.setAuthPassword(m_ui.m_txtHttpPassword->lineEdit()->text());
|
||||
factory.setForceServerSideUpdate(m_ui.m_checkServerSideUpdate->isChecked());
|
||||
factory.setBatchSize(m_ui.m_spinLimitMessages->value());
|
||||
|
||||
TtRssLoginResponse result = factory.login(proxy);
|
||||
|
||||
if (result.isLoaded()) {
|
||||
if (result.hasError()) {
|
||||
QString error = result.error();
|
||||
|
||||
if (error == QSL(TTRSS_API_DISABLED)) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("API access on selected server is not enabled."),
|
||||
tr("API access on selected server is not enabled."));
|
||||
}
|
||||
else if (error == QSL(TTRSS_LOGIN_ERROR)) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Entered credentials are incorrect."),
|
||||
tr("Entered credentials are incorrect."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Other error occurred, contact developers."),
|
||||
tr("Other error occurred, contact developers."));
|
||||
}
|
||||
}
|
||||
else if (result.apiLevel() < TTRSS_MINIMAL_API_LEVEL) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Installed version: %1, required at least: %2.")
|
||||
.arg(QString::number(result.apiLevel()),
|
||||
QString::number(TTRSS_MINIMAL_API_LEVEL)),
|
||||
tr("Selected Tiny Tiny RSS server is running unsupported version of API."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
|
||||
tr("Installed version: %1, required at least: %2.")
|
||||
.arg(QString::number(result.apiLevel()),
|
||||
QString::number(TTRSS_MINIMAL_API_LEVEL)),
|
||||
tr("Tiny Tiny RSS server is okay."));
|
||||
}
|
||||
}
|
||||
else if (factory.lastError() != QNetworkReply::NoError) {
|
||||
m_ui.m_lblTestResult
|
||||
->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Network error: '%1'.").arg(NetworkFactory::networkErrorText(factory.lastError())),
|
||||
tr("Network error, have you entered correct Tiny Tiny RSS API endpoint and password?"));
|
||||
}
|
||||
else {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Unspecified error, did you enter correct URL?"),
|
||||
tr("Unspecified error, did you enter correct URL?"));
|
||||
}
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::onUsernameChanged() {
|
||||
const QString username = m_ui.m_txtUsername->lineEdit()->text();
|
||||
|
||||
if (username.isEmpty()) {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("Username cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Username is okay."));
|
||||
}
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::onPasswordChanged() {
|
||||
const QString password = m_ui.m_txtPassword->lineEdit()->text();
|
||||
|
||||
if (password.isEmpty()) {
|
||||
m_ui.m_txtPassword->setStatus(WidgetWithStatus::StatusType::Error, tr("Password cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtPassword->setStatus(WidgetWithStatus::StatusType::Ok, tr("Password is okay."));
|
||||
}
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::onHttpUsernameChanged() {
|
||||
const bool is_username_ok =
|
||||
!m_ui.m_gbHttpAuthentication->isChecked() || !m_ui.m_txtHttpUsername->lineEdit()->text().isEmpty();
|
||||
|
||||
m_ui.m_txtHttpUsername->setStatus(is_username_ok ? LineEditWithStatus::StatusType::Ok
|
||||
: LineEditWithStatus::StatusType::Warning,
|
||||
is_username_ok ? tr("Username is ok or it is not needed.")
|
||||
: tr("Username is empty."));
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::onHttpPasswordChanged() {
|
||||
const bool is_username_ok =
|
||||
!m_ui.m_gbHttpAuthentication->isChecked() || !m_ui.m_txtHttpPassword->lineEdit()->text().isEmpty();
|
||||
|
||||
m_ui.m_txtHttpPassword->setStatus(is_username_ok ? LineEditWithStatus::StatusType::Ok
|
||||
: LineEditWithStatus::StatusType::Warning,
|
||||
is_username_ok ? tr("Password is ok or it is not needed.")
|
||||
: tr("Password is empty."));
|
||||
}
|
||||
|
||||
void TtRssAccountDetails::onUrlChanged() {
|
||||
const QString url = m_ui.m_txtUrl->lineEdit()->text();
|
||||
|
||||
if (url.isEmpty()) {
|
||||
m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Error, tr("URL cannot be empty."));
|
||||
}
|
||||
else if (url.endsWith(QL1S("/api/")) || url.endsWith(QL1S("/api"))) {
|
||||
m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Warning, tr("URL should NOT end with \"/api/\"."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Ok, tr("URL is okay."));
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSACCOUNTDETAILS_H
|
||||
#define TTRSSACCOUNTDETAILS_H
|
||||
|
||||
#include "ui_ttrssaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
#include <QWidget>
|
||||
|
||||
class TtRssServiceRoot;
|
||||
|
||||
class TtRssAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditTtRssAccount;
|
||||
|
||||
public:
|
||||
explicit TtRssAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void performTest(const QNetworkProxy& proxy);
|
||||
|
||||
void onUsernameChanged();
|
||||
void onPasswordChanged();
|
||||
void onHttpUsernameChanged();
|
||||
void onHttpPasswordChanged();
|
||||
void onUrlChanged();
|
||||
|
||||
private:
|
||||
Ui::TtRssAccountDetails m_ui;
|
||||
};
|
||||
|
||||
#endif // TTRSSACCOUNTDETAILS_H
|
@ -1,252 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TtRssAccountDetails</class>
|
||||
<widget class="QWidget" name="TtRssAccountDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>432</width>
|
||||
<height>396</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="10" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>408</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="m_lblTitle">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtUrl</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<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">
|
||||
<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="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_checkDownloadOnlyUnreadMessages">
|
||||
<property name="text">
|
||||
<string>Download unread articles only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_cbNewAlgorithm">
|
||||
<property name="text">
|
||||
<string>Intelligent synchronization algorithm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_checkServerSideUpdate">
|
||||
<property name="text">
|
||||
<string>Force execution of server-side feeds update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" 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>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="m_gbHttpAuthentication">
|
||||
<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>Requires HTTP authentication</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtHttpUsername</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtHttpUsername" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtHttpPassword</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtHttpPassword" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_btnTestSetup">
|
||||
<property name="text">
|
||||
<string>&Test setup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LabelWithStatus" name="m_lblTestResult" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="HelpSpoiler" name="m_lblNewAlgorithm" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="HelpSpoiler" name="m_lblServerSideUpdateInformation" native="true"/>
|
||||
</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>
|
||||
<customwidget>
|
||||
<class>HelpSpoiler</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>helpspoiler.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,56 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/gui/ttrssfeeddetails.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "services/abstract/category.h"
|
||||
|
||||
TtRssFeedDetails::TtRssFeedDetails(QWidget* parent) : QWidget(parent) {
|
||||
ui.setupUi(this);
|
||||
|
||||
ui.m_txtUrl->lineEdit()->setPlaceholderText(tr("Full feed URL including scheme"));
|
||||
ui.m_txtUrl->lineEdit()->setToolTip(tr("Provide URL for your feed."));
|
||||
|
||||
connect(ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &TtRssFeedDetails::onUrlChanged);
|
||||
onUrlChanged(QString());
|
||||
}
|
||||
|
||||
void TtRssFeedDetails::onUrlChanged(const QString& new_url) {
|
||||
if (QRegularExpression(QSL(URL_REGEXP)).match(new_url).hasMatch()) {
|
||||
// New url is well-formed.
|
||||
ui.m_txtUrl->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok."));
|
||||
}
|
||||
else if (!new_url.simplified().isEmpty()) {
|
||||
// New url is not well-formed but is not empty on the other hand.
|
||||
ui.m_txtUrl->setStatus(
|
||||
LineEditWithStatus::StatusType::Warning,
|
||||
tr(R"(The URL does not meet standard pattern. Does your URL start with "http://" or "https://" prefix.)"));
|
||||
}
|
||||
else {
|
||||
// New url is empty.
|
||||
ui.m_txtUrl->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty."));
|
||||
}
|
||||
}
|
||||
|
||||
void TtRssFeedDetails::loadCategories(const QList<Category*>& categories,
|
||||
RootItem* root_item,
|
||||
RootItem* parent_to_select) {
|
||||
ui.m_cmbParentCategory->addItem(root_item->fullIcon(), root_item->title(), QVariant::fromValue(root_item));
|
||||
|
||||
for (Category* category : categories) {
|
||||
ui.m_cmbParentCategory->addItem(category->fullIcon(), category->title(), QVariant::fromValue(category));
|
||||
}
|
||||
|
||||
if (parent_to_select != nullptr) {
|
||||
if (parent_to_select->kind() == RootItem::Kind::Category) {
|
||||
ui.m_cmbParentCategory->setCurrentIndex(ui.m_cmbParentCategory->findData(QVariant::fromValue(parent_to_select)));
|
||||
}
|
||||
else if (parent_to_select->kind() == RootItem::Kind::Feed) {
|
||||
int target_item = ui.m_cmbParentCategory->findData(QVariant::fromValue(parent_to_select->parent()));
|
||||
|
||||
if (target_item >= 0) {
|
||||
ui.m_cmbParentCategory->setCurrentIndex(target_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSFEEDDETAILS_H
|
||||
#define TTRSSFEEDDETAILS_H
|
||||
|
||||
#include "ui_ttrssfeeddetails.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class Category;
|
||||
class RootItem;
|
||||
|
||||
class TtRssFeedDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormTtRssFeedDetails;
|
||||
|
||||
public:
|
||||
explicit TtRssFeedDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void onUrlChanged(const QString& new_url);
|
||||
|
||||
private:
|
||||
void loadCategories(const QList<Category*>& categories, RootItem* root_item, RootItem* parent_to_select = nullptr);
|
||||
|
||||
private:
|
||||
Ui::TtRssFeedDetails ui;
|
||||
};
|
||||
|
||||
#endif // TTRSSFEEDDETAILS_H
|
@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TtRssFeedDetails</class>
|
||||
<widget class="QWidget" name="TtRssFeedDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>367</width>
|
||||
<height>202</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="m_lblParentCategory">
|
||||
<property name="text">
|
||||
<string>Parent folder</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_cmbParentCategory</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="m_cmbParentCategory">
|
||||
<property name="toolTip">
|
||||
<string>Select parent item for your feed.</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_txtUrl</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEditWithStatus</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>lineeditwithstatus.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -1,61 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/ttrssfeed.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
TtRssFeed::TtRssFeed(RootItem* parent) : Feed(parent), m_actionShareToPublished(nullptr) {}
|
||||
|
||||
TtRssServiceRoot* TtRssFeed::serviceRoot() const {
|
||||
return qobject_cast<TtRssServiceRoot*>(getParentServiceRoot());
|
||||
}
|
||||
|
||||
bool TtRssFeed::canBeDeleted() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TtRssFeed::deleteItem() {
|
||||
TtRssUnsubscribeFeedResponse response =
|
||||
serviceRoot()->network()->unsubscribeFeed(customNumericId(), getParentServiceRoot()->networkProxy());
|
||||
|
||||
if (response.code() == QSL(UFF_OK) && removeItself()) {
|
||||
serviceRoot()->requestItemRemoval(this);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qWarningNN << LOGSEC_TTRSS
|
||||
<< "Unsubscribing from feed failed, received JSON:" << QUOTE_W_SPACE_DOT(response.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QList<QAction*> TtRssFeed::contextMenuFeedsList() {
|
||||
auto menu = Feed::contextMenuFeedsList();
|
||||
|
||||
if (customNumericId() == TTRSS_PUBLISHED_FEED_ID) {
|
||||
if (m_actionShareToPublished == nullptr) {
|
||||
m_actionShareToPublished =
|
||||
new QAction(qApp->icons()->fromTheme(QSL("emblem-shared")), tr("Share to published"), this);
|
||||
|
||||
connect(m_actionShareToPublished, &QAction::triggered, serviceRoot(), &TtRssServiceRoot::shareToPublished);
|
||||
}
|
||||
|
||||
menu.append(m_actionShareToPublished);
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
bool TtRssFeed::removeItself() {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
return DatabaseQueries::deleteFeed(database, this, serviceRoot()->accountId());
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSFEED_H
|
||||
#define TTRSSFEED_H
|
||||
|
||||
#include "services/abstract/feed.h"
|
||||
|
||||
class TtRssServiceRoot;
|
||||
|
||||
class TtRssFeed : public Feed {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TtRssFeed(RootItem* parent = nullptr);
|
||||
|
||||
virtual bool canBeDeleted() const;
|
||||
virtual bool deleteItem();
|
||||
virtual QList<QAction*> contextMenuFeedsList();
|
||||
|
||||
private:
|
||||
TtRssServiceRoot* serviceRoot() const;
|
||||
bool removeItself();
|
||||
|
||||
private:
|
||||
QAction* m_actionShareToPublished;
|
||||
};
|
||||
|
||||
#endif // TTRSSFEED_H
|
File diff suppressed because it is too large
Load Diff
@ -1,246 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSNETWORKFACTORY_H
|
||||
#define TTRSSNETWORKFACTORY_H
|
||||
|
||||
#include "core/message.h"
|
||||
#include "services/tt-rss/ttrssnotetopublish.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
|
||||
class RootItem;
|
||||
class TtRssFeed;
|
||||
class Label;
|
||||
|
||||
class TtRssResponse {
|
||||
public:
|
||||
explicit TtRssResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssResponse();
|
||||
|
||||
bool isLoaded() const;
|
||||
|
||||
int seq() const;
|
||||
int status() const;
|
||||
QString error() const;
|
||||
bool hasError() const;
|
||||
bool isNotLoggedIn() const;
|
||||
bool isUnknownMethod() const;
|
||||
QString toString() const;
|
||||
|
||||
protected:
|
||||
QJsonObject m_rawContent;
|
||||
};
|
||||
|
||||
class TtRssLoginResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssLoginResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssLoginResponse();
|
||||
|
||||
int apiLevel() const;
|
||||
QString sessionId() const;
|
||||
};
|
||||
|
||||
class TtRssGetLabelsResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssGetLabelsResponse(const QString& raw_content = QString());
|
||||
|
||||
QList<RootItem*> labels() const;
|
||||
};
|
||||
|
||||
class TtRssNetworkFactory;
|
||||
|
||||
class TtRssGetFeedsCategoriesResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssGetFeedsCategoriesResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssGetFeedsCategoriesResponse();
|
||||
|
||||
// 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(TtRssNetworkFactory* network,
|
||||
bool obtain_icons,
|
||||
const QNetworkProxy& proxy,
|
||||
const QString& base_address = QString()) const;
|
||||
};
|
||||
|
||||
class ServiceRoot;
|
||||
|
||||
class TtRssGetHeadlinesResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssGetHeadlinesResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssGetHeadlinesResponse();
|
||||
|
||||
QList<Message> messages(ServiceRoot* root) const;
|
||||
};
|
||||
|
||||
class TtRssGetArticleResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssGetArticleResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssGetArticleResponse();
|
||||
|
||||
QList<Message> messages(ServiceRoot* root) const;
|
||||
};
|
||||
|
||||
class TtRssGetCompactHeadlinesResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssGetCompactHeadlinesResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssGetCompactHeadlinesResponse();
|
||||
|
||||
QStringList ids() const;
|
||||
};
|
||||
|
||||
class TtRssUpdateArticleResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssUpdateArticleResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssUpdateArticleResponse();
|
||||
|
||||
QString updateStatus() const;
|
||||
int articlesUpdated() const;
|
||||
};
|
||||
|
||||
class TtRssSubscribeToFeedResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssSubscribeToFeedResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssSubscribeToFeedResponse();
|
||||
|
||||
int code() const;
|
||||
};
|
||||
|
||||
class TtRssUnsubscribeFeedResponse : public TtRssResponse {
|
||||
public:
|
||||
explicit TtRssUnsubscribeFeedResponse(const QString& raw_content = QString());
|
||||
virtual ~TtRssUnsubscribeFeedResponse();
|
||||
|
||||
QString code() const;
|
||||
};
|
||||
|
||||
namespace UpdateArticle {
|
||||
enum class Mode {
|
||||
SetToFalse = 0,
|
||||
SetToTrue = 1,
|
||||
Togggle = 2
|
||||
};
|
||||
|
||||
enum class OperatingField {
|
||||
Starred = 0,
|
||||
Published = 1,
|
||||
Unread = 2
|
||||
};
|
||||
|
||||
} // namespace UpdateArticle
|
||||
|
||||
class TtRssNetworkFactory {
|
||||
public:
|
||||
explicit TtRssNetworkFactory();
|
||||
|
||||
QString url() const;
|
||||
void setUrl(const QString& url);
|
||||
|
||||
QString username() const;
|
||||
void setUsername(const QString& username);
|
||||
|
||||
QString password() const;
|
||||
void setPassword(const QString& password);
|
||||
|
||||
bool authIsUsed() const;
|
||||
void setAuthIsUsed(bool auth_is_used);
|
||||
|
||||
QString authUsername() const;
|
||||
void setAuthUsername(const QString& auth_username);
|
||||
|
||||
QString authPassword() const;
|
||||
void setAuthPassword(const QString& auth_password);
|
||||
|
||||
bool forceServerSideUpdate() const;
|
||||
void setForceServerSideUpdate(bool force_server_side_update);
|
||||
|
||||
bool downloadOnlyUnreadMessages() const;
|
||||
void setDownloadOnlyUnreadMessages(bool download_only_unread_messages);
|
||||
|
||||
// Metadata.
|
||||
QDateTime lastLoginTime() const;
|
||||
QNetworkReply::NetworkError lastError() const;
|
||||
|
||||
// Operations.
|
||||
|
||||
// Logs user in.
|
||||
TtRssLoginResponse login(const QNetworkProxy& proxy);
|
||||
|
||||
// Logs user out.
|
||||
TtRssResponse logout(const QNetworkProxy& proxy);
|
||||
|
||||
// Gets list of labels from the server.
|
||||
TtRssGetLabelsResponse getLabels(const QNetworkProxy& proxy);
|
||||
|
||||
// Shares new item to "published" feed.
|
||||
TtRssResponse shareToPublished(const TtRssNoteToPublish& note, const QNetworkProxy& proxy);
|
||||
|
||||
// Gets feeds from the server.
|
||||
TtRssGetFeedsCategoriesResponse getFeedsCategories(const QNetworkProxy& proxy);
|
||||
|
||||
// Gets message IDs from the server.
|
||||
TtRssGetCompactHeadlinesResponse getCompactHeadlines(int feed_id,
|
||||
int limit,
|
||||
int skip,
|
||||
const QString& view_mode,
|
||||
const QNetworkProxy& proxy);
|
||||
|
||||
TtRssGetHeadlinesResponse getArticle(const QStringList& article_ids, const QNetworkProxy& proxy);
|
||||
|
||||
// Gets headlines (messages) from the server.
|
||||
TtRssGetHeadlinesResponse getHeadlines(int feed_id,
|
||||
int limit,
|
||||
int skip,
|
||||
bool show_content,
|
||||
bool include_attachments,
|
||||
bool sanitize,
|
||||
bool unread_only,
|
||||
const QNetworkProxy& proxy);
|
||||
|
||||
TtRssResponse setArticleLabel(const QStringList& article_ids,
|
||||
const QString& label_custom_id,
|
||||
bool assign,
|
||||
const QNetworkProxy& proxy);
|
||||
|
||||
TtRssUpdateArticleResponse updateArticles(const QStringList& ids,
|
||||
UpdateArticle::OperatingField field,
|
||||
UpdateArticle::Mode mode,
|
||||
const QNetworkProxy& proxy);
|
||||
|
||||
TtRssSubscribeToFeedResponse subscribeToFeed(const QString& url,
|
||||
int category_id,
|
||||
const QNetworkProxy& proxy,
|
||||
bool protectd = false,
|
||||
const QString& username = QString(),
|
||||
const QString& password = QString());
|
||||
|
||||
TtRssUnsubscribeFeedResponse unsubscribeFeed(int feed_id, const QNetworkProxy& proxy);
|
||||
|
||||
int batchSize() const;
|
||||
void setBatchSize(int batch_size);
|
||||
|
||||
bool intelligentSynchronization() const;
|
||||
void setIntelligentSynchronization(bool intelligent_synchronization);
|
||||
|
||||
private:
|
||||
QString m_bareUrl;
|
||||
QString m_fullUrl;
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
int m_batchSize;
|
||||
bool m_forceServerSideUpdate;
|
||||
bool m_downloadOnlyUnreadMessages;
|
||||
bool m_intelligentSynchronization;
|
||||
bool m_authIsUsed;
|
||||
QString m_authUsername;
|
||||
QString m_authPassword;
|
||||
QString m_sessionId;
|
||||
QDateTime m_lastLoginTime;
|
||||
|
||||
QNetworkReply::NetworkError m_lastError;
|
||||
};
|
||||
|
||||
#endif // TTRSSNETWORKFACTORY_H
|
@ -1,15 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSNOTETOPUBLISH_H
|
||||
#define TTRSSNOTETOPUBLISH_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
struct TtRssNoteToPublish {
|
||||
public:
|
||||
QString m_title;
|
||||
QString m_url;
|
||||
QString m_content;
|
||||
};
|
||||
|
||||
#endif // TTRSSNOTETOPUBLISH_H
|
@ -1,47 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/ttrssserviceentrypoint.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/gui/formeditttrssaccount.h"
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
QString TtRssServiceEntryPoint::name() const {
|
||||
return QSL("Tiny Tiny RSS");
|
||||
}
|
||||
|
||||
QString TtRssServiceEntryPoint::description() const {
|
||||
return QObject::tr("This service offers integration with Tiny Tiny RSS.\n\n"
|
||||
"Tiny Tiny RSS is an open source web-based news feed (RSS/Atom) reader and aggregator, "
|
||||
"designed to allow you to read news from any location, while feeling as close to a real "
|
||||
"desktop application as possible.\n\nAt least API level %1 is required.")
|
||||
.arg(TTRSS_MINIMAL_API_LEVEL);
|
||||
}
|
||||
|
||||
QString TtRssServiceEntryPoint::author() const {
|
||||
return QSL(APP_AUTHOR);
|
||||
}
|
||||
|
||||
QIcon TtRssServiceEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("tt-rss"));
|
||||
}
|
||||
|
||||
QString TtRssServiceEntryPoint::code() const {
|
||||
return QSL(SERVICE_CODE_TT_RSS);
|
||||
}
|
||||
|
||||
ServiceRoot* TtRssServiceEntryPoint::createNewRoot() const {
|
||||
FormEditTtRssAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<TtRssServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> TtRssServiceEntryPoint::initializeSubtree() const {
|
||||
// Check DB if standard account is enabled.
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("TtRssServiceEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<TtRssServiceRoot>(database, code());
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSSERVICEENTRYPOINT_H
|
||||
#define TTRSSSERVICEENTRYPOINT_H
|
||||
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
|
||||
class TtRssServiceEntryPoint : public ServiceEntryPoint {
|
||||
public:
|
||||
virtual QString name() const;
|
||||
virtual QString description() const;
|
||||
virtual QString author() const;
|
||||
virtual QIcon icon() const;
|
||||
virtual QString code() const;
|
||||
virtual ServiceRoot* createNewRoot() const;
|
||||
virtual QList<ServiceRoot*> initializeSubtree() const;
|
||||
};
|
||||
|
||||
#endif // TTRSSSERVICEENTRYPOINT_H
|
@ -1,422 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/tt-rss/ttrssserviceroot.h"
|
||||
|
||||
#include "3rd-party/boolinq/boolinq.h"
|
||||
#include "database/databasequeries.h"
|
||||
#include "exceptions/feedfetchexception.h"
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/mutex.h"
|
||||
#include "miscellaneous/textfactory.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "services/abstract/labelsnode.h"
|
||||
#include "services/tt-rss/definitions.h"
|
||||
#include "services/tt-rss/gui/formeditttrssaccount.h"
|
||||
#include "services/tt-rss/gui/formttrssfeeddetails.h"
|
||||
#include "services/tt-rss/gui/formttrssnote.h"
|
||||
#include "services/tt-rss/ttrssfeed.h"
|
||||
#include "services/tt-rss/ttrssnetworkfactory.h"
|
||||
#include "services/tt-rss/ttrssserviceentrypoint.h"
|
||||
|
||||
#include <QPair>
|
||||
#include <QSqlTableModel>
|
||||
|
||||
TtRssServiceRoot::TtRssServiceRoot(RootItem* parent) : ServiceRoot(parent), m_network(new TtRssNetworkFactory()) {
|
||||
setIcon(TtRssServiceEntryPoint().icon());
|
||||
}
|
||||
|
||||
TtRssServiceRoot::~TtRssServiceRoot() {
|
||||
delete m_network;
|
||||
}
|
||||
|
||||
ServiceRoot::LabelOperation TtRssServiceRoot::supportedLabelOperations() const {
|
||||
return ServiceRoot::LabelOperation::Synchronised;
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadRootFromDatabase<Category, TtRssFeed>(this);
|
||||
loadCacheFromFile();
|
||||
|
||||
auto lbls = labelsNode()->labels();
|
||||
|
||||
boolinq::from(lbls).for_each([](Label* lbl) {
|
||||
if (lbl->customNumericId() == TTRSS_PUBLISHED_LABEL_ID) {
|
||||
lbl->setKeepOnTop(true);
|
||||
}
|
||||
});
|
||||
|
||||
boolinq::from(childItems()).for_each([](RootItem* child) {
|
||||
if (child->kind() == RootItem::Kind::Feed && child->customNumericId() == TTRSS_PUBLISHED_FEED_ID) {
|
||||
child->setKeepOnTop(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
syncIn();
|
||||
}
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::stop() {
|
||||
m_network->logout(networkProxy());
|
||||
qDebugNN << LOGSEC_TTRSS << "Stopping Tiny Tiny RSS account, logging out with result"
|
||||
<< QUOTE_W_SPACE_DOT(m_network->lastError());
|
||||
}
|
||||
|
||||
QString TtRssServiceRoot::code() const {
|
||||
return TtRssServiceEntryPoint().code();
|
||||
}
|
||||
|
||||
bool TtRssServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
FormAccountDetails* TtRssServiceRoot::accountSetupDialog() const {
|
||||
return new FormEditTtRssAccount(qApp->mainFormWidget());
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::editItems(const QList<RootItem*>& items) {
|
||||
if (items.first()->kind() == RootItem::Kind::ServiceRoot) {
|
||||
QScopedPointer<FormEditTtRssAccount> p(qobject_cast<FormEditTtRssAccount*>(accountSetupDialog()));
|
||||
|
||||
p->addEditAccount(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceRoot::editItems(items);
|
||||
}
|
||||
|
||||
bool TtRssServiceRoot::supportsFeedAdding() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TtRssServiceRoot::supportsCategoryAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::addNewFeed(RootItem* selected_item, const QString& url) {
|
||||
if (!qApp->feedUpdateLock()->tryLock()) {
|
||||
// Lock was not obtained because
|
||||
// it is used probably by feed updater or application
|
||||
// is quitting.
|
||||
qApp->showGuiMessage(Notification::Event::GeneralEvent,
|
||||
{tr("Cannot add item"),
|
||||
tr("Cannot add feed because another critical operation is ongoing."),
|
||||
QSystemTrayIcon::MessageIcon::Warning});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QScopedPointer<FormTtRssFeedDetails> form_pointer(new FormTtRssFeedDetails(this,
|
||||
selected_item,
|
||||
url,
|
||||
qApp->mainFormWidget()));
|
||||
|
||||
form_pointer->addEditFeed<TtRssFeed>();
|
||||
qApp->feedUpdateLock()->unlock();
|
||||
}
|
||||
|
||||
bool TtRssServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::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()) {
|
||||
auto res = network()->updateArticles(ids,
|
||||
UpdateArticle::OperatingField::Unread,
|
||||
key == RootItem::ReadStatus::Unread ? UpdateArticle::Mode::SetToTrue
|
||||
: UpdateArticle::Mode::SetToFalse,
|
||||
networkProxy());
|
||||
|
||||
if (!ignore_errors && (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError())) {
|
||||
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 ids = customIDsOfMessages(messages);
|
||||
auto res = network()->updateArticles(ids,
|
||||
UpdateArticle::OperatingField::Starred,
|
||||
key == RootItem::Importance::Important ? UpdateArticle::Mode::SetToTrue
|
||||
: UpdateArticle::Mode::SetToFalse,
|
||||
networkProxy());
|
||||
|
||||
if (!ignore_errors && (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError())) {
|
||||
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()) {
|
||||
TtRssResponse res;
|
||||
|
||||
if (label_custom_id.toInt() == TTRSS_PUBLISHED_LABEL_ID) {
|
||||
// "published" label must be added in other method.
|
||||
res = network()->updateArticles(messages,
|
||||
UpdateArticle::OperatingField::Published,
|
||||
UpdateArticle::Mode::SetToTrue,
|
||||
networkProxy());
|
||||
}
|
||||
else {
|
||||
res = network()->setArticleLabel(messages, label_custom_id, true, networkProxy());
|
||||
}
|
||||
|
||||
if (!ignore_errors && (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError())) {
|
||||
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()) {
|
||||
TtRssResponse res;
|
||||
|
||||
if (label_custom_id.toInt() == TTRSS_PUBLISHED_LABEL_ID) {
|
||||
// "published" label must be removed in other method.
|
||||
res = network()->updateArticles(messages,
|
||||
UpdateArticle::OperatingField::Published,
|
||||
UpdateArticle::Mode::SetToFalse,
|
||||
networkProxy());
|
||||
}
|
||||
else {
|
||||
res = network()->setArticleLabel(messages, label_custom_id, false, networkProxy());
|
||||
}
|
||||
|
||||
if (!ignore_errors && (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError())) {
|
||||
addLabelsAssignmentsToCache(messages, label_custom_id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantHash TtRssServiceRoot::customDatabaseData() const {
|
||||
QVariantHash data = ServiceRoot::customDatabaseData();
|
||||
|
||||
data[QSL("username")] = m_network->username();
|
||||
data[QSL("password")] = TextFactory::encrypt(m_network->password());
|
||||
data[QSL("auth_protected")] = m_network->authIsUsed();
|
||||
data[QSL("auth_username")] = m_network->authUsername();
|
||||
data[QSL("auth_password")] = TextFactory::encrypt(m_network->authPassword());
|
||||
data[QSL("url")] = m_network->url();
|
||||
data[QSL("force_update")] = m_network->forceServerSideUpdate();
|
||||
data[QSL("batch_size")] = m_network->batchSize();
|
||||
data[QSL("download_only_unread")] = m_network->downloadOnlyUnreadMessages();
|
||||
data[QSL("intelligent_synchronization")] = m_network->intelligentSynchronization();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
ServiceRoot::setCustomDatabaseData(data);
|
||||
|
||||
m_network->setUsername(data[QSL("username")].toString());
|
||||
m_network->setPassword(TextFactory::decrypt(data[QSL("password")].toString()));
|
||||
m_network->setAuthIsUsed(data[QSL("auth_protected")].toBool());
|
||||
m_network->setAuthUsername(data[QSL("auth_username")].toString());
|
||||
m_network->setAuthPassword(TextFactory::decrypt(data[QSL("auth_password")].toString()));
|
||||
m_network->setUrl(data[QSL("url")].toString());
|
||||
m_network->setForceServerSideUpdate(data[QSL("force_update")].toBool());
|
||||
m_network->setBatchSize(data[QSL("batch_size")].toInt());
|
||||
m_network->setDownloadOnlyUnreadMessages(data[QSL("download_only_unread")].toBool());
|
||||
m_network->setIntelligentSynchronization(data[QSL("intelligent_synchronization")].toBool());
|
||||
}
|
||||
|
||||
QList<Message> TtRssServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>&
|
||||
stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(tagged_messages)
|
||||
|
||||
if (m_network->intelligentSynchronization()) {
|
||||
return obtainMessagesIntelligently(feed, stated_messages);
|
||||
}
|
||||
else {
|
||||
return obtainMessagesViaHeadlines(feed);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Message> TtRssServiceRoot::obtainMessagesIntelligently(Feed* feed,
|
||||
const QHash<BagOfMessages, QStringList>& stated_messages) {
|
||||
// 1. Get unread IDs for a feed.
|
||||
// 2. Get read IDs for a feed.
|
||||
// 3. Get starred IDs for a feed.
|
||||
// 4. Determine IDs needed to download.
|
||||
// 5. Download needed articles.
|
||||
const QStringList remote_all_ids_list =
|
||||
m_network->downloadOnlyUnreadMessages()
|
||||
? QStringList()
|
||||
: m_network->getCompactHeadlines(feed->customNumericId(), 1000000, 0, QSL("all_articles"), networkProxy()).ids();
|
||||
const QStringList remote_unread_ids_list =
|
||||
m_network->getCompactHeadlines(feed->customNumericId(), 1000000, 0, QSL("unread"), networkProxy()).ids();
|
||||
const QStringList remote_starred_ids_list =
|
||||
m_network->getCompactHeadlines(feed->customNumericId(), 1000000, 0, QSL("marked"), networkProxy()).ids();
|
||||
|
||||
const QSet<QString> remote_all_ids = FROM_LIST_TO_SET(QSet<QString>, remote_all_ids_list);
|
||||
|
||||
// 1.
|
||||
auto local_unread_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Unread);
|
||||
const QSet<QString> remote_unread_ids = FROM_LIST_TO_SET(QSet<QString>, remote_unread_ids_list);
|
||||
const QSet<QString> local_unread_ids = FROM_LIST_TO_SET(QSet<QString>, local_unread_ids_list);
|
||||
|
||||
// 2.
|
||||
const auto local_read_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Read);
|
||||
const QSet<QString> remote_read_ids = remote_all_ids - remote_unread_ids;
|
||||
const QSet<QString> local_read_ids = FROM_LIST_TO_SET(QSet<QString>, local_read_ids_list);
|
||||
|
||||
// 3.
|
||||
const auto local_starred_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Starred);
|
||||
const QSet<QString> remote_starred_ids = FROM_LIST_TO_SET(QSet<QString>, remote_starred_ids_list);
|
||||
const QSet<QString> local_starred_ids = FROM_LIST_TO_SET(QSet<QString>, local_starred_ids_list);
|
||||
|
||||
// 4.
|
||||
QSet<QString> to_download;
|
||||
|
||||
if (!m_network->downloadOnlyUnreadMessages()) {
|
||||
to_download += remote_all_ids - local_read_ids - local_unread_ids;
|
||||
}
|
||||
else {
|
||||
to_download += remote_unread_ids - local_read_ids - local_unread_ids;
|
||||
}
|
||||
|
||||
auto moved_read = local_read_ids & remote_unread_ids;
|
||||
|
||||
to_download += moved_read;
|
||||
|
||||
if (!m_network->downloadOnlyUnreadMessages()) {
|
||||
auto moved_unread = local_unread_ids & remote_read_ids;
|
||||
|
||||
to_download += moved_unread;
|
||||
}
|
||||
|
||||
auto moved_starred = (local_starred_ids + remote_starred_ids) - (local_starred_ids & remote_starred_ids);
|
||||
|
||||
to_download += moved_starred;
|
||||
|
||||
// 5.
|
||||
auto msgs = m_network->getArticle(to_download.values(), networkProxy());
|
||||
|
||||
return msgs.messages(this);
|
||||
}
|
||||
|
||||
QList<Message> TtRssServiceRoot::obtainMessagesViaHeadlines(Feed* feed) {
|
||||
QList<Message> messages;
|
||||
int newly_added_messages = 0;
|
||||
int limit = network()->batchSize() <= 0 ? TTRSS_MAX_MESSAGES : network()->batchSize();
|
||||
int skip = 0;
|
||||
|
||||
do {
|
||||
TtRssGetHeadlinesResponse headlines = network()->getHeadlines(feed->customNumericId(),
|
||||
limit,
|
||||
skip,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
network()->downloadOnlyUnreadMessages(),
|
||||
networkProxy());
|
||||
|
||||
if (network()->lastError() != QNetworkReply::NetworkError::NoError) {
|
||||
throw FeedFetchException(Feed::Status::NetworkError, headlines.error());
|
||||
}
|
||||
else {
|
||||
QList<Message> new_messages = headlines.messages(this);
|
||||
|
||||
messages << new_messages;
|
||||
newly_added_messages = new_messages.size();
|
||||
skip += newly_added_messages;
|
||||
}
|
||||
}
|
||||
while (newly_added_messages > 0 && (network()->batchSize() <= 0 || messages.size() < network()->batchSize()));
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
QString TtRssServiceRoot::additionalTooltip() const {
|
||||
return ServiceRoot::additionalTooltip() + QSL("\n") +
|
||||
tr("Username: %1\nServer: %2\n"
|
||||
"Last error: %3\nLast login on: %4")
|
||||
.arg(m_network->username(),
|
||||
m_network->url(),
|
||||
NetworkFactory::networkErrorText(m_network->lastError()),
|
||||
m_network->lastLoginTime().isValid()
|
||||
? QLocale().toString(m_network->lastLoginTime(), QLocale::FormatType::ShortFormat)
|
||||
: QSL("-"));
|
||||
}
|
||||
|
||||
TtRssNetworkFactory* TtRssServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::shareToPublished() {
|
||||
FormTtRssNote(this).exec();
|
||||
}
|
||||
|
||||
void TtRssServiceRoot::updateTitle() {
|
||||
QString host = QUrl(m_network->url()).host();
|
||||
|
||||
if (host.isEmpty()) {
|
||||
host = m_network->url();
|
||||
}
|
||||
|
||||
setTitle(TextFactory::extractUsernameFromEmail(m_network->username()) + QSL(" (Tiny Tiny RSS)"));
|
||||
}
|
||||
|
||||
RootItem* TtRssServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
TtRssGetFeedsCategoriesResponse feed_cats = m_network->getFeedsCategories(networkProxy());
|
||||
TtRssGetLabelsResponse labels = m_network->getLabels(networkProxy());
|
||||
|
||||
auto lst_error = m_network->lastError();
|
||||
|
||||
if (lst_error == QNetworkReply::NoError) {
|
||||
auto* tree = feed_cats.feedsCategories(m_network, true, networkProxy(), m_network->url());
|
||||
auto* lblroot = new LabelsNode(tree);
|
||||
|
||||
lblroot->setChildItems(labels.labels());
|
||||
tree->appendChild(lblroot);
|
||||
|
||||
return tree;
|
||||
}
|
||||
else {
|
||||
throw NetworkException(lst_error, tr("cannot get list of feeds, network error '%1'").arg(lst_error));
|
||||
}
|
||||
}
|
||||
|
||||
bool TtRssServiceRoot::wantsBaggedIdsOfExistingMessages() const {
|
||||
return m_network->intelligentSynchronization();
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSSERVICEROOT_H
|
||||
#define TTRSSSERVICEROOT_H
|
||||
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
class TtRssCategory;
|
||||
class TtRssFeed;
|
||||
class TtRssNetworkFactory;
|
||||
|
||||
class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TtRssServiceRoot(RootItem* parent = nullptr);
|
||||
virtual ~TtRssServiceRoot();
|
||||
|
||||
virtual bool wantsBaggedIdsOfExistingMessages() const;
|
||||
virtual LabelOperation supportedLabelOperations() const;
|
||||
virtual void start(bool freshly_activated);
|
||||
virtual void stop();
|
||||
virtual QString code() const;
|
||||
virtual bool isSyncable() const;
|
||||
virtual bool canBeEdited() const;
|
||||
virtual void editItems(const QList<RootItem*>& items);
|
||||
virtual FormAccountDetails* accountSetupDialog() const;
|
||||
virtual bool supportsFeedAdding() const;
|
||||
virtual bool supportsCategoryAdding() const;
|
||||
virtual void addNewFeed(RootItem* selected_item, const QString& url = QString());
|
||||
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);
|
||||
|
||||
// Access to network.
|
||||
TtRssNetworkFactory* network() const;
|
||||
|
||||
public slots:
|
||||
void shareToPublished();
|
||||
|
||||
protected:
|
||||
virtual RootItem* obtainNewTreeForSyncIn() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
QList<Message> obtainMessagesIntelligently(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages);
|
||||
QList<Message> obtainMessagesViaHeadlines(Feed* feed);
|
||||
|
||||
private:
|
||||
TtRssNetworkFactory* m_network;
|
||||
};
|
||||
|
||||
#endif // TTRSSSERVICEROOT_H
|
Loading…
x
Reference in New Issue
Block a user