Clementine-audio-player-Mac.../src/podcasts/podcastdownloader.cpp

230 lines
6.9 KiB
C++
Raw Normal View History

2012-03-10 16:32:36 +01:00
/* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.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 "podcastbackend.h"
#include "podcastdownloader.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/utilities.h"
#include "library/librarydirectorymodel.h"
#include "library/librarymodel.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QNetworkReply>
#include <QSettings>
const char* PodcastDownloader::kSettingsGroup = "Podcasts";
struct PodcastDownloader::Task {
Task() : file(NULL) {}
~Task() { delete file; }
PodcastEpisode episode;
QFile* file;
};
PodcastDownloader::PodcastDownloader(Application* app, QObject* parent)
: QObject(parent),
app_(app),
backend_(app_->podcast_backend()),
2012-03-10 16:32:36 +01:00
network_(new NetworkAccessManager(this)),
disallowed_filename_characters_("[^a-zA-Z0-9_~ -]"),
current_task_(NULL),
last_progress_signal_(0)
{
connect(backend_, SIGNAL(EpisodesAdded(QList<PodcastEpisode>)),
2012-03-10 16:32:36 +01:00
SLOT(EpisodesAdded(QList<PodcastEpisode>)));
connect(backend_, SIGNAL(SubscriptionAdded(Podcast)),
2012-03-10 16:32:36 +01:00
SLOT(SubscriptionAdded(Podcast)));
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
ReloadSettings();
}
QString PodcastDownloader::DefaultDownloadDir() const {
QString prefix = QDir::homePath();
LibraryDirectoryModel* model = app_->library_model()->directory_model();
if (model->rowCount() > 0) {
// Download to the first library directory if there is one set
prefix = model->index(0, 0).data().toString();
}
return prefix + "/Podcasts";
}
void PodcastDownloader::ReloadSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
auto_download_ = s.value("auto_download", false).toBool();
2012-03-10 16:32:36 +01:00
download_dir_ = s.value("download_dir", DefaultDownloadDir()).toString();
}
void PodcastDownloader::DownloadEpisode(const PodcastEpisode& episode) {
if (downloading_episode_ids_.contains(episode.database_id()))
return;
downloading_episode_ids_.insert(episode.database_id());
2012-03-10 16:32:36 +01:00
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::FinishAndDelete(Task* task) {
downloading_episode_ids_.remove(task->episode.database_id());
emit ProgressChanged(task->episode, Finished, 0);
delete task;
NextTask();
}
2012-03-10 16:32:36 +01:00
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());
2012-03-10 16:32:36 +01:00
if (!podcast.is_valid()) {
qLog(Warning) << "The podcast that contains episode" << task->episode.url()
<< "doesn't exist any more";
FinishAndDelete(task);
2012-03-10 16:32:36 +01:00
return;
}
const QString file_extension = QFileInfo(task->episode.url().path()).suffix();
const QString directory = download_dir_ + "/" +
SanitiseFilenameComponent(podcast.title());
const QString filename =
SanitiseFilenameComponent(task->episode.title()) + "." + file_extension;
const QString filepath = directory + "/" + filename;
// Open the output file
QDir().mkpath(directory);
task->file = new QFile(filepath);
if (!task->file->open(QIODevice::WriteOnly)) {
qLog(Warning) << "Could not open the file" << filepath << "for writing";
FinishAndDelete(task);
2012-03-10 16:32:36 +01:00
return;
}
qLog(Info) << "Downloading" << task->episode.url() << "to" << filepath;
// 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);
}
void PodcastDownloader::NextTask() {
current_task_ = NULL;
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)
2012-03-10 16:32:36 +01:00
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,
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_);
2012-03-10 16:32:36 +01:00
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_);
2012-03-10 16:32:36 +01:00
}
QString PodcastDownloader::SanitiseFilenameComponent(const QString& text) const {
return QString(text).replace(disallowed_filename_characters_, " ").simplified();
}
void PodcastDownloader::SubscriptionAdded(const Podcast& podcast) {
EpisodesAdded(podcast.episodes());
2012-03-10 16:32:36 +01:00
}
void PodcastDownloader::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
if (auto_download_) {
foreach (const PodcastEpisode& episode, episodes) {
DownloadEpisode(episode);
}
}
2012-03-10 16:32:36 +01:00
}