more logging for #730

This commit is contained in:
Martin Rotter 2022-05-10 07:38:24 +02:00
parent d1b904de94
commit 76912b6d80
4 changed files with 305 additions and 314 deletions

View file

@ -26,7 +26,7 @@
<url type="donation">https://github.com/sponsors/martinrotter</url> <url type="donation">https://github.com/sponsors/martinrotter</url>
<content_rating type="oars-1.1" /> <content_rating type="oars-1.1" />
<releases> <releases>
<release version="4.2.2" date="2022-05-06"/> <release version="4.2.2" date="2022-05-10"/>
</releases> </releases>
<content_rating type="oars-1.0"> <content_rating type="oars-1.0">
<content_attribute id="violence-cartoon">none</content_attribute> <content_attribute id="violence-cartoon">none</content_attribute>

View file

@ -42,12 +42,16 @@
#include <cstdlib> #include <cstdlib>
#include <utility> #include <utility>
OAuth2Service::OAuth2Service(const QString& auth_url, const QString& token_url, const QString& client_id, OAuth2Service::OAuth2Service(const QString& auth_url,
const QString& client_secret, const QString& scope, QObject* parent) const QString& token_url,
: QObject(parent), const QString& client_id,
m_id(QString::number(QRandomGenerator::global()->generate())), m_timerId(-1), const QString& client_secret,
m_redirectionHandler(new OAuthHttpHandler(tr("You can close this window now. Go back to %1.").arg(QSL(APP_NAME)), this)), const QString& scope,
m_functorOnLogin(std::function<void()>()) { QObject* parent)
: QObject(parent), m_id(QString::number(QRandomGenerator::global()->generate())), m_timerId(-1),
m_redirectionHandler(new OAuthHttpHandler(tr("You can close this window now. Go back to %1.").arg(QSL(APP_NAME)),
this)),
m_functorOnLogin(std::function<void()>()) {
m_tokenGrantType = QSL("authorization_code"); m_tokenGrantType = QSL("authorization_code");
m_tokenUrl = QUrl(token_url); m_tokenUrl = QUrl(token_url);
m_authUrl = auth_url; m_authUrl = auth_url;
@ -65,14 +69,16 @@ OAuth2Service::OAuth2Service(const QString& auth_url, const QString& token_url,
retrieveAccessToken(auth_code); retrieveAccessToken(auth_code);
} }
}); });
connect(m_redirectionHandler, &OAuthHttpHandler::authRejected, [this](const QString& error_description, const QString& id) { connect(m_redirectionHandler,
Q_UNUSED(error_description) &OAuthHttpHandler::authRejected,
[this](const QString& error_description, const QString& id) {
Q_UNUSED(error_description)
if (id.isEmpty() || id == m_id) { if (id.isEmpty() || id == m_id) {
// We process this further only if handler (static singleton) responded to our original request. // We process this further only if handler (static singleton) responded to our original request.
emit authFailed(); emit authFailed();
} }
}); });
} }
OAuth2Service::~OAuth2Service() { OAuth2Service::~OAuth2Service() {
@ -81,15 +87,14 @@ OAuth2Service::~OAuth2Service() {
QString OAuth2Service::bearer() { QString OAuth2Service::bearer() {
if (!isFullyLoggedIn()) { if (!isFullyLoggedIn()) {
qApp->showGuiMessage(Notification::Event::LoginFailure, { qApp->showGuiMessage(Notification::Event::LoginFailure,
tr("You have to login first"), {tr("You have to login first"),
tr("Click here to login."), tr("Click here to login."),
QSystemTrayIcon::MessageIcon::Critical }, QSystemTrayIcon::MessageIcon::Critical},
{}, { {},
tr("Login"), {tr("Login"), [this]() {
[this]() { login();
login(); }});
} });
return {}; return {};
} }
else { else {
@ -179,11 +184,12 @@ void OAuth2Service::retrieveAccessToken(const QString& auth_code) {
"client_secret=%2&" "client_secret=%2&"
"code=%3&" "code=%3&"
"redirect_uri=%5&" "redirect_uri=%5&"
"grant_type=%4").arg(properClientId(), "grant_type=%4")
properClientSecret(), .arg(properClientId(),
auth_code, properClientSecret(),
m_tokenGrantType, auth_code,
m_redirectionHandler->listenAddressPort()); m_tokenGrantType,
m_redirectionHandler->listenAddressPort());
qDebugNN << LOGSEC_OAUTH << "Posting data for access token retrieval:" << QUOTE_W_SPACE_DOT(content); qDebugNN << LOGSEC_OAUTH << "Posting data for access token retrieval:" << QUOTE_W_SPACE_DOT(content);
m_networkManager.post(network_request, content.toUtf8()); m_networkManager.post(network_request, content.toUtf8());
@ -205,16 +211,14 @@ void OAuth2Service::refreshAccessToken(const QString& refresh_token) {
QString content = QString("client_id=%1&" QString content = QString("client_id=%1&"
"client_secret=%2&" "client_secret=%2&"
"refresh_token=%3&" "refresh_token=%3&"
"grant_type=%4").arg(properClientId(), "grant_type=%4")
properClientSecret(), .arg(properClientId(), properClientSecret(), real_refresh_token, QSL("refresh_token"));
real_refresh_token,
QSL("refresh_token"));
qApp->showGuiMessage(Notification::Event::LoginDataRefreshed, { qApp->showGuiMessage(Notification::Event::LoginDataRefreshed,
tr("Logging in via OAuth 2.0..."), {tr("Logging in via OAuth 2.0..."),
tr("Refreshing login tokens for '%1'...").arg(m_tokenUrl.toString()), tr("Refreshing login tokens for '%1'...").arg(m_tokenUrl.toString()),
QSystemTrayIcon::MessageIcon::Information }, QSystemTrayIcon::MessageIcon::Information},
{ true, false, true }); {true, false, true});
qDebugNN << LOGSEC_OAUTH << "Posting data for access token refreshing:" << QUOTE_W_SPACE_DOT(content); qDebugNN << LOGSEC_OAUTH << "Posting data for access token refreshing:" << QUOTE_W_SPACE_DOT(content);
m_networkManager.post(network_request, content.toUtf8()); m_networkManager.post(network_request, content.toUtf8());
@ -229,8 +233,7 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
if (network_reply->error() != QNetworkReply::NetworkError::NoError) { if (network_reply->error() != QNetworkReply::NetworkError::NoError) {
qWarningNN << LOGSEC_OAUTH qWarningNN << LOGSEC_OAUTH
<< "Network error when obtaining token response:" << "Network error when obtaining token response:" << QUOTE_W_SPACE_DOT(network_reply->error());
<< QUOTE_W_SPACE_DOT(network_reply->error());
emit tokensRetrieveError(QString(), NetworkFactory::networkErrorText(network_reply->error())); emit tokensRetrieveError(QString(), NetworkFactory::networkErrorText(network_reply->error()));
} }
@ -238,9 +241,7 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
QString error = root_obj.value(QSL("error")).toString(); QString error = root_obj.value(QSL("error")).toString();
QString error_description = root_obj.value(QSL("error_description")).toString(); QString error_description = root_obj.value(QSL("error_description")).toString();
qWarningNN << LOGSEC_OAUTH qWarningNN << LOGSEC_OAUTH << "JSON error when obtaining token response:" << QUOTE_W_SPACE(error)
<< "JSON error when obtaining token response:"
<< QUOTE_W_SPACE(error)
<< QUOTE_W_SPACE_DOT(error_description); << QUOTE_W_SPACE_DOT(error_description);
logout(); logout();
@ -259,11 +260,12 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
setRefreshToken(refresh_token); setRefreshToken(refresh_token);
} }
qDebugNN << LOGSEC_OAUTH qDebugNN << LOGSEC_OAUTH << "Obtained refresh token" << QUOTE_W_SPACE(refreshToken()) << "- expires on date/time"
<< "Obtained refresh token" << QUOTE_W_SPACE(refreshToken()) << QUOTE_W_SPACE_DOT(tokensExpireIn());
<< "- expires on date/time" << QUOTE_W_SPACE_DOT(tokensExpireIn());
if (m_functorOnLogin != nullptr) { if (m_functorOnLogin != nullptr) {
qDebugNN << LOGSEC_OAUTH << "Running custom after-login code.";
m_functorOnLogin(); m_functorOnLogin();
} }
@ -274,15 +276,11 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
} }
QString OAuth2Service::properClientId() const { QString OAuth2Service::properClientId() const {
return m_clientId.simplified().isEmpty() return m_clientId.simplified().isEmpty() ? m_clientSecretId : m_clientId;
? m_clientSecretId
: m_clientId;
} }
QString OAuth2Service::properClientSecret() const { QString OAuth2Service::properClientSecret() const {
return m_clientSecret.simplified().isEmpty() return m_clientSecret.simplified().isEmpty() ? m_clientSecretSecret : m_clientSecret;
? m_clientSecretSecret
: m_clientSecret;
} }
QString OAuth2Service::accessToken() const { QString OAuth2Service::accessToken() const {
@ -339,12 +337,12 @@ bool OAuth2Service::login(const std::function<void()>& functor_when_logged_in) {
m_functorOnLogin = functor_when_logged_in; m_functorOnLogin = functor_when_logged_in;
if (!m_redirectionHandler->isListening()) { if (!m_redirectionHandler->isListening()) {
qCriticalNN << LOGSEC_OAUTH qCriticalNN << LOGSEC_OAUTH << "Cannot log-in because OAuth redirection handler is not listening.";
<< "Cannot log-in because OAuth redirection handler is not listening.";
emit tokensRetrieveError(QString(), tr("Failed to start OAuth " emit tokensRetrieveError(QString(),
"redirection listener. Maybe your " tr("Failed to start OAuth "
"rights are not high enough.")); "redirection listener. Maybe your "
"rights are not high enough."));
return false; return false;
} }
@ -402,10 +400,8 @@ void OAuth2Service::retrieveAuthCode() {
"state=%4&" "state=%4&"
"prompt=consent&" "prompt=consent&"
"duration=permanent&" "duration=permanent&"
"access_type=offline").arg(properClientId(), "access_type=offline")
m_scope, .arg(properClientId(), m_scope, m_redirectionHandler->listenAddressPort(), m_id);
m_redirectionHandler->listenAddressPort(),
m_id);
// We run login URL in external browser, response is caught by light HTTP server. // We run login URL in external browser, response is caught by light HTTP server.
qApp->web()->openUrlInExternalBrowser(auth_url); qApp->web()->openUrlInExternalBrowser(auth_url);

View file

@ -23,8 +23,8 @@
ServiceRoot::ServiceRoot(RootItem* parent) ServiceRoot::ServiceRoot(RootItem* parent)
: RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)), : RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)),
m_labelsNode(new LabelsNode(this)), m_unreadNode(new UnreadNode(this)), m_labelsNode(new LabelsNode(this)), m_unreadNode(new UnreadNode(this)), m_accountId(NO_PARENT_CATEGORY),
m_accountId(NO_PARENT_CATEGORY), m_networkProxy(QNetworkProxy()) { m_networkProxy(QNetworkProxy()) {
setKind(RootItem::Kind::ServiceRoot); setKind(RootItem::Kind::ServiceRoot);
appendCommonNodes(); appendCommonNodes();
} }
@ -95,7 +95,8 @@ QList<QAction*> ServiceRoot::contextMenuMessagesList(const QList<Message>& messa
QList<QAction*> ServiceRoot::serviceMenu() { QList<QAction*> ServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) { if (m_serviceMenu.isEmpty()) {
if (isSyncable()) { if (isSyncable()) {
auto* act_sync_tree = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Synchronize folders && other items"), this); auto* act_sync_tree =
new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Synchronize folders && other items"), this);
connect(act_sync_tree, &QAction::triggered, this, &ServiceRoot::syncIn); connect(act_sync_tree, &QAction::triggered, this, &ServiceRoot::syncIn);
m_serviceMenu.append(act_sync_tree); m_serviceMenu.append(act_sync_tree);
@ -103,7 +104,8 @@ QList<QAction*> ServiceRoot::serviceMenu() {
auto* cache = toCache(); auto* cache = toCache();
if (cache != nullptr) { if (cache != nullptr) {
auto* act_sync_cache = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Synchronize article cache"), this); auto* act_sync_cache =
new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Synchronize article cache"), this);
connect(act_sync_cache, &QAction::triggered, this, [cache]() { connect(act_sync_cache, &QAction::triggered, this, [cache]() {
cache->saveAllCachedData(false); cache->saveAllCachedData(false);
@ -139,8 +141,7 @@ void ServiceRoot::updateCounts(bool including_total_count) {
if (child->kind() == RootItem::Kind::Feed) { if (child->kind() == RootItem::Kind::Feed) {
feeds.append(child->toFeed()); feeds.append(child->toFeed());
} }
else if (child->kind() != RootItem::Kind::Labels && else if (child->kind() != RootItem::Kind::Labels && child->kind() != RootItem::Kind::Category &&
child->kind() != RootItem::Kind::Category &&
child->kind() != RootItem::Kind::ServiceRoot) { child->kind() != RootItem::Kind::ServiceRoot) {
child->updateCounts(including_total_count); child->updateCounts(including_total_count);
} }
@ -152,7 +153,8 @@ void ServiceRoot::updateCounts(bool including_total_count) {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
bool ok; bool ok;
QMap<QString, QPair<int, int>> counts = DatabaseQueries::getMessageCountsForAccount(database, accountId(), including_total_count, &ok); QMap<QString, QPair<int, int>> counts =
DatabaseQueries::getMessageCountsForAccount(database, accountId(), including_total_count, &ok);
if (ok) { if (ok) {
for (Feed* feed : feeds) { for (Feed* feed : feeds) {
@ -183,7 +185,7 @@ void ServiceRoot::completelyRemoveAllData() {
cleanAllItemsFromModel(true); cleanAllItemsFromModel(true);
removeOldAccountFromDatabase(true, true); removeOldAccountFromDatabase(true, true);
updateCounts(true); updateCounts(true);
itemChanged({ this }); itemChanged({this});
requestReloadMessageList(true); requestReloadMessageList(true);
} }
@ -204,20 +206,15 @@ QIcon ServiceRoot::feedIconForMessage(const QString& feed_custom_id) const {
void ServiceRoot::removeOldAccountFromDatabase(bool delete_messages_too, bool delete_labels_too) { void ServiceRoot::removeOldAccountFromDatabase(bool delete_messages_too, bool delete_labels_too) {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
DatabaseQueries::deleteAccountData(database, DatabaseQueries::deleteAccountData(database, accountId(), delete_messages_too, delete_labels_too);
accountId(),
delete_messages_too,
delete_labels_too);
} }
void ServiceRoot::cleanAllItemsFromModel(bool clean_labels_too) { void ServiceRoot::cleanAllItemsFromModel(bool clean_labels_too) {
auto chi = childItems(); auto chi = childItems();
for (RootItem* top_level_item : qAsConst(chi)) { for (RootItem* top_level_item : qAsConst(chi)) {
if (top_level_item->kind() != RootItem::Kind::Bin && if (top_level_item->kind() != RootItem::Kind::Bin && top_level_item->kind() != RootItem::Kind::Important &&
top_level_item->kind() != RootItem::Kind::Important && top_level_item->kind() != RootItem::Kind::Unread && top_level_item->kind() != RootItem::Kind::Labels) {
top_level_item->kind() != RootItem::Kind::Unread &&
top_level_item->kind() != RootItem::Kind::Labels) {
requestItemRemoval(top_level_item); requestItemRemoval(top_level_item);
} }
} }
@ -265,7 +262,9 @@ bool ServiceRoot::cleanFeeds(const QList<Feed*>& items, bool clean_read_only) {
void ServiceRoot::storeNewFeedTree(RootItem* root) { void ServiceRoot::storeNewFeedTree(RootItem* root) {
try { try {
DatabaseQueries::storeAccountTree(qApp->database()->driver()->connection(metaObject()->className()), root, accountId()); DatabaseQueries::storeAccountTree(qApp->database()->driver()->connection(metaObject()->className()),
root,
accountId());
} }
catch (const ApplicationException& ex) { catch (const ApplicationException& ex) {
qFatal("Cannot store account tree: '%s'.", qPrintable(ex.message())); qFatal("Cannot store account tree: '%s'.", qPrintable(ex.message()));
@ -405,7 +404,8 @@ void ServiceRoot::restoreCustomFeedsData(const QMap<QString, QVariantMap>& data,
QVariantMap feed_custom_data = i.value(); QVariantMap feed_custom_data = i.value();
feed->setAutoUpdateInitialInterval(feed_custom_data.value(QSL("auto_update_interval")).toInt()); feed->setAutoUpdateInitialInterval(feed_custom_data.value(QSL("auto_update_interval")).toInt());
feed->setAutoUpdateType(static_cast<Feed::AutoUpdateType>(feed_custom_data.value(QSL("auto_update_type")).toInt())); feed
->setAutoUpdateType(static_cast<Feed::AutoUpdateType>(feed_custom_data.value(QSL("auto_update_type")).toInt()));
feed->setMessageFilters(feed_custom_data.value(QSL("msg_filters")).value<QList<QPointer<MessageFilter>>>()); feed->setMessageFilters(feed_custom_data.value(QSL("msg_filters")).value<QList<QPointer<MessageFilter>>>());
feed->setIsSwitchedOff(feed_custom_data.value(QSL("is_off")).toBool()); feed->setIsSwitchedOff(feed_custom_data.value(QSL("is_off")).toBool());
@ -440,14 +440,20 @@ void ServiceRoot::syncIn() {
QIcon original_icon = icon(); QIcon original_icon = icon();
setIcon(qApp->icons()->fromTheme(QSL("view-refresh"))); setIcon(qApp->icons()->fromTheme(QSL("view-refresh")));
itemChanged({ this }); itemChanged({this});
qDebugNN << LOGSEC_CORE << "Starting sync-in process.";
RootItem* new_tree = obtainNewTreeForSyncIn(); RootItem* new_tree = obtainNewTreeForSyncIn();
if (new_tree != nullptr) { if (new_tree != nullptr) {
qDebugNN << LOGSEC_CORE << "New feed tree for sync-in obtained.";
auto feed_custom_data = storeCustomFeedsData(); auto feed_custom_data = storeCustomFeedsData();
// Remove from feeds model, then from SQL but leave messages intact. // Remove from feeds model, then from SQL but leave messages intact.
bool uses_remote_labels = (supportedLabelOperations() & LabelOperation::Synchronised) == LabelOperation::Synchronised; bool uses_remote_labels =
(supportedLabelOperations() & LabelOperation::Synchronised) == LabelOperation::Synchronised;
// Remove stuff. // Remove stuff.
cleanAllItemsFromModel(uses_remote_labels); cleanAllItemsFromModel(uses_remote_labels);
@ -492,6 +498,9 @@ void ServiceRoot::syncIn() {
updateCounts(true); updateCounts(true);
requestReloadMessageList(true); requestReloadMessageList(true);
} }
else {
qCriticalNN << LOGSEC_CORE << "New feed tree for sync-in NOT obtained.";
}
setIcon(original_icon); setIcon(original_icon);
itemChanged(getSubTree()); itemChanged(getSubTree());
@ -609,7 +618,8 @@ QStringList ServiceRoot::textualFeedUrls(const QList<Feed*>& feeds) const {
} }
QStringList ServiceRoot::textualFeedIds(const QList<Feed*>& feeds) const { QStringList ServiceRoot::textualFeedIds(const QList<Feed*>& feeds) const {
QStringList stringy_ids; stringy_ids.reserve(feeds.size()); QStringList stringy_ids;
stringy_ids.reserve(feeds.size());
for (const Feed* feed : feeds) { for (const Feed* feed : feeds) {
stringy_ids.append(QSL("'%1'").arg(feed->customId())); stringy_ids.append(QSL("'%1'").arg(feed->customId()));
@ -619,7 +629,8 @@ QStringList ServiceRoot::textualFeedIds(const QList<Feed*>& feeds) const {
} }
QStringList ServiceRoot::customIDsOfMessages(const QList<ImportanceChange>& changes) { QStringList ServiceRoot::customIDsOfMessages(const QList<ImportanceChange>& changes) {
QStringList list; list.reserve(changes.size()); QStringList list;
list.reserve(changes.size());
for (const auto& change : changes) { for (const auto& change : changes) {
list.append(change.first.m_customId); list.append(change.first.m_customId);
@ -629,7 +640,8 @@ QStringList ServiceRoot::customIDsOfMessages(const QList<ImportanceChange>& chan
} }
QStringList ServiceRoot::customIDsOfMessages(const QList<Message>& messages) { QStringList ServiceRoot::customIDsOfMessages(const QList<Message>& messages) {
QStringList list; list.reserve(messages.size()); QStringList list;
list.reserve(messages.size());
for (const Message& message : messages) { for (const Message& message : messages) {
list.append(message.m_customId); list.append(message.m_customId);
@ -655,32 +667,35 @@ void ServiceRoot::setAccountId(int account_id) {
bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) { bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) {
if (item->kind() == RootItem::Kind::Bin) { if (item->kind() == RootItem::Kind::Bin) {
model->setFilter(QSL("Messages.is_deleted = 1 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1") model->setFilter(QSL("Messages.is_deleted = 1 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1")
.arg(QString::number(accountId()))); .arg(QString::number(accountId())));
} }
else if (item->kind() == RootItem::Kind::Important) { else if (item->kind() == RootItem::Kind::Important) {
model->setFilter(QSL("Messages.is_important = 1 AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1") model->setFilter(QSL("Messages.is_important = 1 AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND "
.arg(QString::number(accountId()))); "Messages.account_id = %1")
.arg(QString::number(accountId())));
} }
else if (item->kind() == RootItem::Kind::Unread) { else if (item->kind() == RootItem::Kind::Unread) {
model->setFilter(QSL("Messages.is_read = 0 AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1") model->setFilter(QSL("Messages.is_read = 0 AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND "
.arg(QString::number(accountId()))); "Messages.account_id = %1")
.arg(QString::number(accountId())));
} }
else if (item->kind() == RootItem::Kind::Label) { else if (item->kind() == RootItem::Kind::Label) {
// Show messages with particular label. // Show messages with particular label.
model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND " model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND "
"(SELECT COUNT(*) FROM LabelsInMessages WHERE account_id = %1 AND message = Messages.custom_id AND label = '%2') > 0") "(SELECT COUNT(*) FROM LabelsInMessages WHERE account_id = %1 AND message = "
.arg(QString::number(accountId()), item->customId())); "Messages.custom_id AND label = '%2') > 0")
.arg(QString::number(accountId()), item->customId()));
} }
else if (item->kind() == RootItem::Kind::Labels) { else if (item->kind() == RootItem::Kind::Labels) {
// Show messages with any label. // Show messages with any label.
model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND " model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND "
"(SELECT COUNT(*) FROM LabelsInMessages WHERE account_id = %1 AND message = Messages.custom_id) > 0") "(SELECT COUNT(*) FROM LabelsInMessages WHERE account_id = %1 AND message = "
.arg(QString::number(accountId()))); "Messages.custom_id) > 0")
.arg(QString::number(accountId())));
} }
else if (item->kind() == RootItem::Kind::ServiceRoot) { else if (item->kind() == RootItem::Kind::ServiceRoot) {
model->setFilter( model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1")
QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1").arg( .arg(QString::number(accountId())));
QString::number(accountId())));
qDebugNN << "Displaying messages from account:" << QUOTE_W_SPACE_DOT(accountId()); qDebugNN << "Displaying messages from account:" << QUOTE_W_SPACE_DOT(accountId());
} }
@ -692,11 +707,9 @@ bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) {
filter_clause = QSL("null"); filter_clause = QSL("null");
} }
model->setFilter( model->setFilter(QSL("Feeds.custom_id IN (%1) AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND "
QSL("Feeds.custom_id IN (%1) AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %2").arg( "Messages.account_id = %2")
filter_clause, .arg(filter_clause, QString::number(accountId())));
QString::
number(accountId())));
QString urls = textualFeedUrls(children).join(QSL(", ")); QString urls = textualFeedUrls(children).join(QSL(", "));
@ -707,7 +720,9 @@ bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) {
return true; return true;
} }
bool ServiceRoot::onBeforeSetMessagesRead(RootItem* selected_item, const QList<Message>& messages, RootItem::ReadStatus read) { bool ServiceRoot::onBeforeSetMessagesRead(RootItem* selected_item,
const QList<Message>& messages,
RootItem::ReadStatus read) {
Q_UNUSED(selected_item) Q_UNUSED(selected_item)
auto cache = dynamic_cast<CacheForServiceRoot*>(this); auto cache = dynamic_cast<CacheForServiceRoot*>(this);
@ -719,7 +734,9 @@ bool ServiceRoot::onBeforeSetMessagesRead(RootItem* selected_item, const QList<M
return true; return true;
} }
bool ServiceRoot::onAfterSetMessagesRead(RootItem* selected_item, const QList<Message>& messages, RootItem::ReadStatus read) { bool ServiceRoot::onAfterSetMessagesRead(RootItem* selected_item,
const QList<Message>& messages,
RootItem::ReadStatus read) {
Q_UNUSED(selected_item) Q_UNUSED(selected_item)
Q_UNUSED(messages) Q_UNUSED(messages)
Q_UNUSED(read) Q_UNUSED(read)
@ -808,9 +825,11 @@ bool ServiceRoot::onAfterLabelMessageAssignmentChanged(const QList<Label*>& labe
lbl->updateCounts(true); lbl->updateCounts(true);
}); });
auto list = boolinq::from(labels).select([](Label* lbl) { auto list = boolinq::from(labels)
return static_cast<RootItem*>(lbl); .select([](Label* lbl) {
}).toStdList(); return static_cast<RootItem*>(lbl);
})
.toStdList();
getParentServiceRoot()->itemChanged(FROM_STD_LIST(QList<RootItem*>, list)); getParentServiceRoot()->itemChanged(FROM_STD_LIST(QList<RootItem*>, list));
return true; return true;
@ -888,25 +907,21 @@ ServiceRoot::LabelOperation operator&(ServiceRoot::LabelOperation lhs, ServiceRo
} }
QPair<int, int> ServiceRoot::updateMessages(QList<Message>& messages, Feed* feed, bool force_update) { QPair<int, int> ServiceRoot::updateMessages(QList<Message>& messages, Feed* feed, bool force_update) {
QPair<int, int> updated_messages = { 0, 0 }; QPair<int, int> updated_messages = {0, 0};
if (messages.isEmpty()) { if (messages.isEmpty()) {
qDebugNN << "No messages to be updated/added in DB for feed" qDebugNN << "No messages to be updated/added in DB for feed" << QUOTE_W_SPACE_DOT(feed->customId());
<< QUOTE_W_SPACE_DOT(feed->customId());
return updated_messages; return updated_messages;
} }
QList<RootItem*> items_to_update; QList<RootItem*> items_to_update;
bool is_main_thread = QThread::currentThread() == qApp->thread(); bool is_main_thread = QThread::currentThread() == qApp->thread();
qDebugNN << LOGSEC_CORE qDebugNN << LOGSEC_CORE << "Updating messages in DB. Main thread:" << QUOTE_W_SPACE_DOT(is_main_thread);
<< "Updating messages in DB. Main thread:"
<< QUOTE_W_SPACE_DOT(is_main_thread);
bool ok = false; bool ok = false;
QSqlDatabase database = is_main_thread ? QSqlDatabase database = is_main_thread ? qApp->database()->driver()->connection(metaObject()->className())
qApp->database()->driver()->connection(metaObject()->className()) : : qApp->database()->driver()->connection(QSL("feed_upd"));
qApp->database()->driver()->connection(QSL("feed_upd"));
updated_messages = DatabaseQueries::updateMessages(database, messages, feed, force_update, &ok); updated_messages = DatabaseQueries::updateMessages(database, messages, feed, force_update, &ok);

View file

@ -22,11 +22,11 @@
GreaderNetwork::GreaderNetwork(QObject* parent) GreaderNetwork::GreaderNetwork(QObject* parent)
: QObject(parent), m_root(nullptr), m_service(GreaderServiceRoot::Service::FreshRss), m_username(QString()), : QObject(parent), m_root(nullptr), m_service(GreaderServiceRoot::Service::FreshRss), m_username(QString()),
m_password(QString()), m_baseUrl(QString()), m_batchSize(GREADER_DEFAULT_BATCH_SIZE), m_downloadOnlyUnreadMessages(false), m_password(QString()), m_baseUrl(QString()), m_batchSize(GREADER_DEFAULT_BATCH_SIZE),
m_prefetchedMessages({}), m_prefetchedStatus(Feed::Status::Normal), m_performGlobalFetching(false), m_downloadOnlyUnreadMessages(false), m_prefetchedMessages({}), m_prefetchedStatus(Feed::Status::Normal),
m_intelligentSynchronization(true), m_newerThanFilter(QDate::currentDate().addYears(-1)), m_performGlobalFetching(false), m_intelligentSynchronization(true),
m_oauth(new OAuth2Service(QSL(INO_OAUTH_AUTH_URL), QSL(INO_OAUTH_TOKEN_URL), m_newerThanFilter(QDate::currentDate().addYears(-1)),
{}, {}, QSL(INO_OAUTH_SCOPE), this)) { m_oauth(new OAuth2Service(QSL(INO_OAUTH_AUTH_URL), QSL(INO_OAUTH_TOKEN_URL), {}, {}, QSL(INO_OAUTH_SCOPE), this)) {
initializeOauth(); initializeOauth();
clearCredentials(); clearCredentials();
} }
@ -44,13 +44,15 @@ QNetworkReply::NetworkError GreaderNetwork::editLabels(const QString& state,
return network_err; return network_err;
} }
QStringList trimmed_ids; trimmed_ids.reserve(msg_custom_ids.size()); QStringList trimmed_ids;
trimmed_ids.reserve(msg_custom_ids.size());
for (const QString& id : msg_custom_ids) { for (const QString& id : msg_custom_ids) {
trimmed_ids.append(QSL("i=") + id); trimmed_ids.append(QSL("i=") + id);
} }
QStringList working_subset; working_subset.reserve(std::min(GREADER_API_EDIT_TAG_BATCH, int(trimmed_ids.size()))); QStringList working_subset;
working_subset.reserve(std::min(GREADER_API_EDIT_TAG_BATCH, int(trimmed_ids.size())));
// Now, we perform messages update in batches (max X messages per batch). // Now, we perform messages update in batches (max X messages per batch).
while (!trimmed_ids.isEmpty()) { while (!trimmed_ids.isEmpty()) {
@ -76,18 +78,19 @@ QNetworkReply::NetworkError GreaderNetwork::editLabels(const QString& state,
// We send this batch. // We send this batch.
QByteArray output; QByteArray output;
auto result_edit = NetworkFactory::performNetworkOperation(full_url, auto result_edit =
timeout, NetworkFactory::performNetworkOperation(full_url,
args.toUtf8(), timeout,
output, args.toUtf8(),
QNetworkAccessManager::Operation::PostOperation, output,
{ authHeader(), QNetworkAccessManager::Operation::PostOperation,
{ QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(), {authHeader(),
QSL("application/x-www-form-urlencoded").toLocal8Bit() } }, {QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
false, QSL("application/x-www-form-urlencoded").toLocal8Bit()}},
{}, false,
{}, {},
proxy); {},
proxy);
if (result_edit.m_networkError != QNetworkReply::NetworkError::NoError) { if (result_edit.m_networkError != QNetworkReply::NetworkError::NoError) {
return result_edit.m_networkError; return result_edit.m_networkError;
@ -115,7 +118,7 @@ QVariantHash GreaderNetwork::userInfo(const QNetworkProxy& proxy) {
{}, {},
output, output,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
@ -135,7 +138,8 @@ void GreaderNetwork::clearPrefetchedMessages() {
void GreaderNetwork::prepareFeedFetching(GreaderServiceRoot* root, void GreaderNetwork::prepareFeedFetching(GreaderServiceRoot* root,
const QList<Feed*>& feeds, const QList<Feed*>& feeds,
const QHash<QString, QHash<ServiceRoot::BagOfMessages, QStringList>>& stated_messages, const QHash<QString, QHash<ServiceRoot::BagOfMessages, QStringList>>&
stated_messages,
const QHash<QString, QStringList>& tagged_messages, const QHash<QString, QStringList>& tagged_messages,
const QNetworkProxy& proxy) { const QNetworkProxy& proxy) {
Q_UNUSED(tagged_messages) Q_UNUSED(tagged_messages)
@ -148,9 +152,7 @@ void GreaderNetwork::prepareFeedFetching(GreaderServiceRoot* root,
m_performGlobalFetching = perc_of_fetching > GREADER_GLOBAL_UPDATE_THRES; m_performGlobalFetching = perc_of_fetching > GREADER_GLOBAL_UPDATE_THRES;
qDebugNN << LOGSEC_GREADER qDebugNN << LOGSEC_GREADER << "Percentage of feeds for fetching:" << QUOTE_W_SPACE_DOT(perc_of_fetching * 100.0);
<< "Percentage of feeds for fetching:"
<< QUOTE_W_SPACE_DOT(perc_of_fetching * 100.0);
auto remote_starred_ids_list = itemIds(QSL(GREADER_API_FULL_STATE_IMPORTANT), false, proxy, -1, m_newerThanFilter); auto remote_starred_ids_list = itemIds(QSL(GREADER_API_FULL_STATE_IMPORTANT), false, proxy, -1, m_newerThanFilter);
@ -174,10 +176,12 @@ void GreaderNetwork::prepareFeedFetching(GreaderServiceRoot* root,
if (m_performGlobalFetching) { if (m_performGlobalFetching) {
qWarningNN << LOGSEC_GREADER << "Performing global contents fetching."; qWarningNN << LOGSEC_GREADER << "Performing global contents fetching.";
QStringList remote_all_ids_list = m_downloadOnlyUnreadMessages QStringList remote_all_ids_list =
? QStringList() m_downloadOnlyUnreadMessages
: itemIds(QSL(GREADER_API_FULL_STATE_READING_LIST), false, proxy, -1, m_newerThanFilter); ? QStringList()
QStringList remote_unread_ids_list = itemIds(QSL(GREADER_API_FULL_STATE_READING_LIST), true, proxy, -1, m_newerThanFilter); : itemIds(QSL(GREADER_API_FULL_STATE_READING_LIST), false, proxy, -1, m_newerThanFilter);
QStringList remote_unread_ids_list =
itemIds(QSL(GREADER_API_FULL_STATE_READING_LIST), true, proxy, -1, m_newerThanFilter);
for (int i = 0; i < remote_all_ids_list.size(); i++) { for (int i = 0; i < remote_all_ids_list.size(); i++) {
remote_all_ids_list.replace(i, convertShortStreamIdToLongStreamId(remote_all_ids_list.at(i))); remote_all_ids_list.replace(i, convertShortStreamIdToLongStreamId(remote_all_ids_list.at(i)));
@ -238,29 +242,24 @@ void GreaderNetwork::prepareFeedFetching(GreaderServiceRoot* root,
catch (const FeedFetchException& fex) { catch (const FeedFetchException& fex) {
m_prefetchedStatus = fex.feedStatus(); m_prefetchedStatus = fex.feedStatus();
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for common stream:" << QUOTE_W_SPACE_DOT(fex.message());
<< "Failed to fetch item IDs for common stream:"
<< QUOTE_W_SPACE_DOT(fex.message());
} }
catch (const NetworkException& nex) { catch (const NetworkException& nex) {
m_prefetchedStatus = Feed::Status::NetworkError; m_prefetchedStatus = Feed::Status::NetworkError;
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for common stream:" << QUOTE_W_SPACE_DOT(nex.message());
<< "Failed to fetch item IDs for common stream:"
<< QUOTE_W_SPACE_DOT(nex.message());
} }
catch (const ApplicationException& aex) { catch (const ApplicationException& aex) {
m_prefetchedStatus = Feed::Status::OtherError; m_prefetchedStatus = Feed::Status::OtherError;
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for common stream:" << QUOTE_W_SPACE_DOT(aex.message());
<< "Failed to fetch item IDs for common stream:"
<< QUOTE_W_SPACE_DOT(aex.message());
} }
} }
QList<Message> GreaderNetwork::getMessagesIntelligently(ServiceRoot* root, QList<Message> GreaderNetwork::getMessagesIntelligently(ServiceRoot* root,
const QString& stream_id, const QString& stream_id,
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages, const QHash<ServiceRoot::BagOfMessages, QStringList>&
stated_messages,
const QHash<QString, QStringList>& tagged_messages, const QHash<QString, QStringList>& tagged_messages,
Feed::Status& error, Feed::Status& error,
const QNetworkProxy& proxy) { const QNetworkProxy& proxy) {
@ -281,35 +280,28 @@ QList<Message> GreaderNetwork::getMessagesIntelligently(ServiceRoot* root,
QStringList remote_all_ids_list, remote_unread_ids_list; QStringList remote_all_ids_list, remote_unread_ids_list;
try { try {
remote_all_ids_list = m_downloadOnlyUnreadMessages remote_all_ids_list =
? QStringList() m_downloadOnlyUnreadMessages ? QStringList() : itemIds(stream_id, false, proxy, -1, m_newerThanFilter);
: itemIds(stream_id, false, proxy, -1, m_newerThanFilter);
remote_unread_ids_list = itemIds(stream_id, true, proxy, -1, m_newerThanFilter); remote_unread_ids_list = itemIds(stream_id, true, proxy, -1, m_newerThanFilter);
} }
catch (const FeedFetchException& fex) { catch (const FeedFetchException& fex) {
error = fex.feedStatus(); error = fex.feedStatus();
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for specific stream:" << QUOTE_W_SPACE_DOT(fex.message());
<< "Failed to fetch item IDs for specific stream:"
<< QUOTE_W_SPACE_DOT(fex.message());
return msgs; return msgs;
} }
catch (const NetworkException& nex) { catch (const NetworkException& nex) {
error = Feed::Status::NetworkError; error = Feed::Status::NetworkError;
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for specific stream:" << QUOTE_W_SPACE_DOT(nex.message());
<< "Failed to fetch item IDs for specific stream:"
<< QUOTE_W_SPACE_DOT(nex.message());
return msgs; return msgs;
} }
catch (const ApplicationException& aex) { catch (const ApplicationException& aex) {
error = Feed::Status::OtherError; error = Feed::Status::OtherError;
qCriticalNN << LOGSEC_CORE qCriticalNN << LOGSEC_CORE << "Failed to fetch item IDs for specific stream:" << QUOTE_W_SPACE_DOT(aex.message());
<< "Failed to fetch item IDs for specific stream:"
<< QUOTE_W_SPACE_DOT(aex.message());
return msgs; return msgs;
} }
@ -372,10 +364,9 @@ QList<Message> GreaderNetwork::getMessagesIntelligently(ServiceRoot* root,
for (int i = 0; i < m_prefetchedMessages.size(); i++) { for (int i = 0; i < m_prefetchedMessages.size(); i++) {
auto prefetched_msg = m_prefetchedMessages.at(i); auto prefetched_msg = m_prefetchedMessages.at(i);
if (prefetched_msg.m_feedId == stream_id && if (prefetched_msg.m_feedId == stream_id && !boolinq::from(msgs).any([&prefetched_msg](const Message& ms) {
!boolinq::from(msgs).any([&prefetched_msg](const Message& ms) { return ms.m_customId == prefetched_msg.m_customId;
return ms.m_customId == prefetched_msg.m_customId; })) {
})) {
msgs.append(prefetched_msg); msgs.append(prefetched_msg);
m_prefetchedMessages.removeAt(i--); m_prefetchedMessages.removeAt(i--);
} }
@ -399,8 +390,11 @@ QNetworkReply::NetworkError GreaderNetwork::markMessagesStarred(RootItem::Import
proxy); proxy);
} }
QStringList GreaderNetwork::itemIds(const QString& stream_id, bool unread_only, const QNetworkProxy& proxy, QStringList GreaderNetwork::itemIds(const QString& stream_id,
int max_count, QDate newer_than) { bool unread_only,
const QNetworkProxy& proxy,
int max_count,
QDate newer_than) {
if (!ensureLogin(proxy)) { if (!ensureLogin(proxy)) {
throw FeedFetchException(Feed::Status::AuthError, tr("login failed")); throw FeedFetchException(Feed::Status::AuthError, tr("login failed"));
} }
@ -409,12 +403,10 @@ QStringList GreaderNetwork::itemIds(const QString& stream_id, bool unread_only,
QStringList ids; QStringList ids;
do { do {
QString full_url = generateFullUrl(Operations::ItemIds).arg(m_service == GreaderServiceRoot::Service::TheOldReader QString full_url =
? stream_id generateFullUrl(Operations::ItemIds)
: QUrl::toPercentEncoding(stream_id), .arg(m_service == GreaderServiceRoot::Service::TheOldReader ? stream_id : QUrl::toPercentEncoding(stream_id),
QString::number(max_count <= 0 QString::number(max_count <= 0 ? GREADET_API_ITEM_IDS_MAX : max_count));
? GREADET_API_ITEM_IDS_MAX
: max_count));
auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
if (unread_only) { if (unread_only) {
@ -430,9 +422,10 @@ QStringList GreaderNetwork::itemIds(const QString& stream_id, bool unread_only,
#if QT_VERSION < 0x050E00 // Qt < 5.14.0 #if QT_VERSION < 0x050E00 // Qt < 5.14.0
QDateTime(newer_than) QDateTime(newer_than)
#else #else
newer_than.startOfDay() newer_than
.startOfDay()
#endif #endif
.toSecsSinceEpoch()); .toSecsSinceEpoch());
} }
QByteArray output_stream; QByteArray output_stream;
@ -441,18 +434,15 @@ QStringList GreaderNetwork::itemIds(const QString& stream_id, bool unread_only,
{}, {},
output_stream, output_stream,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
proxy); proxy);
if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) { if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) {
qCriticalNN << LOGSEC_GREADER qCriticalNN << LOGSEC_GREADER << "Cannot download item IDs for " << QUOTE_NO_SPACE(stream_id)
<< "Cannot download item IDs for " << ", network error:" << QUOTE_W_SPACE_DOT(result_stream.m_networkError);
<< QUOTE_NO_SPACE(stream_id)
<< ", network error:"
<< QUOTE_W_SPACE_DOT(result_stream.m_networkError);
throw NetworkException(result_stream.m_networkError); throw NetworkException(result_stream.m_networkError);
} }
else { else {
@ -464,8 +454,10 @@ QStringList GreaderNetwork::itemIds(const QString& stream_id, bool unread_only,
return ids; return ids;
} }
QList<Message> GreaderNetwork::itemContents(ServiceRoot* root, const QList<QString>& stream_ids, QList<Message> GreaderNetwork::itemContents(ServiceRoot* root,
Feed::Status& error, const QNetworkProxy& proxy) { const QList<QString>& stream_ids,
Feed::Status& error,
const QNetworkProxy& proxy) {
QString continuation; QString continuation;
if (!ensureLogin(proxy)) { if (!ensureLogin(proxy)) {
@ -477,12 +469,11 @@ QList<Message> GreaderNetwork::itemContents(ServiceRoot* root, const QList<QStri
QList<QString> my_stream_ids(stream_ids); QList<QString> my_stream_ids(stream_ids);
while (!my_stream_ids.isEmpty()) { while (!my_stream_ids.isEmpty()) {
int batch = (m_service == GreaderServiceRoot::Service::TheOldReader || int batch =
m_service == GreaderServiceRoot::Service::FreshRss) (m_service == GreaderServiceRoot::Service::TheOldReader || m_service == GreaderServiceRoot::Service::FreshRss)
? TOR_ITEM_CONTENTS_BATCH ? TOR_ITEM_CONTENTS_BATCH
: (m_service == GreaderServiceRoot::Service::Inoreader : (m_service == GreaderServiceRoot::Service::Inoreader ? INO_ITEM_CONTENTS_BATCH
? INO_ITEM_CONTENTS_BATCH : GREADER_API_ITEM_CONTENTS_BATCH);
: GREADER_API_ITEM_CONTENTS_BATCH);
QList<QString> batch_ids = my_stream_ids.mid(0, batch); QList<QString> batch_ids = my_stream_ids.mid(0, batch);
my_stream_ids = my_stream_ids.mid(batch); my_stream_ids = my_stream_ids.mid(batch);
@ -495,32 +486,32 @@ QList<Message> GreaderNetwork::itemContents(ServiceRoot* root, const QList<QStri
full_url += QSL("&c=%1").arg(continuation); full_url += QSL("&c=%1").arg(continuation);
} }
std::list inp = boolinq::from(batch_ids).select([this](const QString& id) { std::list inp = boolinq::from(batch_ids)
return QSL("i=%1").arg(m_service == GreaderServiceRoot::Service::TheOldReader .select([this](const QString& id) {
? id return QSL("i=%1").arg(m_service == GreaderServiceRoot::Service::TheOldReader
: QUrl::toPercentEncoding(id)); ? id
}).toStdList(); : QUrl::toPercentEncoding(id));
})
.toStdList();
QByteArray input = FROM_STD_LIST(QStringList, inp).join(QSL("&")).toUtf8(); QByteArray input = FROM_STD_LIST(QStringList, inp).join(QSL("&")).toUtf8();
QByteArray output_stream; QByteArray output_stream;
auto result_stream = NetworkFactory::performNetworkOperation(full_url, auto result_stream =
timeout, NetworkFactory::performNetworkOperation(full_url,
input, timeout,
output_stream, input,
QNetworkAccessManager::Operation::PostOperation, output_stream,
{ authHeader(), QNetworkAccessManager::Operation::PostOperation,
{ QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(), {authHeader(),
QSL("application/x-www-form-urlencoded").toLocal8Bit() } }, {QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
false, QSL("application/x-www-form-urlencoded").toLocal8Bit()}},
{}, false,
{}, {},
proxy); {},
proxy);
if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) { if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) {
qCriticalNN << LOGSEC_GREADER qCriticalNN << LOGSEC_GREADER << "Cannot download messages for " << batch_ids
<< "Cannot download messages for " << ", network error:" << QUOTE_W_SPACE_DOT(result_stream.m_networkError);
<< batch_ids
<< ", network error:"
<< QUOTE_W_SPACE_DOT(result_stream.m_networkError);
error = Feed::Status::NetworkError; error = Feed::Status::NetworkError;
return {}; return {};
} }
@ -535,8 +526,10 @@ QList<Message> GreaderNetwork::itemContents(ServiceRoot* root, const QList<QStri
return msgs; return msgs;
} }
QList<Message> GreaderNetwork::streamContents(ServiceRoot* root, const QString& stream_id, QList<Message> GreaderNetwork::streamContents(ServiceRoot* root,
Feed::Status& error, const QNetworkProxy& proxy) { const QString& stream_id,
Feed::Status& error,
const QNetworkProxy& proxy) {
QString continuation; QString continuation;
if (!ensureLogin(proxy)) { if (!ensureLogin(proxy)) {
@ -545,15 +538,15 @@ QList<Message> GreaderNetwork::streamContents(ServiceRoot* root, const QString&
} }
QList<Message> msgs; QList<Message> msgs;
int target_msgs_size = batchSize() <= 0 ? 2000000: batchSize(); int target_msgs_size = batchSize() <= 0 ? 2000000 : batchSize();
do { do {
QString full_url = generateFullUrl(Operations::StreamContents).arg( QString full_url = generateFullUrl(Operations::StreamContents)
(m_service == GreaderServiceRoot::Service::TheOldReader || .arg((m_service == GreaderServiceRoot::Service::TheOldReader ||
m_service == GreaderServiceRoot::Service::FreshRss) m_service == GreaderServiceRoot::Service::FreshRss)
? stream_id ? stream_id
: QUrl::toPercentEncoding(stream_id), : QUrl::toPercentEncoding(stream_id),
QString::number(target_msgs_size)); QString::number(target_msgs_size));
auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
if (downloadOnlyUnreadMessages()) { if (downloadOnlyUnreadMessages()) {
@ -569,9 +562,10 @@ QList<Message> GreaderNetwork::streamContents(ServiceRoot* root, const QString&
#if QT_VERSION < 0x050E00 // Qt < 5.14.0 #if QT_VERSION < 0x050E00 // Qt < 5.14.0
QDateTime(m_newerThanFilter) QDateTime(m_newerThanFilter)
#else #else
m_newerThanFilter.startOfDay() m_newerThanFilter
.startOfDay()
#endif #endif
.toSecsSinceEpoch()); .toSecsSinceEpoch());
} }
QByteArray output_stream; QByteArray output_stream;
@ -580,18 +574,15 @@ QList<Message> GreaderNetwork::streamContents(ServiceRoot* root, const QString&
{}, {},
output_stream, output_stream,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
proxy); proxy);
if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) { if (result_stream.m_networkError != QNetworkReply::NetworkError::NoError) {
qCriticalNN << LOGSEC_GREADER qCriticalNN << LOGSEC_GREADER << "Cannot download messages for " << QUOTE_NO_SPACE(stream_id)
<< "Cannot download messages for " << ", network error:" << QUOTE_W_SPACE_DOT(result_stream.m_networkError);
<< QUOTE_NO_SPACE(stream_id)
<< ", network error:"
<< QUOTE_W_SPACE_DOT(result_stream.m_networkError);
error = Feed::Status::NetworkError; error = Feed::Status::NetworkError;
return {}; return {};
} }
@ -610,6 +601,7 @@ RootItem* GreaderNetwork::categoriesFeedsLabelsTree(bool obtain_icons, const QNe
auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
if (!ensureLogin(proxy)) { if (!ensureLogin(proxy)) {
qCriticalNN << LOGSEC_GREADER << "Cannot get feed tree, not logged-in.";
return nullptr; return nullptr;
} }
@ -619,7 +611,7 @@ RootItem* GreaderNetwork::categoriesFeedsLabelsTree(bool obtain_icons, const QNe
{}, {},
output_labels, output_labels,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
@ -636,28 +628,31 @@ RootItem* GreaderNetwork::categoriesFeedsLabelsTree(bool obtain_icons, const QNe
{}, {},
output_feeds, output_feeds,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
proxy); proxy);
if (result_feeds.m_networkError != QNetworkReply::NetworkError::NoError) { if (result_feeds.m_networkError != QNetworkReply::NetworkError::NoError) {
qCriticalNN << LOGSEC_GREADER
<< "Cannot get feed tree, network error:" << QUOTE_W_SPACE_DOT(result_feeds.m_networkError);
return nullptr; return nullptr;
} }
return decodeTagsSubscriptions(output_labels, output_feeds, obtain_icons, proxy); return decodeTagsSubscriptions(output_labels, output_feeds, obtain_icons, proxy);
} }
RootItem* GreaderNetwork::decodeTagsSubscriptions(const QString& categories, const QString& feeds, RootItem* GreaderNetwork::decodeTagsSubscriptions(const QString& categories,
bool obtain_icons, const QNetworkProxy& proxy) { const QString& feeds,
bool obtain_icons,
const QNetworkProxy& proxy) {
auto* parent = new RootItem(); auto* parent = new RootItem();
QMap<QString, RootItem*> cats; QMap<QString, RootItem*> cats;
QList<RootItem*> lbls; QList<RootItem*> lbls;
QJsonArray json; QJsonArray json;
if (m_service == GreaderServiceRoot::Service::Bazqux || if (m_service == GreaderServiceRoot::Service::Bazqux || m_service == GreaderServiceRoot::Service::Reedah ||
m_service == GreaderServiceRoot::Service::Reedah ||
m_service == GreaderServiceRoot::Service::Inoreader) { m_service == GreaderServiceRoot::Service::Inoreader) {
// We need to process subscription list first and extract categories. // We need to process subscription list first and extract categories.
json = QJsonDocument::fromJson(feeds.toUtf8()).object()[QSL("subscriptions")].toArray(); json = QJsonDocument::fromJson(feeds.toUtf8()).object()[QSL("subscriptions")].toArray();
@ -691,8 +686,7 @@ RootItem* GreaderNetwork::decodeTagsSubscriptions(const QString& categories, con
QString label_id = label[QSL("id")].toString(); QString label_id = label[QSL("id")].toString();
if ((label[QSL("type")].toString() == QSL("folder") || if ((label[QSL("type")].toString() == QSL("folder") ||
(m_service == GreaderServiceRoot::Service::TheOldReader && (m_service == GreaderServiceRoot::Service::TheOldReader && label_id.contains(QSL("/label/"))))) {
label_id.contains(QSL("/label/"))))) {
// We have category (not "state" or "tag" or "label"). // We have category (not "state" or "tag" or "label").
auto* category = new Category(); auto* category = new Category();
@ -711,8 +705,7 @@ RootItem* GreaderNetwork::decodeTagsSubscriptions(const QString& categories, con
new_lbl->setCustomId(label_id); new_lbl->setCustomId(label_id);
lbls.append(new_lbl); lbls.append(new_lbl);
} }
else if ((m_service == GreaderServiceRoot::Service::Bazqux || else if ((m_service == GreaderServiceRoot::Service::Bazqux || m_service == GreaderServiceRoot::Service::Reedah ||
m_service == GreaderServiceRoot::Service::Reedah ||
m_service == GreaderServiceRoot::Service::Inoreader) && m_service == GreaderServiceRoot::Service::Inoreader) &&
label_id.contains(QSL("/label/"))) { label_id.contains(QSL("/label/"))) {
if (!cats.contains(label_id)) { if (!cats.contains(label_id)) {
@ -775,18 +768,14 @@ RootItem* GreaderNetwork::decodeTagsSubscriptions(const QString& categories, con
} }
} }
icon_urls.append({ icon_url, true }); icon_urls.append({icon_url, true});
} }
icon_urls.append({ url, false }); icon_urls.append({url, false});
QIcon icon; QIcon icon;
if (NetworkFactory::downloadIcon(icon_urls, if (NetworkFactory::downloadIcon(icon_urls, 1000, icon, {}, proxy) == QNetworkReply::NetworkError::NoError) {
1000,
icon,
{},
proxy) == QNetworkReply::NetworkError::NoError) {
feed->setIcon(icon); feed->setIcon(icon);
} }
} }
@ -808,21 +797,22 @@ QNetworkReply::NetworkError GreaderNetwork::clientLogin(const QNetworkProxy& pro
QString full_url = generateFullUrl(Operations::ClientLogin); QString full_url = generateFullUrl(Operations::ClientLogin);
auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); auto timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QByteArray output; QByteArray output;
QByteArray args = QSL("Email=%1&Passwd=%2").arg(QString::fromLocal8Bit(QUrl::toPercentEncoding(username())), QByteArray args = QSL("Email=%1&Passwd=%2")
QString::fromLocal8Bit(QUrl::toPercentEncoding(password()))).toLocal8Bit(); .arg(QString::fromLocal8Bit(QUrl::toPercentEncoding(username())),
auto network_result = NetworkFactory::performNetworkOperation(full_url, QString::fromLocal8Bit(QUrl::toPercentEncoding(password())))
timeout, .toLocal8Bit();
args, auto network_result =
output, NetworkFactory::performNetworkOperation(full_url,
QNetworkAccessManager::Operation::PostOperation, timeout,
{ { args,
QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(), output,
QSL("application/x-www-form-urlencoded").toLocal8Bit() QNetworkAccessManager::Operation::PostOperation,
} }, {{QSL(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
false, QSL("application/x-www-form-urlencoded").toLocal8Bit()}},
{}, false,
{}, {},
proxy); {},
proxy);
if (network_result.m_networkError == QNetworkReply::NetworkError::NoError) { if (network_result.m_networkError == QNetworkReply::NetworkError::NoError) {
// Save credentials. // Save credentials.
@ -867,7 +857,7 @@ QNetworkReply::NetworkError GreaderNetwork::clientLogin(const QNetworkProxy& pro
args, args,
output, output,
QNetworkAccessManager::Operation::GetOperation, QNetworkAccessManager::Operation::GetOperation,
{ authHeader() }, {authHeader()},
false, false,
{}, {},
{}, {},
@ -919,12 +909,10 @@ void GreaderNetwork::setBaseUrl(const QString& base_url) {
QPair<QByteArray, QByteArray> GreaderNetwork::authHeader() const { QPair<QByteArray, QByteArray> GreaderNetwork::authHeader() const {
if (m_service == GreaderServiceRoot::Service::Inoreader) { if (m_service == GreaderServiceRoot::Service::Inoreader) {
return { QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), return {QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), m_oauth->bearer().toLocal8Bit()};
m_oauth->bearer().toLocal8Bit() };
} }
else { else {
return { QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), return {QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), QSL("GoogleLogin auth=%1").arg(m_authAuth).toLocal8Bit()};
QSL("GoogleLogin auth=%1").arg(m_authAuth).toLocal8Bit() };
} }
} }
@ -942,8 +930,7 @@ bool GreaderNetwork::ensureLogin(const QNetworkProxy& proxy, QNetworkReply::Netw
if (login != QNetworkReply::NetworkError::NoError) { if (login != QNetworkReply::NetworkError::NoError) {
qCriticalNN << LOGSEC_GREADER qCriticalNN << LOGSEC_GREADER
<< "Login failed with error:" << "Login failed with error:" << QUOTE_W_SPACE_DOT(NetworkFactory::networkErrorText(login));
<< QUOTE_W_SPACE_DOT(NetworkFactory::networkErrorText(login));
return false; return false;
} }
else { else {
@ -955,8 +942,9 @@ bool GreaderNetwork::ensureLogin(const QNetworkProxy& proxy, QNetworkReply::Netw
} }
QString GreaderNetwork::convertLongStreamIdToShortStreamId(const QString& stream_id) const { QString GreaderNetwork::convertLongStreamIdToShortStreamId(const QString& stream_id) const {
return QString::number(QString(stream_id).replace(QSL("tag:google.com,2005:reader/item/"), return QString::number(QString(stream_id)
QString()).toULongLong(nullptr, 16)); .replace(QSL("tag:google.com,2005:reader/item/"), QString())
.toULongLong(nullptr, 16));
} }
QString GreaderNetwork::convertShortStreamIdToLongStreamId(const QString& stream_id) const { QString GreaderNetwork::convertShortStreamIdToLongStreamId(const QString& stream_id) const {
@ -968,10 +956,7 @@ QString GreaderNetwork::convertShortStreamIdToLongStreamId(const QString& stream
return QSL("tag:google.com,2005:reader/item/%1").arg(stream_id); return QSL("tag:google.com,2005:reader/item/%1").arg(stream_id);
} }
else { else {
return QSL("tag:google.com,2005:reader/item/%1").arg(stream_id.toULongLong(), return QSL("tag:google.com,2005:reader/item/%1").arg(stream_id.toULongLong(), 16, 16, QL1C('0'));
16,
16,
QL1C('0'));
} }
} }
@ -1064,9 +1049,8 @@ QList<Message> GreaderNetwork::decodeStreamContents(ServiceRoot* root,
message.m_contents = message_obj[QSL("summary")].toObject()[QSL("content")].toString(); message.m_contents = message_obj[QSL("summary")].toObject()[QSL("content")].toString();
message.m_rawContents = QJsonDocument(message_obj).toJson(QJsonDocument::JsonFormat::Compact); message.m_rawContents = QJsonDocument(message_obj).toJson(QJsonDocument::JsonFormat::Compact);
message.m_feedId = stream_id.isEmpty() message.m_feedId =
? message_obj[QSL("origin")].toObject()[QSL("streamId")].toString() stream_id.isEmpty() ? message_obj[QSL("origin")].toObject()[QSL("streamId")].toString() : stream_id;
: stream_id;
if (message.m_title.isEmpty()) { if (message.m_title.isEmpty()) {
message.m_title = message.m_url; message.m_title = message.m_url;
@ -1091,9 +1075,7 @@ void GreaderNetwork::clearCredentials() {
} }
QString GreaderNetwork::sanitizedBaseUrl() const { QString GreaderNetwork::sanitizedBaseUrl() const {
QString base_url = m_service == GreaderServiceRoot::Service::Inoreader QString base_url = m_service == GreaderServiceRoot::Service::Inoreader ? QSL(GREADER_URL_INOREADER) : m_baseUrl;
? QSL(GREADER_URL_INOREADER)
: m_baseUrl;
if (!base_url.endsWith('/')) { if (!base_url.endsWith('/')) {
base_url = base_url + QL1C('/'); base_url = base_url + QL1C('/');
@ -1148,29 +1130,27 @@ QString GreaderNetwork::generateFullUrl(GreaderNetwork::Operations operation) co
void GreaderNetwork::onTokensError(const QString& error, const QString& error_description) { void GreaderNetwork::onTokensError(const QString& error, const QString& error_description) {
Q_UNUSED(error) Q_UNUSED(error)
qApp->showGuiMessage(Notification::Event::LoginFailure, { qApp->showGuiMessage(Notification::Event::LoginFailure,
tr("Inoreader: authentication error"), {tr("Inoreader: authentication error"),
tr("Click this to login again. Error is: '%1'").arg(error_description), tr("Click this to login again. Error is: '%1'").arg(error_description),
QSystemTrayIcon::MessageIcon::Critical }, QSystemTrayIcon::MessageIcon::Critical},
{}, { {},
tr("Login"), {tr("Login"), [this]() {
[this]() { m_oauth->setAccessToken(QString());
m_oauth->setAccessToken(QString()); m_oauth->setRefreshToken(QString());
m_oauth->setRefreshToken(QString()); m_oauth->login();
m_oauth->login(); }});
} });
} }
void GreaderNetwork::onAuthFailed() { void GreaderNetwork::onAuthFailed() {
qApp->showGuiMessage(Notification::Event::LoginFailure, { qApp->showGuiMessage(Notification::Event::LoginFailure,
tr("Inoreader: authorization denied"), {tr("Inoreader: authorization denied"),
tr("Click this to login again."), tr("Click this to login again."),
QSystemTrayIcon::MessageIcon::Critical }, QSystemTrayIcon::MessageIcon::Critical},
{}, { {},
tr("Login"), {tr("Login"), [this]() {
[this]() { m_oauth->login();
m_oauth->login(); }});
} });
} }
void GreaderNetwork::initializeOauth() { void GreaderNetwork::initializeOauth() {
@ -1179,23 +1159,23 @@ void GreaderNetwork::initializeOauth() {
m_oauth->setClientSecretSecret(TextFactory::decrypt(QSL(INOREADER_CLIENT_SECRET), OAUTH_DECRYPTION_KEY)); m_oauth->setClientSecretSecret(TextFactory::decrypt(QSL(INOREADER_CLIENT_SECRET), OAUTH_DECRYPTION_KEY));
#endif #endif
m_oauth->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + m_oauth->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(INO_OAUTH_REDIRECT_URI_PORT), false);
QL1C(':') +
QString::number(INO_OAUTH_REDIRECT_URI_PORT),
false);
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &GreaderNetwork::onTokensError); connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &GreaderNetwork::onTokensError);
connect(m_oauth, &OAuth2Service::authFailed, this, &GreaderNetwork::onAuthFailed); connect(m_oauth, &OAuth2Service::authFailed, this, &GreaderNetwork::onAuthFailed);
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, QString refresh_token, int expires_in) { connect(m_oauth,
Q_UNUSED(expires_in) &OAuth2Service::tokensRetrieved,
Q_UNUSED(access_token) this,
[this](QString access_token, QString refresh_token, int expires_in) {
Q_UNUSED(expires_in)
Q_UNUSED(access_token)
if (m_root != nullptr && m_root->accountId() > 0 && !refresh_token.isEmpty()) { if (m_root != nullptr && m_root->accountId() > 0 && !refresh_token.isEmpty()) {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_root->accountId()); DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_root->accountId());
} }
}); });
} }
QDate GreaderNetwork::newerThanFilter() const { QDate GreaderNetwork::newerThanFilter() const {