Merge branch 'master' of github.com:martinrotter/rssguard
This commit is contained in:
commit
b9cbc1ad5b
@ -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")
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class RSSGUARD_DLLSPEC FeedReader : public QObject {
|
||||
|
||||
private slots:
|
||||
void executeNextAutoUpdate();
|
||||
void onFeedUpdatesFinished(FeedDownloadResults updated_feeds);
|
||||
|
||||
signals:
|
||||
void feedUpdatesStarted();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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({});
|
||||
|
@ -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);
|
||||
}
|
||||
*/
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
GmailNetworkFactory::GmailNetworkFactory(QObject* parent)
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
RedditNetworkFactory::RedditNetworkFactory(QObject* parent)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user