feedly, gmail
This commit is contained in:
parent
82d8c0340c
commit
4f1386a837
@ -325,6 +325,7 @@ add_subdirectory(localization)
|
||||
# Plugins.
|
||||
add_subdirectory(src/librssguard-standard)
|
||||
add_subdirectory(src/librssguard-feedly)
|
||||
add_subdirectory(src/librssguard-gmail)
|
||||
|
||||
# GUI executable.
|
||||
add_subdirectory(src/rssguard)
|
||||
|
76
src/librssguard-feedly/CMakeLists.txt
Normal file
76
src/librssguard-feedly/CMakeLists.txt
Normal file
@ -0,0 +1,76 @@
|
||||
if(NOT DEFINED LIBRSSGUARD_BINARY_PATH)
|
||||
set(LIBRSSGUARD_SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
src/definitions.h
|
||||
src/feedlyentrypoint.cpp
|
||||
src/feedlyentrypoint.h
|
||||
src/feedlynetwork.cpp
|
||||
src/feedlynetwork.h
|
||||
src/feedlyserviceroot.cpp
|
||||
src/feedlyserviceroot.h
|
||||
src/gui/feedlyaccountdetails.cpp
|
||||
src/gui/feedlyaccountdetails.h
|
||||
src/gui/formeditfeedlyaccount.cpp
|
||||
src/gui/formeditfeedlyaccount.h
|
||||
)
|
||||
|
||||
set(UI_FILES
|
||||
src/gui/feedlyaccountdetails.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-feedly SHARED ${SOURCES} ${QM_FILES})
|
||||
|
||||
# Add specific definitions.
|
||||
target_compile_definitions(rssguard-feedly
|
||||
PRIVATE
|
||||
RSSGUARD_DLLSPEC=Q_DECL_IMPORT
|
||||
RSSGUARD_DLLSPEC_EXPORT=Q_DECL_EXPORT
|
||||
)
|
||||
|
||||
target_include_directories(rssguard-feedly
|
||||
PUBLIC
|
||||
${LIBRSSGUARD_SOURCE_PATH}
|
||||
)
|
||||
|
||||
# Qt.
|
||||
target_link_libraries(rssguard-feedly 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-feedly DESTINATION plugins)
|
||||
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
include (GNUInstallDirs)
|
||||
install(TARGETS rssguard-feedly
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/rssguard
|
||||
)
|
||||
elseif(APPLE)
|
||||
install(TARGETS rssguard-feedly
|
||||
DESTINATION Contents/MacOS
|
||||
)
|
||||
endif()
|
5
src/librssguard-feedly/plugin.json
Normal file
5
src/librssguard-feedly/plugin.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Feedly",
|
||||
"author": "Martin Rotter",
|
||||
"website": "https://github.com/martinrotter/rssguard"
|
||||
}
|
35
src/librssguard-feedly/src/definitions.h
Normal file
35
src/librssguard-feedly/src/definitions.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef FEEDLY_DEFINITIONS_H
|
||||
#define FEEDLY_DEFINITIONS_H
|
||||
|
||||
#define FEEDLY_DEFAULT_BATCH_SIZE 100
|
||||
#define FEEDLY_MAX_BATCH_SIZE 500
|
||||
#define FEEDLY_MAX_TOTAL_SIZE 5000
|
||||
#define FEEDLY_UNTAG_BATCH_SIZE 100
|
||||
|
||||
#define FEEDLY_GENERATE_DAT "https://feedly.com/v3/auth/dev"
|
||||
|
||||
#define FEEDLY_API_REDIRECT_URI_PORT 14466
|
||||
#define FEEDLY_API_SCOPE "https://cloud.feedly.com/subscriptions"
|
||||
|
||||
// #define FEEDLY_API_URL_BASE "https://sandbox7.feedly.com/v3/"
|
||||
#define FEEDLY_API_URL_BASE "https://cloud.feedly.com/v3/"
|
||||
|
||||
#define FEEDLY_API_SYSTEM_TAG_READ "global.read"
|
||||
#define FEEDLY_API_SYSTEM_TAG_SAVED "global.saved"
|
||||
|
||||
#define FEEDLY_MARKERS_READ "markAsRead"
|
||||
#define FEEDLY_MARKERS_UNREAD "keepUnread"
|
||||
#define FEEDLY_MARKERS_IMPORTANT "markAsSaved"
|
||||
#define FEEDLY_MARKERS_UNIMPORTANT "markAsUnsaved"
|
||||
|
||||
#define FEEDLY_API_URL_AUTH "auth/auth"
|
||||
#define FEEDLY_API_URL_TOKEN "auth/token"
|
||||
#define FEEDLY_API_URL_PROFILE "profile"
|
||||
#define FEEDLY_API_URL_COLLETIONS "collections"
|
||||
#define FEEDLY_API_URL_TAGS "tags"
|
||||
#define FEEDLY_API_URL_STREAM_CONTENTS "streams/contents?streamId=%1"
|
||||
#define FEEDLY_API_URL_STREAM_IDS "streams/%1/ids"
|
||||
#define FEEDLY_API_URL_MARKERS "markers"
|
||||
#define FEEDLY_API_URL_ENTRIES "entries/.mget"
|
||||
|
||||
#endif // FEEDLY_DEFINITIONS_H
|
55
src/librssguard-feedly/src/feedlyentrypoint.cpp
Normal file
55
src/librssguard-feedly/src/feedlyentrypoint.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/feedlyentrypoint.h"
|
||||
|
||||
#include "src/feedlyserviceroot.h"
|
||||
#include "src/gui/formeditfeedlyaccount.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
FeedlyEntryPoint::FeedlyEntryPoint(QObject* parent) : QObject(parent) {}
|
||||
|
||||
FeedlyEntryPoint::~FeedlyEntryPoint() {
|
||||
qDebugNN << LOGSEC_CORE << "Destructing" << QUOTE_W_SPACE(QSL(SERVICE_CODE_FEEDLY)) << "plugin.";
|
||||
}
|
||||
|
||||
ServiceRoot* FeedlyEntryPoint::createNewRoot() const {
|
||||
FormEditFeedlyAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
return form_acc.addEditAccount<FeedlyServiceRoot>();
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> FeedlyEntryPoint::initializeSubtree() const {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(QSL("FeedlyEntryPoint"));
|
||||
|
||||
return DatabaseQueries::getAccounts<FeedlyServiceRoot>(database, code());
|
||||
}
|
||||
|
||||
QString FeedlyEntryPoint::name() const {
|
||||
return QSL("Feedly");
|
||||
}
|
||||
|
||||
QString FeedlyEntryPoint::code() const {
|
||||
return QSL(SERVICE_CODE_FEEDLY);
|
||||
}
|
||||
|
||||
QString FeedlyEntryPoint::description() const {
|
||||
return QObject::tr("Keep up with the topics and trends you care about, without the overwhelm.\n\n"
|
||||
"Feedly is a secure space where you can privately organize and research the "
|
||||
"topics and trends that matter to you.");
|
||||
}
|
||||
|
||||
QString FeedlyEntryPoint::author() const {
|
||||
return QSL(APP_AUTHOR);
|
||||
}
|
||||
|
||||
QIcon FeedlyEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("feedly"));
|
||||
}
|
||||
|
||||
bool FeedlyEntryPoint::isDynamicallyLoaded() const {
|
||||
return true;
|
||||
}
|
27
src/librssguard-feedly/src/feedlyentrypoint.h
Normal file
27
src/librssguard-feedly/src/feedlyentrypoint.h
Normal file
@ -0,0 +1,27 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FEEDLYENTRYPOINT_H
|
||||
#define FEEDLYENTRYPOINT_H
|
||||
|
||||
#include <librssguard/services/abstract/serviceentrypoint.h>
|
||||
|
||||
class FeedlyEntryPoint : public QObject, public ServiceEntryPoint {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "io.github.martinrotter.rssguard.feedly" FILE "plugin.json")
|
||||
Q_INTERFACES(ServiceEntryPoint)
|
||||
|
||||
public:
|
||||
explicit FeedlyEntryPoint(QObject* parent = nullptr);
|
||||
virtual ~FeedlyEntryPoint();
|
||||
|
||||
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;
|
||||
virtual bool isDynamicallyLoaded() const;
|
||||
};
|
||||
|
||||
#endif // FEEDLYENTRYPOINT_H
|
822
src/librssguard-feedly/src/feedlynetwork.cpp
Normal file
822
src/librssguard-feedly/src/feedlynetwork.cpp
Normal file
@ -0,0 +1,822 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/feedlynetwork.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/feedlyserviceroot.h"
|
||||
|
||||
#include <librssguard/3rd-party/boolinq/boolinq.h>
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/settings.h>
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/network-web/webfactory.h>
|
||||
#include <librssguard/services/abstract/category.h>
|
||||
#include <librssguard/services/abstract/label.h>
|
||||
#include <librssguard/services/abstract/labelsnode.h>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#endif
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
FeedlyNetwork::FeedlyNetwork(QObject* parent)
|
||||
: QObject(parent), m_service(nullptr),
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_oauth(new OAuth2Service(QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_AUTH),
|
||||
QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_TOKEN),
|
||||
TextFactory::decrypt(QSL(FEEDLY_CLIENT_ID), OAUTH_DECRYPTION_KEY),
|
||||
TextFactory::decrypt(QSL(FEEDLY_CLIENT_SECRET), OAUTH_DECRYPTION_KEY),
|
||||
QSL(FEEDLY_API_SCOPE),
|
||||
this)),
|
||||
#endif
|
||||
m_username(QString()), m_developerAccessToken(QString()), m_batchSize(FEEDLY_DEFAULT_BATCH_SIZE),
|
||||
m_downloadOnlyUnreadMessages(false), m_intelligentSynchronization(true) {
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_oauth->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(FEEDLY_API_REDIRECT_URI_PORT), true);
|
||||
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyNetwork::onTokensError);
|
||||
connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyNetwork::onAuthFailed);
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &FeedlyNetwork::onTokensRetrieved);
|
||||
#endif
|
||||
}
|
||||
|
||||
QList<Message> FeedlyNetwork::messages(const QString& stream_id,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages) {
|
||||
if (!m_intelligentSynchronization) {
|
||||
return streamContents(stream_id);
|
||||
}
|
||||
|
||||
// 1. Get unread IDs for a feed.
|
||||
// 2. Get read IDs for a feed.
|
||||
// 3. Download messages/contents for missing or changed IDs.
|
||||
QStringList remote_all_ids_list, remote_unread_ids_list;
|
||||
|
||||
remote_unread_ids_list = streamIds(stream_id, true, batchSize());
|
||||
|
||||
if (!downloadOnlyUnreadMessages()) {
|
||||
remote_all_ids_list = streamIds(stream_id, false, batchSize());
|
||||
}
|
||||
|
||||
// 1.
|
||||
auto local_unread_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Unread);
|
||||
QSet<QString> local_unread_ids = FROM_LIST_TO_SET(QSet<QString>, local_unread_ids_list);
|
||||
QSet<QString> remote_unread_ids = FROM_LIST_TO_SET(QSet<QString>, remote_unread_ids_list);
|
||||
|
||||
// 2.
|
||||
auto local_read_ids_list = stated_messages.value(ServiceRoot::BagOfMessages::Read);
|
||||
QSet<QString> local_read_ids = FROM_LIST_TO_SET(QSet<QString>, local_read_ids_list);
|
||||
QSet<QString> remote_read_ids = FROM_LIST_TO_SET(QSet<QString>, remote_all_ids_list) - remote_unread_ids;
|
||||
|
||||
// 3.
|
||||
QSet<QString> to_download;
|
||||
|
||||
// Undownloaded unread articles.
|
||||
to_download += remote_unread_ids - local_unread_ids;
|
||||
|
||||
// Undownloaded read articles.
|
||||
if (!m_downloadOnlyUnreadMessages) {
|
||||
to_download += remote_read_ids - local_read_ids;
|
||||
}
|
||||
|
||||
// Read articles newly marked as unread in service.
|
||||
auto moved_read = local_read_ids.intersect(remote_unread_ids);
|
||||
|
||||
to_download += moved_read;
|
||||
|
||||
// Unread articles newly marked as read in service.
|
||||
if (!m_downloadOnlyUnreadMessages) {
|
||||
auto moved_unread = local_unread_ids.intersect(remote_read_ids);
|
||||
|
||||
to_download += moved_unread;
|
||||
}
|
||||
|
||||
qDebugNN << LOGSEC_FEEDLY << "Will download" << QUOTE_W_SPACE(to_download.size()) << "articles.";
|
||||
|
||||
if (to_download.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return entries(QStringList(to_download.values()));
|
||||
}
|
||||
}
|
||||
|
||||
void FeedlyNetwork::untagEntries(const QString& tag_id, const QStringList& msg_custom_ids) {
|
||||
if (msg_custom_ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot untag entries, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::TagEntries) + QSL("/%1/").arg(QString(QUrl::toPercentEncoding(tag_id)));
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
auto msg_batch = msg_custom_ids.mid(i, FEEDLY_UNTAG_BATCH_SIZE);
|
||||
|
||||
i += FEEDLY_UNTAG_BATCH_SIZE;
|
||||
|
||||
auto ids = boolinq::from(msg_batch)
|
||||
.select([](const QString& msg_id) {
|
||||
return QString(QUrl::toPercentEncoding(msg_id));
|
||||
})
|
||||
.toStdList();
|
||||
QString final_url = target_url + FROM_STD_LIST(QStringList, ids).join(',');
|
||||
auto result = NetworkFactory::performNetworkOperation(final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::DeleteOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
}
|
||||
while (i < msg_custom_ids.size());
|
||||
}
|
||||
|
||||
void FeedlyNetwork::tagEntries(const QString& tag_id, const QStringList& msg_custom_ids) {
|
||||
if (msg_custom_ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot tag entries, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::TagEntries) + QSL("/%1").arg(QString(QUrl::toPercentEncoding(tag_id)));
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
QByteArray input_data;
|
||||
QJsonObject input;
|
||||
|
||||
input[QSL("entryIds")] = QJsonArray::fromStringList(msg_custom_ids);
|
||||
input_data = QJsonDocument(input).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
|
||||
auto result =
|
||||
NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
input_data,
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PutOperation,
|
||||
{bearerHeader(bear), {HTTP_HEADERS_CONTENT_TYPE, "application/json"}},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
}
|
||||
|
||||
void FeedlyNetwork::markers(const QString& action, const QStringList& msg_custom_ids) {
|
||||
if (msg_custom_ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot mark entries, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::Markers);
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
|
||||
for (int i = 0; i < msg_custom_ids.size(); i += 500) {
|
||||
QJsonObject input;
|
||||
|
||||
input[QSL("action")] = action;
|
||||
input[QSL("type")] = QSL("entries");
|
||||
input[QSL("entryIds")] = QJsonArray::fromStringList(msg_custom_ids.mid(i, 500));
|
||||
|
||||
QByteArray input_data = QJsonDocument(input).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
auto result =
|
||||
NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
input_data,
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PostOperation,
|
||||
{bearerHeader(bear), {HTTP_HEADERS_CONTENT_TYPE, "application/json"}},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<Message> FeedlyNetwork::entries(const QStringList& ids) {
|
||||
const QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QList<Message> msgs;
|
||||
int next_message = 0;
|
||||
QString continuation;
|
||||
const QString target_url = fullUrl(Service::Entries);
|
||||
const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
|
||||
do {
|
||||
QJsonArray json;
|
||||
|
||||
for (int window = next_message + 1000; next_message < window && next_message < ids.size(); next_message++) {
|
||||
json.append(QJsonValue(ids.at(next_message)));
|
||||
}
|
||||
|
||||
QByteArray output;
|
||||
auto result =
|
||||
NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PostOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
msgs += decodeStreamContents(output, false, continuation);
|
||||
}
|
||||
while (next_message < ids.size());
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
QList<Message> FeedlyNetwork::streamContents(const QString& stream_id) {
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
QString continuation;
|
||||
QList<Message> messages;
|
||||
|
||||
// We download in batches.
|
||||
do {
|
||||
QString target_url = fullUrl(Service::StreamContents).arg(QString(QUrl::toPercentEncoding(stream_id)));
|
||||
|
||||
if (m_downloadOnlyUnreadMessages) {
|
||||
target_url += QSL("&unreadOnly=true");
|
||||
}
|
||||
|
||||
if (!continuation.isEmpty()) {
|
||||
target_url += QSL("&continuation=%1").arg(continuation);
|
||||
}
|
||||
|
||||
if (m_batchSize > 0) {
|
||||
target_url += QSL("&count=%1").arg(QString::number(m_batchSize));
|
||||
}
|
||||
else {
|
||||
// User wants to download all messages. Make sure we use large batches
|
||||
// to limit network requests.
|
||||
target_url += QSL("&count=%1").arg(QString::number(FEEDLY_MAX_BATCH_SIZE));
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
messages += decodeStreamContents(output, true, continuation);
|
||||
}
|
||||
while (!continuation.isEmpty() && (m_batchSize <= 0 || messages.size() < m_batchSize) &&
|
||||
messages.size() <= FEEDLY_MAX_TOTAL_SIZE);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
QStringList FeedlyNetwork::streamIds(const QString& stream_id, bool unread_only, int batch_size) {
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain stream IDs, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
QString continuation;
|
||||
QStringList messages;
|
||||
|
||||
// We download in batches.
|
||||
do {
|
||||
QString target_url = fullUrl(Service::StreamIds).arg(QString(QUrl::toPercentEncoding(stream_id)));
|
||||
|
||||
if (batch_size > 0) {
|
||||
target_url += QSL("?count=%1").arg(QString::number(batch_size));
|
||||
}
|
||||
else {
|
||||
// User wants to download all messages. Make sure we use large batches
|
||||
// to limit network requests.
|
||||
target_url += QSL("?count=%1").arg(QString::number(10000));
|
||||
}
|
||||
|
||||
if (unread_only) {
|
||||
target_url += QSL("&unreadOnly=true");
|
||||
}
|
||||
|
||||
if (!continuation.isEmpty()) {
|
||||
target_url += QSL("&continuation=%1").arg(continuation);
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
messages += decodeStreamIds(output, continuation);
|
||||
}
|
||||
while (!continuation.isEmpty() && (batch_size <= 0 || messages.size() < batch_size));
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
QStringList FeedlyNetwork::decodeStreamIds(const QByteArray& stream_ids, QString& continuation) const {
|
||||
QStringList messages;
|
||||
QJsonDocument json = QJsonDocument::fromJson(stream_ids);
|
||||
|
||||
continuation = json.object()[QSL("continuation")].toString();
|
||||
|
||||
for (const QJsonValue& id_val : json.object()[QSL("ids")].toArray()) {
|
||||
messages << id_val.toString();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
QList<Message> FeedlyNetwork::decodeStreamContents(const QByteArray& stream_contents,
|
||||
bool nested_items,
|
||||
QString& continuation) const {
|
||||
QList<Message> messages;
|
||||
QJsonDocument json = QJsonDocument::fromJson(stream_contents);
|
||||
auto active_labels = m_service->labelsNode() != nullptr ? m_service->labelsNode()->labels() : QList<Label*>();
|
||||
|
||||
continuation = json.object()[QSL("continuation")].toString();
|
||||
|
||||
auto items = nested_items ? json.object()[QSL("items")].toArray() : json.array();
|
||||
|
||||
for (const QJsonValue& entry : std::as_const(items)) {
|
||||
const QJsonObject& entry_obj = entry.toObject();
|
||||
Message message;
|
||||
|
||||
message.m_feedId = entry_obj[QSL("origin")].toObject()[QSL("streamId")].toString();
|
||||
message.m_title = entry_obj[QSL("title")].toString();
|
||||
message.m_author = entry_obj[QSL("author")].toString();
|
||||
message.m_contents = entry_obj[QSL("content")].toObject()[QSL("content")].toString();
|
||||
message.m_rawContents = QJsonDocument(entry_obj).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
|
||||
if (message.m_contents.isEmpty()) {
|
||||
message.m_contents = entry_obj[QSL("summary")].toObject()[QSL("content")].toString();
|
||||
}
|
||||
|
||||
message.m_createdFromFeed = true;
|
||||
message.m_created =
|
||||
QDateTime::fromMSecsSinceEpoch(entry_obj[QSL("published")].toVariant().toLongLong(), Qt::TimeSpec::UTC);
|
||||
message.m_customId = entry_obj[QSL("id")].toString();
|
||||
message.m_isRead = !entry_obj[QSL("unread")].toBool();
|
||||
message.m_url = entry_obj[QSL("canonicalUrl")].toString();
|
||||
|
||||
if (message.m_url.isEmpty()) {
|
||||
auto canonical_arr = entry_obj[QSL("canonical")].toArray();
|
||||
|
||||
if (!canonical_arr.isEmpty()) {
|
||||
message.m_url = canonical_arr.first().toObject()[QSL("href")].toString();
|
||||
}
|
||||
else {
|
||||
auto alternate_arr = entry_obj[QSL("alternate")].toArray();
|
||||
|
||||
if (!alternate_arr.isEmpty()) {
|
||||
message.m_url = alternate_arr.first().toObject()[QSL("href")].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto enclosures = entry_obj[QSL("enclosure")].toArray();
|
||||
|
||||
for (const QJsonValue& enc : std::as_const(enclosures)) {
|
||||
const QJsonObject& enc_obj = enc.toObject();
|
||||
const QString& enc_href = enc_obj[QSL("href")].toString();
|
||||
|
||||
if (!boolinq::from(message.m_enclosures).any([enc_href](const Enclosure& existing_enclosure) {
|
||||
return existing_enclosure.m_url == enc_href;
|
||||
})) {
|
||||
message.m_enclosures.append(Enclosure(enc_href, enc_obj[QSL("type")].toString()));
|
||||
}
|
||||
}
|
||||
|
||||
auto tags = entry_obj[QSL("tags")].toArray();
|
||||
|
||||
for (const QJsonValue& tag : std::as_const(tags)) {
|
||||
const QJsonObject& tag_obj = tag.toObject();
|
||||
const QString& tag_id = tag_obj[QSL("id")].toString();
|
||||
|
||||
if (tag_id.endsWith(FEEDLY_API_SYSTEM_TAG_SAVED)) {
|
||||
message.m_isImportant = true;
|
||||
}
|
||||
else if (tag_id.endsWith(FEEDLY_API_SYSTEM_TAG_READ)) {
|
||||
// NOTE: We don't do anything with "global read" tag.
|
||||
}
|
||||
else {
|
||||
Label* label = boolinq::from(active_labels.begin(), active_labels.end()).firstOrDefault([tag_id](Label* lbl) {
|
||||
return lbl->customId() == tag_id;
|
||||
});
|
||||
|
||||
if (label != nullptr) {
|
||||
message.m_assignedLabels.append(label);
|
||||
}
|
||||
else {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Failed to find live Label object for tag" << QUOTE_W_SPACE_DOT(tag_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(message);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
RootItem* FeedlyNetwork::collections(bool obtain_icons) {
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain personal collections, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::Collections);
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
return decodeCollections(output, obtain_icons, m_service->networkProxy(), timeout);
|
||||
}
|
||||
|
||||
RootItem* FeedlyNetwork::decodeCollections(const QByteArray& json,
|
||||
bool obtain_icons,
|
||||
const QNetworkProxy& proxy,
|
||||
int timeout) const {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json);
|
||||
auto* parent = new RootItem();
|
||||
QList<QString> used_feeds;
|
||||
auto coll = doc.array();
|
||||
|
||||
for (const QJsonValue& cat : std::as_const(coll)) {
|
||||
QJsonObject cat_obj = cat.toObject();
|
||||
auto* category = new Category(parent);
|
||||
|
||||
category->setTitle(cat_obj[QSL("label")].toString());
|
||||
category->setCustomId(cat_obj[QSL("id")].toString());
|
||||
|
||||
auto feeds = cat[QSL("feeds")].toArray();
|
||||
|
||||
for (const QJsonValue& fee : std::as_const(feeds)) {
|
||||
QJsonObject fee_obj = fee.toObject();
|
||||
|
||||
if (used_feeds.contains(fee_obj[QSL("id")].toString())) {
|
||||
qWarningNN << LOGSEC_FEEDLY << "Feed" << QUOTE_W_SPACE(fee_obj[QSL("id")].toString())
|
||||
<< "is already decoded and cannot be placed under several categories.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* feed = new Feed(category);
|
||||
|
||||
feed->setSource(fee_obj[QSL("website")].toString());
|
||||
feed->setTitle(fee_obj[QSL("title")].toString());
|
||||
feed->setDescription(qApp->web()->stripTags(fee_obj[QSL("description")].toString()));
|
||||
feed->setCustomId(fee_obj[QSL("id")].toString());
|
||||
|
||||
if (feed->title().isEmpty()) {
|
||||
feed->setTitle(feed->description());
|
||||
}
|
||||
|
||||
if (feed->title().isEmpty()) {
|
||||
feed->setTitle(feed->source());
|
||||
}
|
||||
|
||||
if (feed->title().isEmpty()) {
|
||||
feed->setTitle(feed->customId());
|
||||
qWarningNN << LOGSEC_FEEDLY
|
||||
<< "Some feed does not have nor title, neither description. Using its ID for its title.";
|
||||
}
|
||||
|
||||
if (obtain_icons) {
|
||||
QPixmap icon;
|
||||
auto result = NetworkFactory::downloadIcon({{fee_obj[QSL("iconUrl")].toString(), true},
|
||||
{fee_obj[QSL("website")].toString(), false},
|
||||
{fee_obj[QSL("logo")].toString(), true}},
|
||||
timeout,
|
||||
icon,
|
||||
{},
|
||||
proxy);
|
||||
|
||||
if (result == QNetworkReply::NetworkError::NoError && !icon.isNull()) {
|
||||
feed->setIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
used_feeds.append(feed->customId());
|
||||
category->appendChild(feed);
|
||||
}
|
||||
|
||||
if (category->childCount() == 0) {
|
||||
delete category;
|
||||
}
|
||||
else {
|
||||
parent->appendChild(category);
|
||||
}
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
QVariantHash FeedlyNetwork::profile(const QNetworkProxy& network_proxy) {
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain profile information, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::Profile);
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
|
||||
// This method uses proxy via parameter,
|
||||
// not via "m_service" field.
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
network_proxy);
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
return QJsonDocument::fromJson(output).object().toVariantHash();
|
||||
}
|
||||
|
||||
QList<RootItem*> FeedlyNetwork::tags() {
|
||||
QString bear = bearer();
|
||||
|
||||
if (bear.isEmpty()) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Cannot obtain tags, because bearer is empty.";
|
||||
throw NetworkException(QNetworkReply::NetworkError::AuthenticationRequiredError);
|
||||
}
|
||||
|
||||
QString target_url = fullUrl(Service::Tags);
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray output;
|
||||
auto result = NetworkFactory::performNetworkOperation(target_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
{bearerHeader(bear)},
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
m_service->networkProxy());
|
||||
|
||||
if (result.m_networkError != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result.m_networkError, output);
|
||||
}
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(output);
|
||||
QList<RootItem*> lbls;
|
||||
auto tags = json.array();
|
||||
|
||||
for (const QJsonValue& tag : std::as_const(tags)) {
|
||||
const QJsonObject& tag_obj = tag.toObject();
|
||||
QString name_id = tag_obj[QSL("id")].toString();
|
||||
|
||||
if (name_id.endsWith(FEEDLY_API_SYSTEM_TAG_READ) || name_id.endsWith(FEEDLY_API_SYSTEM_TAG_SAVED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString plain_name = tag_obj[QSL("label")].toString();
|
||||
auto* new_lbl = new Label(plain_name, TextFactory::generateColorFromText(name_id));
|
||||
|
||||
new_lbl->setCustomId(name_id);
|
||||
lbls.append(new_lbl);
|
||||
}
|
||||
|
||||
return lbls;
|
||||
}
|
||||
|
||||
QString FeedlyNetwork::username() const {
|
||||
return m_username;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setUsername(const QString& username) {
|
||||
m_username = username;
|
||||
}
|
||||
|
||||
QString FeedlyNetwork::developerAccessToken() const {
|
||||
return m_developerAccessToken;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setDeveloperAccessToken(const QString& dev_acc_token) {
|
||||
m_developerAccessToken = dev_acc_token;
|
||||
}
|
||||
|
||||
int FeedlyNetwork::batchSize() const {
|
||||
return m_batchSize;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setBatchSize(int batch_size) {
|
||||
m_batchSize = batch_size;
|
||||
}
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
|
||||
void FeedlyNetwork::onTokensError(const QString& error, const QString& error_description) {
|
||||
Q_UNUSED(error)
|
||||
|
||||
qApp->showGuiMessage(Notification::Event::LoginFailure,
|
||||
{tr("Feedly: authentication error"),
|
||||
tr("Click this to login again. Error is: '%1'").arg(error_description),
|
||||
QSystemTrayIcon::MessageIcon::Critical},
|
||||
{},
|
||||
{tr("Login"), [this]() {
|
||||
m_oauth->setAccessToken(QString());
|
||||
m_oauth->setRefreshToken(QString());
|
||||
|
||||
// m_oauth->logout(false);
|
||||
m_oauth->login();
|
||||
}});
|
||||
}
|
||||
|
||||
void FeedlyNetwork::onAuthFailed() {
|
||||
qApp->showGuiMessage(Notification::Event::LoginFailure,
|
||||
{tr("Feedly: authorization denied"),
|
||||
tr("Click this to login again."),
|
||||
QSystemTrayIcon::MessageIcon::Critical},
|
||||
{},
|
||||
{tr("Login"), [this]() {
|
||||
// m_oauth->logout(false);
|
||||
m_oauth->login();
|
||||
}});
|
||||
}
|
||||
|
||||
void FeedlyNetwork::onTokensRetrieved(const QString& access_token, const QString& refresh_token, int expires_in) {
|
||||
Q_UNUSED(expires_in)
|
||||
Q_UNUSED(access_token)
|
||||
|
||||
if (m_service != nullptr && !refresh_token.isEmpty()) {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
|
||||
}
|
||||
}
|
||||
|
||||
OAuth2Service* FeedlyNetwork::oauth() const {
|
||||
return m_oauth;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setOauth(OAuth2Service* oauth) {
|
||||
m_oauth = oauth;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QString FeedlyNetwork::fullUrl(FeedlyNetwork::Service service) const {
|
||||
switch (service) {
|
||||
case Service::Profile:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_PROFILE);
|
||||
|
||||
case Service::Collections:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_COLLETIONS);
|
||||
|
||||
case Service::Tags:
|
||||
case Service::TagEntries:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_TAGS);
|
||||
|
||||
case Service::StreamContents:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_STREAM_CONTENTS);
|
||||
|
||||
case Service::StreamIds:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_STREAM_IDS);
|
||||
|
||||
case Service::Entries:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_ENTRIES);
|
||||
|
||||
case Service::Markers:
|
||||
return QSL(FEEDLY_API_URL_BASE) + QSL(FEEDLY_API_URL_MARKERS);
|
||||
|
||||
default:
|
||||
return QSL(FEEDLY_API_URL_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
QString FeedlyNetwork::bearer() const {
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
if (m_developerAccessToken.simplified().isEmpty()) {
|
||||
return m_oauth->bearer().toLocal8Bit();
|
||||
}
|
||||
#endif
|
||||
|
||||
return QSL("Bearer %1").arg(m_developerAccessToken);
|
||||
}
|
||||
|
||||
QPair<QByteArray, QByteArray> FeedlyNetwork::bearerHeader(const QString& bearer) const {
|
||||
return {QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit()};
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setIntelligentSynchronization(bool intelligent_sync) {
|
||||
m_intelligentSynchronization = intelligent_sync;
|
||||
}
|
||||
|
||||
bool FeedlyNetwork::intelligentSynchronization() const {
|
||||
return m_intelligentSynchronization;
|
||||
}
|
||||
|
||||
bool FeedlyNetwork::downloadOnlyUnreadMessages() const {
|
||||
return m_downloadOnlyUnreadMessages;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) {
|
||||
m_downloadOnlyUnreadMessages = download_only_unread_messages;
|
||||
}
|
||||
|
||||
void FeedlyNetwork::setService(FeedlyServiceRoot* service) {
|
||||
m_service = service;
|
||||
}
|
110
src/librssguard-feedly/src/feedlynetwork.h
Normal file
110
src/librssguard-feedly/src/feedlynetwork.h
Normal file
@ -0,0 +1,110 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FEEDLYNETWORK_H
|
||||
#define FEEDLYNETWORK_H
|
||||
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/services/abstract/feed.h>
|
||||
#include <librssguard/services/abstract/serviceroot.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
class OAuth2Service;
|
||||
#endif
|
||||
|
||||
class FeedlyServiceRoot;
|
||||
|
||||
class FeedlyNetwork : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FeedlyNetwork(QObject* parent = nullptr);
|
||||
|
||||
QList<Message> messages(const QString& stream_id,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages);
|
||||
|
||||
// API operations.
|
||||
void untagEntries(const QString& tag_id, const QStringList& msg_custom_ids);
|
||||
void tagEntries(const QString& tag_id, const QStringList& msg_custom_ids);
|
||||
void markers(const QString& action, const QStringList& msg_custom_ids);
|
||||
QList<Message> entries(const QStringList& ids);
|
||||
QList<Message> streamContents(const QString& stream_id);
|
||||
QStringList streamIds(const QString& stream_id, bool unread_only, int batch_size);
|
||||
QVariantHash profile(const QNetworkProxy& network_proxy);
|
||||
QList<RootItem*> tags();
|
||||
RootItem* collections(bool obtain_icons);
|
||||
|
||||
// Getters and setters.
|
||||
QString username() const;
|
||||
void setUsername(const QString& username);
|
||||
|
||||
QString developerAccessToken() const;
|
||||
void setDeveloperAccessToken(const QString& dev_acc_token);
|
||||
|
||||
bool downloadOnlyUnreadMessages() const;
|
||||
void setDownloadOnlyUnreadMessages(bool download_only_unread_messages);
|
||||
|
||||
bool intelligentSynchronization() const;
|
||||
void setIntelligentSynchronization(bool intelligent_sync);
|
||||
|
||||
int batchSize() const;
|
||||
void setBatchSize(int batch_size);
|
||||
|
||||
void setService(FeedlyServiceRoot* service);
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
OAuth2Service* oauth() const;
|
||||
void setOauth(OAuth2Service* oauth);
|
||||
|
||||
private slots:
|
||||
void onTokensError(const QString& error, const QString& error_description);
|
||||
void onAuthFailed();
|
||||
void onTokensRetrieved(const QString& access_token, const QString& refresh_token, int expires_in);
|
||||
#endif
|
||||
|
||||
private:
|
||||
enum class Service {
|
||||
Profile,
|
||||
Collections,
|
||||
Tags,
|
||||
StreamContents,
|
||||
Markers,
|
||||
TagEntries,
|
||||
StreamIds,
|
||||
Entries
|
||||
};
|
||||
|
||||
QString fullUrl(Service service) const;
|
||||
QString bearer() const;
|
||||
QStringList decodeStreamIds(const QByteArray& stream_ids, QString& continuation) const;
|
||||
QList<Message> decodeStreamContents(const QByteArray& stream_contents,
|
||||
bool nested_items,
|
||||
QString& continuation) const;
|
||||
RootItem* decodeCollections(const QByteArray& json,
|
||||
bool obtain_icons,
|
||||
const QNetworkProxy& proxy,
|
||||
int timeout = 0) const;
|
||||
QPair<QByteArray, QByteArray> bearerHeader(const QString& bearer) const;
|
||||
|
||||
private:
|
||||
FeedlyServiceRoot* m_service;
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
OAuth2Service* m_oauth;
|
||||
#endif
|
||||
|
||||
QString m_username;
|
||||
QString m_developerAccessToken;
|
||||
|
||||
// Only download N newest messages per feed.
|
||||
int m_batchSize;
|
||||
|
||||
// Only download unread messages.
|
||||
bool m_downloadOnlyUnreadMessages;
|
||||
|
||||
// Better synchronization algorithm.
|
||||
bool m_intelligentSynchronization;
|
||||
};
|
||||
|
||||
#endif // FEEDLYNETWORK_H
|
250
src/librssguard-feedly/src/feedlyserviceroot.cpp
Normal file
250
src/librssguard-feedly/src/feedlyserviceroot.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/feedlyserviceroot.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/feedlyentrypoint.h"
|
||||
#include "src/feedlynetwork.h"
|
||||
#include "src/gui/formeditfeedlyaccount.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/exceptions/applicationexception.h>
|
||||
#include <librssguard/exceptions/feedfetchexception.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/textfactory.h>
|
||||
#include <librssguard/services/abstract/labelsnode.h>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#endif
|
||||
|
||||
FeedlyServiceRoot::FeedlyServiceRoot(RootItem* parent) : ServiceRoot(parent), m_network(new FeedlyNetwork(this)) {
|
||||
setIcon(FeedlyEntryPoint().icon());
|
||||
m_network->setService(this);
|
||||
}
|
||||
|
||||
bool FeedlyServiceRoot::isSyncable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FeedlyServiceRoot::canBeEdited() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
FormAccountDetails* FeedlyServiceRoot::accountSetupDialog() const {
|
||||
return new FormEditFeedlyAccount(qApp->mainFormWidget());
|
||||
}
|
||||
|
||||
void FeedlyServiceRoot::editItems(const QList<RootItem*>& items) {
|
||||
if (items.first()->kind() == RootItem::Kind::ServiceRoot) {
|
||||
QScopedPointer<FormEditFeedlyAccount> p(qobject_cast<FormEditFeedlyAccount*>(accountSetupDialog()));
|
||||
|
||||
p->addEditAccount(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceRoot::editItems(items);
|
||||
}
|
||||
|
||||
QVariantHash FeedlyServiceRoot::customDatabaseData() const {
|
||||
QVariantHash data = ServiceRoot::customDatabaseData();
|
||||
|
||||
data[QSL("username")] = m_network->username();
|
||||
data[QSL("dat")] = m_network->developerAccessToken();
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
data[QSL("refresh_token")] = m_network->oauth()->refreshToken();
|
||||
#endif
|
||||
|
||||
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 FeedlyServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
ServiceRoot::setCustomDatabaseData(data);
|
||||
|
||||
m_network->setUsername(data[QSL("username")].toString());
|
||||
m_network->setDeveloperAccessToken(data[QSL("dat")].toString());
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_network->oauth()->setRefreshToken(data[QSL("refresh_token")].toString());
|
||||
#endif
|
||||
|
||||
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> FeedlyServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>&
|
||||
stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages) {
|
||||
Q_UNUSED(tagged_messages)
|
||||
|
||||
try {
|
||||
return m_network->messages(feed->customId(), stated_messages);
|
||||
}
|
||||
catch (const ApplicationException& ex) {
|
||||
throw FeedFetchException(Feed::Status::NetworkError, ex.message());
|
||||
}
|
||||
}
|
||||
|
||||
void FeedlyServiceRoot::start(bool freshly_activated) {
|
||||
if (!freshly_activated) {
|
||||
DatabaseQueries::loadRootFromDatabase<Category, Feed>(this);
|
||||
loadCacheFromFile();
|
||||
}
|
||||
|
||||
updateTitle();
|
||||
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_network->oauth()->login([this]() {
|
||||
syncIn();
|
||||
});
|
||||
#else
|
||||
syncIn();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
else {
|
||||
m_network->oauth()->login();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString FeedlyServiceRoot::code() const {
|
||||
return FeedlyEntryPoint().code();
|
||||
}
|
||||
|
||||
void FeedlyServiceRoot::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()) {
|
||||
try {
|
||||
network()->markers(key == RootItem::ReadStatus::Read ? QSL(FEEDLY_MARKERS_READ) : QSL(FEEDLY_MARKERS_UNREAD),
|
||||
ids);
|
||||
}
|
||||
catch (const NetworkException& net_ex) {
|
||||
qCriticalNN << LOGSEC_FEEDLY
|
||||
<< "Failed to synchronize read/unread state with error:" << QUOTE_W_SPACE(net_ex.message())
|
||||
<< "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError());
|
||||
|
||||
if (!ignore_errors) {
|
||||
addMessageStatesToCache(ids, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<RootItem::Importance, QList<Message>> j(msg_cache.m_cachedStatesImportant);
|
||||
|
||||
// Save the actual data important/not important.
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
auto key = j.key();
|
||||
QList<Message> messages = j.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
QStringList ids = customIDsOfMessages(messages);
|
||||
|
||||
try {
|
||||
network()->markers(key == RootItem::Importance::Important ? FEEDLY_MARKERS_IMPORTANT
|
||||
: FEEDLY_MARKERS_UNIMPORTANT,
|
||||
ids);
|
||||
}
|
||||
catch (const NetworkException& net_ex) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Failed to synchronize important/unimportant state with error:"
|
||||
<< QUOTE_W_SPACE(net_ex.message()) << "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError());
|
||||
|
||||
if (!ignore_errors) {
|
||||
addMessageStatesToCache(messages, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<QString, QStringList> k(msg_cache.m_cachedLabelAssignments);
|
||||
|
||||
// Assign label for these messages.
|
||||
while (k.hasNext()) {
|
||||
k.next();
|
||||
auto label_custom_id = k.key();
|
||||
QStringList messages = k.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
try {
|
||||
network()->tagEntries(label_custom_id, messages);
|
||||
}
|
||||
catch (const NetworkException& net_ex) {
|
||||
qCriticalNN << LOGSEC_FEEDLY
|
||||
<< "Failed to synchronize tag assignments with error:" << QUOTE_W_SPACE(net_ex.message())
|
||||
<< "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError());
|
||||
|
||||
if (!ignore_errors) {
|
||||
addLabelsAssignmentsToCache(messages, label_custom_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMapIterator<QString, QStringList> l(msg_cache.m_cachedLabelDeassignments);
|
||||
|
||||
// Remove label from these messages.
|
||||
while (l.hasNext()) {
|
||||
l.next();
|
||||
auto label_custom_id = l.key();
|
||||
QStringList messages = l.value();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
try {
|
||||
network()->untagEntries(label_custom_id, messages);
|
||||
}
|
||||
catch (const NetworkException& net_ex) {
|
||||
qCriticalNN << LOGSEC_FEEDLY
|
||||
<< "Failed to synchronize tag DEassignments with error:" << QUOTE_W_SPACE(net_ex.message())
|
||||
<< "and HTTP code" << QUOTE_W_SPACE_DOT(net_ex.networkError());
|
||||
|
||||
if (!ignore_errors) {
|
||||
addLabelsAssignmentsToCache(messages, label_custom_id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServiceRoot::LabelOperation FeedlyServiceRoot::supportedLabelOperations() const {
|
||||
return ServiceRoot::LabelOperation::Synchronised;
|
||||
}
|
||||
|
||||
void FeedlyServiceRoot::updateTitle() {
|
||||
setTitle(QSL("%1 (Feedly)").arg(TextFactory::extractUsernameFromEmail(m_network->username())));
|
||||
}
|
||||
|
||||
RootItem* FeedlyServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
auto tree = m_network->collections(true);
|
||||
auto* lblroot = new LabelsNode(tree);
|
||||
auto labels = m_network->tags();
|
||||
|
||||
lblroot->setChildItems(labels);
|
||||
tree->appendChild(lblroot);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
bool FeedlyServiceRoot::wantsBaggedIdsOfExistingMessages() const {
|
||||
return m_network->intelligentSynchronization();
|
||||
}
|
48
src/librssguard-feedly/src/feedlyserviceroot.h
Normal file
48
src/librssguard-feedly/src/feedlyserviceroot.h
Normal file
@ -0,0 +1,48 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FEEDLYSERVICEROOT_H
|
||||
#define FEEDLYSERVICEROOT_H
|
||||
|
||||
#include <librssguard/services/abstract/cacheforserviceroot.h>
|
||||
#include <librssguard/services/abstract/serviceroot.h>
|
||||
|
||||
class FeedlyNetwork;
|
||||
|
||||
class FeedlyServiceRoot : public ServiceRoot, public CacheForServiceRoot {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FeedlyServiceRoot(RootItem* parent = nullptr);
|
||||
|
||||
virtual bool isSyncable() const;
|
||||
virtual bool canBeEdited() const;
|
||||
virtual void editItems(const QList<RootItem*>& items);
|
||||
virtual FormAccountDetails* accountSetupDialog() const;
|
||||
virtual void start(bool freshly_activated);
|
||||
virtual QString code() const;
|
||||
virtual void saveAllCachedData(bool ignore_errors);
|
||||
virtual LabelOperation supportedLabelOperations() const;
|
||||
virtual QVariantHash customDatabaseData() const;
|
||||
virtual void setCustomDatabaseData(const QVariantHash& data);
|
||||
virtual bool wantsBaggedIdsOfExistingMessages() const;
|
||||
virtual QList<Message> obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
|
||||
const QHash<QString, QStringList>& tagged_messages);
|
||||
|
||||
FeedlyNetwork* network() const;
|
||||
|
||||
protected:
|
||||
virtual RootItem* obtainNewTreeForSyncIn() const;
|
||||
|
||||
private:
|
||||
void updateTitle();
|
||||
|
||||
private:
|
||||
FeedlyNetwork* m_network;
|
||||
};
|
||||
|
||||
inline FeedlyNetwork* FeedlyServiceRoot::network() const {
|
||||
return m_network;
|
||||
}
|
||||
|
||||
#endif // FEEDLYSERVICEROOT_H
|
184
src/librssguard-feedly/src/gui/feedlyaccountdetails.cpp
Normal file
184
src/librssguard-feedly/src/gui/feedlyaccountdetails.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/feedlyaccountdetails.h"
|
||||
|
||||
#include "src/definitions.h"
|
||||
#include "src/feedlynetwork.h"
|
||||
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/network-web/webfactory.h>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#endif
|
||||
|
||||
#include <QVariantHash>
|
||||
|
||||
FeedlyAccountDetails::FeedlyAccountDetails(QWidget* parent) : QWidget(parent), m_lastProxy({}) {
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_oauth = nullptr;
|
||||
#endif
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.m_lblTestResult->label()->setWordWrap(true);
|
||||
m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your account"));
|
||||
m_ui.m_txtDeveloperAccessToken->lineEdit()->setPlaceholderText(tr("Developer access token"));
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
|
||||
tr("No test done yet."),
|
||||
tr("Here, results of connection test are shown."));
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_ui.m_lblInfo->setHelpText(tr("Your %1 build has official Feedly support. You do not have to use \"developer access "
|
||||
"token\". You can therefore leave corresponding field empty.")
|
||||
.arg(QSL(APP_NAME)),
|
||||
false);
|
||||
#else
|
||||
m_ui.m_lblInfo->setHelpText(tr("Your %1 does not offer official Feedly support, thus you must "
|
||||
"authorize via special authorization code called \"developer access token\". "
|
||||
"These tokens are usually valid only for 1 month and allow only 250 API calls "
|
||||
"each day.")
|
||||
.arg(QSL(APP_NAME)),
|
||||
true);
|
||||
#endif
|
||||
|
||||
m_ui.m_lblLimitMessagesInfo->setHelpText(tr("Beware of downloading too many articles, because "
|
||||
"Feedly permanently caches ALL articles of the feed, so you might "
|
||||
"end up with thousands of articles which you will never read anyway."),
|
||||
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."),
|
||||
false);
|
||||
|
||||
connect(m_ui.m_btnGetToken, &QPushButton::clicked, this, &FeedlyAccountDetails::getDeveloperAccessToken);
|
||||
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &FeedlyAccountDetails::onUsernameChanged);
|
||||
connect(m_ui.m_txtDeveloperAccessToken->lineEdit(),
|
||||
&BaseLineEdit::textChanged,
|
||||
this,
|
||||
&FeedlyAccountDetails::onDeveloperAccessTokenChanged);
|
||||
|
||||
setTabOrder(m_ui.m_txtUsername->lineEdit(), m_ui.m_btnGetToken);
|
||||
setTabOrder(m_ui.m_btnGetToken, m_ui.m_txtDeveloperAccessToken->lineEdit());
|
||||
setTabOrder(m_ui.m_txtDeveloperAccessToken->lineEdit(), m_ui.m_checkDownloadOnlyUnreadMessages);
|
||||
setTabOrder(m_ui.m_checkDownloadOnlyUnreadMessages, m_ui.m_cbNewAlgorithm);
|
||||
setTabOrder(m_ui.m_cbNewAlgorithm, m_ui.m_spinLimitMessages);
|
||||
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup);
|
||||
|
||||
onDeveloperAccessTokenChanged();
|
||||
onUsernameChanged();
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
hookNetwork();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::getDeveloperAccessToken() {
|
||||
qApp->web()->openUrlInExternalBrowser(QSL(FEEDLY_GENERATE_DAT));
|
||||
}
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
|
||||
void FeedlyAccountDetails::hookNetwork() {
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &FeedlyAccountDetails::onAuthGranted);
|
||||
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyAccountDetails::onAuthError);
|
||||
connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyAccountDetails::onAuthFailed);
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::onAuthFailed() {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("You did not grant access."),
|
||||
tr("There was error during testing."));
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::onAuthError(const QString& error, const QString& detailed_description) {
|
||||
Q_UNUSED(error)
|
||||
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("There is error. %1").arg(detailed_description),
|
||||
tr("There was error during testing."));
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::onAuthGranted() {
|
||||
FeedlyNetwork factory;
|
||||
|
||||
factory.setOauth(m_oauth);
|
||||
|
||||
try {
|
||||
auto prof = factory.profile(m_lastProxy);
|
||||
|
||||
m_ui.m_txtUsername->lineEdit()->setText(prof[QSL("email")].toString());
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
|
||||
tr("Tested successfully. You may be prompted to login once more."),
|
||||
tr("Your access was approved."));
|
||||
}
|
||||
catch (const ApplicationException& ex) {
|
||||
qCriticalNN << LOGSEC_FEEDLY << "Failed to obtain profile with error:" << QUOTE_W_SPACE_DOT(ex.message());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void FeedlyAccountDetails::performTest(const QNetworkProxy& custom_proxy) {
|
||||
m_lastProxy = custom_proxy;
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_oauth->logout(false);
|
||||
|
||||
if (m_ui.m_txtDeveloperAccessToken->lineEdit()->text().simplified().isEmpty()) {
|
||||
m_oauth->login();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
FeedlyNetwork factory;
|
||||
|
||||
factory.setDeveloperAccessToken(m_ui.m_txtDeveloperAccessToken->lineEdit()->text());
|
||||
|
||||
try {
|
||||
auto prof = factory.profile(custom_proxy);
|
||||
|
||||
m_ui.m_txtUsername->lineEdit()->setText(prof[QSL("email")].toString());
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok,
|
||||
tr("Login was successful."),
|
||||
tr("Access granted."));
|
||||
}
|
||||
catch (const NetworkException& ex) {
|
||||
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
|
||||
tr("Error: '%1'").arg(ex.message()),
|
||||
tr("Some problems."));
|
||||
}
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::onUsernameChanged() {
|
||||
const QString username = m_ui.m_txtUsername->lineEdit()->text();
|
||||
|
||||
if (username.isEmpty()) {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("Username cannot be empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Username is okay."));
|
||||
}
|
||||
}
|
||||
|
||||
void FeedlyAccountDetails::onDeveloperAccessTokenChanged() {
|
||||
const QString token = m_ui.m_txtDeveloperAccessToken->lineEdit()->text();
|
||||
|
||||
if (token.isEmpty()) {
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Ok;
|
||||
#else
|
||||
WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Error;
|
||||
#endif
|
||||
|
||||
m_ui.m_txtDeveloperAccessToken->setStatus(stat, tr("Access token is empty."));
|
||||
}
|
||||
else {
|
||||
m_ui.m_txtDeveloperAccessToken->setStatus(WidgetWithStatus::StatusType::Ok, tr("Access token is okay."));
|
||||
}
|
||||
}
|
51
src/librssguard-feedly/src/gui/feedlyaccountdetails.h
Normal file
51
src/librssguard-feedly/src/gui/feedlyaccountdetails.h
Normal file
@ -0,0 +1,51 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FEEDLYACCOUNTDETAILS_H
|
||||
#define FEEDLYACCOUNTDETAILS_H
|
||||
|
||||
#include "src/feedlyserviceroot.h"
|
||||
|
||||
#include "ui_feedlyaccountdetails.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
#include <QWidget>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
class OAuth2Service;
|
||||
#endif
|
||||
|
||||
class FeedlyAccountDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class FormEditFeedlyAccount;
|
||||
|
||||
public:
|
||||
explicit FeedlyAccountDetails(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void getDeveloperAccessToken();
|
||||
void performTest(const QNetworkProxy& custom_proxy);
|
||||
void onUsernameChanged();
|
||||
void onDeveloperAccessTokenChanged();
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
private slots:
|
||||
void onAuthFailed();
|
||||
void onAuthError(const QString& error, const QString& detailed_description);
|
||||
void onAuthGranted();
|
||||
|
||||
private:
|
||||
void hookNetwork();
|
||||
#endif
|
||||
|
||||
private:
|
||||
Ui::FeedlyAccountDetails m_ui;
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
OAuth2Service* m_oauth;
|
||||
#endif
|
||||
|
||||
QNetworkProxy m_lastProxy;
|
||||
};
|
||||
|
||||
#endif // FEEDLYACCOUNTDETAILS_H
|
159
src/librssguard-feedly/src/gui/feedlyaccountdetails.ui
Normal file
159
src/librssguard-feedly/src/gui/feedlyaccountdetails.ui
Normal file
@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FeedlyAccountDetails</class>
|
||||
<widget class="QWidget" name="FeedlyAccountDetails">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>421</width>
|
||||
<height>321</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="m_lblUsername">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="LineEditWithStatus" name="m_txtUsername" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="m_lblUsername_2">
|
||||
<property name="text">
|
||||
<string>Developer access token</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_btnGetToken">
|
||||
<property name="text">
|
||||
<string>Get token</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LineEditWithStatus" name="m_txtDeveloperAccessToken" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="HelpSpoiler" name="m_lblInfo" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_checkDownloadOnlyUnreadMessages">
|
||||
<property name="text">
|
||||
<string>Download unread articles only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" 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="HelpSpoiler" name="m_lblNewAlgorithm" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Only download newest X articles per feed</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_spinLimitMessages</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="MessageCountSpinBox" name="m_spinLimitMessages">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="HelpSpoiler" name="m_lblLimitMessagesInfo" native="true"/>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>86</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="m_btnTestSetup">
|
||||
<property name="text">
|
||||
<string>&Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="LabelWithStatus" name="m_lblTestResult" native="true">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</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>
|
||||
<tabstops>
|
||||
<tabstop>m_btnGetToken</tabstop>
|
||||
<tabstop>m_checkDownloadOnlyUnreadMessages</tabstop>
|
||||
<tabstop>m_cbNewAlgorithm</tabstop>
|
||||
<tabstop>m_spinLimitMessages</tabstop>
|
||||
<tabstop>m_btnTestSetup</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
74
src/librssguard-feedly/src/gui/formeditfeedlyaccount.cpp
Normal file
74
src/librssguard-feedly/src/gui/formeditfeedlyaccount.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "src/gui/formeditfeedlyaccount.h"
|
||||
|
||||
#include "src/feedlynetwork.h"
|
||||
#include "src/feedlyserviceroot.h"
|
||||
#include "src/gui/feedlyaccountdetails.h"
|
||||
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#endif
|
||||
|
||||
FormEditFeedlyAccount::FormEditFeedlyAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("feedly")), parent), m_details(new FeedlyAccountDetails(this)) {
|
||||
insertCustomTab(m_details, tr("Service setup"), 0);
|
||||
activateTab(0);
|
||||
|
||||
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditFeedlyAccount::performTest);
|
||||
m_details->m_ui.m_txtUsername->setFocus();
|
||||
}
|
||||
|
||||
void FormEditFeedlyAccount::apply() {
|
||||
FormAccountDetails::apply();
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
account<FeedlyServiceRoot>()->network()->oauth()->logout(false);
|
||||
#endif
|
||||
|
||||
bool using_another_acc =
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->text() != account<FeedlyServiceRoot>()->network()->username();
|
||||
|
||||
account<FeedlyServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
|
||||
account<FeedlyServiceRoot>()
|
||||
->network()
|
||||
->setDownloadOnlyUnreadMessages(m_details->m_ui.m_checkDownloadOnlyUnreadMessages->isChecked());
|
||||
account<FeedlyServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
|
||||
account<FeedlyServiceRoot>()->network()->setDeveloperAccessToken(m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()
|
||||
->text());
|
||||
account<FeedlyServiceRoot>()->network()->setIntelligentSynchronization(m_details->m_ui.m_cbNewAlgorithm->isChecked());
|
||||
|
||||
account<FeedlyServiceRoot>()->saveAccountDataToDatabase();
|
||||
accept();
|
||||
|
||||
if (!m_creatingNew) {
|
||||
if (using_another_acc) {
|
||||
account<FeedlyServiceRoot>()->completelyRemoveAllData();
|
||||
}
|
||||
|
||||
account<FeedlyServiceRoot>()->start(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FormEditFeedlyAccount::loadAccountData() {
|
||||
FormAccountDetails::loadAccountData();
|
||||
|
||||
#if defined(FEEDLY_OFFICIAL_SUPPORT)
|
||||
m_details->m_oauth = account<FeedlyServiceRoot>()->network()->oauth();
|
||||
m_details->hookNetwork();
|
||||
#endif
|
||||
|
||||
m_details->m_ui.m_txtUsername->lineEdit()->setText(account<FeedlyServiceRoot>()->network()->username());
|
||||
m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()
|
||||
->setText(account<FeedlyServiceRoot>()->network()->developerAccessToken());
|
||||
m_details->m_ui.m_checkDownloadOnlyUnreadMessages
|
||||
->setChecked(account<FeedlyServiceRoot>()->network()->downloadOnlyUnreadMessages());
|
||||
m_details->m_ui.m_spinLimitMessages->setValue(account<FeedlyServiceRoot>()->network()->batchSize());
|
||||
m_details->m_ui.m_cbNewAlgorithm->setChecked(account<FeedlyServiceRoot>()->network()->intelligentSynchronization());
|
||||
}
|
||||
|
||||
void FormEditFeedlyAccount::performTest() {
|
||||
m_details->performTest(m_proxyDetails->proxy());
|
||||
}
|
30
src/librssguard-feedly/src/gui/formeditfeedlyaccount.h
Normal file
30
src/librssguard-feedly/src/gui/formeditfeedlyaccount.h
Normal file
@ -0,0 +1,30 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FORMEDITFEEDLYACCOUNT_H
|
||||
#define FORMEDITFEEDLYACCOUNT_H
|
||||
|
||||
#include <librssguard/services/abstract/gui/formaccountdetails.h>
|
||||
|
||||
class FeedlyAccountDetails;
|
||||
class FeedlyServiceRoot;
|
||||
|
||||
class FormEditFeedlyAccount : public FormAccountDetails {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FormEditFeedlyAccount(QWidget* parent = nullptr);
|
||||
|
||||
protected slots:
|
||||
virtual void apply();
|
||||
|
||||
protected:
|
||||
virtual void loadAccountData();
|
||||
|
||||
private slots:
|
||||
void performTest();
|
||||
|
||||
private:
|
||||
FeedlyAccountDetails* m_details;
|
||||
};
|
||||
|
||||
#endif // FORMEDITFEEDLYACCOUNT_H
|
96
src/librssguard-gmail/CMakeLists.txt
Normal file
96
src/librssguard-gmail/CMakeLists.txt
Normal file
@ -0,0 +1,96 @@
|
||||
if(NOT DEFINED LIBRSSGUARD_BINARY_PATH)
|
||||
set(LIBRSSGUARD_SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
src/definitions.h
|
||||
src/gmailentrypoint.cpp
|
||||
src/gmailentrypoint.h
|
||||
src/gmailnetworkfactory.cpp
|
||||
src/gmailnetworkfactory.h
|
||||
src/gmailserviceroot.cpp
|
||||
src/gmailserviceroot.h
|
||||
src/gui/emailpreviewer.cpp
|
||||
src/gui/emailpreviewer.h
|
||||
src/gui/emailrecipientcontrol.cpp
|
||||
src/gui/emailrecipientcontrol.h
|
||||
src/gui/formaddeditemail.cpp
|
||||
src/gui/formaddeditemail.h
|
||||
src/gui/formeditgmailaccount.cpp
|
||||
src/gui/formeditgmailaccount.h
|
||||
src/gui/gmailaccountdetails.cpp
|
||||
src/gui/gmailaccountdetails.h
|
||||
|
||||
src/3rd-party/mimesis/mimesis.cpp
|
||||
src/3rd-party/mimesis/mimesis.hpp
|
||||
src/3rd-party/mimesis/quoted-printable.cpp
|
||||
src/3rd-party/mimesis/quoted-printable.hpp
|
||||
|
||||
src/3rd-party/richtexteditor/mrichtextedit.cpp
|
||||
src/3rd-party/richtexteditor/mrichtextedit.h
|
||||
src/3rd-party/richtexteditor/mtextedit.cpp
|
||||
src/3rd-party/richtexteditor/mtextedit.h
|
||||
)
|
||||
|
||||
set(UI_FILES
|
||||
src/gui/emailpreviewer.ui
|
||||
src/gui/formaddeditemail.ui
|
||||
src/gui/gmailaccountdetails.ui
|
||||
src/3rd-party/richtexteditor/mrichtextedit.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-gmail SHARED ${SOURCES} ${QM_FILES})
|
||||
|
||||
# Add specific definitions.
|
||||
target_compile_definitions(rssguard-gmail
|
||||
PRIVATE
|
||||
RSSGUARD_DLLSPEC=Q_DECL_IMPORT
|
||||
RSSGUARD_DLLSPEC_EXPORT=Q_DECL_EXPORT
|
||||
)
|
||||
|
||||
target_include_directories(rssguard-gmail
|
||||
PUBLIC
|
||||
${LIBRSSGUARD_SOURCE_PATH}
|
||||
src/3rd-party/richtexteditor
|
||||
)
|
||||
|
||||
# Qt.
|
||||
target_link_libraries(rssguard-gmail 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-gmail DESTINATION plugins)
|
||||
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
include (GNUInstallDirs)
|
||||
install(TARGETS rssguard-gmail
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/rssguard
|
||||
)
|
||||
elseif(APPLE)
|
||||
install(TARGETS rssguard-gmail
|
||||
DESTINATION Contents/MacOS
|
||||
)
|
||||
endif()
|
5
src/librssguard-gmail/plugin.json
Normal file
5
src/librssguard-gmail/plugin.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Gmail",
|
||||
"author": "Martin Rotter",
|
||||
"website": "https://github.com/martinrotter/rssguard"
|
||||
}
|
@ -25,11 +25,11 @@
|
||||
** $QT_END_LICENSE$
|
||||
*/
|
||||
|
||||
#include "gui/richtexteditor/mrichtextedit.h"
|
||||
#include "src/3rd-party/richtexteditor/mrichtextedit.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
@ -2,10 +2,9 @@
|
||||
//
|
||||
// For license of this file, see <project-root-folder>/resources/text/COPYING_GNU_LGPL_21.
|
||||
|
||||
#include "gui/richtexteditor/mtextedit.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
#include "src/3rd-party/richtexteditor/mtextedit.h"
|
||||
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <QBuffer>
|
@ -1,16 +1,23 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gmailentrypoint.h"
|
||||
#include "src/gmailentrypoint.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "services/gmail/gui/formeditgmailaccount.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
#include "src/gui/formeditgmailaccount.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
GmailEntryPoint::GmailEntryPoint(QObject* parent) : QObject(parent) {}
|
||||
|
||||
GmailEntryPoint::~GmailEntryPoint() {
|
||||
qDebugNN << LOGSEC_GMAIL << "Destructing" << QUOTE_W_SPACE(QSL(SERVICE_CODE_GMAIL)) << "plugin.";
|
||||
}
|
||||
|
||||
ServiceRoot* GmailEntryPoint::createNewRoot() const {
|
||||
FormEditGmailAccount form_acc(qApp->mainFormWidget());
|
||||
|
||||
@ -42,3 +49,7 @@ QString GmailEntryPoint::author() const {
|
||||
QIcon GmailEntryPoint::icon() const {
|
||||
return qApp->icons()->miscIcon(QSL("gmail"));
|
||||
}
|
||||
|
||||
bool GmailEntryPoint::isDynamicallyLoaded() const {
|
||||
return true;
|
||||
}
|
@ -3,10 +3,17 @@
|
||||
#ifndef GMAILENTRYPOINT_H
|
||||
#define GMAILENTRYPOINT_H
|
||||
|
||||
#include "services/abstract/serviceentrypoint.h"
|
||||
#include <librssguard/services/abstract/serviceentrypoint.h>
|
||||
|
||||
class GmailEntryPoint : public QObject, public ServiceEntryPoint {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "io.github.martinrotter.rssguard.gmail" FILE "plugin.json")
|
||||
Q_INTERFACES(ServiceEntryPoint)
|
||||
|
||||
class GmailEntryPoint : public ServiceEntryPoint {
|
||||
public:
|
||||
explicit GmailEntryPoint(QObject* parent = nullptr);
|
||||
virtual ~GmailEntryPoint();
|
||||
|
||||
virtual ServiceRoot* createNewRoot() const;
|
||||
virtual QList<ServiceRoot*> initializeSubtree() const;
|
||||
virtual QString name() const;
|
||||
@ -14,6 +21,7 @@ class GmailEntryPoint : public ServiceEntryPoint {
|
||||
virtual QString description() const;
|
||||
virtual QString author() const;
|
||||
virtual QIcon icon() const;
|
||||
virtual bool isDynamicallyLoaded() const;
|
||||
};
|
||||
|
||||
#endif // GMAILENTRYPOINT_H
|
@ -1,20 +1,21 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
|
||||
#include "3rd-party/boolinq/boolinq.h"
|
||||
#include "database/databasequeries.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/settings.h"
|
||||
#include "miscellaneous/textfactory.h"
|
||||
#include "network-web/networkfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/abstract/labelsnode.h"
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "src/definitions.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
|
||||
#include <librssguard/3rd-party/boolinq/boolinq.h>
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/definitions/definitions.h>
|
||||
#include <librssguard/exceptions/applicationexception.h>
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/settings.h>
|
||||
#include <librssguard/miscellaneous/textfactory.h>
|
||||
#include <librssguard/network-web/networkfactory.h>
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#include <librssguard/services/abstract/labelsnode.h>
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QJsonArray>
|
@ -3,11 +3,12 @@
|
||||
#ifndef GMAILNETWORKFACTORY_H
|
||||
#define GMAILNETWORKFACTORY_H
|
||||
|
||||
#include "3rd-party/mimesis/mimesis.hpp"
|
||||
#include "core/message.h"
|
||||
#include "services/abstract/feed.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
#include "src/3rd-party/mimesis/mimesis.hpp"
|
||||
|
||||
#include <librssguard/core/message.h>
|
||||
#include <librssguard/services/abstract/feed.h>
|
||||
#include <librssguard/services/abstract/rootitem.h>
|
||||
#include <librssguard/services/abstract/serviceroot.h>
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
@ -1,19 +1,20 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
|
||||
#include "database/databasequeries.h"
|
||||
#include "exceptions/feedfetchexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/abstract/labelsnode.h"
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "services/gmail/gmailentrypoint.h"
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "services/gmail/gui/emailpreviewer.h"
|
||||
#include "services/gmail/gui/formaddeditemail.h"
|
||||
#include "services/gmail/gui/formeditgmailaccount.h"
|
||||
#include "src/definitions.h"
|
||||
#include "src/gmailentrypoint.h"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
#include "src/gui/emailpreviewer.h"
|
||||
#include "src/gui/formaddeditemail.h"
|
||||
#include "src/gui/formeditgmailaccount.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/exceptions/feedfetchexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#include <librssguard/services/abstract/labelsnode.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
|
@ -3,9 +3,10 @@
|
||||
#ifndef GMAILSERVICEROOT_H
|
||||
#define GMAILSERVICEROOT_H
|
||||
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
#include "services/gmail/gui/emailpreviewer.h"
|
||||
#include "src/gui/emailpreviewer.h"
|
||||
|
||||
#include <librssguard/services/abstract/cacheforserviceroot.h>
|
||||
#include <librssguard/services/abstract/serviceroot.h>
|
||||
|
||||
class GmailNetworkFactory;
|
||||
|
@ -1,15 +1,16 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gui/emailpreviewer.h"
|
||||
#include "src/gui/emailpreviewer.h"
|
||||
|
||||
#include "exceptions/networkexception.h"
|
||||
#include "gui/messagebox.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "services/gmail/gui/formaddeditemail.h"
|
||||
#include "src/definitions.h"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
#include "src/gui/formaddeditemail.h"
|
||||
|
||||
#include <librssguard/exceptions/networkexception.h>
|
||||
#include <librssguard/gui/messagebox.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#include <QJsonObject>
|
||||
|
@ -3,11 +3,11 @@
|
||||
#ifndef EMAILPREVIEWER_H
|
||||
#define EMAILPREVIEWER_H
|
||||
|
||||
#include "gui/webbrowser.h"
|
||||
#include "services/abstract/gui/custommessagepreviewer.h"
|
||||
|
||||
#include "ui_emailpreviewer.h"
|
||||
|
||||
#include <librssguard/gui/webbrowser.h>
|
||||
#include <librssguard/services/abstract/gui/custommessagepreviewer.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
class GmailServiceRoot;
|
@ -1,11 +1,12 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gui/emailrecipientcontrol.h"
|
||||
#include "src/gui/emailrecipientcontrol.h"
|
||||
|
||||
#include "gui/reusable/plaintoolbutton.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "src/definitions.h"
|
||||
|
||||
#include <librssguard/gui/reusable/plaintoolbutton.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCompleter>
|
@ -3,7 +3,7 @@
|
||||
#ifndef EMAILRECIPIENTCONTROL_H
|
||||
#define EMAILRECIPIENTCONTROL_H
|
||||
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "src/definitions.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
@ -1,17 +1,18 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gui/formaddeditemail.h"
|
||||
#include "src/gui/formaddeditemail.h"
|
||||
|
||||
#include "3rd-party/mimesis/mimesis.hpp"
|
||||
#include "database/databasequeries.h"
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "gui/guiutilities.h"
|
||||
#include "gui/messagebox.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "services/gmail/gui/emailrecipientcontrol.h"
|
||||
#include "src/3rd-party/mimesis/mimesis.hpp"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
#include "src/gui/emailrecipientcontrol.h"
|
||||
|
||||
#include <librssguard/database/databasequeries.h>
|
||||
#include <librssguard/exceptions/applicationexception.h>
|
||||
#include <librssguard/gui/guiutilities.h>
|
||||
#include <librssguard/gui/messagebox.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QPushButton>
|
@ -7,10 +7,6 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class FormAddEditEmail;
|
||||
}
|
||||
|
||||
class GmailServiceRoot;
|
||||
class Message;
|
||||
class EmailRecipientControl;
|
@ -1,12 +1,13 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gui/formeditgmailaccount.h"
|
||||
#include "src/gui/formeditgmailaccount.h"
|
||||
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "services/gmail/gui/gmailaccountdetails.h"
|
||||
#include "src/gmailserviceroot.h"
|
||||
#include "src/gui/gmailaccountdetails.h"
|
||||
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/miscellaneous/iconfactory.h>
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
|
||||
FormEditGmailAccount::FormEditGmailAccount(QWidget* parent)
|
||||
: FormAccountDetails(qApp->icons()->miscIcon(QSL("gmail")), parent), m_details(new GmailAccountDetails(this)) {
|
@ -3,8 +3,9 @@
|
||||
#ifndef FORMEDITINOREADERACCOUNT_H
|
||||
#define FORMEDITINOREADERACCOUNT_H
|
||||
|
||||
#include "services/abstract/gui/formaccountdetails.h"
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
|
||||
#include <librssguard/services/abstract/gui/formaccountdetails.h>
|
||||
|
||||
class GmailServiceRoot;
|
||||
class GmailAccountDetails;
|
@ -1,13 +1,14 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "services/gmail/gui/gmailaccountdetails.h"
|
||||
#include "src/gui/gmailaccountdetails.h"
|
||||
|
||||
#include "exceptions/applicationexception.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "network-web/oauth2service.h"
|
||||
#include "network-web/webfactory.h"
|
||||
#include "services/gmail/definitions.h"
|
||||
#include "services/gmail/gmailnetworkfactory.h"
|
||||
#include "src/definitions.h"
|
||||
#include "src/gmailnetworkfactory.h"
|
||||
|
||||
#include <librssguard/exceptions/applicationexception.h>
|
||||
#include <librssguard/miscellaneous/application.h>
|
||||
#include <librssguard/network-web/oauth2service.h>
|
||||
#include <librssguard/network-web/webfactory.h>
|
||||
|
||||
GmailAccountDetails::GmailAccountDetails(QWidget* parent) : QWidget(parent), m_oauth(nullptr), m_lastProxy({}) {
|
||||
m_ui.setupUi(this);
|
@ -159,10 +159,6 @@ set(SOURCES
|
||||
gui/reusable/treeviewcolumnsmenu.h
|
||||
gui/reusable/widgetwithstatus.cpp
|
||||
gui/reusable/widgetwithstatus.h
|
||||
gui/richtexteditor/mrichtextedit.cpp
|
||||
gui/richtexteditor/mrichtextedit.h
|
||||
gui/richtexteditor/mtextedit.cpp
|
||||
gui/richtexteditor/mtextedit.h
|
||||
gui/settings/settingsbrowsermail.cpp
|
||||
gui/settings/settingsbrowsermail.h
|
||||
gui/settings/settingsdatabase.cpp
|
||||
@ -328,23 +324,6 @@ set(SOURCES
|
||||
services/abstract/serviceroot.h
|
||||
services/abstract/unreadnode.cpp
|
||||
services/abstract/unreadnode.h
|
||||
services/gmail/definitions.h
|
||||
services/gmail/gmailentrypoint.cpp
|
||||
services/gmail/gmailentrypoint.h
|
||||
services/gmail/gmailnetworkfactory.cpp
|
||||
services/gmail/gmailnetworkfactory.h
|
||||
services/gmail/gmailserviceroot.cpp
|
||||
services/gmail/gmailserviceroot.h
|
||||
services/gmail/gui/emailpreviewer.cpp
|
||||
services/gmail/gui/emailpreviewer.h
|
||||
services/gmail/gui/emailrecipientcontrol.cpp
|
||||
services/gmail/gui/emailrecipientcontrol.h
|
||||
services/gmail/gui/formaddeditemail.cpp
|
||||
services/gmail/gui/formaddeditemail.h
|
||||
services/gmail/gui/formeditgmailaccount.cpp
|
||||
services/gmail/gui/formeditgmailaccount.h
|
||||
services/gmail/gui/gmailaccountdetails.cpp
|
||||
services/gmail/gui/gmailaccountdetails.h
|
||||
services/greader/definitions.h
|
||||
services/greader/greaderentrypoint.cpp
|
||||
services/greader/greaderentrypoint.h
|
||||
@ -431,7 +410,6 @@ set(UI_FILES
|
||||
gui/reusable/articleamountcontrol.ui
|
||||
gui/reusable/networkproxydetails.ui
|
||||
gui/reusable/searchtextwidget.ui
|
||||
gui/richtexteditor/mrichtextedit.ui
|
||||
gui/settings/settingsbrowsermail.ui
|
||||
gui/settings/settingsdatabase.ui
|
||||
gui/settings/settingsdownloads.ui
|
||||
@ -454,9 +432,6 @@ set(UI_FILES
|
||||
services/abstract/gui/formaddeditprobe.ui
|
||||
services/abstract/gui/formcategorydetails.ui
|
||||
services/abstract/gui/formfeeddetails.ui
|
||||
services/gmail/gui/emailpreviewer.ui
|
||||
services/gmail/gui/formaddeditemail.ui
|
||||
services/gmail/gui/gmailaccountdetails.ui
|
||||
services/greader/gui/greaderaccountdetails.ui
|
||||
services/greader/gui/greaderfeeddetails.ui
|
||||
services/owncloud/gui/owncloudaccountdetails.ui
|
||||
@ -538,14 +513,6 @@ list(APPEND SOURCES
|
||||
gui/webviewers/qtextbrowser/textbrowserviewer.cpp
|
||||
)
|
||||
|
||||
# Add mimesis.
|
||||
list(APPEND SOURCES
|
||||
3rd-party/mimesis/mimesis.cpp
|
||||
3rd-party/mimesis/mimesis.hpp
|
||||
3rd-party/mimesis/quoted-printable.cpp
|
||||
3rd-party/mimesis/quoted-printable.hpp
|
||||
)
|
||||
|
||||
# Add boolinq.
|
||||
list(APPEND SOURCES
|
||||
3rd-party/boolinq/boolinq.h
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <QToolButton>
|
||||
|
||||
class ColorToolButton : public QToolButton {
|
||||
class RSSGUARD_DLLSPEC ColorToolButton : public QToolButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <QToolButton>
|
||||
|
||||
class PlainToolButton : public QToolButton {
|
||||
class RSSGUARD_DLLSPEC PlainToolButton : public QToolButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -23,7 +23,7 @@ class WebViewer;
|
||||
class LocationLineEdit;
|
||||
class SearchTextWidget;
|
||||
|
||||
class WebBrowser : public TabContent {
|
||||
class RSSGUARD_DLLSPEC WebBrowser : public TabContent {
|
||||
Q_OBJECT
|
||||
|
||||
friend class WebEngineViewer;
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "miscellaneous/settings.h"
|
||||
#include "services/abstract/cacheforserviceroot.h"
|
||||
#include "services/abstract/serviceroot.h"
|
||||
#include "services/gmail/gmailentrypoint.h"
|
||||
#include "services/greader/greaderentrypoint.h"
|
||||
#include "services/owncloud/owncloudserviceentrypoint.h"
|
||||
#include "services/reddit/redditentrypoint.h"
|
||||
@ -68,7 +67,6 @@ FeedReader::~FeedReader() {
|
||||
|
||||
QList<ServiceEntryPoint*> FeedReader::feedServices() {
|
||||
if (m_feedServices.isEmpty()) {
|
||||
m_feedServices.append(new GmailEntryPoint());
|
||||
m_feedServices.append(new GreaderEntryPoint());
|
||||
m_feedServices.append(new OwnCloudServiceEntryPoint());
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace Ui {
|
||||
class DownloadManager;
|
||||
}
|
||||
|
||||
class DownloadItem : public QWidget {
|
||||
class RSSGUARD_DLLSPEC DownloadItem : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
friend class DownloadManager;
|
||||
@ -89,7 +89,7 @@ class DownloadItem : public QWidget {
|
||||
class WebBrowser;
|
||||
class SilentNetworkAccessManager;
|
||||
|
||||
class DownloadManager : public TabContent {
|
||||
class RSSGUARD_DLLSPEC DownloadManager : public TabContent {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(RemovePolicy removePolicy READ removePolicy WRITE setRemovePolicy NOTIFY removePolicyChanged)
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
typedef QPair<QString, QString> HttpHeader;
|
||||
|
||||
class HttpResponse {
|
||||
class RSSGUARD_DLLSPEC HttpResponse {
|
||||
public:
|
||||
explicit HttpResponse();
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
class OAuth2Service : public QObject {
|
||||
class RSSGUARD_DLLSPEC OAuth2Service : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -3,7 +3,6 @@
|
||||
#ifndef REDDITNETWORKFACTORY_H
|
||||
#define REDDITNETWORKFACTORY_H
|
||||
|
||||
#include "3rd-party/mimesis/mimesis.hpp"
|
||||
#include "core/message.h"
|
||||
#include "services/abstract/feed.h"
|
||||
#include "services/abstract/rootitem.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user