Implement the "Delete after x days" feature for listened podcasts.
This commit is contained in:
parent
a5107b7dfc
commit
5327b72f0b
@ -27,6 +27,7 @@ CREATE TABLE podcast_episodes (
|
||||
url TEXT,
|
||||
|
||||
listened BOOLEAN,
|
||||
listened_date INTEGER,
|
||||
downloaded BOOLEAN,
|
||||
local_url TEXT,
|
||||
|
||||
|
@ -31,4 +31,6 @@ const qint64 kNsecPerUsec = 1000ll;
|
||||
const qint64 kNsecPerMsec = 1000000ll;
|
||||
const qint64 kNsecPerSec = 1000000000ll;
|
||||
|
||||
const qint64 kSecsPerDay = 24 * 60 * 60;
|
||||
|
||||
#endif // TIMECONSTANTS_H
|
||||
|
@ -136,12 +136,14 @@ void PodcastBackend::UpdateEpisodes(const PodcastEpisodeList& episodes) {
|
||||
|
||||
QSqlQuery q("UPDATE podcast_episodes"
|
||||
" SET listened = :listened,"
|
||||
" listened_date = :listened_date,"
|
||||
" downloaded = :downloaded,"
|
||||
" local_url = :local_url"
|
||||
" WHERE ROWID = :id", db);
|
||||
|
||||
foreach (const PodcastEpisode& episode, episodes) {
|
||||
q.bindValue(":listened", episode.listened());
|
||||
q.bindValue(":listened_date", episode.listened_date().toTime_t());
|
||||
q.bindValue(":downloaded", episode.downloaded());
|
||||
q.bindValue(":local_url", episode.local_url().toEncoded());
|
||||
q.bindValue(":id", episode.database_id());
|
||||
@ -287,3 +289,27 @@ PodcastEpisode PodcastBackend::GetEpisodeByUrlOrLocalUrl(const QUrl& url) {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PodcastEpisodeList PodcastBackend::GetOldDownloadedEpisodes(const QDateTime& max_listened_date) {
|
||||
PodcastEpisodeList ret;
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QSqlQuery q("SELECT ROWID, " + PodcastEpisode::kColumnSpec +
|
||||
" FROM podcast_episodes"
|
||||
" WHERE downloaded = 'true'"
|
||||
" AND listened_date <= :max_listened_date", db);
|
||||
q.bindValue(":max_listened_date", max_listened_date.toTime_t());
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q))
|
||||
return ret;
|
||||
|
||||
while (q.next()) {
|
||||
PodcastEpisode episode;
|
||||
episode.InitFromQuery(q);
|
||||
ret << episode;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -48,18 +48,24 @@ public:
|
||||
Podcast GetSubscriptionById(int id);
|
||||
Podcast GetSubscriptionByUrl(const QUrl& url);
|
||||
|
||||
// Returns a list of the episodes in the podcast with the given ID.
|
||||
// Returns podcast episodes that match various keys. All these queries are
|
||||
// indexed.
|
||||
PodcastEpisodeList GetEpisodes(int podcast_id);
|
||||
PodcastEpisode GetEpisodeById(int id);
|
||||
PodcastEpisode GetEpisodeByUrl(const QUrl& url);
|
||||
PodcastEpisode GetEpisodeByUrlOrLocalUrl(const QUrl& url);
|
||||
|
||||
// Returns a list of episodes that have local data (downloaded=true) but were
|
||||
// last listened to before the given QDateTime. This query is NOT indexed so
|
||||
// it involves a full search of the table.
|
||||
PodcastEpisodeList GetOldDownloadedEpisodes(const QDateTime& max_listened_date);
|
||||
|
||||
// Adds episodes to the database. Every episode must have a valid
|
||||
// podcast_database_id set already.
|
||||
void AddEpisodes(PodcastEpisodeList* episodes);
|
||||
|
||||
// Updates the editable fields (listened, downloaded, and local_url) on
|
||||
// episodes that must already exist in the database.
|
||||
// Updates the editable fields (listened, listened_date, downloaded, and
|
||||
// local_url) on episodes that must already exist in the database.
|
||||
void UpdateEpisodes(const PodcastEpisodeList& episodes);
|
||||
|
||||
signals:
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
@ -29,9 +30,10 @@
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
const char* PodcastDownloader::kSettingsGroup = "Podcasts";
|
||||
|
||||
const int PodcastDownloader::kAutoDeleteCheckIntervalMsec = 15 * 60 * kMsecPerSec; // 15 minutes
|
||||
|
||||
struct PodcastDownloader::Task {
|
||||
Task() : file(NULL) {}
|
||||
@ -47,14 +49,21 @@ PodcastDownloader::PodcastDownloader(Application* app, QObject* parent)
|
||||
backend_(app_->podcast_backend()),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
disallowed_filename_characters_("[^a-zA-Z0-9_~ -]"),
|
||||
auto_download_(false),
|
||||
delete_after_secs_(0),
|
||||
current_task_(NULL),
|
||||
last_progress_signal_(0)
|
||||
last_progress_signal_(0),
|
||||
auto_delete_timer_(new QTimer(this))
|
||||
{
|
||||
connect(backend_, SIGNAL(EpisodesAdded(QList<PodcastEpisode>)),
|
||||
SLOT(EpisodesAdded(QList<PodcastEpisode>)));
|
||||
connect(backend_, SIGNAL(SubscriptionAdded(Podcast)),
|
||||
SLOT(SubscriptionAdded(Podcast)));
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
|
||||
connect(auto_delete_timer_, SIGNAL(timeout()), SLOT(AutoDelete()));
|
||||
|
||||
auto_delete_timer_->setInterval(kAutoDeleteCheckIntervalMsec);
|
||||
auto_delete_timer_->start();
|
||||
|
||||
ReloadSettings();
|
||||
}
|
||||
@ -77,6 +86,7 @@ void PodcastDownloader::ReloadSettings() {
|
||||
|
||||
auto_download_ = s.value("auto_download", false).toBool();
|
||||
download_dir_ = s.value("download_dir", DefaultDownloadDir()).toString();
|
||||
delete_after_secs_ = s.value("delete_after", 0).toInt();
|
||||
}
|
||||
|
||||
void PodcastDownloader::DownloadEpisode(const PodcastEpisode& episode) {
|
||||
@ -244,3 +254,25 @@ void PodcastDownloader::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::AutoDelete() {
|
||||
if (delete_after_secs_ <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDateTime max_date = QDateTime::currentDateTime();
|
||||
max_date.addSecs(-delete_after_secs_);
|
||||
|
||||
PodcastEpisodeList old_episodes = backend_->GetOldDownloadedEpisodes(max_date);
|
||||
if (old_episodes.isEmpty())
|
||||
return;
|
||||
|
||||
qLog(Info) << "Deleting" << old_episodes.count()
|
||||
<< "episodes because they were last listened to"
|
||||
<< (delete_after_secs_ / kSecsPerDay)
|
||||
<< "days ago";
|
||||
|
||||
foreach (const PodcastEpisode& episode, old_episodes) {
|
||||
DeleteEpisode(episode);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public:
|
||||
};
|
||||
|
||||
static const char* kSettingsGroup;
|
||||
static const int kAutoDeleteCheckIntervalMsec;
|
||||
|
||||
QString DefaultDownloadDir() const;
|
||||
|
||||
@ -70,6 +71,8 @@ private slots:
|
||||
void ReplyFinished();
|
||||
void ReplyDownloadProgress(qint64 received, qint64 total);
|
||||
|
||||
void AutoDelete();
|
||||
|
||||
private:
|
||||
struct Task;
|
||||
|
||||
@ -89,12 +92,15 @@ private:
|
||||
|
||||
bool auto_download_;
|
||||
QString download_dir_;
|
||||
int delete_after_secs_;
|
||||
|
||||
Task* current_task_;
|
||||
QQueue<Task*> queued_tasks_;
|
||||
QSet<int> downloading_episode_ids_;
|
||||
|
||||
time_t last_progress_signal_;
|
||||
|
||||
QTimer* auto_delete_timer_;
|
||||
};
|
||||
|
||||
#endif // PODCASTDOWNLOADER_H
|
||||
|
@ -29,7 +29,7 @@
|
||||
const QStringList PodcastEpisode::kColumns = QStringList()
|
||||
<< "podcast_id" << "title" << "description" << "author"
|
||||
<< "publication_date" << "duration_secs" << "url" << "listened"
|
||||
<< "downloaded" << "local_url" << "extra";
|
||||
<< "listened_date" << "downloaded" << "local_url" << "extra";
|
||||
|
||||
const QString PodcastEpisode::kColumnSpec = PodcastEpisode::kColumns.join(", ");
|
||||
const QString PodcastEpisode::kJoinSpec = Utilities::Prepend("e.", PodcastEpisode::kColumns).join(", ");
|
||||
@ -50,8 +50,9 @@ struct PodcastEpisode::Private : public QSharedData {
|
||||
QUrl url_;
|
||||
|
||||
bool listened_;
|
||||
bool downloaded_;
|
||||
QDateTime listened_date_;
|
||||
|
||||
bool downloaded_;
|
||||
QUrl local_url_;
|
||||
|
||||
QVariantMap extra_;
|
||||
@ -93,6 +94,7 @@ const QDateTime& PodcastEpisode::publication_date() const { return d->publicatio
|
||||
int PodcastEpisode::duration_secs() const { return d->duration_secs_; }
|
||||
const QUrl& PodcastEpisode::url() const { return d->url_; }
|
||||
bool PodcastEpisode::listened() const { return d->listened_; }
|
||||
const QDateTime& PodcastEpisode::listened_date() const { return d->listened_date_; }
|
||||
bool PodcastEpisode::downloaded() const { return d->downloaded_; }
|
||||
const QUrl& PodcastEpisode::local_url() const { return d->local_url_; }
|
||||
const QVariantMap& PodcastEpisode::extra() const { return d->extra_; }
|
||||
@ -107,6 +109,7 @@ void PodcastEpisode::set_publication_date(const QDateTime& v) { d->publication_d
|
||||
void PodcastEpisode::set_duration_secs(int v) { d->duration_secs_ = v; }
|
||||
void PodcastEpisode::set_url(const QUrl& v) { d->url_ = v; }
|
||||
void PodcastEpisode::set_listened(bool v) { d->listened_ = v; }
|
||||
void PodcastEpisode::set_listened_date(const QDateTime& v) { d->listened_date_ = v; }
|
||||
void PodcastEpisode::set_downloaded(bool v) { d->downloaded_ = v; }
|
||||
void PodcastEpisode::set_local_url(const QUrl& v) { d->local_url_ = v; }
|
||||
void PodcastEpisode::set_extra(const QVariantMap& v) { d->extra_ = v; }
|
||||
@ -122,10 +125,11 @@ void PodcastEpisode::InitFromQuery(const QSqlQuery& query) {
|
||||
d->duration_secs_ = query.value(6).toInt();
|
||||
d->url_ = QUrl::fromEncoded(query.value(7).toByteArray());
|
||||
d->listened_ = query.value(8).toBool();
|
||||
d->downloaded_ = query.value(9).toBool();
|
||||
d->local_url_ = QUrl::fromEncoded(query.value(10).toByteArray());
|
||||
d->listened_date_ = QDateTime::fromTime_t(query.value(9).toUInt());
|
||||
d->downloaded_ = query.value(10).toBool();
|
||||
d->local_url_ = QUrl::fromEncoded(query.value(11).toByteArray());
|
||||
|
||||
QDataStream extra_stream(query.value(11).toByteArray());
|
||||
QDataStream extra_stream(query.value(12).toByteArray());
|
||||
extra_stream >> d->extra_;
|
||||
}
|
||||
|
||||
@ -138,6 +142,7 @@ void PodcastEpisode::BindToQuery(QSqlQuery* query) const {
|
||||
query->bindValue(":duration_secs", d->duration_secs_);
|
||||
query->bindValue(":url", d->url_.toEncoded());
|
||||
query->bindValue(":listened", d->listened_);
|
||||
query->bindValue(":listened_date", d->listened_date_.toTime_t());
|
||||
query->bindValue(":downloaded", d->downloaded_);
|
||||
query->bindValue(":local_url", d->local_url_.toEncoded());
|
||||
|
||||
|
@ -55,6 +55,7 @@ public:
|
||||
int duration_secs() const;
|
||||
const QUrl& url() const;
|
||||
bool listened() const;
|
||||
const QDateTime& listened_date() const;
|
||||
bool downloaded() const;
|
||||
const QUrl& local_url() const;
|
||||
const QVariantMap& extra() const;
|
||||
@ -69,6 +70,7 @@ public:
|
||||
void set_duration_secs(int v);
|
||||
void set_url(const QUrl& v);
|
||||
void set_listened(bool v);
|
||||
void set_listened_date(const QDateTime& v);
|
||||
void set_downloaded(bool v);
|
||||
void set_local_url(const QUrl& v);
|
||||
void set_extra(const QVariantMap& v);
|
||||
|
@ -511,6 +511,7 @@ void PodcastService::CurrentSongChanged(const Song& metadata) {
|
||||
// Mark it as listened if it's not already
|
||||
if (!episode.listened()) {
|
||||
episode.set_listened(true);
|
||||
episode.set_listened_date(QDateTime::currentDateTime());
|
||||
backend_->UpdateEpisodes(PodcastEpisodeList() << episode);
|
||||
}
|
||||
}
|
||||
@ -526,9 +527,14 @@ void PodcastService::SetListened() {
|
||||
void PodcastService::SetListened(const QModelIndexList& indexes, bool listened) {
|
||||
PodcastEpisodeList episodes;
|
||||
|
||||
QDateTime current_date_time = QDateTime::currentDateTime();
|
||||
|
||||
foreach (const QModelIndex& index, indexes) {
|
||||
PodcastEpisode episode = index.data(Role_Episode).value<PodcastEpisode>();
|
||||
episode.set_listened(listened);
|
||||
if (listened) {
|
||||
episode.set_listened_date(current_date_time);
|
||||
}
|
||||
episodes << episode;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "ui_podcastsettingspage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "ui/settingsdialog.h"
|
||||
@ -73,8 +74,7 @@ void PodcastSettingsPage::Load() {
|
||||
s.value("download_dir", default_download_dir).toString()));
|
||||
|
||||
ui_->auto_download->setChecked(s.value("auto_download", false).toBool());
|
||||
ui_->delete_after->setValue(s.value("delete_after", 0).toInt() / (24*60*60));
|
||||
ui_->delete_unplayed->setChecked(s.value("delete_unplayed", false).toBool());
|
||||
ui_->delete_after->setValue(s.value("delete_after", 0).toInt() / kSecsPerDay);
|
||||
ui_->username->setText(s.value("gpodder_username").toString());
|
||||
ui_->device_name->setText(s.value("gpodder_device_name", GPodderSync::DefaultDeviceName()).toString());
|
||||
|
||||
@ -93,8 +93,7 @@ void PodcastSettingsPage::Save() {
|
||||
ui_->check_interval->itemData(ui_->check_interval->currentIndex()));
|
||||
s.setValue("download_dir", QDir::fromNativeSeparators(ui_->download_dir->text()));
|
||||
s.setValue("auto_download", ui_->auto_download->isChecked());
|
||||
s.setValue("delete_after", ui_->delete_after->value());
|
||||
s.setValue("delete_unplayed", ui_->delete_unplayed->isChecked());
|
||||
s.setValue("delete_after", ui_->delete_after->value() * kSecsPerDay);
|
||||
s.setValue("gpodder_device_name", ui_->device_name->text());
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,9 @@
|
||||
<string>Cleaning up</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
@ -138,13 +141,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="delete_unplayed">
|
||||
<property name="text">
|
||||
<string>Also delete unplayed episodes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -256,7 +252,6 @@
|
||||
<tabstop>download_dir_browse</tabstop>
|
||||
<tabstop>auto_download</tabstop>
|
||||
<tabstop>delete_after</tabstop>
|
||||
<tabstop>delete_unplayed</tabstop>
|
||||
<tabstop>username</tabstop>
|
||||
<tabstop>password</tabstop>
|
||||
<tabstop>device_name</tabstop>
|
||||
|
Loading…
x
Reference in New Issue
Block a user