files
This commit is contained in:
parent
4318929bb0
commit
d0670c22fa
78
src/librssguard-nextcloud/CMakeLists.txt
Normal file
78
src/librssguard-nextcloud/CMakeLists.txt
Normal file
@ -0,0 +1,78 @@
|
||||
if(NOT DEFINED LIBRSSGUARD_BINARY_PATH)
|
||||
set(LIBRSSGUARD_SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
src/definitions.h
|
||||
src/gui/formeditnextcloudaccount.cpp
|
||||
src/gui/formeditnextcloudaccount.h
|
||||
src/gui/nextcloudaccountdetails.cpp
|
||||
src/gui/nextcloudaccountdetails.h
|
||||
src/nextcloudfeed.cpp
|
||||
src/nextcloudfeed.h
|
||||
src/nextcloudnetworkfactory.cpp
|
||||
src/nextcloudnetworkfactory.h
|
||||
src/nextcloudserviceentrypoint.cpp
|
||||
src/nextcloudserviceentrypoint.h
|
||||
src/nextcloudserviceroot.cpp
|
||||
src/nextcloudserviceroot.h
|
||||
)
|
||||
|
||||
set(UI_FILES
|
||||
src/gui/nextcloudaccountdetails.ui
|
||||
)
|
||||
|
||||
# Deal with .ui files.
|
||||
qt_wrap_ui(SOURCES ${UI_FILES})
|
||||
|
||||
# Bundle version info.
|
||||
if(WIN32)
|
||||
enable_language("RC")
|
||||
list(APPEND SOURCES "${CMAKE_BINARY_DIR}/rssguard.rc")
|
||||
endif()
|
||||
|
||||
add_library(rssguard-nextcloud SHARED ${SOURCES} ${QM_FILES})
|
||||
|
||||
# Add specific definitions.
|
||||
target_compile_definitions(rssguard-nextcloud
|
||||
PRIVATE
|
||||
RSSGUARD_DLLSPEC=Q_DECL_IMPORT
|
||||
RSSGUARD_DLLSPEC_EXPORT=Q_DECL_EXPORT
|
||||
)
|
||||
|
||||
target_include_directories(rssguard-nextcloud
|
||||
PUBLIC
|
||||
${LIBRSSGUARD_SOURCE_PATH}
|
||||
)
|
||||
|
||||
# Qt.
|
||||
target_link_libraries(rssguard-nextcloud PUBLIC
|
||||
rssguard
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Xml
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
)
|
||||
|
||||
#if(QT_VERSION_MAJOR EQUAL 6)
|
||||
# target_link_libraries(rssguard-feedly PUBLIC
|
||||
# Qt${QT_VERSION_MAJOR}::Core5Compat
|
||||
# )
|
||||
#endif()
|
||||
|
||||
if(WIN32 OR OS2)
|
||||
install(TARGETS rssguard-nextcloud DESTINATION plugins)
|
||||
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
include (GNUInstallDirs)
|
||||
install(TARGETS rssguard-nextcloud
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/rssguard
|
||||
)
|
||||
elseif(APPLE)
|
||||
install(TARGETS rssguard-nextcloud
|
||||
DESTINATION Contents/MacOS
|
||||
)
|
||||
endif()
|
5
src/librssguard-nextcloud/plugin.json
Normal file
5
src/librssguard-nextcloud/plugin.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Nextcloud News",
|
||||
"author": "Martin Rotter",
|
||||
"website": "https://github.com/martinrotter/rssguard"
|
||||
}
|
13
src/librssguard-nextcloud/src/definitions.h
Normal file
13
src/librssguard-nextcloud/src/definitions.h
Normal file
@ -0,0 +1,13 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUD_DEFINITIONS_H
|
||||
#define NEXTCLOUD_DEFINITIONS_H
|
||||
|
||||
#define NEXTCLOUD_CONTENT_TYPE_JSON "application/json; charset=utf-8"
|
||||
#define NEXTCLOUD_API_VERSION "1.2"
|
||||
#define NEXTCLOUD_API_PATH "index.php/apps/news/api/v1-2/"
|
||||
#define NEXTCLOUD_MIN_VERSION "6.0.5"
|
||||
#define NEXTCLOUD_UNLIMITED_BATCH_SIZE -1
|
||||
#define NEXTCLOUD_DEFAULT_BATCH_SIZE 100
|
||||
|
||||
#endif // NEXTCLOUD_DEFINITIONS_H
|
@ -0,0 +1,63 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/formeditnextcloudaccount.h"
|
||||
|
||||
#include "src/gui/nextcloudaccountdetails.h"
|
||||
#include "src/nextcloudnetworkfactory.h"
|
||||
#include "src/nextcloudserviceroot.h"
|
||||
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
FormEditNextcloudAccount::FormEditNextcloudAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("nextcloud")), parent),
|
||||
m_details(new NextcloudAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Server setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditNextcloudAccount::performTest);
|
||||
|
||||
m_details->m_ui.m_txtUrl->setFocus();
|
||||
}
|
||||
|
||||
void FormEditNextcloudAccount::apply() {
|
||||
FormAccountDetails::apply();
|
||||
|
||||
bool using_another_acc =
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->text() != account<NextcloudServiceRoot>()->network()->authUsername() ||
|
||||
m_details->m_ui.m_txtUrl->lineEdit()->text() != account<NextcloudServiceRoot>()->network()->url();
|
||||
|
||||
account<NextcloudServiceRoot>()->network()->setUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
|
||||
account<NextcloudServiceRoot>()->network()->setAuthUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<NextcloudServiceRoot>()->network()->setAuthPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
|
||||
account<NextcloudServiceRoot>()->network()->setForceServerSideUpdate(m_details->m_ui.m_checkServerSideUpdate
|
||||
->isChecked());
|
||||
account<NextcloudServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<NextcloudServiceRoot>()
|
||||
->network()
|
||||
->setDownloadOnlyUnreadMessages(m_details->m_ui.m_checkDownloadOnlyUnreadMessages->isChecked());
|
||||
|
||||
account<NextcloudServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew && using_another_acc) {
|
||||
account<NextcloudServiceRoot>()->completelyRemoveAllData();
|
||||
account<NextcloudServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditNextcloudAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
NextcloudServiceRoot* existing_root = account<NextcloudServiceRoot>();
|
||||
|
||||
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 FormEditNextcloudAccount::performTest() {
|
||||
m_details->performTest(m_proxyDetails->proxy());
|
||||
}
|
30
src/librssguard-nextcloud/src/gui/formeditnextcloudaccount.h
Normal file
30
src/librssguard-nextcloud/src/gui/formeditnextcloudaccount.h
Normal file
@ -0,0 +1,30 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITNEXTCLOUDACCOUNT_H
|
||||
#define FORMEDITNEXTCLOUDACCOUNT_H
|
||||
|
||||
#include <librssguard/services/abstract/gui/formaccountdetails.h>
|
||||
|
||||
class NextcloudAccountDetails;
|
||||
class NextcloudServiceRoot;
|
||||
|
||||
class FormEditNextcloudAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditNextcloudAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private slots:
|
||||
void performTest();
|
||||
|
||||
private:
|
||||
NextcloudAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITNEXTCLOUDACCOUNT_H
|
129
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.cpp
Normal file
129
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/nextcloudaccountdetails.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/nextcloudnetworkfactory.h"
|
||||
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/systemfactory.h>
|
||||
|
||||
NextcloudAccountDetails::NextcloudAccountDetails(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,
|
||||
&NextcloudAccountDetails::onPasswordChanged);
|
||||
connect(m_ui.m_txtUsername->lineEdit(),
|
||||
&BaseLineEdit::textChanged,
|
||||
this,
|
||||
&NextcloudAccountDetails::onUsernameChanged);
|
||||
connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &NextcloudAccountDetails::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 NextcloudAccountDetails::performTest(const QNetworkProxy& custom_proxy) {
|
||||
NextcloudNetworkFactory 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());
|
||||
|
||||
NextcloudStatusResponse 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(NEXTCLOUD_MIN_VERSION))) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Installed version: %1, required at least: %2.")
|
||||
.arg(result.version(), QSL(NEXTCLOUD_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(NEXTCLOUD_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 NextcloudAccountDetails::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 NextcloudAccountDetails::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 NextcloudAccountDetails::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."));
|
||||
}
|
||||
}
|
29
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.h
Normal file
29
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.h
Normal file
@ -0,0 +1,29 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUDACCOUNTDETAILS_H
|
||||
#define NEXTCLOUDACCOUNTDETAILS_H
|
||||
|
||||
#include "ui_nextcloudaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
#include <QWidget>
|
||||
|
||||
class NextcloudAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditNextcloudAccount;
|
||||
|
||||
public:
|
||||
explicit NextcloudAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void performTest(const QNetworkProxy& custom_proxy);
|
||||
void onUsernameChanged();
|
||||
void onPasswordChanged();
|
||||
void onUrlChanged();
|
||||
|
||||
private:
|
||||
Ui::NextcloudAccountDetails m_ui;
|
||||
};
|
||||
|
||||
#endif // NEXTCLOUDACCOUNTDETAILS_H
|
201
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.ui
Normal file
201
src/librssguard-nextcloud/src/gui/nextcloudaccountdetails.ui
Normal file
@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NextcloudAccountDetails</class>
|
||||
<widget class="QWidget" name="NextcloudAccountDetails">
|
||||
<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>
|
36
src/librssguard-nextcloud/src/nextcloudfeed.cpp
Normal file
36
src/librssguard-nextcloud/src/nextcloudfeed.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/nextcloudfeed.h"
|
||||
|
||||
#include "src/nextcloudnetworkfactory.h"
|
||||
#include "src/nextcloudserviceroot.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
NextcloudFeed::NextcloudFeed(RootItem* parent) : Feed(parent) {}
|
||||
|
||||
bool NextcloudFeed::canBeDeleted() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NextcloudFeed::deleteItem() {
|
||||
if (serviceRoot()->network()->deleteFeed(customId(), getParentServiceRoot()->networkProxy()) && removeItself()) {
|
||||
serviceRoot()->requestItemRemoval(this);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NextcloudFeed::removeItself() {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
return DatabaseQueries::deleteFeed(database, this, serviceRoot()->accountId());
|
||||
}
|
||||
|
||||
NextcloudServiceRoot* NextcloudFeed::serviceRoot() const {
|
||||
return qobject_cast<NextcloudServiceRoot*>(getParentServiceRoot());
|
||||
}
|
24
src/librssguard-nextcloud/src/nextcloudfeed.h
Normal file
24
src/librssguard-nextcloud/src/nextcloudfeed.h
Normal file
@ -0,0 +1,24 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUDFEED_H
|
||||
#define NEXTCLOUDFEED_H
|
||||
|
||||
#include <librssguard/services/abstract/feed.h>
|
||||
|
||||
class NextcloudServiceRoot;
|
||||
|
||||
class NextcloudFeed : public Feed {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NextcloudFeed(RootItem* parent = nullptr);
|
||||
|
||||
virtual bool canBeDeleted() const;
|
||||
virtual bool deleteItem();
|
||||
|
||||
private:
|
||||
bool removeItself();
|
||||
NextcloudServiceRoot* serviceRoot() const;
|
||||
};
|
||||
|
||||
#endif // NEXTCLOUDFEED_H
|
660
src/librssguard-nextcloud/src/nextcloudnetworkfactory.cpp
Normal file
660
src/librssguard-nextcloud/src/nextcloudnetworkfactory.cpp
Normal file
@ -0,0 +1,660 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/nextcloudnetworkfactory.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/nextcloudfeed.h"
|
||||
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/settings.h>
|
||||
#include <librssguard/miscellaneous/textfactory.h>
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/services/abstract/category.h>
|
||||
#include <librssguard/services/abstract/rootitem.h>
|
||||
#include <utility>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPixmap>
|
||||
|
||||
NextcloudNetworkFactory::NextcloudNetworkFactory()
|
||||
: m_url(QString()), m_fixedUrl(QString()), m_downloadOnlyUnreadMessages(false), m_forceServerSideUpdate(false),
|
||||
m_authUsername(QString()), m_authPassword(QString()), m_batchSize(NEXTCLOUD_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()) {}
|
||||
|
||||
NextcloudNetworkFactory::~NextcloudNetworkFactory() = default;
|
||||
|
||||
QString NextcloudNetworkFactory::url() const {
|
||||
return m_url;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setUrl(const QString& url) {
|
||||
m_url = url;
|
||||
|
||||
if (url.endsWith('/')) {
|
||||
m_fixedUrl = url;
|
||||
}
|
||||
else {
|
||||
m_fixedUrl = url + '/';
|
||||
}
|
||||
|
||||
// Store endpoints.
|
||||
m_urlUser = m_fixedUrl + NEXTCLOUD_API_PATH + "user";
|
||||
m_urlStatus = m_fixedUrl + NEXTCLOUD_API_PATH + "status";
|
||||
m_urlFolders = m_fixedUrl + NEXTCLOUD_API_PATH + "folders";
|
||||
m_urlFeeds = m_fixedUrl + NEXTCLOUD_API_PATH + "feeds";
|
||||
m_urlMessages = m_fixedUrl + NEXTCLOUD_API_PATH + "items?id=%1&batchSize=%2&type=%3&getRead=%4";
|
||||
m_urlFeedsUpdate = m_fixedUrl + NEXTCLOUD_API_PATH + "feeds/update?userId=%1&feedId=%2";
|
||||
m_urlDeleteFeed = m_fixedUrl + NEXTCLOUD_API_PATH + "feeds/%1";
|
||||
m_urlRenameFeed = m_fixedUrl + NEXTCLOUD_API_PATH + "feeds/%1/rename";
|
||||
}
|
||||
|
||||
bool NextcloudNetworkFactory::forceServerSideUpdate() const {
|
||||
return m_forceServerSideUpdate;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setForceServerSideUpdate(bool force_update) {
|
||||
m_forceServerSideUpdate = force_update;
|
||||
}
|
||||
|
||||
QString NextcloudNetworkFactory::authUsername() const {
|
||||
return m_authUsername;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setAuthUsername(const QString& auth_username) {
|
||||
m_authUsername = auth_username;
|
||||
}
|
||||
|
||||
QString NextcloudNetworkFactory::authPassword() const {
|
||||
return m_authPassword;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setAuthPassword(const QString& auth_password) {
|
||||
m_authPassword = auth_password;
|
||||
}
|
||||
|
||||
NextcloudStatusResponse NextcloudNetworkFactory::status(const QNetworkProxy& custom_proxy) {
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, NEXTCLOUD_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);
|
||||
NextcloudStatusResponse 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;
|
||||
}
|
||||
|
||||
NextcloudGetFeedsCategoriesResponse NextcloudNetworkFactory::feedsCategories(const QNetworkProxy& custom_proxy) {
|
||||
QByteArray result_raw;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, NEXTCLOUD_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 NextcloudGetFeedsCategoriesResponse(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 NextcloudGetFeedsCategoriesResponse(network_reply.m_networkError);
|
||||
}
|
||||
|
||||
QString content_feeds = QString::fromUtf8(result_raw);
|
||||
|
||||
return NextcloudGetFeedsCategoriesResponse(network_reply.m_networkError, content_categories, content_feeds);
|
||||
}
|
||||
|
||||
bool NextcloudNetworkFactory::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, NEXTCLOUD_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 NextcloudNetworkFactory::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, NEXTCLOUD_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 NextcloudNetworkFactory::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, NEXTCLOUD_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;
|
||||
}
|
||||
}
|
||||
|
||||
NextcloudGetMessagesResponse NextcloudNetworkFactory::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, NEXTCLOUD_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);
|
||||
NextcloudGetMessagesResponse 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 NextcloudNetworkFactory::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, NEXTCLOUD_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 NextcloudNetworkFactory::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(NEXTCLOUD_API_PATH) + QSL("items/read/multiple");
|
||||
}
|
||||
else {
|
||||
final_url = m_fixedUrl + QSL(NEXTCLOUD_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, NEXTCLOUD_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 NextcloudNetworkFactory::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 + NEXTCLOUD_API_PATH + "items/star/multiple";
|
||||
}
|
||||
else {
|
||||
final_url = m_fixedUrl + NEXTCLOUD_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, NEXTCLOUD_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 NextcloudNetworkFactory::batchSize() const {
|
||||
return m_batchSize;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setBatchSize(int batch_size) {
|
||||
m_batchSize = batch_size;
|
||||
}
|
||||
|
||||
bool NextcloudNetworkFactory::downloadOnlyUnreadMessages() const {
|
||||
return m_downloadOnlyUnreadMessages;
|
||||
}
|
||||
|
||||
void NextcloudNetworkFactory::setDownloadOnlyUnreadMessages(bool dowload_only_unread_messages) {
|
||||
m_downloadOnlyUnreadMessages = dowload_only_unread_messages;
|
||||
}
|
||||
|
||||
NextcloudResponse::NextcloudResponse(QNetworkReply::NetworkError response, const QString& raw_content)
|
||||
: m_networkError(response), m_rawContent(QJsonDocument::fromJson(raw_content.toUtf8()).object()),
|
||||
m_emptyString(raw_content.isEmpty()) {}
|
||||
|
||||
NextcloudResponse::~NextcloudResponse() = default;
|
||||
|
||||
bool NextcloudResponse::isLoaded() const {
|
||||
return !m_emptyString && !m_rawContent.isEmpty();
|
||||
}
|
||||
|
||||
QString NextcloudResponse::toString() const {
|
||||
return QJsonDocument(m_rawContent).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
}
|
||||
|
||||
QNetworkReply::NetworkError NextcloudResponse::networkError() const {
|
||||
return m_networkError;
|
||||
}
|
||||
|
||||
NextcloudStatusResponse::NextcloudStatusResponse(QNetworkReply::NetworkError response, const QString& raw_content)
|
||||
: NextcloudResponse(response, raw_content) {}
|
||||
|
||||
NextcloudStatusResponse::~NextcloudStatusResponse() = default;
|
||||
|
||||
QString NextcloudStatusResponse::version() const {
|
||||
if (isLoaded()) {
|
||||
return m_rawContent[QSL("version")].toString();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
NextcloudGetFeedsCategoriesResponse::NextcloudGetFeedsCategoriesResponse(QNetworkReply::NetworkError response,
|
||||
QString raw_categories,
|
||||
QString raw_feeds)
|
||||
: NextcloudResponse(response), m_contentCategories(std::move(raw_categories)), m_contentFeeds(std::move(raw_feeds)) {}
|
||||
|
||||
NextcloudGetFeedsCategoriesResponse::~NextcloudGetFeedsCategoriesResponse() = default;
|
||||
|
||||
RootItem* NextcloudGetFeedsCategoriesResponse::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 NextcloudFeed();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NextcloudGetMessagesResponse::NextcloudGetMessagesResponse(QNetworkReply::NetworkError response,
|
||||
const QString& raw_content)
|
||||
: NextcloudResponse(response, raw_content) {}
|
||||
|
||||
NextcloudGetMessagesResponse::~NextcloudGetMessagesResponse() = default;
|
||||
|
||||
QList<Message> NextcloudGetMessagesResponse::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;
|
||||
}
|
138
src/librssguard-nextcloud/src/nextcloudnetworkfactory.h
Normal file
138
src/librssguard-nextcloud/src/nextcloudnetworkfactory.h
Normal file
@ -0,0 +1,138 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUDNETWORKFACTORY_H
|
||||
#define NEXTCLOUDNETWORKFACTORY_H
|
||||
|
||||
#include <librssguard/core/message.h>
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/services/abstract/rootitem.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QString>
|
||||
|
||||
class NextcloudResponse {
|
||||
public:
|
||||
explicit NextcloudResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~NextcloudResponse();
|
||||
|
||||
bool isLoaded() const;
|
||||
QString toString() const;
|
||||
QNetworkReply::NetworkError networkError() const;
|
||||
|
||||
protected:
|
||||
QNetworkReply::NetworkError m_networkError;
|
||||
QJsonObject m_rawContent;
|
||||
bool m_emptyString;
|
||||
};
|
||||
|
||||
class NextcloudGetMessagesResponse : public NextcloudResponse {
|
||||
public:
|
||||
explicit NextcloudGetMessagesResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~NextcloudGetMessagesResponse();
|
||||
|
||||
QList<Message> messages() const;
|
||||
};
|
||||
|
||||
class NextcloudStatusResponse : public NextcloudResponse {
|
||||
public:
|
||||
explicit NextcloudStatusResponse(QNetworkReply::NetworkError response, const QString& raw_content = QString());
|
||||
virtual ~NextcloudStatusResponse();
|
||||
|
||||
QString version() const;
|
||||
};
|
||||
|
||||
class RootItem;
|
||||
|
||||
class NextcloudGetFeedsCategoriesResponse : public NextcloudResponse {
|
||||
public:
|
||||
explicit NextcloudGetFeedsCategoriesResponse(QNetworkReply::NetworkError response,
|
||||
QString raw_categories = QString(),
|
||||
QString raw_feeds = QString());
|
||||
virtual ~NextcloudGetFeedsCategoriesResponse();
|
||||
|
||||
// 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 NextcloudNetworkFactory {
|
||||
public:
|
||||
explicit NextcloudNetworkFactory();
|
||||
virtual ~NextcloudNetworkFactory();
|
||||
|
||||
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.
|
||||
NextcloudStatusResponse status(const QNetworkProxy& custom_proxy);
|
||||
|
||||
// Get feeds & categories (used for sync-in).
|
||||
NextcloudGetFeedsCategoriesResponse 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.
|
||||
NextcloudGetMessagesResponse 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 // NEXTCLOUDNETWORKFACTORY_H
|
52
src/librssguard-nextcloud/src/nextcloudserviceentrypoint.cpp
Normal file
52
src/librssguard-nextcloud/src/nextcloudserviceentrypoint.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/nextcloudserviceentrypoint.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/gui/formeditnextcloudaccount.h"
|
||||
#include "src/nextcloudserviceroot.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
NextcloudServiceEntryPoint::NextcloudServiceEntryPoint(QObject* parent) : QObject(parent) {}
|
||||
|
||||
NextcloudServiceEntryPoint::~NextcloudServiceEntryPoint() {
|
||||
qDebugNN << LOGSEC_GMAIL << "Destructing" << QUOTE_W_SPACE(QSL(SERVICE_CODE_NEXTCLOUD)) << "plugin.";
|
||||
}
|
||||
|
||||
ServiceRoot* NextcloudServiceEntryPoint::createNewRoot() const {
|
||||
FormEditNextcloudAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<NextcloudServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> NextcloudServiceEntryPoint::initializeSubtree() const {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("NextcloudServiceEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<NextcloudServiceRoot>(database, code());
|
||||
}
|
||||
|
||||
QString NextcloudServiceEntryPoint::name() const {
|
||||
return QSL("Nextcloud News");
|
||||
}
|
||||
|
||||
QString NextcloudServiceEntryPoint::code() const {
|
||||
return QSL(SERVICE_CODE_NEXTCLOUD);
|
||||
}
|
||||
|
||||
QString NextcloudServiceEntryPoint::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(NEXTCLOUD_API_VERSION));
|
||||
}
|
||||
|
||||
QString NextcloudServiceEntryPoint::author() const {
|
||||
return QSL(APP_AUTHOR);
|
||||
}
|
||||
|
||||
QIcon NextcloudServiceEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("nextcloud"));
|
||||
}
|
26
src/librssguard-nextcloud/src/nextcloudserviceentrypoint.h
Normal file
26
src/librssguard-nextcloud/src/nextcloudserviceentrypoint.h
Normal file
@ -0,0 +1,26 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUDSERVICEENTRYPOINT_H
|
||||
#define NEXTCLOUDSERVICEENTRYPOINT_H
|
||||
|
||||
#include <librssguard/services/abstract/serviceentrypoint.h>
|
||||
|
||||
class NextcloudServiceEntryPoint : public QObject, public ServiceEntryPoint {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "io.github.martinrotter.rssguard.nextcloud" FILE "plugin.json")
|
||||
Q_INTERFACES(ServiceEntryPoint)
|
||||
|
||||
public:
|
||||
explicit NextcloudServiceEntryPoint(QObject* parent = nullptr);
|
||||
virtual ~NextcloudServiceEntryPoint();
|
||||
|
||||
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 // NEXTCLOUDSERVICEENTRYPOINT_H
|
177
src/librssguard-nextcloud/src/nextcloudserviceroot.cpp
Normal file
177
src/librssguard-nextcloud/src/nextcloudserviceroot.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/nextcloudserviceroot.h"
|
||||
|
||||
#include "src/gui/formeditnextcloudaccount.h"
|
||||
#include "src/nextcloudfeed.h"
|
||||
#include "src/nextcloudnetworkfactory.h"
|
||||
#include "src/nextcloudserviceentrypoint.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/exceptions/feedfetchexception.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/textfactory.h>
|
||||
|
||||
NextcloudServiceRoot::NextcloudServiceRoot(RootItem* parent)
|
||||
: ServiceRoot(parent), m_network(new NextcloudNetworkFactory()) {
|
||||
setIcon(NextcloudServiceEntryPoint().icon());
|
||||
}
|
||||
|
||||
NextcloudServiceRoot::~NextcloudServiceRoot() {
|
||||
delete m_network;
|
||||
}
|
||||
|
||||
bool NextcloudServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NextcloudServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
FormAccountDetails* NextcloudServiceRoot::accountSetupDialog() const {
|
||||
return new FormEditNextcloudAccount(qApp->mainFormWidget());
|
||||
}
|
||||
|
||||
void NextcloudServiceRoot::editItems(const QList<RootItem*>& items) {
|
||||
if (items.first()->kind() == RootItem::Kind::ServiceRoot) {
|
||||
QScopedPointer<FormEditNextcloudAccount> p(qobject_cast<FormEditNextcloudAccount*>(accountSetupDialog()));
|
||||
|
||||
p->addEditAccount(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceRoot::editItems(items);
|
||||
}
|
||||
|
||||
bool NextcloudServiceRoot::supportsFeedAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NextcloudServiceRoot::supportsCategoryAdding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NextcloudServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadRootFromDatabase<Category, NextcloudFeed>(this);
|
||||
loadCacheFromFile();
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
syncIn();
|
||||
}
|
||||
}
|
||||
|
||||
QString NextcloudServiceRoot::code() const {
|
||||
return NextcloudServiceEntryPoint().code();
|
||||
}
|
||||
|
||||
NextcloudNetworkFactory* NextcloudServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
void NextcloudServiceRoot::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 NextcloudServiceRoot::updateTitle() {
|
||||
setTitle(m_network->authUsername() + QSL(" (Nextcloud News)"));
|
||||
}
|
||||
|
||||
RootItem* NextcloudServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
NextcloudGetFeedsCategoriesResponse 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 NextcloudServiceRoot::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 NextcloudServiceRoot::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> NextcloudServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>&
|
||||
stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(stated_messages)
|
||||
Q_UNUSED(tagged_messages)
|
||||
|
||||
NextcloudGetMessagesResponse messages = network()->getMessages(feed->customNumericId(), networkProxy());
|
||||
|
||||
if (messages.networkError() != QNetworkReply::NetworkError::NoError) {
|
||||
throw FeedFetchException(Feed::Status::NetworkError);
|
||||
}
|
||||
else {
|
||||
return messages.messages();
|
||||
}
|
||||
}
|
48
src/librssguard-nextcloud/src/nextcloudserviceroot.h
Normal file
48
src/librssguard-nextcloud/src/nextcloudserviceroot.h
Normal file
@ -0,0 +1,48 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef NEXTCLOUDSERVICEROOT_H
|
||||
#define NEXTCLOUDSERVICEROOT_H
|
||||
|
||||
#include <librssguard/services/abstract/cacheforserviceroot.h>
|
||||
#include <librssguard/services/abstract/serviceroot.h>
|
||||
|
||||
#include <QMap>
|
||||
|
||||
class NextcloudNetworkFactory;
|
||||
class Mutex;
|
||||
|
||||
class NextcloudServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NextcloudServiceRoot(RootItem* parent = nullptr);
|
||||
virtual ~NextcloudServiceRoot();
|
||||
|
||||
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);
|
||||
|
||||
NextcloudNetworkFactory* network() const;
|
||||
|
||||
protected:
|
||||
virtual RootItem* obtainNewTreeForSyncIn() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
|
||||
private:
|
||||
NextcloudNetworkFactory* m_network;
|
||||
};
|
||||
|
||||
#endif // NEXTCLOUDSERVICEROOT_H
|
88
src/librssguard-ttrss/CMakeLists.txt
Normal file
88
src/librssguard-ttrss/CMakeLists.txt
Normal file
@ -0,0 +1,88 @@
|
||||
if(NOT DEFINED LIBRSSGUARD_BINARY_PATH)
|
||||
set(LIBRSSGUARD_SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
src/definitions.h
|
||||
src/gui/formeditttrssaccount.cpp
|
||||
src/gui/formeditttrssaccount.h
|
||||
src/gui/formttrssfeeddetails.cpp
|
||||
src/gui/formttrssfeeddetails.h
|
||||
src/gui/formttrssnote.cpp
|
||||
src/gui/formttrssnote.h
|
||||
src/gui/ttrssaccountdetails.cpp
|
||||
src/gui/ttrssaccountdetails.h
|
||||
src/gui/ttrssfeeddetails.cpp
|
||||
src/gui/ttrssfeeddetails.h
|
||||
src/ttrssfeed.cpp
|
||||
src/ttrssfeed.h
|
||||
src/ttrssnetworkfactory.cpp
|
||||
src/ttrssnetworkfactory.h
|
||||
src/ttrssnotetopublish.h
|
||||
src/ttrssserviceentrypoint.cpp
|
||||
src/ttrssserviceentrypoint.h
|
||||
src/ttrssserviceroot.cpp
|
||||
src/ttrssserviceroot.h
|
||||
)
|
||||
|
||||
set(UI_FILES
|
||||
src/gui/formttrssnote.ui
|
||||
src/gui/ttrssaccountdetails.ui
|
||||
src/gui/ttrssfeeddetails.ui
|
||||
)
|
||||
|
||||
# Deal with .ui files.
|
||||
qt_wrap_ui(SOURCES ${UI_FILES})
|
||||
|
||||
# Bundle version info.
|
||||
if(WIN32)
|
||||
enable_language("RC")
|
||||
list(APPEND SOURCES "${CMAKE_BINARY_DIR}/rssguard.rc")
|
||||
endif()
|
||||
|
||||
add_library(rssguard-ttrss SHARED ${SOURCES} ${QM_FILES})
|
||||
|
||||
# Add specific definitions.
|
||||
target_compile_definitions(rssguard-ttrss
|
||||
PRIVATE
|
||||
RSSGUARD_DLLSPEC=Q_DECL_IMPORT
|
||||
RSSGUARD_DLLSPEC_EXPORT=Q_DECL_EXPORT
|
||||
)
|
||||
|
||||
target_include_directories(rssguard-ttrss
|
||||
PUBLIC
|
||||
${LIBRSSGUARD_SOURCE_PATH}
|
||||
src/3rd-party/richtexteditor
|
||||
)
|
||||
|
||||
# Qt.
|
||||
target_link_libraries(rssguard-ttrss PUBLIC
|
||||
rssguard
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Xml
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
)
|
||||
|
||||
#if(QT_VERSION_MAJOR EQUAL 6)
|
||||
# target_link_libraries(rssguard-feedly PUBLIC
|
||||
# Qt${QT_VERSION_MAJOR}::Core5Compat
|
||||
# )
|
||||
#endif()
|
||||
|
||||
if(WIN32 OR OS2)
|
||||
install(TARGETS rssguard-ttrss DESTINATION plugins)
|
||||
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
include (GNUInstallDirs)
|
||||
install(TARGETS rssguard-ttrss
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/rssguard
|
||||
)
|
||||
elseif(APPLE)
|
||||
install(TARGETS rssguard-ttrss
|
||||
DESTINATION Contents/MacOS
|
||||
)
|
||||
endif()
|
5
src/librssguard-ttrss/plugin.json
Normal file
5
src/librssguard-ttrss/plugin.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Tiny Tiny RSS",
|
||||
"author": "Martin Rotter",
|
||||
"website": "https://github.com/martinrotter/rssguard"
|
||||
}
|
49
src/librssguard-ttrss/src/definitions.h
Normal file
49
src/librssguard-ttrss/src/definitions.h
Normal file
@ -0,0 +1,49 @@
|
||||
// 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
|
70
src/librssguard-ttrss/src/gui/formeditttrssaccount.cpp
Normal file
70
src/librssguard-ttrss/src/gui/formeditttrssaccount.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/formeditttrssaccount.h"
|
||||
|
||||
#include "src/gui/ttrssaccountdetails.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include <librssguard/miscellaneous/iconfactory.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());
|
||||
}
|
31
src/librssguard-ttrss/src/gui/formeditttrssaccount.h
Normal file
31
src/librssguard-ttrss/src/gui/formeditttrssaccount.h
Normal file
@ -0,0 +1,31 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITACCOUNT_H
|
||||
#define FORMEDITACCOUNT_H
|
||||
|
||||
#include <librssguard/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
|
74
src/librssguard-ttrss/src/gui/formttrssfeeddetails.cpp
Normal file
74
src/librssguard-ttrss/src/gui/formttrssfeeddetails.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/formttrssfeeddetails.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/gui/ttrssfeeddetails.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include <librssguard/exceptions/applicationexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/services/abstract/gui/authenticationdetails.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();
|
||||
}
|
||||
}
|
32
src/librssguard-ttrss/src/gui/formttrssfeeddetails.h
Normal file
32
src/librssguard-ttrss/src/gui/formttrssfeeddetails.h
Normal file
@ -0,0 +1,32 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMTTRSSFEEDDETAILS_H
|
||||
#define FORMTTRSSFEEDDETAILS_H
|
||||
|
||||
#include <librssguard/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
|
79
src/librssguard-ttrss/src/gui/formttrssnote.cpp
Normal file
79
src/librssguard-ttrss/src/gui/formttrssnote.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/formttrssnote.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
#include "src/ttrssnotetopublish.h"
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include <librssguard/gui/guiutilities.h>
|
||||
#include <librssguard/gui/messagebox.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.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);
|
||||
}
|
33
src/librssguard-ttrss/src/gui/formttrssnote.h
Normal file
33
src/librssguard-ttrss/src/gui/formttrssnote.h
Normal file
@ -0,0 +1,33 @@
|
||||
// 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
|
105
src/librssguard-ttrss/src/gui/formttrssnote.ui
Normal file
105
src/librssguard-ttrss/src/gui/formttrssnote.ui
Normal file
@ -0,0 +1,105 @@
|
||||
<?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>
|
189
src/librssguard-ttrss/src/gui/ttrssaccountdetails.cpp
Normal file
189
src/librssguard-ttrss/src/gui/ttrssaccountdetails.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/ttrssaccountdetails.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/network-web/networkfactory.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."));
|
||||
}
|
||||
}
|
34
src/librssguard-ttrss/src/gui/ttrssaccountdetails.h
Normal file
34
src/librssguard-ttrss/src/gui/ttrssaccountdetails.h
Normal file
@ -0,0 +1,34 @@
|
||||
// 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
|
252
src/librssguard-ttrss/src/gui/ttrssaccountdetails.ui
Normal file
252
src/librssguard-ttrss/src/gui/ttrssaccountdetails.ui
Normal file
@ -0,0 +1,252 @@
|
||||
<?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>
|
56
src/librssguard-ttrss/src/gui/ttrssfeeddetails.cpp
Normal file
56
src/librssguard-ttrss/src/gui/ttrssfeeddetails.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/ttrssfeeddetails.h"
|
||||
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
src/librssguard-ttrss/src/gui/ttrssfeeddetails.h
Normal file
31
src/librssguard-ttrss/src/gui/ttrssfeeddetails.h
Normal file
@ -0,0 +1,31 @@
|
||||
// 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
|
68
src/librssguard-ttrss/src/gui/ttrssfeeddetails.ui
Normal file
68
src/librssguard-ttrss/src/gui/ttrssfeeddetails.ui
Normal file
@ -0,0 +1,68 @@
|
||||
<?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>
|
62
src/librssguard-ttrss/src/ttrssfeed.cpp
Normal file
62
src/librssguard-ttrss/src/ttrssfeed.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/ttrssfeed.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.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());
|
||||
}
|
28
src/librssguard-ttrss/src/ttrssfeed.h
Normal file
28
src/librssguard-ttrss/src/ttrssfeed.h
Normal file
@ -0,0 +1,28 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSFEED_H
|
||||
#define TTRSSFEED_H
|
||||
|
||||
#include <librssguard/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
|
1206
src/librssguard-ttrss/src/ttrssnetworkfactory.cpp
Normal file
1206
src/librssguard-ttrss/src/ttrssnetworkfactory.cpp
Normal file
File diff suppressed because it is too large
Load Diff
247
src/librssguard-ttrss/src/ttrssnetworkfactory.h
Normal file
247
src/librssguard-ttrss/src/ttrssnetworkfactory.h
Normal file
@ -0,0 +1,247 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSNETWORKFACTORY_H
|
||||
#define TTRSSNETWORKFACTORY_H
|
||||
|
||||
#include "src/ttrssnotetopublish.h"
|
||||
|
||||
#include <librssguard/core/message.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
|
15
src/librssguard-ttrss/src/ttrssnotetopublish.h
Normal file
15
src/librssguard-ttrss/src/ttrssnotetopublish.h
Normal file
@ -0,0 +1,15 @@
|
||||
// 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
|
54
src/librssguard-ttrss/src/ttrssserviceentrypoint.cpp
Normal file
54
src/librssguard-ttrss/src/ttrssserviceentrypoint.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/ttrssserviceentrypoint.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/gui/formeditttrssaccount.h"
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
TtRssServiceEntryPoint::TtRssServiceEntryPoint(QObject* parent) : QObject(parent) {}
|
||||
|
||||
TtRssServiceEntryPoint::~TtRssServiceEntryPoint() {
|
||||
qDebugNN << LOGSEC_GMAIL << "Destructing" << QUOTE_W_SPACE(QSL(SERVICE_CODE_TT_RSS)) << "plugin.";
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
26
src/librssguard-ttrss/src/ttrssserviceentrypoint.h
Normal file
26
src/librssguard-ttrss/src/ttrssserviceentrypoint.h
Normal file
@ -0,0 +1,26 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSSERVICEENTRYPOINT_H
|
||||
#define TTRSSSERVICEENTRYPOINT_H
|
||||
|
||||
#include <librssguard/services/abstract/serviceentrypoint.h>
|
||||
|
||||
class TtRssServiceEntryPoint : public QObject, public ServiceEntryPoint {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "io.github.martinrotter.rssguard.ttrss" FILE "plugin.json")
|
||||
Q_INTERFACES(ServiceEntryPoint)
|
||||
|
||||
public:
|
||||
explicit TtRssServiceEntryPoint(QObject* parent = nullptr);
|
||||
virtual ~TtRssServiceEntryPoint();
|
||||
|
||||
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
|
423
src/librssguard-ttrss/src/ttrssserviceroot.cpp
Normal file
423
src/librssguard-ttrss/src/ttrssserviceroot.cpp
Normal file
@ -0,0 +1,423 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/ttrssserviceroot.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/gui/formeditttrssaccount.h"
|
||||
#include "src/gui/formttrssfeeddetails.h"
|
||||
#include "src/gui/formttrssnote.h"
|
||||
#include "src/ttrssfeed.h"
|
||||
#include "src/ttrssnetworkfactory.h"
|
||||
#include "src/ttrssserviceentrypoint.h"
|
||||
|
||||
#include <librssguard/3rd-party/boolinq/boolinq.h>
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/exceptions/feedfetchexception.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/mutex.h>
|
||||
#include <librssguard/miscellaneous/textfactory.h>
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/services/abstract/labelsnode.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();
|
||||
}
|
61
src/librssguard-ttrss/src/ttrssserviceroot.h
Normal file
61
src/librssguard-ttrss/src/ttrssserviceroot.h
Normal file
@ -0,0 +1,61 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef TTRSSSERVICEROOT_H
|
||||
#define TTRSSSERVICEROOT_H
|
||||
|
||||
#include <librssguard/services/abstract/cacheforserviceroot.h>
|
||||
#include <librssguard/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
|
@ -67,7 +67,7 @@ QStringList PluginFactory::pluginPaths() const {
|
||||
paths << QCoreApplication::applicationDirPath();
|
||||
#endif
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#if defined(NDEBUG)
|
||||
paths << QCoreApplication::applicationDirPath() + QDir::separator() + QL1S("..") + QDir::separator();
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user