commit
946dc0ce3e
|
@ -282,6 +282,7 @@ set(SOURCES
|
|||
podcasts/podcast.cpp
|
||||
podcasts/podcastbackend.cpp
|
||||
podcasts/podcastdiscoverymodel.cpp
|
||||
podcasts/podcastdeleter.cpp
|
||||
podcasts/podcastdownloader.cpp
|
||||
podcasts/podcastepisode.cpp
|
||||
podcasts/podcastinfowidget.cpp
|
||||
|
@ -578,6 +579,7 @@ set(HEADERS
|
|||
podcasts/itunessearchpage.h
|
||||
podcasts/podcastbackend.h
|
||||
podcasts/podcastdiscoverymodel.h
|
||||
podcasts/podcastdeleter.h
|
||||
podcasts/podcastdownloader.h
|
||||
podcasts/podcastinfowidget.h
|
||||
podcasts/podcastservice.h
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "playlist/playlistmanager.h"
|
||||
#include "podcasts/gpoddersync.h"
|
||||
#include "podcasts/podcastbackend.h"
|
||||
#include "podcasts/podcastdeleter.h"
|
||||
#include "podcasts/podcastdownloader.h"
|
||||
#include "podcasts/podcastupdater.h"
|
||||
|
||||
|
@ -73,6 +74,7 @@ Application::Application(QObject* parent)
|
|||
library_(nullptr),
|
||||
device_manager_(nullptr),
|
||||
podcast_updater_(nullptr),
|
||||
podcast_deleter_(nullptr),
|
||||
podcast_downloader_(nullptr),
|
||||
gpodder_sync_(nullptr),
|
||||
moodbar_loader_(nullptr),
|
||||
|
@ -107,6 +109,10 @@ Application::Application(QObject* parent)
|
|||
library_ = new Library(this, this);
|
||||
device_manager_ = new DeviceManager(this, this);
|
||||
podcast_updater_ = new PodcastUpdater(this, this);
|
||||
|
||||
podcast_deleter_ = new PodcastDeleter(this, this);
|
||||
MoveToNewThread(podcast_deleter_);
|
||||
|
||||
podcast_downloader_ = new PodcastDownloader(this, this);
|
||||
gpodder_sync_ = new GPodderSync(this, this);
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ class NetworkRemote;
|
|||
class NetworkRemoteHelper;
|
||||
class Player;
|
||||
class PlaylistBackend;
|
||||
class PodcastDeleter;
|
||||
class PodcastDownloader;
|
||||
class PlaylistManager;
|
||||
class PodcastBackend;
|
||||
|
@ -83,6 +84,7 @@ class Application : public QObject {
|
|||
Library* library() const { return library_; }
|
||||
DeviceManager* device_manager() const { return device_manager_; }
|
||||
PodcastUpdater* podcast_updater() const { return podcast_updater_; }
|
||||
PodcastDeleter* podcast_deleter() const { return podcast_deleter_; }
|
||||
PodcastDownloader* podcast_downloader() const { return podcast_downloader_; }
|
||||
GPodderSync* gpodder_sync() const { return gpodder_sync_; }
|
||||
MoodbarLoader* moodbar_loader() const { return moodbar_loader_; }
|
||||
|
@ -128,6 +130,7 @@ class Application : public QObject {
|
|||
Library* library_;
|
||||
DeviceManager* device_manager_;
|
||||
PodcastUpdater* podcast_updater_;
|
||||
PodcastDeleter* podcast_deleter_;
|
||||
PodcastDownloader* podcast_downloader_;
|
||||
GPodderSync* gpodder_sync_;
|
||||
MoodbarLoader* moodbar_loader_;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2010-2011, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010-2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2011, Tyler Rhodes <tyler.s.rhodes@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010-2011, 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010-2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
Copyright 2014, Andreas <asfa194@gmail.com>
|
||||
Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
Copyright 2014, Andreas <asfa194@gmail.com>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
Copyright 2011, Angus Gratton <gus@projectgus.com>
|
||||
Copyright 2012, Kacper "mattrick" Banasik <mattrick@jabster.pl>
|
||||
Copyright 2013, Martin Brodbeck <martin@brodbeck-online.de>
|
||||
Copyright 2013, Andreas <asfa194@gmail.com>
|
||||
Copyright 2013-2014, Andreas <asfa194@gmail.com>
|
||||
Copyright 2013, Joel Bradshaw <cincodenada@gmail.com>
|
||||
Copyright 2013, Uwe Klotz <uwe.klotz@gmail.com>
|
||||
Copyright 2013, Mateusz Kowalczyk <fuuzetsu@fuuzetsu.co.uk>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
Copyright 2013, Joel Bradshaw <cincodenada@gmail.com>
|
||||
Copyright 2013, Uwe Klotz <uwe.klotz@gmail.com>
|
||||
Copyright 2013, Mateusz Kowalczyk <fuuzetsu@fuuzetsu.co.uk>
|
||||
Copyright 2014, Andreas <asfa194@gmail.com>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -18,15 +18,16 @@
|
|||
*/
|
||||
|
||||
#include "addpodcastbyurl.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "ui_addpodcastbyurl.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QNetworkReply>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "ui_addpodcastbyurl.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
AddPodcastByUrl::AddPodcastByUrl(Application* app, QWidget* parent)
|
||||
: AddPodcastPage(app, parent),
|
||||
ui_(new Ui_AddPodcastByUrl),
|
||||
|
|
|
@ -18,6 +18,11 @@
|
|||
*/
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
#include "addpodcastbyurl.h"
|
||||
#include "fixedopmlpage.h"
|
||||
#include "gpoddersearchpage.h"
|
||||
|
@ -30,10 +35,6 @@
|
|||
#include "ui/iconloader.h"
|
||||
#include "widgets/widgetfadehelper.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
const char* AddPodcastDialog::kBbcOpmlUrl =
|
||||
"http://www.bbc.co.uk/podcasts.opml";
|
||||
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
*/
|
||||
|
||||
#include "fixedopmlpage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
FixedOpmlPage::FixedOpmlPage(const QUrl& opml_url, const QString& title,
|
||||
const QIcon& icon, Application* app,
|
||||
QWidget* parent)
|
||||
|
|
|
@ -18,14 +18,15 @@
|
|||
*/
|
||||
|
||||
#include "gpoddersearchpage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "podcast.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "ui_gpoddersearchpage.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
GPodderSearchPage::GPodderSearchPage(Application* app, QWidget* parent)
|
||||
: AddPodcastPage(app, parent),
|
||||
ui_(new Ui_GPodderSearchPage),
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
*/
|
||||
|
||||
#include "gpoddersync.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include "podcastbackend.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "core/application.h"
|
||||
|
@ -28,12 +35,6 @@
|
|||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
const char* GPodderSync::kSettingsGroup = "Podcasts";
|
||||
const int GPodderSync::kFlushUpdateQueueDelay = 30 * kMsecPerSec; // 30 seconds
|
||||
const int GPodderSync::kGetUpdatesInterval =
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
*/
|
||||
|
||||
#include "gpoddertoptagsmodel.h"
|
||||
|
||||
#include <ApiRequest.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "gpoddertoptagspage.h"
|
||||
#include "podcast.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
#include <ApiRequest.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
GPodderTopTagsModel::GPodderTopTagsModel(mygpo::ApiRequest* api,
|
||||
Application* app, QObject* parent)
|
||||
: PodcastDiscoveryModel(app, parent), api_(api) {}
|
||||
|
|
|
@ -17,13 +17,14 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gpoddertoptagsmodel.h"
|
||||
#include "gpoddertoptagspage.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "gpoddertoptagsmodel.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
const int GPodderTopTagsPage::kMaxTagCount = 100;
|
||||
|
||||
GPodderTopTagsPage::GPodderTopTagsPage(Application* app, QWidget* parent)
|
||||
|
|
|
@ -18,16 +18,16 @@
|
|||
*/
|
||||
|
||||
#include "itunessearchpage.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "podcast.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "ui_itunessearchpage.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
|
||||
const char* ITunesSearchPage::kUrlBase =
|
||||
"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStoreServices.woa/"
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
*/
|
||||
|
||||
#include "podcast.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <Podcast.h>
|
||||
|
||||
#include "core/utilities.h"
|
||||
|
||||
const QStringList Podcast::kColumns = QStringList() << "url"
|
||||
<< "title"
|
||||
<< "description"
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
*/
|
||||
|
||||
#include "podcastbackend.h"
|
||||
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/database.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/scopedtransaction.h"
|
||||
|
||||
#include <QMutexLocker>
|
||||
|
||||
PodcastBackend::PodcastBackend(Application* app, QObject* parent)
|
||||
: QObject(parent), app_(app), db_(app->database()) {}
|
||||
|
||||
|
@ -322,6 +323,26 @@ PodcastEpisodeList PodcastBackend::GetOldDownloadedEpisodes(
|
|||
return ret;
|
||||
}
|
||||
|
||||
PodcastEpisode PodcastBackend::GetOldestDownloadedListenedEpisode() {
|
||||
PodcastEpisode ret;
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QSqlQuery q("SELECT ROWID, " + PodcastEpisode::kColumnSpec +
|
||||
" FROM podcast_episodes"
|
||||
" WHERE downloaded = 'true'"
|
||||
" AND listened = 'true'"
|
||||
" ORDER BY listened_date ASC",
|
||||
db);
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q)) return ret;
|
||||
q.next();
|
||||
ret.InitFromQuery(q);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PodcastEpisodeList PodcastBackend::GetNewDownloadedEpisodes() {
|
||||
PodcastEpisodeList ret;
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ class PodcastBackend : public QObject {
|
|||
PodcastEpisode GetEpisodeById(int id);
|
||||
PodcastEpisode GetEpisodeByUrl(const QUrl& url);
|
||||
PodcastEpisode GetEpisodeByUrlOrLocalUrl(const QUrl& url);
|
||||
PodcastEpisode GetOldestDownloadedListenedEpisode();
|
||||
|
||||
// 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
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcastdeleter.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "podcastbackend.h"
|
||||
|
||||
const char* PodcastDeleter::kSettingsGroup = "Podcasts";
|
||||
const int PodcastDeleter::kAutoDeleteCheckIntervalMsec =
|
||||
60 * 6 * 60 * kMsecPerSec;
|
||||
|
||||
PodcastDeleter::PodcastDeleter(Application* app, QObject* parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
backend_(app_->podcast_backend()),
|
||||
delete_after_secs_(0),
|
||||
auto_delete_timer_(new QTimer(this)) {
|
||||
ReloadSettings();
|
||||
auto_delete_timer_->setSingleShot(true);
|
||||
AutoDelete();
|
||||
connect(auto_delete_timer_, SIGNAL(timeout()), SLOT(AutoDelete()));
|
||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
|
||||
}
|
||||
|
||||
void PodcastDeleter::DeleteEpisode(const PodcastEpisode& episode) {
|
||||
// Delete the local file
|
||||
if (!QFile::remove(episode.local_url().toLocalFile())) {
|
||||
qLog(Warning) << "The local file" << episode.local_url().toLocalFile()
|
||||
<< "could not be removed";
|
||||
}
|
||||
|
||||
// Update the episode in the DB
|
||||
PodcastEpisode episode_copy(episode);
|
||||
episode_copy.set_downloaded(false);
|
||||
episode_copy.set_local_url(QUrl());
|
||||
episode_copy.set_listened_date(QDateTime());
|
||||
backend_->UpdateEpisodes(PodcastEpisodeList() << episode_copy);
|
||||
}
|
||||
|
||||
void PodcastDeleter::ReloadSettings() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
delete_after_secs_ = s.value("delete_after", 0).toInt();
|
||||
AutoDelete();
|
||||
}
|
||||
|
||||
void PodcastDeleter::AutoDelete() {
|
||||
if (delete_after_secs_ <= 0) {
|
||||
return;
|
||||
}
|
||||
auto_delete_timer_->stop();
|
||||
QDateTime max_date = QDateTime::currentDateTime();
|
||||
qint64 timeout_ms;
|
||||
PodcastEpisode oldest_episode;
|
||||
QDateTime oldest_episode_time;
|
||||
max_date = max_date.addSecs(-delete_after_secs_);
|
||||
|
||||
PodcastEpisodeList old_episodes =
|
||||
backend_->GetOldDownloadedEpisodes(max_date);
|
||||
|
||||
qLog(Info) << "Deleting" << old_episodes.count()
|
||||
<< "episodes because they were last listened to"
|
||||
<< (delete_after_secs_ / kSecsPerDay) << "days ago";
|
||||
|
||||
for (const PodcastEpisode& episode : old_episodes) {
|
||||
DeleteEpisode(episode);
|
||||
}
|
||||
|
||||
oldest_episode = backend_->GetOldestDownloadedListenedEpisode();
|
||||
if (!oldest_episode.listened_date().isValid()) {
|
||||
oldest_episode_time = QDateTime::currentDateTime();
|
||||
} else {
|
||||
oldest_episode_time = oldest_episode.listened_date();
|
||||
}
|
||||
|
||||
timeout_ms = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
||||
timeout_ms -= oldest_episode_time.toMSecsSinceEpoch();
|
||||
timeout_ms = (delete_after_secs_ * kMsecPerSec) - timeout_ms;
|
||||
if (timeout_ms >= 0) {
|
||||
auto_delete_timer_->setInterval(timeout_ms);
|
||||
} else {
|
||||
auto_delete_timer_->setInterval(kAutoDeleteCheckIntervalMsec);
|
||||
}
|
||||
auto_delete_timer_->start();
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PODCASTS_PODCASTDELETER_H_
|
||||
#define PODCASTS_PODCASTDELETER_H_
|
||||
|
||||
#include "core/network.h"
|
||||
#include "podcast.h"
|
||||
#include "podcastepisode.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QRegExp>
|
||||
#include <QSet>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <time.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
class Application;
|
||||
class PodcastBackend;
|
||||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class PodcastDeleter : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PodcastDeleter(Application* app, QObject* parent = nullptr);
|
||||
static const char* kSettingsGroup;
|
||||
static const int kAutoDeleteCheckIntervalMsec;
|
||||
|
||||
public slots:
|
||||
// Deletes downloaded data for this episode
|
||||
void DeleteEpisode(const PodcastEpisode& episode);
|
||||
void AutoDelete();
|
||||
void ReloadSettings();
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
PodcastBackend* backend_;
|
||||
int delete_after_secs_;
|
||||
QTimer* auto_delete_timer_;
|
||||
};
|
||||
|
||||
#endif // PODCASTS_PODCASTDELETER_H_
|
|
@ -17,16 +17,17 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "opmlcontainer.h"
|
||||
#include "podcast.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "core/application.h"
|
||||
#include "ui/iconloader.h"
|
||||
#include "ui/standarditemiconloader.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QSet>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "opmlcontainer.h"
|
||||
#include "podcast.h"
|
||||
#include "ui/iconloader.h"
|
||||
#include "ui/standarditemiconloader.h"
|
||||
|
||||
PodcastDiscoveryModel::PodcastDiscoveryModel(Application* app, QObject* parent)
|
||||
: QStandardItemModel(parent),
|
||||
app_(app),
|
||||
|
|
|
@ -17,16 +17,7 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
|
@ -35,17 +26,90 @@
|
|||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "podcastbackend.h"
|
||||
|
||||
const char* PodcastDownloader::kSettingsGroup = "Podcasts";
|
||||
const int PodcastDownloader::kAutoDeleteCheckIntervalMsec =
|
||||
15 * 60 * kMsecPerSec; // 15 minutes
|
||||
|
||||
struct PodcastDownloader::Task {
|
||||
Task() : file(nullptr) {}
|
||||
~Task() { delete file; }
|
||||
Task::Task(PodcastEpisode episode, QFile* file, PodcastBackend* backend)
|
||||
: file_(file),
|
||||
episode_(episode),
|
||||
req_(QNetworkRequest(episode.url())),
|
||||
backend_(backend),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
repl(new RedirectFollower(network_->get(req_))) {
|
||||
connect(repl.get(), SIGNAL(readyRead()), SLOT(reading()));
|
||||
connect(repl.get(), SIGNAL(finished()), SLOT(finishedInternal()));
|
||||
connect(repl.get(), SIGNAL(downloadProgress(qint64, qint64)),
|
||||
SLOT(downloadProgressInternal(qint64, qint64)));
|
||||
emit ProgressChanged(episode_, PodcastDownload::Downloading, 0);
|
||||
}
|
||||
|
||||
PodcastEpisode episode;
|
||||
QFile* file;
|
||||
};
|
||||
PodcastEpisode Task::episode() const { return episode_; }
|
||||
|
||||
void Task::reading() {
|
||||
qint64 bytes = 0;
|
||||
forever {
|
||||
bytes = repl->bytesAvailable();
|
||||
if (bytes <= 0) break;
|
||||
|
||||
file_->write(repl->reply()->read(bytes));
|
||||
}
|
||||
}
|
||||
void Task::finishedPublic() {
|
||||
disconnect(repl.get(), SIGNAL(readyRead()), 0, 0);
|
||||
disconnect(repl.get(), SIGNAL(downloadProgress(qint64, qint64)), 0, 0);
|
||||
disconnect(repl.get(), SIGNAL(finished()), 0, 0);
|
||||
emit ProgressChanged(episode_, PodcastDownload::NotDownloading, 0);
|
||||
// Delete the file
|
||||
file_->remove();
|
||||
emit finished(this);
|
||||
}
|
||||
|
||||
void Task::finishedInternal() {
|
||||
if (repl->error() != QNetworkReply::NoError) {
|
||||
qLog(Warning) << "Error downloading episode:" << repl->errorString();
|
||||
emit ProgressChanged(episode_, PodcastDownload::NotDownloading, 0);
|
||||
// Delete the file
|
||||
file_->remove();
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Download of" << file_->fileName() << "finished";
|
||||
|
||||
// Tell the database the episode has been updated. Get it from the DB again
|
||||
// in case the listened field changed in the mean time.
|
||||
PodcastEpisode episode = episode_;
|
||||
episode.set_downloaded(true);
|
||||
episode.set_local_url(QUrl::fromLocalFile(file_->fileName()));
|
||||
backend_->UpdateEpisodes(PodcastEpisodeList() << episode);
|
||||
Podcast podcast =
|
||||
backend_->GetSubscriptionById(episode.podcast_database_id());
|
||||
Song song = episode_.ToSong(podcast);
|
||||
|
||||
emit ProgressChanged(episode_, PodcastDownload::Finished, 0);
|
||||
|
||||
// I didn't ecountered even a single podcast with a corect metadata
|
||||
TagReaderClient::Instance()->SaveFileBlocking(file_->fileName(), song);
|
||||
emit finished(this);
|
||||
}
|
||||
|
||||
void Task::downloadProgressInternal(qint64 received, qint64 total) {
|
||||
if (total <= 0) {
|
||||
emit ProgressChanged(episode_, PodcastDownload::Downloading, 0);
|
||||
} else {
|
||||
emit ProgressChanged(episode_, PodcastDownload::Downloading,
|
||||
static_cast<float>(received) / total * 100);
|
||||
}
|
||||
}
|
||||
|
||||
PodcastDownloader::PodcastDownloader(Application* app, QObject* parent)
|
||||
: QObject(parent),
|
||||
|
@ -53,20 +117,12 @@ 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_(nullptr),
|
||||
last_progress_signal_(0),
|
||||
auto_delete_timer_(new QTimer(this)) {
|
||||
auto_download_(false) {
|
||||
connect(backend_, SIGNAL(EpisodesAdded(PodcastEpisodeList)),
|
||||
SLOT(EpisodesAdded(PodcastEpisodeList)));
|
||||
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();
|
||||
}
|
||||
|
@ -89,61 +145,10 @@ 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) {
|
||||
if (downloading_episode_ids_.contains(episode.database_id())) return;
|
||||
downloading_episode_ids_.insert(episode.database_id());
|
||||
|
||||
Task* task = new Task;
|
||||
task->episode = episode;
|
||||
|
||||
if (current_task_) {
|
||||
// Add it to the queue
|
||||
queued_tasks_.enqueue(task);
|
||||
emit ProgressChanged(episode, Queued, 0);
|
||||
} else {
|
||||
// Start downloading now
|
||||
StartDownloading(task);
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::DeleteEpisode(const PodcastEpisode& episode) {
|
||||
if (!episode.downloaded() ||
|
||||
downloading_episode_ids_.contains(episode.database_id()))
|
||||
return;
|
||||
|
||||
// Delete the local file
|
||||
if (!QFile::remove(episode.local_url().toLocalFile())) {
|
||||
qLog(Warning) << "The local file" << episode.local_url().toLocalFile()
|
||||
<< "could not be removed";
|
||||
}
|
||||
|
||||
// Update the episode in the DB
|
||||
PodcastEpisode episode_copy(episode);
|
||||
episode_copy.set_downloaded(false);
|
||||
episode_copy.set_local_url(QUrl());
|
||||
backend_->UpdateEpisodes(PodcastEpisodeList() << episode_copy);
|
||||
}
|
||||
|
||||
void PodcastDownloader::FinishAndDelete(Task* task) {
|
||||
Podcast podcast =
|
||||
backend_->GetSubscriptionById(task->episode.podcast_database_id());
|
||||
Song song = task->episode.ToSong(podcast);
|
||||
|
||||
downloading_episode_ids_.remove(task->episode.database_id());
|
||||
emit ProgressChanged(task->episode, Finished, 0);
|
||||
|
||||
// I didn't ecountered even a single podcast with a corect metadata
|
||||
TagReaderClient::Instance()->SaveFileBlocking(task->file->fileName(), song);
|
||||
delete task;
|
||||
|
||||
NextTask();
|
||||
}
|
||||
|
||||
QString PodcastDownloader::FilenameForEpisode(
|
||||
const QString& directory, const PodcastEpisode& episode) const {
|
||||
QString PodcastDownloader::FilenameForEpisode(const QString& directory,
|
||||
const PodcastEpisode& episode) const {
|
||||
const QString file_extension = QFileInfo(episode.url().path()).suffix();
|
||||
int count = 0;
|
||||
|
||||
|
@ -173,103 +178,46 @@ QString PodcastDownloader::FilenameForEpisode(
|
|||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::StartDownloading(Task* task) {
|
||||
current_task_ = task;
|
||||
|
||||
// Need to get the name of the podcast to use in the directory name.
|
||||
Podcast podcast =
|
||||
backend_->GetSubscriptionById(task->episode.podcast_database_id());
|
||||
if (!podcast.is_valid()) {
|
||||
qLog(Warning) << "The podcast that contains episode" << task->episode.url()
|
||||
<< "doesn't exist any more";
|
||||
FinishAndDelete(task);
|
||||
return;
|
||||
void PodcastDownloader::DownloadEpisode(const PodcastEpisode& episode) {
|
||||
for (Task* tas : list_tasks_) {
|
||||
if (tas->episode().database_id() == episode.database_id()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Podcast podcast =
|
||||
backend_->GetSubscriptionById(episode.podcast_database_id());
|
||||
if (!podcast.is_valid()) {
|
||||
qLog(Warning) << "The podcast that contains episode" << episode.url()
|
||||
<< "doesn't exist any more";
|
||||
return;
|
||||
}
|
||||
const QString directory =
|
||||
download_dir_ + "/" + SanitiseFilenameComponent(podcast.title());
|
||||
const QString filepath = FilenameForEpisode(directory, task->episode);
|
||||
const QString filepath = FilenameForEpisode(directory, episode);
|
||||
|
||||
// Open the output file
|
||||
QDir().mkpath(directory);
|
||||
task->file = new QFile(filepath);
|
||||
if (!task->file->open(QIODevice::WriteOnly)) {
|
||||
QFile* file = new QFile(filepath);
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
qLog(Warning) << "Could not open the file" << filepath << "for writing";
|
||||
FinishAndDelete(task);
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Downloading" << task->episode.url() << "to" << filepath;
|
||||
Task* task = new Task(episode, file, backend_);
|
||||
|
||||
// Get the URL
|
||||
QNetworkRequest req(task->episode.url());
|
||||
RedirectFollower* reply = new RedirectFollower(network_->get(req));
|
||||
connect(reply, SIGNAL(readyRead()), SLOT(ReplyReadyRead()));
|
||||
connect(reply, SIGNAL(finished()), SLOT(ReplyFinished()));
|
||||
connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||
SLOT(ReplyDownloadProgress(qint64, qint64)));
|
||||
|
||||
emit ProgressChanged(task->episode, Downloading, 0);
|
||||
list_tasks_ << task;
|
||||
qLog(Info) << "Downloading" << task->episode().url() << "to" << filepath;
|
||||
connect(task, SIGNAL(finished(Task*)), SLOT(ReplyFinished(Task*)));
|
||||
connect(task, SIGNAL(ProgressChanged(const PodcastEpisode&,
|
||||
PodcastDownload::State, int)),
|
||||
SIGNAL(ProgressChanged(const PodcastEpisode&,
|
||||
PodcastDownload::State, int)));
|
||||
}
|
||||
|
||||
void PodcastDownloader::NextTask() {
|
||||
current_task_ = nullptr;
|
||||
|
||||
if (!queued_tasks_.isEmpty()) {
|
||||
StartDownloading(queued_tasks_.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::ReplyReadyRead() {
|
||||
QNetworkReply* reply = qobject_cast<RedirectFollower*>(sender())->reply();
|
||||
if (!reply || !current_task_ || !current_task_->file) return;
|
||||
|
||||
forever {
|
||||
const qint64 bytes = reply->bytesAvailable();
|
||||
if (bytes <= 0) break;
|
||||
|
||||
current_task_->file->write(reply->read(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::ReplyDownloadProgress(qint64 received, qint64 total) {
|
||||
if (!current_task_ || !current_task_->file || total < 1024) return;
|
||||
|
||||
const time_t current_time = QDateTime::currentDateTime().toTime_t();
|
||||
if (last_progress_signal_ == current_time) return;
|
||||
last_progress_signal_ = current_time;
|
||||
|
||||
emit ProgressChanged(current_task_->episode, Downloading,
|
||||
static_cast<float>(received) / total * 100);
|
||||
}
|
||||
|
||||
void PodcastDownloader::ReplyFinished() {
|
||||
RedirectFollower* reply = qobject_cast<RedirectFollower*>(sender());
|
||||
if (!reply || !current_task_ || !current_task_->file) return;
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qLog(Warning) << "Error downloading episode:" << reply->errorString();
|
||||
|
||||
// Delete the file
|
||||
current_task_->file->remove();
|
||||
|
||||
FinishAndDelete(current_task_);
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Download of" << current_task_->file->fileName() << "finished";
|
||||
|
||||
// Tell the database the episode has been updated. Get it from the DB again
|
||||
// in case the listened field changed in the mean time.
|
||||
PodcastEpisode episode =
|
||||
backend_->GetEpisodeById(current_task_->episode.database_id());
|
||||
episode.set_downloaded(true);
|
||||
episode.set_local_url(QUrl::fromLocalFile(current_task_->file->fileName()));
|
||||
backend_->UpdateEpisodes(PodcastEpisodeList() << episode);
|
||||
|
||||
FinishAndDelete(current_task_);
|
||||
void PodcastDownloader::ReplyFinished(Task* task) {
|
||||
list_tasks_.removeAll(task);
|
||||
delete task;
|
||||
}
|
||||
|
||||
QString PodcastDownloader::SanitiseFilenameComponent(const QString& text)
|
||||
|
@ -291,23 +239,29 @@ void PodcastDownloader::EpisodesAdded(const PodcastEpisodeList& episodes) {
|
|||
}
|
||||
}
|
||||
|
||||
void PodcastDownloader::AutoDelete() {
|
||||
if (delete_after_secs_ <= 0) {
|
||||
return;
|
||||
PodcastEpisodeList PodcastDownloader::EpisodesDownloading(const PodcastEpisodeList& episodes) {
|
||||
PodcastEpisodeList ret;
|
||||
for (Task* tas : list_tasks_) {
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
if (tas->episode().database_id() == episode.database_id()) {
|
||||
ret << episode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
for (const PodcastEpisode& episode : old_episodes) {
|
||||
DeleteEpisode(episode);
|
||||
void PodcastDownloader::cancelDownload(const PodcastEpisodeList& episodes) {
|
||||
QList<Task*> ta;
|
||||
for (Task* tas : list_tasks_) {
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
if (tas->episode().database_id() == episode.database_id()) {
|
||||
ta << tas;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Task* tas : ta) {
|
||||
tas->finishedPublic();
|
||||
list_tasks_.removeAll(tas);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,12 @@
|
|||
#ifndef PODCASTS_PODCASTDOWNLOADER_H_
|
||||
#define PODCASTS_PODCASTDOWNLOADER_H_
|
||||
|
||||
#include "core/network.h"
|
||||
#include "podcast.h"
|
||||
#include "podcastepisode.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
|
@ -40,29 +43,57 @@ class PodcastBackend;
|
|||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
namespace PodcastDownload {
|
||||
enum State { NotDownloading, Queued, Downloading, Finished };
|
||||
}
|
||||
|
||||
class Task : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Task(PodcastEpisode episode, QFile* file, PodcastBackend* backend);
|
||||
PodcastEpisode episode() const;
|
||||
|
||||
signals:
|
||||
void ProgressChanged(const PodcastEpisode& episode,
|
||||
PodcastDownload::State state, int percent);
|
||||
void finished(Task* task);
|
||||
|
||||
public slots:
|
||||
void finishedPublic();
|
||||
|
||||
private slots:
|
||||
void reading();
|
||||
void downloadProgressInternal(qint64 received, qint64 total);
|
||||
void finishedInternal();
|
||||
|
||||
private:
|
||||
std::unique_ptr<QFile> file_;
|
||||
PodcastEpisode episode_;
|
||||
QNetworkRequest req_;
|
||||
PodcastBackend* backend_;
|
||||
std::unique_ptr<NetworkAccessManager> network_;
|
||||
std::unique_ptr<RedirectFollower> repl;
|
||||
};
|
||||
|
||||
class PodcastDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PodcastDownloader(Application* app, QObject* parent = nullptr);
|
||||
|
||||
enum State { NotDownloading, Queued, Downloading, Finished };
|
||||
|
||||
static const char* kSettingsGroup;
|
||||
static const int kAutoDeleteCheckIntervalMsec;
|
||||
|
||||
PodcastEpisodeList EpisodesDownloading(const PodcastEpisodeList& episodes);
|
||||
QString DefaultDownloadDir() const;
|
||||
|
||||
public slots:
|
||||
// Adds the episode to the download queue
|
||||
void DownloadEpisode(const PodcastEpisode& episode);
|
||||
|
||||
// Deletes downloaded data for this episode
|
||||
void DeleteEpisode(const PodcastEpisode& episode);
|
||||
void cancelDownload(const PodcastEpisodeList& episodes);
|
||||
|
||||
signals:
|
||||
void ProgressChanged(const PodcastEpisode& episode,
|
||||
PodcastDownloader::State state, int percent);
|
||||
PodcastDownload::State state, int percent);
|
||||
|
||||
private slots:
|
||||
void ReloadSettings();
|
||||
|
@ -70,19 +101,9 @@ class PodcastDownloader : public QObject {
|
|||
void SubscriptionAdded(const Podcast& podcast);
|
||||
void EpisodesAdded(const PodcastEpisodeList& episodes);
|
||||
|
||||
void ReplyReadyRead();
|
||||
void ReplyFinished();
|
||||
void ReplyDownloadProgress(qint64 received, qint64 total);
|
||||
|
||||
void AutoDelete();
|
||||
void ReplyFinished(Task* task);
|
||||
|
||||
private:
|
||||
struct Task;
|
||||
|
||||
void StartDownloading(Task* task);
|
||||
void NextTask();
|
||||
void FinishAndDelete(Task* task);
|
||||
|
||||
QString FilenameForEpisode(const QString& directory,
|
||||
const PodcastEpisode& episode) const;
|
||||
QString SanitiseFilenameComponent(const QString& text) const;
|
||||
|
@ -96,15 +117,8 @@ class PodcastDownloader : public QObject {
|
|||
|
||||
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_;
|
||||
QList<Task*> list_tasks_;
|
||||
};
|
||||
|
||||
#endif // PODCASTS_PODCASTDOWNLOADER_H_
|
||||
|
|
|
@ -17,17 +17,18 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcast.h"
|
||||
#include "podcastepisode.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "podcast.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
const QStringList PodcastEpisode::kColumns = QStringList() << "podcast_id"
|
||||
<< "title"
|
||||
<< "description"
|
||||
|
@ -183,8 +184,10 @@ Song PodcastEpisode::ToSong(const Podcast& podcast) const {
|
|||
ret.set_comment(description());
|
||||
ret.set_id(database_id());
|
||||
ret.set_ctime(publication_date().toTime_t());
|
||||
ret.set_genre(QString("Podcast"));
|
||||
ret.set_genre_id3(186);
|
||||
|
||||
if (listened()) {
|
||||
if (listened() && listened_date().isValid()) {
|
||||
ret.set_mtime(listened_date().toTime_t());
|
||||
} else {
|
||||
ret.set_mtime(publication_date().toTime_t());
|
||||
|
|
|
@ -17,14 +17,15 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "opmlcontainer.h"
|
||||
#include "podcastparser.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "opmlcontainer.h"
|
||||
|
||||
// Namespace constants must be lower case.
|
||||
const char* PodcastParser::kAtomNamespace = "http://www.w3.org/2005/atom";
|
||||
const char* PodcastParser::kItunesNamespace =
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2012-2013, David Sansome <me@davidsansome.com>
|
||||
Copyright 2013-2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2013-2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, Simeon Bird <sbird@andrew.cmu.edu>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,13 +18,13 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
#include "opmlcontainer.h"
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "podcastservice.h"
|
||||
#include "podcastservicemodel.h"
|
||||
#include "podcastupdater.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mergedproxymodel.h"
|
||||
|
@ -32,15 +33,17 @@
|
|||
#include "devices/deviceview.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "library/libraryview.h"
|
||||
#include "opmlcontainer.h"
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastdeleter.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "podcastservicemodel.h"
|
||||
#include "podcastupdater.h"
|
||||
#include "ui/iconloader.h"
|
||||
#include "ui/organisedialog.h"
|
||||
#include "ui/organiseerrordialog.h"
|
||||
#include "ui/standarditemiconloader.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
const char* PodcastService::kServiceName = "Podcasts";
|
||||
const char* PodcastService::kSettingsGroup = "Podcasts";
|
||||
|
||||
|
@ -179,6 +182,32 @@ void PodcastService::CopyToDevice(const QModelIndexList& episode_indexes,
|
|||
if (organise_dialog_->SetSongs(songs)) organise_dialog_->show();
|
||||
}
|
||||
|
||||
void PodcastService::CancelDownload() {
|
||||
CancelDownload(selected_episodes_, explicitly_selected_podcasts_);
|
||||
}
|
||||
|
||||
void PodcastService::CancelDownload(const QModelIndexList& episode_indexes,
|
||||
const QModelIndexList& podcast_indexes) {
|
||||
PodcastEpisode episode_tmp;
|
||||
SongList songs;
|
||||
PodcastEpisodeList episodes;
|
||||
Podcast podcast;
|
||||
for (const QModelIndex& index : episode_indexes) {
|
||||
episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
|
||||
episodes << episode_tmp;
|
||||
}
|
||||
|
||||
for (const QModelIndex& podcast : podcast_indexes) {
|
||||
for (int i = 0; i < podcast.model()->rowCount(podcast); ++i) {
|
||||
const QModelIndex& index = podcast.child(i, 0);
|
||||
episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
|
||||
episodes << episode_tmp;
|
||||
}
|
||||
}
|
||||
episodes = app_->podcast_downloader()->EpisodesDownloading(episodes);
|
||||
app_->podcast_downloader()->cancelDownload(episodes);
|
||||
}
|
||||
|
||||
void PodcastService::LazyPopulate(QStandardItem* parent) {
|
||||
switch (parent->data(InternetModel::Role_Type).toInt()) {
|
||||
case InternetModel::Type_Service:
|
||||
|
@ -190,11 +219,9 @@ void PodcastService::LazyPopulate(QStandardItem* parent) {
|
|||
|
||||
void PodcastService::PopulatePodcastList(QStandardItem* parent) {
|
||||
// Do this here since the downloader won't be created yet in the ctor.
|
||||
connect(
|
||||
app_->podcast_downloader(),
|
||||
SIGNAL(ProgressChanged(PodcastEpisode, PodcastDownloader::State, int)),
|
||||
SLOT(DownloadProgressChanged(PodcastEpisode, PodcastDownloader::State,
|
||||
int)));
|
||||
connect(app_->podcast_downloader(),
|
||||
SIGNAL(ProgressChanged(PodcastEpisode, PodcastDownload::State, int)),
|
||||
SLOT(DownloadProgressChanged(PodcastEpisode, PodcastDownload::State, int)));
|
||||
|
||||
if (default_icon_.isNull()) {
|
||||
default_icon_ = QIcon(":providers/podcast16.png");
|
||||
|
@ -225,7 +252,7 @@ void PodcastService::UpdatePodcastText(QStandardItem* item,
|
|||
}
|
||||
|
||||
void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
||||
PodcastDownloader::State state,
|
||||
PodcastDownload::State state,
|
||||
int percent) {
|
||||
const PodcastEpisode episode =
|
||||
item->data(Role_Episode).value<PodcastEpisode>();
|
||||
|
@ -250,7 +277,7 @@ void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
|||
|
||||
// Queued or downloading episodes get icons, tooltips, and maybe a title.
|
||||
switch (state) {
|
||||
case PodcastDownloader::Queued:
|
||||
case PodcastDownload::Queued:
|
||||
if (queued_icon_.isNull()) {
|
||||
queued_icon_ = QIcon(":icons/22x22/user-away.png");
|
||||
}
|
||||
|
@ -258,7 +285,7 @@ void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
|||
tooltip = tr("Download queued");
|
||||
break;
|
||||
|
||||
case PodcastDownloader::Downloading:
|
||||
case PodcastDownload::Downloading:
|
||||
if (downloading_icon_.isNull()) {
|
||||
downloading_icon_ = IconLoader::Load("go-down");
|
||||
}
|
||||
|
@ -268,8 +295,8 @@ void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
|||
QString("[ %1% ] %2").arg(QString::number(percent), episode.title());
|
||||
break;
|
||||
|
||||
case PodcastDownloader::Finished:
|
||||
case PodcastDownloader::NotDownloading:
|
||||
case PodcastDownload::Finished:
|
||||
case PodcastDownload::NotDownloading:
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -278,6 +305,45 @@ void PodcastService::UpdateEpisodeText(QStandardItem* item,
|
|||
item->setIcon(icon);
|
||||
}
|
||||
|
||||
void PodcastService::UpdatePodcastText(QStandardItem* item,
|
||||
PodcastDownload::State state,
|
||||
int percent) {
|
||||
const Podcast podcast = item->data(Role_Podcast).value<Podcast>();
|
||||
|
||||
QString tooltip;
|
||||
QIcon icon;
|
||||
|
||||
// Queued or downloading podcasts get icons, tooltips, and maybe a title.
|
||||
switch (state) {
|
||||
case PodcastDownload::Queued:
|
||||
if (queued_icon_.isNull()) {
|
||||
queued_icon_ = QIcon(":icons/22x22/user-away.png");
|
||||
}
|
||||
icon = queued_icon_;
|
||||
item->setIcon(icon);
|
||||
tooltip = tr("Download queued");
|
||||
break;
|
||||
|
||||
case PodcastDownload::Downloading:
|
||||
if (downloading_icon_.isNull()) {
|
||||
downloading_icon_ = IconLoader::Load("go-down");
|
||||
}
|
||||
icon = downloading_icon_;
|
||||
item->setIcon(icon);
|
||||
tooltip = tr("Downloading (%1%)...").arg(percent);
|
||||
break;
|
||||
|
||||
case PodcastDownload::Finished:
|
||||
case PodcastDownload::NotDownloading:
|
||||
if (podcast.ImageUrlSmall().isValid()) {
|
||||
icon_loader_->LoadIcon(podcast.ImageUrlSmall().toString(), QString(), item);
|
||||
} else {
|
||||
item->setIcon(default_icon_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QStandardItem* PodcastService::CreatePodcastItem(const Podcast& podcast) {
|
||||
QStandardItem* item = new QStandardItem;
|
||||
|
||||
|
@ -352,6 +418,9 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
|
|||
copy_to_device_ = context_menu_->addAction(
|
||||
IconLoader::Load("multimedia-player-ipod-mini-blue"),
|
||||
tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
cancel_download_ = context_menu_->addAction(
|
||||
IconLoader::Load("cancel"),
|
||||
tr("Cancel download"), this, SLOT(CancelDownload()));
|
||||
remove_selected_action_ = context_menu_->addAction(
|
||||
IconLoader::Load("list-remove"), tr("Unsubscribe"), this,
|
||||
SLOT(RemoveSelectedPodcast()));
|
||||
|
@ -413,6 +482,7 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
|
|||
remove_selected_action_->setEnabled(podcasts);
|
||||
set_new_action_->setEnabled(episodes || podcasts);
|
||||
set_listened_action_->setEnabled(episodes || podcasts);
|
||||
cancel_download_->setEnabled(episodes || podcasts);
|
||||
|
||||
if (selected_episodes_.count() == 1) {
|
||||
const PodcastEpisode episode =
|
||||
|
@ -583,18 +653,20 @@ void PodcastService::DownloadSelectedEpisode() {
|
|||
|
||||
void PodcastService::DeleteDownloadedData() {
|
||||
for (const QModelIndex& index : selected_episodes_) {
|
||||
app_->podcast_downloader()->DeleteEpisode(
|
||||
app_->podcast_deleter()->DeleteEpisode(
|
||||
index.data(Role_Episode).value<PodcastEpisode>());
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastService::DownloadProgressChanged(const PodcastEpisode& episode,
|
||||
PodcastDownloader::State state,
|
||||
PodcastDownload::State state,
|
||||
int percent) {
|
||||
QStandardItem* item = episodes_by_database_id_[episode.database_id()];
|
||||
if (!item) return;
|
||||
QStandardItem* item2 = podcasts_by_database_id_[episode.podcast_database_id()];
|
||||
if (!item || !item2) return;
|
||||
|
||||
UpdateEpisodeText(item, state, percent);
|
||||
UpdatePodcastText(item2, state, percent);
|
||||
}
|
||||
|
||||
void PodcastService::ShowConfig() {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012-2013, David Sansome <me@davidsansome.com>
|
||||
Copyright 2013-2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2013-2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Simeon Bird <sbird@andrew.cmu.edu>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -20,6 +21,7 @@
|
|||
#ifndef PODCASTS_PODCASTSERVICE_H_
|
||||
#define PODCASTS_PODCASTSERVICE_H_
|
||||
|
||||
#include "podcastdeleter.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/internetservice.h"
|
||||
|
@ -84,7 +86,7 @@ class PodcastService : public InternetService {
|
|||
void EpisodesUpdated(const PodcastEpisodeList& episodes);
|
||||
|
||||
void DownloadProgressChanged(const PodcastEpisode& episode,
|
||||
PodcastDownloader::State state, int percent);
|
||||
PodcastDownload::State state, int percent);
|
||||
|
||||
void CurrentSongChanged(const Song& metadata);
|
||||
|
||||
|
@ -92,6 +94,9 @@ class PodcastService : public InternetService {
|
|||
void CopyToDevice(const PodcastEpisodeList& episodes_list);
|
||||
void CopyToDevice(const QModelIndexList& episode_indexes,
|
||||
const QModelIndexList& podcast_indexes);
|
||||
void CancelDownload();
|
||||
void CancelDownload(const QModelIndexList& episode_indexes,
|
||||
const QModelIndexList& podcast_indexes);
|
||||
|
||||
private:
|
||||
void EnsureAddPodcastDialogCreated();
|
||||
|
@ -101,7 +106,11 @@ class PodcastService : public InternetService {
|
|||
void UpdatePodcastText(QStandardItem* item, int unlistened_count) const;
|
||||
void UpdateEpisodeText(
|
||||
QStandardItem* item,
|
||||
PodcastDownloader::State state = PodcastDownloader::NotDownloading,
|
||||
PodcastDownload::State state = PodcastDownload::NotDownloading,
|
||||
int percent = 0);
|
||||
void UpdatePodcastText(
|
||||
QStandardItem* item,
|
||||
PodcastDownload::State state = PodcastDownload::NotDownloading,
|
||||
int percent = 0);
|
||||
|
||||
QStandardItem* CreatePodcastItem(const Podcast& podcast);
|
||||
|
@ -139,6 +148,7 @@ class PodcastService : public InternetService {
|
|||
QAction* set_new_action_;
|
||||
QAction* set_listened_action_;
|
||||
QAction* copy_to_device_;
|
||||
QAction* cancel_download_;
|
||||
QStandardItem* root_;
|
||||
std::unique_ptr<OrganiseDialog> organise_dialog_;
|
||||
|
||||
|
|
|
@ -17,21 +17,22 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gpoddersync.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "podcastsettingspage.h"
|
||||
#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"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "gpoddersync.h"
|
||||
#include "library/librarydirectorymodel.h"
|
||||
#include "library/librarymodel.h"
|
||||
#include "podcastdownloader.h"
|
||||
#include "ui/settingsdialog.h"
|
||||
|
||||
const char* PodcastSettingsPage::kSettingsGroup = "Podcasts";
|
||||
|
||||
PodcastSettingsPage::PodcastSettingsPage(SettingsDialog* dialog)
|
||||
|
|
|
@ -17,17 +17,18 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcastbackend.h"
|
||||
#include "podcastupdater.h"
|
||||
#include "podcasturlloader.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/qhash_qurl.h"
|
||||
#include "core/timeconstants.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include "podcastbackend.h"
|
||||
#include "podcasturlloader.h"
|
||||
|
||||
const char* PodcastUpdater::kSettingsGroup = "Podcasts";
|
||||
|
||||
|
|
Loading…
Reference in New Issue