Merge pull request #4145 from sobkas/master

Initial support for "Copy to device" in a podcast section
This commit is contained in:
John Maguire 2014-01-31 04:31:43 -08:00
commit 7ddf3aab45
11 changed files with 253 additions and 167 deletions

View File

@ -56,16 +56,14 @@ struct sqlite3_tokenizer_module {
int (*xCreate)(
int argc, /* Size of argv array */
const char *const*argv, /* Tokenizer argument strings */
sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
);
sqlite3_tokenizer **ppTokenizer); /* OUT: Created tokenizer */
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
int (*xOpen)(
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);
sqlite3_tokenizer_cursor **ppCursor);/* OUT: Created tokenizer cursor */
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
@ -74,8 +72,7 @@ struct sqlite3_tokenizer_module {
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
int *piPosition /* OUT: Number of tokens returned before this one */
);
int *piPosition); /* OUT: Number of tokens returned before this one */
};
struct sqlite3_tokenizer {
@ -221,7 +218,7 @@ Database::Database(Application* app, QObject* parent, const QString& database_na
{
{
QMutexLocker l(&sNextConnectionIdMutex);
connection_id_ = sNextConnectionId ++;
connection_id_ = sNextConnectionId++;
}
directory_ = QDir::toNativeSeparators(
@ -288,7 +285,7 @@ QSqlDatabase Database::Connect() {
}
// Attach external databases
foreach (const QString& key, attached_databases_.keys()) {
for (const QString& key : attached_databases_.keys()) {
QString filename = attached_databases_[key].filename_;
if (!injected_database_name_.isNull())
@ -303,13 +300,13 @@ QSqlDatabase Database::Connect() {
}
}
if(startup_schema_version_ == -1) {
if (startup_schema_version_ == -1) {
UpdateMainSchema(&db);
}
// We might have to initialise the schema in some attached databases now, if
// they were deleted and don't match up with the main schema version.
foreach (const QString& key, attached_databases_.keys()) {
for (const QString& key : attached_databases_.keys()) {
if (attached_databases_[key].is_temporary_ &&
attached_databases_[key].schema_.isEmpty())
continue;
@ -344,7 +341,7 @@ void Database::UpdateMainSchema(QSqlDatabase* db) {
}
if (schema_version < kSchemaVersion) {
// Update the schema
for (int v=schema_version+1 ; v<= kSchemaVersion ; ++v) {
for (int v = schema_version+1; v <= kSchemaVersion; ++v) {
UpdateDatabaseSchema(v, *db);
}
}
@ -377,7 +374,7 @@ void Database::RecreateAttachedDb(const QString& database_name) {
// We can't just re-attach the database now because it needs to be done for
// each thread. Close all the database connections, so each thread will
// re-attach it when they next connect.
foreach (const QString& name, QSqlDatabase::connectionNames()) {
for (const QString& name : QSqlDatabase::connectionNames()) {
QSqlDatabase::removeDatabase(name);
}
}
@ -432,7 +429,7 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
UrlEncodeFilenameColumn("songs", db);
UrlEncodeFilenameColumn("playlist_items", db);
foreach (const QString& table, db.tables()) {
for (const QString& table : db.tables()) {
if (table.startsWith("device_") && table.endsWith("_songs")) {
UrlEncodeFilenameColumn(table, db);
}
@ -511,12 +508,12 @@ void Database::ExecSchemaCommands(QSqlDatabase& db,
void Database::ExecSongTablesCommands(QSqlDatabase& db,
const QStringList& song_tables,
const QStringList& commands) {
foreach (const QString& command, commands) {
for (const QString& command : commands) {
// There are now lots of "songs" tables that need to have the same schema:
// songs, magnatune_songs, and device_*_songs. We allow a magic value
// in the schema files to update all songs tables at once.
if (command.contains(kMagicAllSongsTables)) {
foreach (const QString& table, song_tables) {
for (const QString& table : song_tables) {
// Another horrible hack: device songs tables don't have matching _fts
// tables, so if this command tries to touch one, ignore it.
if (table.startsWith("device_") &&
@ -543,17 +540,17 @@ QStringList Database::SongsTables(QSqlDatabase& db, int schema_version) const {
QStringList ret;
// look for the tables in the main db
foreach (const QString& table, db.tables()) {
for (const QString& table : db.tables()) {
if (table == "songs" || table.endsWith("_songs"))
ret << table;
}
// look for the tables in attached dbs
foreach (const QString& key, attached_databases_.keys()) {
for (const QString& key : attached_databases_.keys()) {
QSqlQuery q(QString("SELECT NAME FROM %1.sqlite_master"
" WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key), db);
if (q.exec()) {
while(q.next()) {
while (q.next()) {
QString tab_name = key + "." + q.value(0).toString();
ret << tab_name;
}
@ -575,7 +572,6 @@ bool Database::CheckErrors(const QSqlQuery& query) {
qLog(Error) << "faulty query: " << query.lastQuery();
qLog(Error) << "bound values: " << query.boundValues();
app_->AddError("LibraryBackend: " + last_error.text());
return true;
}

View File

@ -35,7 +35,7 @@ const int Organise::kTranscodeProgressInterval = 500;
Organise::Organise(TaskManager* task_manager,
boost::shared_ptr<MusicStorage> destination,
const OrganiseFormat &format, bool copy, bool overwrite,
const QStringList& files, bool eject_after)
const SongList& songs, bool eject_after)
: thread_(NULL),
task_manager_(task_manager),
transcoder_(new Transcoder(this)),
@ -44,7 +44,7 @@ Organise::Organise(TaskManager* task_manager,
copy_(copy),
overwrite_(overwrite),
eject_after_(eject_after),
task_count_(files.count()),
task_count_(songs.count()),
transcode_suffix_(1),
tasks_complete_(0),
started_(false),
@ -53,8 +53,8 @@ Organise::Organise(TaskManager* task_manager,
{
original_thread_ = thread();
foreach (const QString& filename, files) {
tasks_pending_ << Task(filename);
for (const Song& song : songs) {
tasks_pending_ << Task(song);
}
}
@ -67,7 +67,7 @@ void Organise::Start() {
thread_ = new QThread;
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
connect(transcoder_, SIGNAL(JobComplete(QString,bool)), SLOT(FileTranscoded(QString,bool)));
connect(transcoder_, SIGNAL(JobComplete(QString, bool)), SLOT(FileTranscoded(QString, bool)));
moveToThread(thread_);
thread_->start();
@ -79,8 +79,8 @@ void Organise::ProcessSomeFiles() {
if (!destination_->StartCopy(&supported_filetypes_)) {
// Failed to start - mark everything as failed :(
foreach (const Task& task, tasks_pending_)
files_with_errors_ << task.filename_;
for (const Task& task : tasks_pending_)
files_with_errors_ << task.song_.url().toLocalFile();
tasks_pending_.clear();
}
started_ = true;
@ -116,29 +116,17 @@ void Organise::ProcessSomeFiles() {
}
// We process files in batches so we can be cancelled part-way through.
for (int i=0 ; i<kBatchSize ; ++i) {
for (int i = 0; i < kBatchSize; ++i) {
SetSongProgress(0);
if (tasks_pending_.isEmpty())
break;
Task task = tasks_pending_.takeFirst();
qLog(Info) << "Processing" << task.filename_;
qLog(Info) << "Processing" << task.song_.url().toLocalFile();
// Is it a directory?
if (QFileInfo(task.filename_).isDir()) {
QDir dir(task.filename_);
foreach (const QString& entry, dir.entryList(
QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable)) {
tasks_pending_ << Task(task.filename_ + "/" + entry);
task_count_ ++;
}
continue;
}
// Read metadata from the file
Song song;
TagReaderClient::Instance()->ReadFileBlocking(task.filename_, &song);
// Use a Song instead of a tag reader
Song song = task.song_;
if (!song.is_valid())
continue;
@ -150,7 +138,7 @@ void Organise::ProcessSomeFiles() {
song.set_filetype(task.new_filetype_);
// Fiddle the filename extension as well to match the new type
song.set_url(QUrl::fromLocalFile(FiddleFileExtension(song.url().toLocalFile(), task.new_extension_)));
song.set_url(QUrl::fromLocalFile(FiddleFileExtension(song.basefilename(), task.new_extension_)));
song.set_basefilename(FiddleFileExtension(song.basefilename(), task.new_extension_));
// Have to set this to the size of the new file or else funny stuff happens
@ -168,14 +156,14 @@ void Organise::ProcessSomeFiles() {
QString::number(transcode_suffix_++);
task.new_extension_ = preset.extension_;
task.new_filetype_ = dest_type;
tasks_transcoding_[task.filename_] = task;
tasks_transcoding_[task.song_.url().toLocalFile()] = task;
qLog(Debug) << "Transcoding to" << task.transcoded_filename_;
// Start the transcoding - this will happen in the background and
// FileTranscoded() will get called when it's done. At that point the
// task will get re-added to the pending queue with the new filename.
transcoder_->AddJob(task.filename_, preset, task.transcoded_filename_);
transcoder_->AddJob(task.song_.url().toLocalFile(), preset, task.transcoded_filename_);
transcoder_->Start();
continue;
}
@ -183,7 +171,7 @@ void Organise::ProcessSomeFiles() {
MusicStorage::CopyJob job;
job.source_ = task.transcoded_filename_.isEmpty() ?
task.filename_ : task.transcoded_filename_;
task.song_.url().toLocalFile() : task.transcoded_filename_;
job.destination_ = format_.GetFilenameForSong(song);
job.metadata_ = song;
job.overwrite_ = overwrite_;
@ -192,7 +180,7 @@ void Organise::ProcessSomeFiles() {
this, _1, !task.transcoded_filename_.isEmpty());
if (!destination_->CopyToStorage(job)) {
files_with_errors_ << task.filename_;
files_with_errors_ << task.song_.basefilename();
}
// Clean up the temporary transcoded file
@ -240,7 +228,7 @@ Song::FileType Organise::CheckTranscode(Song::FileType original_type) const {
void Organise::SetSongProgress(float progress, bool transcoded) {
const int max = transcoded ? 50 : 100;
current_copy_progress_ = (transcoded ? 50 : 0) +
qBound(0, int(progress * max), max-1);
qBound(0, static_cast<int>(progress * max), max-1);
UpdateProgress();
}
@ -249,7 +237,7 @@ void Organise::UpdateProgress() {
// Update transcoding progress
QMap<QString, float> transcode_progress = transcoder_->GetProgress();
foreach (const QString& filename, transcode_progress.keys()) {
for (const QString& filename : transcode_progress.keys()) {
if (!tasks_transcoding_.contains(filename))
continue;
tasks_transcoding_[filename].transcode_progress_ = transcode_progress[filename];
@ -260,11 +248,11 @@ void Organise::UpdateProgress() {
// only need to be copied total 100.
int progress = tasks_complete_ * 100;
foreach (const Task& task, tasks_pending_) {
progress += qBound(0, int(task.transcode_progress_ * 50), 50);
for (const Task& task : tasks_pending_) {
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
}
foreach (const Task& task, tasks_transcoding_.values()) {
progress += qBound(0, int(task.transcode_progress_ * 50), 50);
for (const Task& task : tasks_transcoding_.values()) {
progress += qBound(0, static_cast<int>(task.transcode_progress_ * 50), 50);
}
// Add the progress of the track that's currently copying

View File

@ -37,7 +37,7 @@ public:
Organise(TaskManager* task_manager,
boost::shared_ptr<MusicStorage> destination,
const OrganiseFormat& format, bool copy, bool overwrite,
const QStringList& files, bool eject_after);
const SongList& songs, bool eject_after);
static const int kBatchSize;
static const int kTranscodeProgressInterval;
@ -63,10 +63,10 @@ private:
private:
struct Task {
explicit Task(const QString& filename = QString())
: filename_(filename), transcode_progress_(0.0) {}
explicit Task(const Song& song = Song())
: song_(song), transcode_progress_(0.0) {}
QString filename_;
Song song_;
float transcode_progress_;
QString transcoded_filename_;

View File

@ -62,7 +62,7 @@ void PodcastBackend::Subscribe(Podcast* podcast) {
// Update the IDs of any episodes.
PodcastEpisodeList* episodes = podcast->mutable_episodes();
for (PodcastEpisodeList::iterator it = episodes->begin() ; it != episodes->end() ; ++it) {
for (auto it = episodes->begin() ; it != episodes->end() ; ++it) {
it->set_podcast_database_id(database_id);
}
@ -107,7 +107,7 @@ void PodcastBackend::AddEpisodes(PodcastEpisodeList* episodes, QSqlDatabase* db)
QSqlQuery q("INSERT INTO podcast_episodes (" + PodcastEpisode::kColumnSpec + ")"
" VALUES (" + PodcastEpisode::kBindSpec + ")", *db);
for (PodcastEpisodeList::iterator it = episodes->begin() ; it != episodes->end() ; ++it) {
for (auto it = episodes->begin() ; it != episodes->end() ; ++it) {
it->BindToQuery(&q);
q.exec();
if (db_->CheckErrors(q))
@ -141,7 +141,7 @@ void PodcastBackend::UpdateEpisodes(const PodcastEpisodeList& episodes) {
" local_url = :local_url"
" WHERE ROWID = :id", db);
foreach (const PodcastEpisode& episode, episodes) {
for (const PodcastEpisode& episode : episodes) {
q.bindValue(":listened", episode.listened());
q.bindValue(":listened_date", episode.listened_date().toTime_t());
q.bindValue(":downloaded", episode.downloaded());
@ -313,3 +313,26 @@ PodcastEpisodeList PodcastBackend::GetOldDownloadedEpisodes(const QDateTime& max
return ret;
}
PodcastEpisodeList PodcastBackend::GetNewDownloadedEpisodes() {
PodcastEpisodeList ret;
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QSqlQuery q("SELECT ROWID, " + PodcastEpisode::kColumnSpec +
" FROM podcast_episodes"
" WHERE downloaded = 'true'"
" AND listened = 'false'", db);
q.exec();
if (db_->CheckErrors(q))
return ret;
while (q.next()) {
PodcastEpisode episode;
episode.InitFromQuery(q);
ret << episode;
}
return ret;
}

View File

@ -59,6 +59,7 @@ public:
// 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);
PodcastEpisodeList GetNewDownloadedEpisodes();
// Adds episodes to the database. Every episode must have a valid
// podcast_database_id set already.
@ -73,10 +74,10 @@ signals:
void SubscriptionRemoved(const Podcast& podcast);
// Emitted when episodes are added to a subscription that *already exists*.
void EpisodesAdded(const QList<PodcastEpisode>& episodes);
void EpisodesAdded(const PodcastEpisodeList& episodes);
// Emitted when existing episodes are updated.
void EpisodesUpdated(const QList<PodcastEpisode>& episodes);
void EpisodesUpdated(const PodcastEpisodeList& episodes);
private:
// Adds each episode to the database, setting their IDs after inserting each

View File

@ -20,6 +20,7 @@
#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"
@ -55,8 +56,8 @@ PodcastDownloader::PodcastDownloader(Application* app, QObject* parent)
last_progress_signal_(0),
auto_delete_timer_(new QTimer(this))
{
connect(backend_, SIGNAL(EpisodesAdded(QList<PodcastEpisode>)),
SLOT(EpisodesAdded(QList<PodcastEpisode>)));
connect(backend_, SIGNAL(EpisodesAdded(PodcastEpisodeList)),
SLOT(EpisodesAdded(PodcastEpisodeList)));
connect(backend_, SIGNAL(SubscriptionAdded(Podcast)),
SLOT(SubscriptionAdded(Podcast)));
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
@ -125,9 +126,15 @@ void PodcastDownloader::DeleteEpisode(const PodcastEpisode& episode) {
}
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();
@ -137,17 +144,17 @@ QString PodcastDownloader::FilenameForEpisode(const QString& directory,
const PodcastEpisode& episode) const {
const QString file_extension = QFileInfo(episode.url().path()).suffix();
int count = 0;
// The file name contains the publication date and episode title
QString base_filename =
episode.publication_date().date().toString(Qt::ISODate) + "-" +
SanitiseFilenameComponent(episode.title());
// Add numbers on to the end of the filename until we find one that doesn't
// exist.
forever {
QString filename;
if (count == 0) {
filename = QString("%1/%2.%3").arg(
directory, base_filename, file_extension);
@ -155,12 +162,12 @@ QString PodcastDownloader::FilenameForEpisode(const QString& directory,
filename = QString("%1/%2 (%3).%4").arg(
directory, base_filename, QString::number(count), file_extension);
}
if (!QFile::exists(filename)) {
return filename;
}
count ++;
count++;
}
}
@ -197,8 +204,8 @@ void PodcastDownloader::StartDownloading(Task* task) {
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)));
connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
SLOT(ReplyDownloadProgress(qint64, qint64)));
emit ProgressChanged(task->episode, Downloading, 0);
}
@ -235,7 +242,7 @@ void PodcastDownloader::ReplyDownloadProgress(qint64 received, qint64 total) {
last_progress_signal_ = current_time;
emit ProgressChanged(current_task_->episode, Downloading,
float(received) / total * 100);
static_cast<float>(received) / total * 100);
}
void PodcastDownloader::ReplyFinished() {
@ -275,9 +282,9 @@ void PodcastDownloader::SubscriptionAdded(const Podcast& podcast) {
EpisodesAdded(podcast.episodes());
}
void PodcastDownloader::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
void PodcastDownloader::EpisodesAdded(const PodcastEpisodeList& episodes) {
if (auto_download_) {
foreach (const PodcastEpisode& episode, episodes) {
for (const PodcastEpisode& episode : episodes) {
DownloadEpisode(episode);
}
}
@ -300,7 +307,7 @@ void PodcastDownloader::AutoDelete() {
<< (delete_after_secs_ / kSecsPerDay)
<< "days ago";
foreach (const PodcastEpisode& episode, old_episodes) {
for (const PodcastEpisode& episode : old_episodes) {
DeleteEpisode(episode);
}
}

View File

@ -174,7 +174,9 @@ Song PodcastEpisode::ToSong(const Podcast& podcast) const {
if (podcast.is_valid()) {
ret.set_album(podcast.title().simplified());
ret.set_art_automatic(podcast.ImageUrlLarge().toString());
}
if (author().isNull() || author().isEmpty())
ret.set_artist(podcast.title().simplified());
}
return ret;
}

View File

@ -25,9 +25,14 @@
#include "core/application.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "devices/devicemanager.h"
#include "devices/devicestatefiltermodel.h"
#include "devices/deviceview.h"
#include "internet/internetmodel.h"
#include "library/libraryview.h"
#include "ui/iconloader.h"
#include "ui/organisedialog.h"
#include "ui/organiseerrordialog.h"
#include "ui/standarditemiconloader.h"
#include <QMenu>
@ -54,7 +59,8 @@ PodcastService::PodcastService(Application* app, InternetModel* parent)
model_(new PodcastServiceModel(this)),
proxy_(new PodcastSortProxyModel(this)),
context_menu_(NULL),
root_(NULL)
root_(NULL),
organise_dialog_(new OrganiseDialog(app_->task_manager()))
{
icon_loader_->SetModel(model_);
proxy_->setSourceModel(model_);
@ -63,8 +69,8 @@ PodcastService::PodcastService(Application* app, InternetModel* parent)
connect(backend_, SIGNAL(SubscriptionAdded(Podcast)), SLOT(SubscriptionAdded(Podcast)));
connect(backend_, SIGNAL(SubscriptionRemoved(Podcast)), SLOT(SubscriptionRemoved(Podcast)));
connect(backend_, SIGNAL(EpisodesAdded(QList<PodcastEpisode>)), SLOT(EpisodesAdded(QList<PodcastEpisode>)));
connect(backend_, SIGNAL(EpisodesUpdated(QList<PodcastEpisode>)), SLOT(EpisodesUpdated(QList<PodcastEpisode>)));
connect(backend_, SIGNAL(EpisodesAdded(PodcastEpisodeList)), SLOT(EpisodesAdded(PodcastEpisodeList)));
connect(backend_, SIGNAL(EpisodesUpdated(PodcastEpisodeList)), SLOT(EpisodesUpdated(PodcastEpisodeList)));
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
}
@ -112,6 +118,59 @@ QStandardItem* PodcastService::CreateRootItem() {
return root_;
}
void PodcastService::CopyToDevice() {
if (selected_episodes_.isEmpty() && explicitly_selected_podcasts_.isEmpty()) {
CopyToDevice(backend_->GetNewDownloadedEpisodes());
} else {
CopyToDevice(selected_episodes_, explicitly_selected_podcasts_);
}
}
void PodcastService::CopyToDevice(const PodcastEpisodeList& episodes_list) {
SongList songs;
Podcast podcast;
for (const PodcastEpisode& episode : episodes_list) {
podcast = backend_->GetSubscriptionById(episode.podcast_database_id());
songs.append(episode.ToSong(podcast));
}
organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
organise_dialog_->SetCopy(true);
if (organise_dialog_->SetSongs(songs))
organise_dialog_->show();
}
void PodcastService::CopyToDevice(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>();
if (episode_tmp.downloaded())
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>();
if (episode_tmp.downloaded() && !episode_tmp.listened())
episodes << episode_tmp;
}
}
for (const PodcastEpisode& episode : episodes) {
podcast = backend_->GetSubscriptionById(episode.podcast_database_id());
songs.append(episode.ToSong(podcast));
}
organise_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
organise_dialog_->SetCopy(true);
if (organise_dialog_->SetSongs(songs))
organise_dialog_->show();
}
void PodcastService::LazyPopulate(QStandardItem* parent) {
switch (parent->data(InternetModel::Role_Type).toInt()) {
case InternetModel::Type_Service:
@ -124,14 +183,14 @@ 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)));
SIGNAL(ProgressChanged(PodcastEpisode, PodcastDownloader::State, int)),
SLOT(DownloadProgressChanged(PodcastEpisode, PodcastDownloader::State, int)));
if (default_icon_.isNull()) {
default_icon_ = QIcon(":providers/podcast16.png");
}
foreach (const Podcast& podcast, backend_->GetAllSubscriptions()) {
for (const Podcast& podcast : backend_->GetAllSubscriptions()) {
parent->appendRow(CreatePodcastItem(podcast));
}
}
@ -211,9 +270,9 @@ QStandardItem* PodcastService::CreatePodcastItem(const Podcast& podcast) {
// Add the episodes in this podcast and gather aggregate stats.
int unlistened_count = 0;
foreach (const PodcastEpisode& episode, backend_->GetEpisodes(podcast.database_id())) {
for (const PodcastEpisode& episode : backend_->GetEpisodes(podcast.database_id())) {
if (!episode.listened()) {
unlistened_count ++;
unlistened_count++;
}
item->appendRow(CreatePodcastEpisodeItem(episode));
@ -259,7 +318,7 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
context_menu_->addAction(
IconLoader::Load("view-refresh"), tr("Update all podcasts"),
app_->podcast_updater(), SLOT(UpdateAllPodcastsNow()));
context_menu_->addSeparator();
context_menu_->addActions(GetPlaylistActions());
@ -273,6 +332,9 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
delete_downloaded_action_ = context_menu_->addAction(
IconLoader::Load("edit-delete"), tr("Delete downloaded data"),
this, SLOT(DeleteDownloadedData()));
copy_to_device_ = context_menu_->addAction(
IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Copy to device..."),
this, SLOT(CopyToDevice()));
remove_selected_action_ = context_menu_->addAction(
IconLoader::Load("list-remove"), tr("Unsubscribe"),
this, SLOT(RemoveSelectedPodcast()));
@ -287,6 +349,10 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
context_menu_->addAction(
IconLoader::Load("configure"), tr("Configure podcasts..."),
this, SLOT(ShowConfig()));
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)),
copy_to_device_, SLOT(setDisabled(bool)));
}
selected_episodes_.clear();
@ -294,7 +360,7 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
explicitly_selected_podcasts_.clear();
QSet<int> podcast_ids;
foreach (const QModelIndex& index, model()->selected_indexes()) {
for (const QModelIndex& index : model()->selected_indexes()) {
switch (index.data(InternetModel::Role_Type).toInt()) {
case Type_Podcast: {
const int id = index.data(Role_Podcast).value<Podcast>().database_id();
@ -346,12 +412,17 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
delete_downloaded_action_->setEnabled(episodes);
}
if (explicitly_selected_podcasts_.isEmpty() && selected_episodes_.isEmpty()) {
PodcastEpisodeList epis = backend_->GetNewDownloadedEpisodes();
set_listened_action_->setEnabled(!epis.isEmpty());
}
if (selected_episodes_.count() > 1) {
download_selected_action_->setText(tr("Download %n episodes", "", selected_episodes_.count()));
} else {
download_selected_action_->setText(tr("Download this episode"));
}
GetAppendToPlaylistAction()->setEnabled(episodes || podcasts);
GetReplacePlaylistAction()->setEnabled(episodes || podcasts);
GetOpenInNewPlaylistAction()->setEnabled(episodes || podcasts);
@ -360,14 +431,14 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
}
void PodcastService::UpdateSelectedPodcast() {
foreach (const QModelIndex& index, selected_podcasts_) {
for (const QModelIndex& index : selected_podcasts_) {
app_->podcast_updater()->UpdatePodcastNow(
index.data(Role_Podcast).value<Podcast>());
}
}
void PodcastService::RemoveSelectedPodcast() {
foreach (const QModelIndex& index, selected_podcasts_) {
for (const QModelIndex& index : selected_podcasts_) {
backend_->Unsubscribe(index.data(Role_Podcast).value<Podcast>());
}
}
@ -394,7 +465,7 @@ void PodcastService::AddPodcast() {
void PodcastService::SubscriptionAdded(const Podcast& podcast) {
// Ensure the root item is lazy loaded already
LazyLoadRoot();
// The podcast might already be in the list - maybe the LazyLoadRoot() above
// added it.
QStandardItem* item = podcasts_by_database_id_[podcast.database_id()];
@ -410,7 +481,7 @@ void PodcastService::SubscriptionRemoved(const Podcast& podcast) {
QStandardItem* item = podcasts_by_database_id_.take(podcast.database_id());
if (item) {
// Remove any episode ID -> item mappings for the episodes in this podcast.
for (int i=0 ; i<item->rowCount() ; ++i) {
for (int i = 0; i < item->rowCount(); ++i) {
QStandardItem* episode_item = item->child(i);
const int episode_id =
episode_item->data(Role_Episode).value<PodcastEpisode>().database_id();
@ -423,10 +494,10 @@ void PodcastService::SubscriptionRemoved(const Podcast& podcast) {
}
}
void PodcastService::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
void PodcastService::EpisodesAdded(const PodcastEpisodeList& episodes) {
QSet<int> seen_podcast_ids;
foreach (const PodcastEpisode& episode, episodes) {
for (const PodcastEpisode& episode : episodes) {
const int database_id = episode.podcast_database_id();
QStandardItem* parent = podcasts_by_database_id_[database_id];
if (!parent)
@ -437,9 +508,9 @@ void PodcastService::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
if (!seen_podcast_ids.contains(database_id)) {
// Update the unlistened count text once for each podcast
int unlistened_count = 0;
foreach (const PodcastEpisode& episode, backend_->GetEpisodes(database_id)) {
for (const PodcastEpisode& episode : backend_->GetEpisodes(database_id)) {
if (!episode.listened()) {
unlistened_count ++;
unlistened_count++;
}
}
@ -449,10 +520,10 @@ void PodcastService::EpisodesAdded(const QList<PodcastEpisode>& episodes) {
}
}
void PodcastService::EpisodesUpdated(const QList<PodcastEpisode>& episodes) {
void PodcastService::EpisodesUpdated(const PodcastEpisodeList& episodes) {
QSet<int> seen_podcast_ids;
foreach (const PodcastEpisode& episode, episodes) {
for (const PodcastEpisode& episode : episodes) {
const int podcast_database_id = episode.podcast_database_id();
QStandardItem* item = episodes_by_database_id_[episode.database_id()];
QStandardItem* parent = podcasts_by_database_id_[podcast_database_id];
@ -467,9 +538,9 @@ void PodcastService::EpisodesUpdated(const QList<PodcastEpisode>& episodes) {
if (!seen_podcast_ids.contains(podcast_database_id)) {
// Update the unlistened count text once for each podcast
int unlistened_count = 0;
foreach (const PodcastEpisode& episode, backend_->GetEpisodes(podcast_database_id)) {
for (const PodcastEpisode& episode : backend_->GetEpisodes(podcast_database_id)) {
if (!episode.listened()) {
unlistened_count ++;
unlistened_count++;
}
}
@ -480,14 +551,14 @@ void PodcastService::EpisodesUpdated(const QList<PodcastEpisode>& episodes) {
}
void PodcastService::DownloadSelectedEpisode() {
foreach (const QModelIndex& index, selected_episodes_) {
for (const QModelIndex& index : selected_episodes_) {
app_->podcast_downloader()->DownloadEpisode(
index.data(Role_Episode).value<PodcastEpisode>());
}
}
void PodcastService::DeleteDownloadedData() {
foreach (const QModelIndex& index, selected_episodes_) {
for (const QModelIndex& index : selected_episodes_) {
app_->podcast_downloader()->DeleteEpisode(
index.data(Role_Episode).value<PodcastEpisode>());
}
@ -526,7 +597,25 @@ void PodcastService::SetNew() {
}
void PodcastService::SetListened() {
SetListened(selected_episodes_, explicitly_selected_podcasts_, true);
if (selected_episodes_.isEmpty() && explicitly_selected_podcasts_.isEmpty())
SetListened(backend_->GetNewDownloadedEpisodes(), true);
else
SetListened(selected_episodes_, explicitly_selected_podcasts_, true);
}
void PodcastService::SetListened(const PodcastEpisodeList& episodes_list,
bool listened) {
PodcastEpisodeList episodes;
QDateTime current_date_time = QDateTime::currentDateTime();
for (PodcastEpisode episode : episodes_list) {
episode.set_listened(listened);
if (listened) {
episode.set_listened_date(current_date_time);
}
episodes << episode;
}
backend_->UpdateEpisodes(episodes);
}
void PodcastService::SetListened(const QModelIndexList& episode_indexes,
@ -535,12 +624,12 @@ void PodcastService::SetListened(const QModelIndexList& episode_indexes,
PodcastEpisodeList episodes;
// Get all the episodes from the indexes.
foreach (const QModelIndex& index, episode_indexes) {
for (const QModelIndex& index : episode_indexes) {
episodes << index.data(Role_Episode).value<PodcastEpisode>();
}
foreach (const QModelIndex& podcast, podcast_indexes) {
for (int i=0 ; i<podcast.model()->rowCount(podcast) ; ++i) {
for (const QModelIndex& podcast : podcast_indexes) {
for (int i = 0; i < podcast.model()->rowCount(podcast); ++i) {
const QModelIndex& index = podcast.child(i, 0);
episodes << index.data(Role_Episode).value<PodcastEpisode>();
}
@ -548,7 +637,7 @@ void PodcastService::SetListened(const QModelIndexList& episode_indexes,
// Update each one with the new state and maybe the listened time.
QDateTime current_date_time = QDateTime::currentDateTime();
for (int i=0 ; i<episodes.count() ; ++i) {
for (int i = 0; i < episodes.count(); ++i) {
PodcastEpisode* episode = &episodes[i];
episode->set_listened(listened);
if (listened) {

View File

@ -22,9 +22,11 @@
#include "internet/internetmodel.h"
#include "internet/internetservice.h"
#include <memory>
#include <QScopedPointer>
class AddPodcastDialog;
class OrganiseDialog;
class Podcast;
class PodcastBackend;
class PodcastEpisode;
@ -78,8 +80,8 @@ private slots:
void SubscriptionAdded(const Podcast& podcast);
void SubscriptionRemoved(const Podcast& podcast);
void EpisodesAdded(const QList<PodcastEpisode>& episodes);
void EpisodesUpdated(const QList<PodcastEpisode>& episodes);
void EpisodesAdded(const PodcastEpisodeList& episodes);
void EpisodesUpdated(const PodcastEpisodeList& episodes);
void DownloadProgressChanged(const PodcastEpisode& episode,
PodcastDownloader::State state,
@ -87,6 +89,11 @@ private slots:
void CurrentSongChanged(const Song& metadata);
void CopyToDevice();
void CopyToDevice(const PodcastEpisodeList& episodes_list);
void CopyToDevice(const QModelIndexList& episode_indexes,
const QModelIndexList& podcast_indexes);
private:
void EnsureAddPodcastDialogCreated();
@ -104,7 +111,9 @@ private:
void SetListened(const QModelIndexList& episode_indexes,
const QModelIndexList& podcast_indexes,
bool listened);
void SetListened(const PodcastEpisodeList& episodes_list,
bool listened);
void LazyLoadRoot();
private:
@ -130,7 +139,9 @@ private:
QAction* delete_downloaded_action_;
QAction* set_new_action_;
QAction* set_listened_action_;
QAction* copy_to_device_;
QStandardItem* root_;
std::unique_ptr<OrganiseDialog> organise_dialog_;
QModelIndexList explicitly_selected_podcasts_;
QModelIndexList selected_podcasts_;

View File

@ -32,7 +32,6 @@
#include <QSignalMapper>
#include <QtDebug>
const int OrganiseDialog::kNumberOfPreviews = 10;
const char* OrganiseDialog::kDefaultFormat =
"%artist/%album{ (Disc %disc)}/{%track - }%title.%extension";
const char* OrganiseDialog::kSettingsGroup = "OrganiseDialog";
@ -86,7 +85,7 @@ OrganiseDialog::OrganiseDialog(TaskManager* task_manager, QWidget *parent)
// Build the insert menu
QMenu* tag_menu = new QMenu(this);
QSignalMapper* tag_mapper = new QSignalMapper(this);
foreach (const QString& title, tag_titles) {
for (const QString& title : tag_titles) {
QAction* action = tag_menu->addAction(title, tag_mapper, SLOT(map()));
tag_mapper->setMapping(action, tags[title]);
}
@ -107,78 +106,52 @@ void OrganiseDialog::SetDestinationModel(QAbstractItemModel *model, bool devices
int OrganiseDialog::SetSongs(const SongList& songs) {
total_size_ = 0;
filenames_.clear();
preview_songs_.clear();
songs_.clear();
foreach (const Song& song, songs) {
for (const Song& song : songs) {
if (song.url().scheme() != "file") {
continue;
};
}
if (song.filesize() > 0)
total_size_ += song.filesize();
filenames_ << song.url().toLocalFile();
if (preview_songs_.count() < kNumberOfPreviews)
preview_songs_ << song;
songs_ << song;
}
ui_->free_space->set_additional_bytes(total_size_);
UpdatePreviews();
return filenames_.count();
return songs_.count();
}
int OrganiseDialog::SetUrls(const QList<QUrl> &urls, quint64 total_size) {
QStringList filenames;
SongList songs;
Song song;
// Only add file:// URLs
foreach (const QUrl& url, urls) {
for (const QUrl& url : urls) {
if (url.scheme() != "file")
continue;
filenames << url.toLocalFile();
TagReaderClient::Instance()->ReadFileBlocking(url.toLocalFile(), &song);
if (song.is_valid())
songs << song;
}
return SetFilenames(filenames, total_size);
return SetSongs(songs);
}
int OrganiseDialog::SetFilenames(const QStringList& filenames, quint64 total_size) {
filenames_ = filenames;
preview_songs_.clear();
SongList songs;
Song song;
// Load some of the songs to show in the preview
const int n = qMin(filenames_.count(), kNumberOfPreviews);
for (int i=0 ; i<n ; ++i) {
LoadPreviewSongs(filenames_[i]);
for (const QString& filename : filenames) {
TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
if (song.is_valid())
songs << song;
}
ui_->free_space->set_additional_bytes(total_size);
total_size_ = total_size;
UpdatePreviews();
return filenames_.count();
}
void OrganiseDialog::LoadPreviewSongs(const QString& filename) {
if (preview_songs_.count() >= kNumberOfPreviews)
return;
if (QFileInfo(filename).isDir()) {
QDir dir(filename);
QStringList entries = dir.entryList(
QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
foreach (const QString& entry, entries) {
LoadPreviewSongs(filename + "/" + entry);
}
return;
}
Song song;
TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
if (song.is_valid())
preview_songs_ << song;
return SetSongs(songs);
}
void OrganiseDialog::SetCopy(bool copy) {
@ -224,7 +197,7 @@ void OrganiseDialog::UpdatePreviews() {
const bool format_valid = !has_local_destination || format_.IsValid();
// Are we gonna enable the ok button?
bool ok = format_valid && !filenames_.isEmpty();
bool ok = format_valid && !songs_.isEmpty();
if (capacity != 0 && total_size_ > free)
ok = false;
@ -237,7 +210,7 @@ void OrganiseDialog::UpdatePreviews() {
ui_->preview_group->setVisible(has_local_destination);
ui_->naming_group->setVisible(has_local_destination);
if (has_local_destination) {
foreach (const Song& song, preview_songs_) {
for (const Song& song : songs_) {
QString filename = storage->LocalPath() + "/" +
format_.GetFilenameForSong(song);
ui_->preview->addItem(QDir::toNativeSeparators(filename));
@ -305,7 +278,7 @@ void OrganiseDialog::accept() {
const bool copy = ui_->aftercopying->currentIndex() == 0;
Organise* organise = new Organise(
task_manager_, storage, format_, copy, ui_->overwrite->isChecked(),
filenames_, ui_->eject_after->isChecked());
songs_, ui_->eject_after->isChecked());
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList)));
organise->Start();

View File

@ -18,6 +18,7 @@
#ifndef ORGANISEDIALOG_H
#define ORGANISEDIALOG_H
#include <memory>
#include <QDialog>
#include <QMap>
#include <QUrl>
@ -25,8 +26,6 @@
#include "core/organiseformat.h"
#include "core/song.h"
#include <boost/scoped_ptr.hpp>
class LibraryWatcher;
class OrganiseErrorDialog;
class TaskManager;
@ -41,7 +40,6 @@ public:
OrganiseDialog(TaskManager* task_manager, QWidget* parent = 0);
~OrganiseDialog();
static const int kNumberOfPreviews;
static const char* kDefaultFormat;
static const char* kSettingsGroup;
@ -65,7 +63,6 @@ private slots:
void Reset();
void InsertTag(const QString& tag);
void LoadPreviewSongs(const QString& filename);
void UpdatePreviews();
void OrganiseFinished(const QStringList& files_with_errors);
@ -76,11 +73,10 @@ private:
OrganiseFormat format_;
QStringList filenames_;
SongList preview_songs_;
SongList songs_;
quint64 total_size_;
boost::scoped_ptr<OrganiseErrorDialog> error_dialog_;
std::unique_ptr<OrganiseErrorDialog> error_dialog_;
bool resized_by_user_;
};