Merge branch 'master' of github.com:martinrotter/rssguard

This commit is contained in:
Martin Rotter 2023-01-17 17:19:58 +01:00
commit b9cbc1ad5b
35 changed files with 319 additions and 326 deletions

View File

@ -58,6 +58,7 @@ cmake_minimum_required(VERSION 3.9.0)
# Global variables describing the project.
string(TIMESTAMP YEAR "%Y")
string(TIMESTAMP DATE "%Y-%m-%d")
set(APP_NAME "RSS Guard")
set(APP_EMAIL "rotter.martinos@gmail.com")
@ -65,7 +66,7 @@ set(APP_AUTHOR "Martin Rotter")
set(APP_COPYRIGHT "\\251 2011-${YEAR} ${APP_AUTHOR}")
set(APP_REVERSE_NAME "io.github.martinrotter.rssguard")
set(APP_DONATE_URL "https://github.com/sponsors/martinrotter")
set(APP_VERSION "4.2.7")
set(APP_VERSION "4.3.0")
set(APP_URL "https://github.com/martinrotter/rssguard")
set(APP_URL_DOCUMENTATION "https://github.com/martinrotter/rssguard/blob/${APP_VERSION}/resources/docs/Documentation.md")

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017-2022 Martin Rotter <rotter.martinos@gmail.com> -->
<!-- Copyright 2017-@YEAR@ Martin Rotter <rotter.martinos@gmail.com> -->
<component type="desktop-application">
<id>@APP_REVERSE_NAME@</id>
<metadata_license>CC0-1.0</metadata_license>
@ -60,7 +60,7 @@
<content_rating type="oars-1.0" />
<content_rating type="oars-1.1" />
<releases>
<release version="4.2.7" date="2023-01-03" />
<release version="@APP_VERSION@" date="@DATE@" />
</releases>
<provides>
<binary>@APP_LOW_NAME@</binary>

View File

@ -51,7 +51,7 @@ else
USE_QT6="ON"
QTPATH="$(pwd)/Qt"
QTVERSION="6.4.1"
QTVERSION="6.4.2"
QTBIN="$QTPATH/$QTVERSION/$QTOS/bin"
pip3 install aqtinstall

View File

@ -20,12 +20,11 @@ $AllProtocols = [System.Net.SecurityProtocolType]'Tls11,Tls12'
$ProgressPreference = 'SilentlyContinue'
# Get and prepare needed dependencies.
if ($use_qt5 -eq "ON") {
$qt_version = "5.15.2"
}
else {
$qt_version = "6.3.2"
$qt_version = "6.4.2"
}
$maria_version = "10.6.11"

View File

@ -1,17 +0,0 @@
#!/bin/sh
changelog_file="resources/text/CHANGELOG"
datestring="$(date +%F)"
versionstring="$(head -n 1 "$changelog_file")"
for appdata_file in resources/desktop/*.metainfo.xml.in; do
appdata_file_n="${appdata_file}.n"
# Set version and date.
cat "$appdata_file" | sed -e "s@release version\=\"[A-Za-z0-9.]*\"@release version\=\"$versionstring\"@g" | sed -e "s@ date\=\"[0-9\-]*\"@ date\=\"$datestring\"@g" > "$appdata_file_n"
mv "$appdata_file_n" "$appdata_file"
git add "$appdata_file"
done
exit 0

View File

@ -16,20 +16,32 @@
#include <QDebug>
#include <QJSEngine>
#include <QMutexLocker>
#include <QString>
#include <QThread>
#include <QtConcurrentMap>
FeedDownloader::FeedDownloader()
: QObject(), m_isCacheSynchronizationRunning(false), m_stopCacheSynchronization(false), m_mutex(new QMutex()),
m_feedsUpdated(0), m_feedsOriginalCount(0) {
: QObject(), m_isCacheSynchronizationRunning(false), m_stopCacheSynchronization(false) {
qRegisterMetaType<FeedDownloadResults>("FeedDownloadResults");
connect(&m_watcherLookup, &QFutureWatcher<FeedUpdateResult>::resultReadyAt, this, [=](int idx) {
FeedUpdateResult res = m_watcherLookup.resultAt(idx);
emit updateProgress(res.feed, m_watcherLookup.progressValue(), m_watcherLookup.progressMaximum());
});
/*
connect(&m_watcherLookup, &QFutureWatcher<FeedUpdateResult>::progressValueChanged, this, [=](int prog) {
//
});
*/
connect(&m_watcherLookup, &QFutureWatcher<FeedUpdateResult>::finished, this, [=]() {
finalizeUpdate();
});
}
FeedDownloader::~FeedDownloader() {
m_mutex->tryLock();
m_mutex->unlock();
delete m_mutex;
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Destroying FeedDownloader instance.";
}
@ -62,36 +74,22 @@ void FeedDownloader::synchronizeAccountCaches(const QList<CacheForServiceRoot*>&
}
void FeedDownloader::updateFeeds(const QList<Feed*>& feeds) {
QMutexLocker locker(m_mutex);
m_erroredAccounts.clear();
m_results.clear();
m_feeds = feeds;
m_feedsOriginalCount = m_feeds.size();
m_feedsUpdated = 0;
const QDateTime update_time = QDateTime::currentDateTimeUtc();
m_feeds.clear();
if (feeds.isEmpty()) {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "No feeds to update in worker thread, aborting update.";
}
else {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Starting feed updates from worker in thread: '" << QThread::currentThreadId()
<< "'.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Starting feed updates from worker in thread"
<< QUOTE_W_SPACE_DOT(QThread::currentThreadId());
// Job starts now.
emit updateStarted();
QSet<CacheForServiceRoot*> caches;
QMultiHash<ServiceRoot*, Feed*> feeds_per_root;
// 1. key - account.
// 2. key - feed custom ID.
// 3. key - msg state.
QHash<ServiceRoot*, QHash<QString, QHash<ServiceRoot::BagOfMessages, QStringList>>> stated_messages;
// 1. key - account.
// 2. key - label custom ID.
QHash<ServiceRoot*, QHash<QString, QStringList>> tagged_messages;
for (auto* fd : feeds) {
CacheForServiceRoot* fd_cache = fd->getParentServiceRoot()->toCache();
@ -104,24 +102,21 @@ void FeedDownloader::updateFeeds(const QList<Feed*>& feeds) {
synchronizeAccountCaches(caches.values(), false);
QHash<ServiceRoot*, ApplicationException> errored_roots;
auto roots = feeds_per_root.uniqueKeys();
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ? qApp->database()->driver()->connection(metaObject()->className())
: qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
for (auto* rt : roots) {
auto fds = feeds_per_root.values(rt);
QHash<QString, QStringList> per_acc_tags;
QHash<QString, QHash<ServiceRoot::BagOfMessages, QStringList>> per_acc_states;
// Obtain lists of local IDs.
if (rt->wantsBaggedIdsOfExistingMessages()) {
// Tagged messages for the account.
tagged_messages.insert(rt, DatabaseQueries::bagsOfMessages(database, rt->labelsNode()->labels()));
QHash<QString, QHash<ServiceRoot::BagOfMessages, QStringList>> per_acc_states;
// Tags per account.
per_acc_tags = DatabaseQueries::bagsOfMessages(database, rt->labelsNode()->labels());
// This account has activated intelligent downloading of messages.
// Prepare bags.
auto fds = feeds_per_root.values(rt);
for (Feed* fd : fds) {
QHash<ServiceRoot::BagOfMessages, QStringList> per_feed_states;
@ -131,40 +126,66 @@ void FeedDownloader::updateFeeds(const QList<Feed*>& feeds) {
DatabaseQueries::bagOfMessages(database, ServiceRoot::BagOfMessages::Unread, fd));
per_feed_states.insert(ServiceRoot::BagOfMessages::Starred,
DatabaseQueries::bagOfMessages(database, ServiceRoot::BagOfMessages::Starred, fd));
per_acc_states.insert(fd->customId(), per_feed_states);
}
stated_messages.insert(rt, per_acc_states);
per_acc_states.insert(fd->customId(), per_feed_states);
FeedUpdateRequest fu;
fu.account = rt;
fu.feed = fd;
fu.stated_messages = per_feed_states;
fu.tagged_messages = per_acc_tags;
m_feeds.append(fu);
}
}
else {
for (Feed* fd : fds) {
FeedUpdateRequest fu;
fu.account = rt;
fu.feed = fd;
m_feeds.append(fu);
}
}
try {
rt->aboutToBeginFeedFetching(feeds_per_root.values(rt), stated_messages.value(rt), tagged_messages.value(rt));
rt->aboutToBeginFeedFetching(fds, per_acc_states, per_acc_tags);
}
catch (const ApplicationException& ex) {
// Common error showed, all feeds from the root are errored now!
errored_roots.insert(rt, ex);
m_erroredAccounts.insert(rt, ex);
}
}
while (!m_feeds.isEmpty()) {
auto n_f = m_feeds.takeFirst();
auto n_r = n_f->getParentServiceRoot();
std::function<FeedUpdateResult(const FeedUpdateRequest&)> func =
[=](const FeedUpdateRequest& fd) -> FeedUpdateResult {
return updateThreadedFeed(fd);
};
if (errored_roots.contains(n_r)) {
// This feed is errored because its account errored when preparing feed update.
ApplicationException root_ex = errored_roots.value(n_r);
m_watcherLookup.setFuture(QtConcurrent::mapped(m_feeds, func));
}
}
skipFeedUpdateWithError(n_r, n_f, root_ex);
}
else {
updateOneFeed(n_r, n_f, stated_messages.value(n_r).value(n_f->customId()), tagged_messages.value(n_r));
}
FeedUpdateResult FeedDownloader::updateThreadedFeed(const FeedUpdateRequest& fd) {
if (m_erroredAccounts.contains(fd.account)) {
// This feed is errored because its account errored when preparing feed update.
ApplicationException root_ex = m_erroredAccounts.value(fd.account);
n_f->setLastUpdated(QDateTime::currentDateTimeUtc());
}
skipFeedUpdateWithError(fd.account, fd.feed, root_ex);
}
else {
updateOneFeed(fd.account, fd.feed, fd.stated_messages, fd.tagged_messages);
}
finalizeUpdate();
fd.feed->setLastUpdated(QDateTime::currentDateTimeUtc());
FeedUpdateResult res;
res.feed = fd.feed;
return res;
}
void FeedDownloader::skipFeedUpdateWithError(ServiceRoot* acc, Feed* feed, const ApplicationException& ex) {
@ -176,38 +197,38 @@ void FeedDownloader::skipFeedUpdateWithError(ServiceRoot* acc, Feed* feed, const
else {
feed->setStatus(Feed::Status::OtherError, ex.message());
}
acc->itemChanged({feed});
emit updateProgress(feed, ++m_feedsUpdated, m_feedsOriginalCount);
}
void FeedDownloader::stopRunningUpdate() {
m_stopCacheSynchronization = true;
m_watcherLookup.cancel();
m_watcherLookup.waitForFinished();
m_feeds.clear();
m_feedsOriginalCount = m_feedsUpdated = 0;
}
void FeedDownloader::updateOneFeed(ServiceRoot* acc,
Feed* feed,
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
const QHash<QString, QStringList>& tagged_messages) {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Downloading new messages for feed ID '" << feed->customId() << "' URL: '"
<< feed->source() << "' title: '" << feed->title() << "' in thread: '" << QThread::currentThreadId() << "'.";
qlonglong thread_id = qlonglong(QThread::currentThreadId());
int acc_id = feed->getParentServiceRoot()->accountId();
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Downloading new messages for feed ID" << QUOTE_W_SPACE(feed->customId())
<< "URL:" << QUOTE_W_SPACE(feed->source()) << "title:" << QUOTE_W_SPACE(feed->title()) << "in thread "
<< QUOTE_W_SPACE_DOT(thread_id);
int acc_id = acc->accountId();
QElapsedTimer tmr;
tmr.start();
try {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ? qApp->database()->driver()->connection(metaObject()->className())
: qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
QList<Message> msgs = feed->getParentServiceRoot()->obtainNewMessages(feed, stated_messages, tagged_messages);
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Downloaded " << msgs.size() << " messages for feed ID '" << feed->customId()
<< "' URL: '" << feed->source() << "' title: '" << feed->title() << "' in thread: '"
<< QThread::currentThreadId() << "'. Operation took " << tmr.nsecsElapsed() / 1000 << " microseconds.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Downloaded" << NONQUOTE_W_SPACE(msgs.size()) << "messages for feed ID"
<< QUOTE_W_SPACE_COMMA(feed->customId()) << "operation took" << NONQUOTE_W_SPACE(tmr.nsecsElapsed() / 1000)
<< "microseconds.";
bool fix_future_datetimes =
qApp->settings()->value(GROUP(Messages), SETTING(Messages::FixupFutureArticleDateTimes)).toBool();
@ -296,15 +317,15 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc,
}
if (!msg_original.m_isRead && msg_tweaked_by_filter->m_isRead) {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID: '" << msg_original.m_customId
<< "' was marked as read by message scripts.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID:" << QUOTE_W_SPACE(msg_original.m_customId)
<< "was marked as read by message scripts.";
read_msgs << *msg_tweaked_by_filter;
}
if (!msg_original.m_isImportant && msg_tweaked_by_filter->m_isImportant) {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID: '" << msg_original.m_customId
<< "' was marked as important by message scripts.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID:" << QUOTE_W_SPACE(msg_original.m_customId)
<< "was marked as important by message scripts.";
important_msgs << *msg_tweaked_by_filter;
}
@ -312,6 +333,8 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc,
// Process changed labels.
for (Label* lbl : qAsConst(msg_original.m_assignedLabels)) {
if (!msg_tweaked_by_filter->m_assignedLabels.contains(lbl)) {
QMutexLocker lck(&m_mutexDb);
// Label is not there anymore, it was deassigned.
lbl->deassignFromMessage(*msg_tweaked_by_filter);
@ -323,6 +346,8 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc,
for (Label* lbl : qAsConst(msg_tweaked_by_filter->m_assignedLabels)) {
if (!msg_original.m_assignedLabels.contains(lbl)) {
QMutexLocker lck(&m_mutexDb);
// Label is in new message, but is not in old message, it
// was newly assigned.
lbl->assignToMessage(*msg_tweaked_by_filter);
@ -371,16 +396,11 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc,
removeDuplicateMessages(msgs);
// Now make sure, that messages are actually stored to SQL in a locked state.
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Saving messages of feed ID '" << feed->customId() << "' URL: '"
<< feed->source() << "' title: '" << feed->title() << "' in thread: '" << QThread::currentThreadId()
<< "'.";
tmr.restart();
auto updated_messages = acc->updateMessages(msgs, feed, false);
auto updated_messages = acc->updateMessages(msgs, feed, false, &m_mutexDb);
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Updating messages in DB took " << tmr.nsecsElapsed() / 1000
<< " microseconds.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Updating messages in DB took" << NONQUOTE_W_SPACE(tmr.nsecsElapsed() / 1000)
<< "microseconds.";
if (feed->status() != Feed::Status::NewMessages) {
feed->setStatus(updated_messages.first > 0 || updated_messages.second > 0 ? Feed::Status::NewMessages
@ -407,18 +427,16 @@ void FeedDownloader::updateOneFeed(ServiceRoot* acc,
feed->setStatus(Feed::Status::OtherError, app_ex.message());
}
feed->getParentServiceRoot()->itemChanged({feed});
m_feedsUpdated++;
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Made progress in feed updates, total feeds count " << m_feedsUpdated << "/"
<< m_feedsOriginalCount << " (id of feed is " << feed->id() << ").";
emit updateProgress(feed, m_feedsUpdated, m_feedsOriginalCount);
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Made progress in feed updates, total feeds count "
<< m_watcherLookup.progressValue() + 1 << "/" << m_feeds.size() << " (id of feed is " << feed->id() << ").";
}
void FeedDownloader::finalizeUpdate() {
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Finished feed updates in thread: '" << QThread::currentThreadId() << "'.";
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Finished feed updates in thread"
<< QUOTE_W_SPACE_DOT(QThread::currentThreadId());
m_results.sort();
m_feeds.clear();
// Update of feeds has finished.
// NOTE: This means that now "update lock" can be unlocked

View File

@ -5,6 +5,7 @@
#include <QObject>
#include <QFutureWatcher>
#include <QPair>
#include "core/message.h"
@ -13,7 +14,6 @@
#include "services/abstract/feed.h"
class MessageFilter;
class QMutex;
// Represents results of batch feed updates.
class FeedDownloadResults {
@ -30,6 +30,17 @@ class FeedDownloadResults {
QList<QPair<Feed*, int>> m_updatedFeeds;
};
struct FeedUpdateRequest {
Feed* feed = nullptr;
ServiceRoot* account = nullptr;
QHash<ServiceRoot::BagOfMessages, QStringList> stated_messages;
QHash<QString, QStringList> tagged_messages;
};
struct FeedUpdateResult {
Feed* feed = nullptr;
};
// This class offers means to "update" feeds and "special" categories.
// NOTE: This class is used within separate thread.
class FeedDownloader : public QObject {
@ -62,14 +73,16 @@ class FeedDownloader : public QObject {
void finalizeUpdate();
void removeDuplicateMessages(QList<Message>& messages);
FeedUpdateResult updateThreadedFeed(const FeedUpdateRequest& fd);
private:
bool m_isCacheSynchronizationRunning;
bool m_stopCacheSynchronization;
QList<Feed*> m_feeds = {};
QMutex* m_mutex;
QMutex m_mutexDb;
QHash<ServiceRoot*, ApplicationException> m_erroredAccounts;
QList<FeedUpdateRequest> m_feeds = {};
QFutureWatcher<FeedUpdateResult> m_watcherLookup;
FeedDownloadResults m_results;
int m_feedsUpdated;
int m_feedsOriginalCount;
};
#endif // FEEDDOWNLOADER_H

View File

@ -11,7 +11,7 @@
DatabaseCleaner::DatabaseCleaner(QObject* parent) : QObject(parent) {}
void DatabaseCleaner::purgeDatabaseData(CleanerOrders which_data) {
qDebugNN << LOGSEC_DB << "Performing database cleanup in thread: '" << QThread::currentThreadId() << "'.";
qDebugNN << LOGSEC_DB << "Performing database cleanup in thread:" << QUOTE_W_SPACE_DOT(QThread::currentThreadId());
// Inform everyone about the start of the process.
emit purgeStarted();

View File

@ -10,9 +10,20 @@
#include <QRegularExpression>
#include <QSqlError>
#include <QSqlQuery>
#include <QThread>
DatabaseDriver::DatabaseDriver(QObject* parent) : QObject(parent) {}
QSqlDatabase DatabaseDriver::threadSafeConnection(const QString& connection_name, DesiredStorageType desired_type) {
qlonglong thread_id = qlonglong(QThread::currentThreadId());
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database =
connection(is_main_thread ? connection_name : QSL("db_connection_%1").arg(thread_id), desired_type);
return database;
}
void DatabaseDriver::updateDatabaseSchema(QSqlQuery& query,
int source_db_schema_version,
const QString& database_name) {

View File

@ -9,25 +9,21 @@
#include <QSqlQuery>
class DatabaseDriver : public QObject {
Q_OBJECT
Q_OBJECT
public:
// Describes available types of database backend.
enum class DriverType {
SQLite,
MySQL
};
enum class DriverType { SQLite, MySQL };
// Describes what type of database user wants.
enum class DesiredStorageType {
StrictlyFileBased,
StrictlyInMemory,
FromSettings
};
enum class DesiredStorageType { StrictlyFileBased, StrictlyInMemory, FromSettings };
explicit DatabaseDriver(QObject* parent = nullptr);
QSqlDatabase threadSafeConnection(const QString& connection_name,
DatabaseDriver::DesiredStorageType desired_type =
DatabaseDriver::DesiredStorageType::FromSettings);
// API.
virtual QString location() const = 0;
virtual QString humanDriverType() const = 0;
@ -43,19 +39,17 @@ class DatabaseDriver : public QObject {
virtual bool finishRestoration() = 0;
virtual qint64 databaseDataSize() = 0;
virtual QSqlDatabase connection(const QString& connection_name,
DatabaseDriver::DesiredStorageType desired_type = DatabaseDriver::DesiredStorageType::FromSettings) = 0;
DatabaseDriver::DesiredStorageType desired_type =
DatabaseDriver::DesiredStorageType::FromSettings) = 0;
protected:
void updateDatabaseSchema(QSqlQuery& query,
int source_db_schema_version,
const QString& database_name = {});
void updateDatabaseSchema(QSqlQuery& query, int source_db_schema_version, const QString& database_name = {});
void setSchemaVersion(QSqlQuery& query, int new_schema_version, bool empty_table);
QStringList prepareScript(const QString& base_sql_folder,
const QString& sql_file,
const QString& database_name = {});
};
#endif // DATABASEDRIVER_H

View File

@ -1080,13 +1080,13 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
QList<Message>& messages,
Feed* feed,
bool force_update,
QMutex* db_mutex,
bool* ok) {
if (messages.isEmpty()) {
*ok = true;
return {0, 0};
}
bool use_transactions = qApp->settings()->value(GROUP(Database), SETTING(Database::UseTransactions)).toBool();
QPair<int, int> updated_messages = {0, 0};
int account_id = feed->getParentServiceRoot()->accountId();
auto feed_custom_id = feed->customId();
@ -1097,8 +1097,6 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
QSqlQuery query_select_with_custom_id_for_feed(db);
QSqlQuery query_select_with_id(db);
QSqlQuery query_update(db);
QSqlQuery query_insert(db);
QSqlQuery query_begin_transaction(db);
// Here we have query which will check for existence of the "same" message in given feed.
// The two message are the "same" if:
@ -1132,14 +1130,6 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
.prepare(QSL("SELECT date_created, is_read, is_important, contents, feed, title, author FROM Messages "
"WHERE id = :id AND account_id = :account_id;"));
// Used to insert new messages.
query_insert.setForwardOnly(true);
query_insert.prepare(QSL("INSERT INTO Messages "
"(feed, title, is_read, is_important, is_deleted, url, author, score, date_created, "
"contents, enclosures, custom_id, custom_hash, account_id) "
"VALUES (:feed, :title, :is_read, :is_important, :is_deleted, :url, :author, :score, "
":date_created, :contents, :enclosures, :custom_id, :custom_hash, :account_id);"));
// Used to update existing messages.
query_update.setForwardOnly(true);
query_update.prepare(QSL("UPDATE Messages "
@ -1148,12 +1138,6 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
"contents = :contents, enclosures = :enclosures, feed = :feed "
"WHERE id = :id;"));
if (use_transactions && !db.transaction()) {
qCriticalNN << LOGSEC_DB << "Transaction start for message downloader failed:"
<< QUOTE_W_SPACE_DOT(query_begin_transaction.lastError().text());
return updated_messages;
}
QVector<Message*> msgs_to_insert;
for (Message& message : messages) {
@ -1166,6 +1150,8 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
QString title_existing_message;
QString author_existing_message;
QMutexLocker lck(db_mutex);
if (message.m_id > 0) {
// We recognize directly existing message.
// NOTE: Particularly for manual message filter execution.
@ -1357,8 +1343,8 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
updated_messages.second++;
}
else if (query_update.lastError().isValid()) {
qWarningNN << LOGSEC_DB
<< "Failed to update message in DB:" << QUOTE_W_SPACE_DOT(query_update.lastError().text());
qCriticalNN << LOGSEC_DB
<< "Failed to update message in DB:" << QUOTE_W_SPACE_DOT(query_update.lastError().text());
}
query_update.finish();
@ -1415,7 +1401,7 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
.replace(QSL(":date_created"), QString::number(msg->m_created.toMSecsSinceEpoch()))
.replace(QSL(":contents"), DatabaseFactory::escapeQuery(unnulifyString(msg->m_contents)))
.replace(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(msg->m_enclosures))
.replace(QSL(":custom_id"), unnulifyString(msg->m_customId))
.replace(QSL(":custom_id"), DatabaseFactory::escapeQuery(unnulifyString(msg->m_customId)))
.replace(QSL(":custom_hash"), unnulifyString(msg->m_customHash))
.replace(QSL(":score"), QString::number(msg->m_score))
.replace(QSL(":account_id"), QString::number(account_id)));
@ -1423,6 +1409,9 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
if (!vals.isEmpty()) {
QString final_bulk = bulk_insert.arg(vals.join(QSL(", ")));
QMutexLocker lck(db_mutex);
auto bulk_query = db.exec(final_bulk);
auto bulk_error = bulk_query.lastError();
@ -1454,39 +1443,30 @@ QPair<int, int> DatabaseQueries::updateMessages(QSqlDatabase db,
for (Message& message : messages) {
if (!message.m_assignedLabels.isEmpty()) {
if (!message.m_customId.isEmpty() || message.m_id > 0) {
QMutexLocker lck(db_mutex);
setLabelsForMessage(db, message.m_assignedLabels, message);
}
else {
qWarningNN << LOGSEC_DB << "Cannot set labels for message" << QUOTE_W_SPACE(message.m_title)
<< "because we don't have ID or custom ID.";
qCriticalNN << LOGSEC_DB << "Cannot set labels for message" << QUOTE_W_SPACE(message.m_title)
<< "because we don't have ID or custom ID.";
}
}
}
// Now, fixup custom IDS for messages which initially did not have them,
// just to keep the data consistent.
QMutexLocker lck(db_mutex);
if (db.exec("UPDATE Messages "
"SET custom_id = id "
"WHERE custom_id IS NULL OR custom_id = '';")
.lastError()
.isValid()) {
qWarningNN << LOGSEC_DB << "Failed to set custom ID for all messages:" << QUOTE_W_SPACE_DOT(db.lastError().text());
qCriticalNN << LOGSEC_DB << "Failed to set custom ID for all messages:" << QUOTE_W_SPACE_DOT(db.lastError().text());
}
if (use_transactions && !db.commit()) {
qCriticalNN << LOGSEC_DB
<< "Transaction commit for message downloader failed:" << QUOTE_W_SPACE_DOT(db.lastError().text());
db.rollback();
if (ok != nullptr) {
*ok = false;
updated_messages = {0, 0};
}
}
else {
if (ok != nullptr) {
*ok = true;
}
if (ok != nullptr) {
*ok = true;
}
return updated_messages;

View File

@ -140,6 +140,7 @@ class DatabaseQueries {
QList<Message>& messages,
Feed* feed,
bool force_update,
QMutex* db_mutex,
bool* ok = nullptr);
static bool deleteAccount(const QSqlDatabase& db, ServiceRoot* account);
static bool deleteAccountData(const QSqlDatabase& db,

View File

@ -317,7 +317,7 @@
#define OS_ID "OpenBSD"
#elif defined(Q_OS_OS2)
#define OS_ID "OS2"
#elif defined(Q_OS_OSX)
#elif defined(Q_OS_MACOS)
#define OS_ID "macOS"
#elif defined(Q_OS_WIN)
#define OS_ID "Windows"

View File

@ -267,6 +267,13 @@ void FormMain::prepareMenus() {
if (QSysInfo::currentCpuArchitecture().contains(QSL("arm"), Qt::CaseSensitivity::CaseInsensitive)) {
qWarningNN << LOGSEC_GUI << "Disabling native menu bar.";
m_ui->m_menuBar->setNativeMenuBar(false);
#if defined(Q_OS_MACOS)
// This works around a macOS-only Qt crash.
// QTBUG: https://bugreports.qt.io/browse/QTBUG-102107
// TODO: Remove this workaround once the upstream bug gets addressed.
m_ui->m_menuBar->setCornerWidget(nullptr);
#endif
}
}

View File

@ -448,7 +448,7 @@ void FormMessageFiltersManager::processCheckedFeeds() {
}
// Update messages in DB and reload selection.
it->getParentServiceRoot()->updateMessages(msgs, it->toFeed(), true);
it->getParentServiceRoot()->updateMessages(msgs, it->toFeed(), true, nullptr);
displayMessagesOfFeed();
}
}

View File

@ -12,54 +12,70 @@ SettingsDatabase::SettingsDatabase(Settings* settings, QWidget* parent)
: SettingsPanel(settings, parent), m_ui(new Ui::SettingsDatabase) {
m_ui->setupUi(this);
m_ui->m_lblDataStorageWarning->setHelpText(tr("Note that switching to another data storage type will "
"NOT copy existing your data from currently active data "
"storage to newly selected one."),
true);
m_ui->m_lblMysqlInfo->setHelpText(tr("Note that speed of used MySQL server and latency of used connection "
"medium HEAVILY influences the final performance of this application. "
"Using slow database connections leads to bad performance when browsing "
"feeds or messages."),
false);
m_ui->m_lblSqliteInMemoryWarnings->setHelpText(tr("Usage of in-memory working database has several advantages "
"and pitfalls. Make sure that you are familiar with these "
"before you turn this feature on.\n"
"\n"
"Advantages:\n"
" • higher speed for feed/message manipulations "
"(especially with thousands of messages displayed),\n"
" • whole database stored in RAM, thus your hard drive can "
"rest more.\n"
"\n"
"Disadvantages:\n"
" • if application crashes, your changes from last session are lost,\n"
" • application startup and shutdown can take little longer "
"(max. 2 seconds).\n"
"\n"
"Authors of this application are NOT responsible for lost data."),
true);
m_ui->m_lblSqliteInMemoryWarnings
->setHelpText(tr("Usage of in-memory working database has several advantages "
"and pitfalls. Make sure that you are familiar with these "
"before you turn this feature on.\n"
"\n"
"Advantages:\n"
" • higher speed for feed/message manipulations "
"(especially with thousands of messages displayed),\n"
" • whole database stored in RAM, thus your hard drive can "
"rest more.\n"
"\n"
"Disadvantages:\n"
" • if application crashes, your changes from last session are lost,\n"
" • application startup and shutdown can take little longer "
"(max. 2 seconds).\n"
"\n"
"Authors of this application are NOT responsible for lost data."),
true);
m_ui->m_txtMysqlPassword->lineEdit()->setPasswordMode(true);
connect(m_ui->m_cmbDatabaseDriver, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
connect(m_ui->m_cmbDatabaseDriver,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&SettingsDatabase::dirtifySettings);
connect(m_ui->m_checkSqliteUseInMemoryDatabase, &QCheckBox::toggled, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_txtMysqlDatabase->lineEdit(), &QLineEdit::textChanged, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_txtMysqlHostname->lineEdit(), &QLineEdit::textChanged, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_txtMysqlPassword->lineEdit(), &QLineEdit::textChanged, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_checkUseTransactions, &QCheckBox::toggled, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_txtMysqlUsername->lineEdit(), &QLineEdit::textChanged, this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_spinMysqlPort, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &SettingsDatabase::dirtifySettings);
connect(m_ui->m_cmbDatabaseDriver, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
connect(m_ui->m_spinMysqlPort,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this,
&SettingsDatabase::dirtifySettings);
connect(m_ui->m_cmbDatabaseDriver,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&SettingsDatabase::selectSqlBackend);
connect(m_ui->m_txtMysqlUsername->lineEdit(), &BaseLineEdit::textChanged, this, &SettingsDatabase::onMysqlUsernameChanged);
connect(m_ui->m_txtMysqlHostname->lineEdit(), &BaseLineEdit::textChanged, this, &SettingsDatabase::onMysqlHostnameChanged);
connect(m_ui->m_txtMysqlPassword->lineEdit(), &BaseLineEdit::textChanged, this, &SettingsDatabase::onMysqlPasswordChanged);
connect(m_ui->m_txtMysqlDatabase->lineEdit(), &BaseLineEdit::textChanged, this, &SettingsDatabase::onMysqlDatabaseChanged);
connect(m_ui->m_txtMysqlUsername->lineEdit(),
&BaseLineEdit::textChanged,
this,
&SettingsDatabase::onMysqlUsernameChanged);
connect(m_ui->m_txtMysqlHostname->lineEdit(),
&BaseLineEdit::textChanged,
this,
&SettingsDatabase::onMysqlHostnameChanged);
connect(m_ui->m_txtMysqlPassword->lineEdit(),
&BaseLineEdit::textChanged,
this,
&SettingsDatabase::onMysqlPasswordChanged);
connect(m_ui->m_txtMysqlDatabase->lineEdit(),
&BaseLineEdit::textChanged,
this,
&SettingsDatabase::onMysqlDatabaseChanged);
connect(m_ui->m_btnMysqlTestSetup, &QPushButton::clicked, this, &SettingsDatabase::mysqlTestConnection);
connect(m_ui->m_cmbDatabaseDriver, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
connect(m_ui->m_cmbDatabaseDriver,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&SettingsDatabase::requireRestart);
connect(m_ui->m_checkSqliteUseInMemoryDatabase, &QCheckBox::toggled, this, &SettingsDatabase::requireRestart);
connect(m_ui->m_spinMysqlPort, &QSpinBox::editingFinished, this, &SettingsDatabase::requireRestart);
@ -139,17 +155,14 @@ void SettingsDatabase::selectSqlBackend(int index) {
m_ui->m_stackedDatabaseDriver->setCurrentIndex(1);
}
else {
qWarningNN << LOGSEC_GUI
<< "GUI for given database driver '"
<< selected_db_driver
<< "' is not available.";
qWarningNN << LOGSEC_GUI << "GUI for given database driver '" << selected_db_driver << "' is not available.";
}
}
void SettingsDatabase::loadSettings() {
onBeginLoadSettings();
m_ui->m_checkUseTransactions->setChecked(qApp->settings()->value(GROUP(Database), SETTING(Database::UseTransactions)).toBool());
m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Information, tr("No connection test triggered so far."),
m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Information,
tr("No connection test triggered so far."),
tr("You did not executed any connection test yet."));
// Load SQLite.
@ -158,7 +171,8 @@ void SettingsDatabase::loadSettings() {
m_ui->m_cmbDatabaseDriver->addItem(lite_driver->humanDriverType(), lite_driver->qtDriverCode());
// Load in-memory database status.
m_ui->m_checkSqliteUseInMemoryDatabase->setChecked(settings()->value(GROUP(Database), SETTING(Database::UseInMemory)).toBool());
m_ui->m_checkSqliteUseInMemoryDatabase
->setChecked(settings()->value(GROUP(Database), SETTING(Database::UseInMemory)).toBool());
auto* mysq_driver = qApp->database()->driverForType(DatabaseDriver::DriverType::MySQL);
@ -176,16 +190,19 @@ void SettingsDatabase::loadSettings() {
m_ui->m_txtMysqlUsername->lineEdit()->setPlaceholderText(tr("Username to login with"));
m_ui->m_txtMysqlPassword->lineEdit()->setPlaceholderText(tr("Password for your username"));
m_ui->m_txtMysqlDatabase->lineEdit()->setPlaceholderText(tr("Working database which you have full access to."));
m_ui->m_txtMysqlHostname->lineEdit()->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLHostname)).toString());
m_ui->m_txtMysqlUsername->lineEdit()->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLUsername)).toString());
m_ui->m_txtMysqlPassword->lineEdit()->setText(settings()->password(GROUP(Database),
SETTING(Database::MySQLPassword)).toString());
m_ui->m_txtMysqlDatabase->lineEdit()->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLDatabase)).toString());
m_ui->m_txtMysqlHostname->lineEdit()
->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLHostname)).toString());
m_ui->m_txtMysqlUsername->lineEdit()
->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLUsername)).toString());
m_ui->m_txtMysqlPassword->lineEdit()
->setText(settings()->password(GROUP(Database), SETTING(Database::MySQLPassword)).toString());
m_ui->m_txtMysqlDatabase->lineEdit()
->setText(settings()->value(GROUP(Database), SETTING(Database::MySQLDatabase)).toString());
m_ui->m_spinMysqlPort->setValue(settings()->value(GROUP(Database), SETTING(Database::MySQLPort)).toInt());
}
int index_current_backend = m_ui->m_cmbDatabaseDriver->findData(settings()->value(GROUP(Database),
SETTING(Database::ActiveDriver)).toString());
int index_current_backend =
m_ui->m_cmbDatabaseDriver->findData(settings()->value(GROUP(Database), SETTING(Database::ActiveDriver)).toString());
if (index_current_backend >= 0) {
m_ui->m_cmbDatabaseDriver->setCurrentIndex(index_current_backend);
@ -201,11 +218,10 @@ void SettingsDatabase::saveSettings() {
const bool original_inmemory = settings()->value(GROUP(Database), SETTING(Database::UseInMemory)).toBool();
const bool new_inmemory = m_ui->m_checkSqliteUseInMemoryDatabase->isChecked();
qApp->settings()->setValue(GROUP(Database), Database::UseTransactions, m_ui->m_checkUseTransactions->isChecked());
// Save data storage settings.
QString original_db_driver = settings()->value(GROUP(Database), SETTING(Database::ActiveDriver)).toString();
QString selected_db_driver = m_ui->m_cmbDatabaseDriver->itemData(m_ui->m_cmbDatabaseDriver->currentIndex()).toString();
QString selected_db_driver =
m_ui->m_cmbDatabaseDriver->itemData(m_ui->m_cmbDatabaseDriver->currentIndex()).toString();
// Save SQLite.
settings()->setValue(GROUP(Database), Database::UseInMemory, new_inmemory);

View File

@ -11,20 +11,7 @@
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="m_checkUseTransactions">
<property name="toolTip">
<string>Note that turning this option ON will make saving of new messages FASTER, but it might rarely cause some issues with messages saving.</string>
</property>
<property name="text">
<string>Use DB transactions when storing downloaded messages</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="HelpSpoiler" name="m_lblDataStorageWarning" native="true"/>
</item>
<item row="2" column="0">
<item row="0" column="0">
<widget class="QLabel" name="m_lblDatabaseDriver">
<property name="text">
<string>Database driver</string>
@ -34,10 +21,10 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbDatabaseDriver"/>
</item>
<item row="3" column="0" colspan="2">
<item row="1" column="0" colspan="2">
<widget class="QStackedWidget" name="m_stackedDatabaseDriver">
<property name="currentIndex">
<number>0</number>
@ -204,7 +191,7 @@
</widget>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -218,11 +205,6 @@
</spacer>
</item>
</layout>
<zorder>m_lblDatabaseDriver</zorder>
<zorder>m_cmbDatabaseDriver</zorder>
<zorder>m_stackedDatabaseDriver</zorder>
<zorder>m_checkUseTransactions</zorder>
<zorder>m_lblDataStorageWarning</zorder>
</widget>
<customwidgets>
<customwidget>

View File

@ -215,6 +215,12 @@ Application::Application(const QString& id, int& argc, char** argv, const QStrin
QTimer::singleShot(1000, system(), &SystemFactory::checkForUpdatesOnStartup);
auto ideal_th_count = QThread::idealThreadCount();
if (ideal_th_count > 1) {
QThreadPool::globalInstance()->setMaxThreadCount((std::min)(128, 2 * ideal_th_count));
}
qDebugNN << LOGSEC_CORE << "OpenSSL version:" << QUOTE_W_SPACE_DOT(QSslSocket::sslLibraryVersionString());
qDebugNN << LOGSEC_CORE << "OpenSSL supported:" << QUOTE_W_SPACE_DOT(QSslSocket::supportsSsl());
qDebugNN << LOGSEC_CORE << "Global thread pool has"

View File

@ -122,7 +122,7 @@ void FeedReader::initializeFeedDownloader() {
connect(m_feedDownloaderThread, &QThread::finished, m_feedDownloaderThread, &QThread::deleteLater);
connect(m_feedDownloaderThread, &QThread::finished, m_feedDownloader, &FeedDownloader::deleteLater);
connect(m_feedDownloader, &FeedDownloader::updateFinished, this, &FeedReader::feedUpdatesFinished);
connect(m_feedDownloader, &FeedDownloader::updateFinished, this, &FeedReader::onFeedUpdatesFinished);
connect(m_feedDownloader, &FeedDownloader::updateProgress, this, &FeedReader::feedUpdatesProgress);
connect(m_feedDownloader, &FeedDownloader::updateStarted, this, &FeedReader::feedUpdatesStarted);
connect(m_feedDownloader, &FeedDownloader::updateFinished, qApp->feedUpdateLock(), &Mutex::unlock);
@ -348,6 +348,13 @@ void FeedReader::executeNextAutoUpdate() {
}
}
void FeedReader::onFeedUpdatesFinished(FeedDownloadResults updated_feeds) {
m_feedsModel->reloadWholeLayout();
m_feedsModel->notifyWithCounts();
emit feedUpdatesFinished(updated_feeds);
}
QList<MessageFilter*> FeedReader::messageFilters() const {
return m_messageFilters;
}

View File

@ -72,6 +72,7 @@ class RSSGUARD_DLLSPEC FeedReader : public QObject {
private slots:
void executeNextAutoUpdate();
void onFeedUpdatesFinished(FeedDownloadResults updated_feeds);
signals:
void feedUpdatesStarted();

View File

@ -374,9 +374,6 @@ DVALUE(int) Proxy::PortDef = 80;
// Database.
DKEY Database::ID = "database";
DKEY Database::UseTransactions = "use_transactions";
DVALUE(bool) Database::UseTransactionsDef = false;
DKEY Database::UseInMemory = "use_in_memory_db";
DVALUE(bool) Database::UseInMemoryDef = false;
@ -438,7 +435,8 @@ DVALUE(QStringList) Browser::ExternalToolsDef = QStringList();
DKEY CategoriesExpandStates::ID = "categories_expand_states";
Settings::Settings(const QString& file_name, Format format, SettingsProperties::SettingsType type, QObject* parent)
: QSettings(file_name, format, parent), m_initializationStatus(type) {
: QSettings(file_name, format, parent), m_lock(QReadWriteLock(QReadWriteLock::RecursionMode::Recursive)),
m_initializationStatus(type) {
Messages::PreviewerFontStandardDef = QFont(QApplication::font().family(), 12).toString();
}

View File

@ -14,7 +14,9 @@
#include <QColor>
#include <QDateTime>
#include <QNetworkProxy>
#include <QReadWriteLock>
#include <QStringList>
#include <QWriteLocker>
#define KEY extern const QString
#define DKEY const QString
@ -399,9 +401,6 @@ namespace Proxy {
// Database.
namespace Database {
KEY ID;
KEY UseTransactions;
VALUE(bool) UseTransactionsDef;
KEY UseInMemory;
@ -518,12 +517,13 @@ class Settings : public QSettings {
static SettingsProperties determineProperties();
private:
// Constructor.
explicit Settings(const QString& file_name,
Format format,
SettingsProperties::SettingsType type,
QObject* parent = nullptr);
private:
mutable QReadWriteLock m_lock;
SettingsProperties::SettingsType m_initializationStatus;
};
@ -545,10 +545,12 @@ inline QVariant Settings::value(const QString& section, const QString& key, cons
}
inline void Settings::setValue(const QString& section, const QString& key, const QVariant& value) {
QWriteLocker lck(&m_lock);
QSettings::setValue(QString(QSL("%1/%2")).arg(section, key), value);
}
inline void Settings::setValue(const QString& key, const QVariant& value) {
QWriteLocker lck(&m_lock);
QSettings::setValue(key, value);
}
@ -557,6 +559,8 @@ inline bool Settings::contains(const QString& section, const QString& key) const
}
inline void Settings::remove(const QString& section, const QString& key) {
QWriteLocker lck(&m_lock);
if (key.isEmpty()) {
beginGroup(section);
QSettings::remove({});

View File

@ -113,7 +113,6 @@ void CookieJar::saveCookies() {
if (cookie.isSessionCookie()) {
continue;
}
sett->setPassword(GROUP(Cookies),
QSL("%1-%2").arg(QString::number(i++), QString::fromUtf8(cookie.name())),
cookie.toRawForm(QNetworkCookie::RawForm::Full));
@ -121,10 +120,12 @@ void CookieJar::saveCookies() {
}
QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl& url) const {
QReadLocker l(&m_lock);
return QNetworkCookieJar::cookiesForUrl(url);
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie>& cookie_list, const QUrl& url) {
QWriteLocker l(&m_lock);
return QNetworkCookieJar::setCookiesFromUrl(cookie_list, url);
}
@ -192,11 +193,13 @@ bool CookieJar::insertCookie(const QNetworkCookie& cookie) {
return {};
}
else {
QWriteLocker l(&m_lock);
return insertCookieInternal(cookie, false, true);
}
}
bool CookieJar::deleteCookie(const QNetworkCookie& cookie) {
QWriteLocker l(&m_lock);
return deleteCookieInternal(cookie, false);
}
@ -210,5 +213,12 @@ void CookieJar::updateSettings() {
}
bool CookieJar::updateCookie(const QNetworkCookie& cookie) {
QWriteLocker l(&m_lock);
return updateCookieInternal(cookie, false);
}
/*
bool CookieJar::validateCookie(const QNetworkCookie &cookie, const QUrl &url) const {
return QNetworkCookieJar::validateCookie(cookie, url);
}
*/

View File

@ -7,6 +7,8 @@
#include "miscellaneous/autosaver.h"
#include <QReadWriteLock>
#if defined(USE_WEBENGINE)
class QWebEngineCookieStore;
#endif
@ -20,6 +22,7 @@ class CookieJar : public QNetworkCookieJar {
virtual bool insertCookie(const QNetworkCookie& cookie);
virtual bool updateCookie(const QNetworkCookie& cookie);
virtual bool deleteCookie(const QNetworkCookie& cookie);
// virtual bool validateCookie(const QNetworkCookie& cookie, const QUrl& url) const;
void updateSettings();
@ -40,6 +43,7 @@ class CookieJar : public QNetworkCookieJar {
QWebEngineCookieStore* m_webEngineCookies;
#endif
mutable QReadWriteLock m_lock{QReadWriteLock::RecursionMode::Recursive};
bool m_ignoreAllCookies;
AutoSaver m_saver;
};

View File

@ -17,8 +17,6 @@
#include "services/abstract/serviceroot.h"
#include "services/abstract/unreadnode.h"
#include <QThread>
Feed::Feed(RootItem* parent)
: RootItem(parent), m_source(QString()), m_status(Status::Normal), m_statusString(QString()),
m_autoUpdateType(AutoUpdateType::DefaultAutoUpdate), m_autoUpdateInterval(DEFAULT_AUTO_UPDATE_INTERVAL),
@ -196,9 +194,7 @@ void Feed::appendMessageFilter(MessageFilter* filter) {
}
void Feed::updateCounts(bool including_total_count) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ? qApp->database()->driver()->connection(metaObject()->className())
: qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
int account_id = getParentServiceRoot()->accountId();
if (including_total_count) {

View File

@ -8,8 +8,6 @@
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/serviceroot.h"
#include <QThread>
ImportantNode::ImportantNode(RootItem* parent_item) : RootItem(parent_item) {
setKind(RootItem::Kind::Important);
setId(ID_IMPORTANT);
@ -25,10 +23,7 @@ QList<Message> ImportantNode::undeletedMessages() const {
}
void ImportantNode::updateCounts(bool including_total_count) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
int account_id = getParentServiceRoot()->accountId();
if (including_total_count) {

View File

@ -2,17 +2,16 @@
#include "services/abstract/label.h"
#include "gui/dialogs/formaddeditlabel.h"
#include "miscellaneous/application.h"
#include "database/databasefactory.h"
#include "database/databasequeries.h"
#include "gui/dialogs/formaddeditlabel.h"
#include "miscellaneous/application.h"
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/labelsnode.h"
#include "services/abstract/serviceroot.h"
#include <QPainter>
#include <QPainterPath>
#include <QThread>
Label::Label(const QString& name, const QColor& color, RootItem* parent_item) : Label(parent_item) {
setColor(color);
@ -76,10 +75,7 @@ bool Label::deleteViaGui() {
}
void Label::updateCounts(bool including_total_count) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
int account_id = getParentServiceRoot()->accountId();
if (including_total_count) {
@ -110,28 +106,22 @@ QIcon Label::generateIcon(const QColor& color) {
}
void Label::assignToMessage(const Message& msg) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, true)) {
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({this}, {msg}, true)) {
DatabaseQueries::assignLabelToMessage(database, this, msg);
getParentServiceRoot()->onAfterLabelMessageAssignmentChanged({ this }, { msg }, true);
getParentServiceRoot()->onAfterLabelMessageAssignmentChanged({this}, {msg}, true);
}
}
void Label::deassignFromMessage(const Message& msg) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, false)) {
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({this}, {msg}, false)) {
DatabaseQueries::deassignLabelFromMessage(database, this, msg);
getParentServiceRoot()->onAfterLabelMessageAssignmentChanged({ this }, { msg }, false);
getParentServiceRoot()->onAfterLabelMessageAssignmentChanged({this}, {msg}, false);
}
}

View File

@ -9,10 +9,7 @@
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/serviceroot.h"
#include <QThread>
RecycleBin::RecycleBin(RootItem* parent_item) : RootItem(parent_item), m_totalCount(0),
m_unreadCount(0) {
RecycleBin::RecycleBin(RootItem* parent_item) : RootItem(parent_item), m_totalCount(0), m_unreadCount(0) {
setKind(RootItem::Kind::Bin);
setId(ID_RECYCLE_BIN);
setIcon(qApp->icons()->fromTheme(QSL("user-trash")));
@ -33,10 +30,7 @@ int RecycleBin::countOfAllMessages() const {
}
void RecycleBin::updateCounts(bool update_total_count) {
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
m_unreadCount = DatabaseQueries::getMessageCountsForBin(database, getParentServiceRoot()->accountId(), false);
@ -47,12 +41,9 @@ void RecycleBin::updateCounts(bool update_total_count) {
QList<QAction*> RecycleBin::contextMenuFeedsList() {
if (m_contextMenu.isEmpty()) {
QAction* restore_action = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")),
tr("Restore recycle bin"),
this);
QAction* empty_action = new QAction(qApp->icons()->fromTheme(QSL("edit-clear")),
tr("Empty recycle bin"),
this);
QAction* restore_action =
new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Restore recycle bin"), this);
QAction* empty_action = new QAction(qApp->icons()->fromTheme(QSL("edit-clear")), tr("Empty recycle bin"), this);
connect(restore_action, &QAction::triggered, this, &RecycleBin::restore);
connect(empty_action, &QAction::triggered, this, &RecycleBin::empty);
@ -99,7 +90,8 @@ bool RecycleBin::cleanMessages(bool clear_only_read) {
updateCounts(true);
parent_root->itemChanged(QList<RootItem*>() << this);
parent_root->requestReloadMessageList(true);
return true;;
return true;
;
}
else {
return false;

View File

@ -19,8 +19,6 @@
#include "services/abstract/recyclebin.h"
#include "services/abstract/unreadnode.h"
#include <QThread>
ServiceRoot::ServiceRoot(RootItem* parent)
: RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)),
m_labelsNode(new LabelsNode(this)), m_unreadNode(new UnreadNode(this)), m_accountId(NO_PARENT_CATEGORY),
@ -945,7 +943,7 @@ ServiceRoot::LabelOperation operator&(ServiceRoot::LabelOperation lhs, ServiceRo
return static_cast<ServiceRoot::LabelOperation>(static_cast<char>(lhs) & static_cast<char>(rhs));
}
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, QMutex* db_mutex) {
QPair<int, int> updated_messages = {0, 0};
if (messages.isEmpty()) {
@ -953,45 +951,37 @@ QPair<int, int> ServiceRoot::updateMessages(QList<Message>& messages, Feed* feed
return updated_messages;
}
QList<RootItem*> items_to_update;
bool is_main_thread = QThread::currentThread() == qApp->thread();
qDebugNN << LOGSEC_CORE << "Updating messages in DB. Main thread:" << QUOTE_W_SPACE_DOT(is_main_thread);
bool ok = false;
QSqlDatabase database = is_main_thread ? qApp->database()->driver()->connection(metaObject()->className())
: qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
updated_messages = DatabaseQueries::updateMessages(database, messages, feed, force_update, &ok);
qDebugNN << LOGSEC_CORE << "Updating messages in DB.";
updated_messages = DatabaseQueries::updateMessages(database, messages, feed, force_update, db_mutex, &ok);
if (updated_messages.first > 0 || updated_messages.second > 0) {
QMutexLocker lck(db_mutex);
// Something was added or updated in the DB, update numbers.
feed->updateCounts(true);
if (recycleBin() != nullptr) {
recycleBin()->updateCounts(true);
items_to_update.append(recycleBin());
}
if (importantNode() != nullptr) {
importantNode()->updateCounts(true);
items_to_update.append(importantNode());
}
if (unreadNode() != nullptr) {
unreadNode()->updateCounts(true);
items_to_update.append(unreadNode());
}
if (labelsNode() != nullptr) {
labelsNode()->updateCounts(true);
items_to_update.append(labelsNode());
}
}
// Some messages were really added to DB, reload feed in model.
items_to_update.append(feed);
getParentServiceRoot()->itemChanged(items_to_update);
// NOTE: Do not update model items here. We update only once when all feeds are fetched.
return updated_messages;
}

View File

@ -14,6 +14,7 @@
#include <QPair>
class QAction;
class QMutex;
class FeedsModel;
class RecycleBin;
class ImportantNode;
@ -197,7 +198,7 @@ class ServiceRoot : public RootItem {
void completelyRemoveAllData();
// Returns counts of updated messages <unread, all>.
QPair<int, int> updateMessages(QList<Message>& messages, Feed* feed, bool force_update);
QPair<int, int> updateMessages(QList<Message>& messages, Feed* feed, bool force_update, QMutex* db_mutex);
QIcon feedIconForMessage(const QString& feed_custom_id) const;

View File

@ -6,8 +6,6 @@
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include <QThread>
UnreadNode::UnreadNode(RootItem* parent_item) : RootItem(parent_item) {
setKind(RootItem::Kind::Unread);
setId(ID_UNREAD);
@ -25,10 +23,7 @@ QList<Message> UnreadNode::undeletedMessages() const {
void UnreadNode::updateCounts(bool including_total_count) {
Q_UNUSED(including_total_count)
bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->driver()->connection(metaObject()->className()) :
qApp->database()->driver()->connection(QSL("feed_upd"));
QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className());
int account_id = getParentServiceRoot()->accountId();
m_totalCount = m_unreadCount = DatabaseQueries::getUnreadMessageCounts(database, account_id);

View File

@ -23,7 +23,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QThread>
#include <QUrl>
GmailNetworkFactory::GmailNetworkFactory(QObject* parent)

View File

@ -24,7 +24,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QThread>
#include <QUrl>
RedditNetworkFactory::RedditNetworkFactory(QObject* parent)

View File

@ -248,7 +248,8 @@ void FormStandardImportExport::parseImportFile(const QString& file_name, bool fe
QFile input_file(file_name);
QByteArray input_data;
if (input_file.open(QIODevice::Text | QIODevice::Unbuffered | QIODevice::ReadOnly)) {
if (input_file.open(QIODevice::OpenModeFlag::Text | QIODevice::OpenModeFlag::Unbuffered |
QIODevice::OpenModeFlag::ReadOnly)) {
input_data = input_file.readAll();
input_file.close();
}

View File

@ -36,7 +36,7 @@ FeedsImportExportModel::FeedsImportExportModel(QObject* parent)
m_newRoot = nullptr;
emit parsingFinished(number_error, m_lookup.size() - number_error);
emit parsingFinished(number_error, res.size() - number_error);
// Done, remove lookups.
m_lookup.clear();
@ -189,7 +189,7 @@ bool FeedsImportExportModel::produceFeed(const FeedLookup& feed_lookup) {
new_feed->setPostProcessScript(feed_lookup.post_process_script);
}
else {
new_feed = new StandardFeed(feed_lookup.parent);
new_feed = new StandardFeed();
if (feed_lookup.opml_element.isNull()) {
new_feed->setSource(feed_lookup.url);