From 6822304c567b76e7c43d227b3de7eef38856c6ba Mon Sep 17 00:00:00 2001 From: Bart De Vries Date: Tue, 16 Apr 2024 16:08:37 +0200 Subject: [PATCH] Implement interval-based automatic podcast updates This also includes a restructuring of some of the settings. BUG: 466789 --- src/fetcher.cpp | 44 ++++++++++++ src/fetcher.h | 9 +++ src/qml/Settings/GeneralSettingsPage.qml | 87 ++++++++++++++++------- src/settingsmanager.kcfg | 90 +++++++++++++----------- 4 files changed, 161 insertions(+), 69 deletions(-) diff --git a/src/fetcher.cpp b/src/fetcher.cpp index ea1dd937..831f835a 100644 --- a/src/fetcher.cpp +++ b/src/fetcher.cpp @@ -44,6 +44,10 @@ Fetcher::Fetcher() manager->enableStrictTransportSecurityStore(true); QNetworkProxyFactory::setUseSystemConfiguration(true); + + // setup update timer if required + initializeUpdateTimer(); + connect(SettingsManager::self(), &SettingsManager::autoFeedUpdateIntervalChanged, this, &Fetcher::initializeUpdateTimer); } void Fetcher::fetch(const QString &url) @@ -242,3 +246,43 @@ void Fetcher::setHeader(QNetworkRequest &request) const { request.setRawHeader(QByteArray("User-Agent"), QByteArray("Kasts/") + QByteArray(KASTS_VERSION_STRING) + QByteArray(" Syndication")); } + +void Fetcher::initializeUpdateTimer() +{ + qCDebug(kastsFetcher) << "Fetcher::setUpdateTimer"; + qCDebug(kastsFetcher) << "new auto update interval =" << SettingsManager::self()->autoFeedUpdateInterval(); + + if (m_updateTimer) { + m_updateTimer->stop(); + disconnect(m_updateTimer, &QTimer::timeout, this, &Fetcher::checkUpdateTimer); + delete m_updateTimer; + } + + if (SettingsManager::self()->autoFeedUpdateInterval() > 0) { + m_updateTriggerTime = + QDateTime::currentDateTimeUtc().addSecs(3600 * SettingsManager::self()->autoFeedUpdateInterval()); // update interval specified in hours + m_updateTimer = new QTimer(this); + m_updateTimer->setTimerType(Qt::VeryCoarseTimer); + + connect(m_updateTimer, &QTimer::timeout, this, &Fetcher::checkUpdateTimer); + + m_updateTimer->start(m_checkInterval); // trigger every ten minutes + } +} + +void Fetcher::checkUpdateTimer() +{ + qCDebug(kastsFetcher) << "Fetcher::checkUpdateTimer; next automatic feed update in" << m_updateTriggerTime - QDateTime::currentDateTimeUtc(); + + // add a few seconds as "fuzzy match" to avoid that the trigger is delayed + // by another 10 minutes due to a difference of just a few milliseconds + if (QDateTime::currentDateTimeUtc().addSecs(5) > m_updateTriggerTime) { + qCDebug(kastsFetcher) << "Trigger for feed update has been reached; updating feeds now"; + QTimer::singleShot(0, this, &Fetcher::fetchAll); + + // set next update time + m_updateTriggerTime = + QDateTime::currentDateTimeUtc().addSecs(3600 * SettingsManager::self()->autoFeedUpdateInterval()); // update interval specified in hours + qCDebug(kastsFetcher) << "new auto feed update trigger set to" << m_updateTriggerTime; + } +} diff --git a/src/fetcher.h b/src/fetcher.h index 76b7988b..222b93ff 100644 --- a/src/fetcher.h +++ b/src/fetcher.h @@ -7,11 +7,13 @@ #pragma once +#include #include #include #include #include #include +#include #include #include @@ -50,6 +52,9 @@ public: QNetworkReply *get(QNetworkRequest &request) const; QNetworkReply *post(QNetworkRequest &request, const QByteArray &data) const; + void initializeUpdateTimer(); + void checkUpdateTimer(); + Q_SIGNALS: void entryAdded(const QString &feedurl, const QString &id); void entryUpdated(const QString &feedurl, const QString &id); @@ -84,4 +89,8 @@ private: int m_updateProgress; int m_updateTotal; bool m_updating; + + const qint64 m_checkInterval = 10 * 60 * 1000; // trigger timer every 10 minutes + QTimer *m_updateTimer; + QDateTime m_updateTriggerTime; }; diff --git a/src/qml/Settings/GeneralSettingsPage.qml b/src/qml/Settings/GeneralSettingsPage.qml index 3062956e..c3b83212 100644 --- a/src/qml/Settings/GeneralSettingsPage.qml +++ b/src/qml/Settings/GeneralSettingsPage.qml @@ -59,9 +59,8 @@ FormCard.FormCardPage { FormCard.FormCheckDelegate { id: showTimeLeft - Kirigami.FormData.label: i18nc("@option:check Label for settings related to the play time, e.g. whether the total track time is shown or a countdown of the remaining play time", "Play time:") checked: SettingsManager.toggleRemainingTime - text: i18n("Show time left instead of total track time") + text: i18nc("@option:check Label for setting whether the total track time is shown or a countdown of the remaining play time", "Show time left instead of total track time") onToggled: { SettingsManager.toggleRemainingTime = checked; SettingsManager.save(); @@ -121,32 +120,46 @@ FormCard.FormCardPage { } FormCard.FormHeader { - title: i18nc("@title Form header for settings related to the queue", "Queue settings") + title: i18nc("@title Form header for settings related podcast updates", "Podcast update settings") Layout.fillWidth: true } FormCard.FormCard { Layout.fillWidth: true - FormCard.FormCheckDelegate { - id: continuePlayingNextEntry - checked: SettingsManager.continuePlayingNextEntry - text: i18nc("@option:check", "Continue playing next episode after current one finishes") - onToggled: { - SettingsManager.continuePlayingNextEntry = checked; + FormCard.FormComboBoxDelegate { + id: autoFeedUpdateInterval + text: i18nc("@label:listbox", "Automatically fetch podcast feeds") + textRole: "text" + valueRole: "value" + model: [{"text": i18nc("@item:inlistbox automatic podcast update interval", "Never"), "value": 0}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every hour", "Every %1 hours", 1), "value": 1}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every hour", "Every %1 hours", 2), "value": 2}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every hour", "Every %1 hours", 4), "value": 4}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every hour", "Every %1 hours", 8), "value": 8}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every hour", "Every %1 hours", 12), "value": 12}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every day", "Every %1 days", 1), "value": 24}, + {"text": i18ncp("@item:inlistbox automatic podcast update interval", "Every day", "Every %1 days", 3), "value": 72}] + Component.onCompleted: currentIndex = indexOfValue(SettingsManager.autoFeedUpdateInterval) + onActivated: { + SettingsManager.autoFeedUpdateInterval = currentValue; SettingsManager.save(); } } + FormCard.FormCheckDelegate { id: refreshOnStartup - Kirigami.FormData.label: i18nc("@option:check Label for settings related to podcast updates", "Update Settings:") checked: SettingsManager.refreshOnStartup - text: i18n("Automatically fetch podcast updates on startup") + text: i18nc("@option:check", "Fetch podcast updates on startup") onToggled: { SettingsManager.refreshOnStartup = checked; SettingsManager.save(); } } + + + FormCard.FormDelegateSeparator { above: refreshOnStartup; below: doFullUpdate } + FormCard.FormCheckDelegate { id: doFullUpdate checked: SettingsManager.doFullUpdate @@ -183,17 +196,49 @@ FormCard.FormCardPage { SettingsManager.save(); } } + } + + FormCard.FormHeader { + title: i18nc("@title Form header for settings related to the queue", "Queue settings") + Layout.fillWidth: true + } + + FormCard.FormCard { + Layout.fillWidth: true + + FormCard.FormCheckDelegate { + id: continuePlayingNextEntry + checked: SettingsManager.continuePlayingNextEntry + text: i18nc("@option:check", "Continue playing next episode after current one finishes") + onToggled: { + SettingsManager.continuePlayingNextEntry = checked; + SettingsManager.save(); + } + } + + FormCard.FormDelegateSeparator { above: continuePlayingNextEntry; below: resetPositionOnPlayed } + + FormCard.FormCheckDelegate { + id: resetPositionOnPlayed + checked: SettingsManager.resetPositionOnPlayed + text: i18nc("@option:check", "Reset play position after an episode is played") + onToggled: { + SettingsManager.resetPositionOnPlayed = checked; + SettingsManager.save(); + } + } + + FormCard.FormDelegateSeparator { above: resetPositionOnPlayed; below: episodeBehavior } - FormCard.FormDelegateSeparator { above: autoDownload; below: episodeBehavior } FormCard.FormComboBoxDelegate { id: episodeBehavior text: i18nc("@label:listbox", "Played episode behavior") textRole: "text" valueRole: "value" - model: [{"text": i18n("Do not delete"), "value": 0}, - {"text": i18n("Delete immediately"), "value": 1}, - {"text": i18n("Delete at next startup"), "value": 2}] + model: [{"text": i18nc("@item:inlistbox What to do with played episodes", "Do not delete"), "value": 0}, + {"text": i18nc("@item:inlistbox What to do with played episodes", "Delete immediately"), "value": 1}, + {"text": i18nc("@item:inlistbox What to do with played episodes", "Delete at next startup"), "value": 2}] Component.onCompleted: currentIndex = indexOfValue(SettingsManager.autoDeleteOnPlayed) onActivated: { SettingsManager.autoDeleteOnPlayed = currentValue; @@ -218,18 +263,6 @@ FormCard.FormCardPage { } } } - - FormCard.FormDelegateSeparator { above: markAsPlayedGracePeriod; below: resetPositionOnPlayed } - - FormCard.FormCheckDelegate { - id: resetPositionOnPlayed - checked: SettingsManager.resetPositionOnPlayed - text: i18nc("@option:check", "Reset play position after an episode is played") - onToggled: { - SettingsManager.resetPositionOnPlayed = checked; - SettingsManager.save(); - } - } } FormCard.FormHeader { diff --git a/src/settingsmanager.kcfg b/src/settingsmanager.kcfg index 3e6a8228..4a77382c 100644 --- a/src/settingsmanager.kcfg +++ b/src/settingsmanager.kcfg @@ -5,44 +5,6 @@ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > - - - - - - false - - - - - - - - - - - - - - - Dark - - - - true - - - - false - - - - true - - - - false - false @@ -71,6 +33,10 @@ false + + + 0 + @@ -124,15 +90,55 @@ 10 - - - true - + + + + + + + false + + + + true + + + + false + + + + false + + + + + + + + + + + + + + + Dark + + + + true + + + + true + +