restoring CUE related files in playlist after Clementine's restart (fixes issue #68)
This commit is contained in:
parent
d9f7b6750f
commit
a575dac451
|
@ -295,6 +295,7 @@
|
|||
<file>schema/schema-23.sql</file>
|
||||
<file>schema/schema-24.sql</file>
|
||||
<file>schema/schema-25.sql</file>
|
||||
<file>schema/schema-26.sql</file>
|
||||
<file>pythonstartup.py</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE playlist_items ADD COLUMN beginning INTEGER;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN cue_path TEXT;
|
||||
|
||||
UPDATE schema_version SET version=26;
|
|
@ -31,7 +31,7 @@
|
|||
#include <QVariant>
|
||||
|
||||
const char* Database::kDatabaseFilename = "clementine.db";
|
||||
const int Database::kSchemaVersion = 25;
|
||||
const int Database::kSchemaVersion = 26;
|
||||
const char* Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
|
|
|
@ -21,21 +21,33 @@
|
|||
#include "core/song.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "library/sqlrow.h"
|
||||
#include "playlist/songplaylistitem.h"
|
||||
#include "playlistparsers/cueparser.h"
|
||||
#include "smartplaylists/generator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QMutexLocker>
|
||||
#include <QSqlQuery>
|
||||
#include <QtConcurrentMap>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
using smart_playlists::GeneratorPtr;
|
||||
|
||||
using boost::shared_ptr;
|
||||
|
||||
PlaylistBackend::PlaylistBackend(QObject* parent)
|
||||
: QObject(parent)
|
||||
: QObject(parent),
|
||||
library_(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void PlaylistBackend::SetLibrary(LibraryBackend* library) {
|
||||
library_ = library;
|
||||
}
|
||||
|
||||
PlaylistBackend::PlaylistList PlaylistBackend::GetAllPlaylists() {
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
@ -97,7 +109,7 @@ QFuture<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(int playlist) {
|
|||
" magnatune_songs.ROWID, " + Song::JoinSpec("magnatune_songs") + ","
|
||||
" jamendo_songs.ROWID, " + Song::JoinSpec("jamendo_songs") + ","
|
||||
" p.type, p.url, p.title, p.artist, p.album, p.length,"
|
||||
" p.radio_service"
|
||||
" p.radio_service, p.beginning, p.cue_path"
|
||||
" FROM playlist_items AS p"
|
||||
" LEFT JOIN songs"
|
||||
" ON p.library_id = songs.ROWID"
|
||||
|
@ -119,18 +131,74 @@ QFuture<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(int playlist) {
|
|||
rows << SqlRow(q);
|
||||
}
|
||||
|
||||
return QtConcurrent::mapped(rows, &PlaylistBackend::NewSongFromQuery);
|
||||
// it's probable that we'll have a few songs associated with the
|
||||
// same CUE so we're caching results of parsing CUEs
|
||||
boost::shared_ptr<NewSongFromQueryState> state_ptr(new NewSongFromQueryState());
|
||||
return QtConcurrent::mapped(rows, boost::bind(&PlaylistBackend::NewSongFromQuery, this, _1, state_ptr));
|
||||
}
|
||||
|
||||
PlaylistItemPtr PlaylistBackend::NewSongFromQuery(const SqlRow& row) {
|
||||
PlaylistItemPtr PlaylistBackend::NewSongFromQuery(const SqlRow& row, boost::shared_ptr<NewSongFromQueryState> state) {
|
||||
// The song tables get joined first, plus one each for the song ROWIDs
|
||||
const int playlist_row = (Song::kColumns.count() + 1) * 3;
|
||||
|
||||
PlaylistItemPtr item(
|
||||
PlaylistItem::NewFromType(row.value(playlist_row).toString()));
|
||||
PlaylistItemPtr item(PlaylistItem::NewFromType(row.value(playlist_row).toString()));
|
||||
if (item) {
|
||||
item->InitFromQuery(row);
|
||||
return RestoreCueData(item, state);
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// If song had a CUE and the CUE still exists, the metadata from it will
|
||||
// be applied here.
|
||||
PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, boost::shared_ptr<NewSongFromQueryState> state) {
|
||||
// we need library to run a CueParser; also, this method applies only to
|
||||
// file-type PlaylistItems
|
||||
if(!library_ || item->type() != "File") {
|
||||
return item;
|
||||
}
|
||||
CueParser cue_parser(library_);
|
||||
|
||||
Song song = item->Metadata();
|
||||
// we're only interested in .cue songs here
|
||||
if(!song.has_cue()) {
|
||||
return item;
|
||||
}
|
||||
|
||||
QString cue_path = song.cue_path();
|
||||
// if .cue was deleted - reload the song
|
||||
if(!QFile::exists(cue_path)) {
|
||||
item->Reload();
|
||||
return item;
|
||||
}
|
||||
|
||||
SongList song_list;
|
||||
{
|
||||
QMutexLocker locker(&state->mutex_);
|
||||
|
||||
if(!state->cached_cues_.contains(cue_path)) {
|
||||
QFile cue(cue_path);
|
||||
cue.open(QIODevice::ReadOnly);
|
||||
|
||||
song_list = cue_parser.Load(&cue, cue_path, QDir(cue_path.section('/', 0, -2)));
|
||||
state->cached_cues_[cue_path] = song_list;
|
||||
} else {
|
||||
song_list = state->cached_cues_[cue_path];
|
||||
}
|
||||
}
|
||||
|
||||
foreach(const Song& from_list, song_list) {
|
||||
if(from_list.filename() == song.filename() &&
|
||||
from_list.beginning() == song.beginning()) {
|
||||
// we found a matching section; replace the input
|
||||
// item with a new one containing CUE metadata
|
||||
return PlaylistItemPtr(new SongPlaylistItem(from_list));
|
||||
}
|
||||
}
|
||||
|
||||
// there's no such section in the related .cue -> reload the song
|
||||
item->Reload();
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -151,8 +219,8 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList& items,
|
|||
QSqlQuery clear("DELETE FROM playlist_items WHERE playlist = :playlist", db);
|
||||
QSqlQuery insert("INSERT INTO playlist_items"
|
||||
" (playlist, type, library_id, url, title, artist, album,"
|
||||
" length, radio_service)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", db);
|
||||
" length, radio_service, beginning, cue_path)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", db);
|
||||
QSqlQuery update("UPDATE playlists SET "
|
||||
" last_played=:last_played,"
|
||||
" dynamic_playlist_type=:dynamic_type,"
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
#define PLAYLISTBACKEND_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include "playlistitem.h"
|
||||
|
@ -28,6 +30,7 @@
|
|||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class Database;
|
||||
class LibraryBackend;
|
||||
|
||||
class PlaylistBackend : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -58,12 +61,22 @@ class PlaylistBackend : public QObject {
|
|||
void RemovePlaylist(int id);
|
||||
void RenamePlaylist(int id, const QString& new_name);
|
||||
|
||||
void SetLibrary(LibraryBackend* library);
|
||||
|
||||
public slots:
|
||||
void SavePlaylist(int playlist, const PlaylistItemList& items,
|
||||
int last_played, smart_playlists::GeneratorPtr dynamic);
|
||||
|
||||
private:
|
||||
static PlaylistItemPtr NewSongFromQuery(const SqlRow& row);
|
||||
struct NewSongFromQueryState {
|
||||
QHash<QString, SongList> cached_cues_;
|
||||
QMutex mutex_;
|
||||
};
|
||||
|
||||
PlaylistItemPtr NewSongFromQuery(const SqlRow& row, boost::shared_ptr<NewSongFromQueryState> state);
|
||||
PlaylistItemPtr RestoreCueData(PlaylistItemPtr item, boost::shared_ptr<NewSongFromQueryState> state);
|
||||
|
||||
LibraryBackend* library_;
|
||||
|
||||
boost::shared_ptr<Database> db_;
|
||||
};
|
||||
|
|
|
@ -71,6 +71,8 @@ void PlaylistItem::BindToQuery(QSqlQuery* query) const {
|
|||
query->bindValue(6, DatabaseValue(Column_Album));
|
||||
query->bindValue(7, DatabaseValue(Column_Length));
|
||||
query->bindValue(8, DatabaseValue(Column_RadioService));
|
||||
query->bindValue(9, DatabaseValue(Column_Beginning));
|
||||
query->bindValue(10, DatabaseValue(Column_CuePath));
|
||||
}
|
||||
|
||||
void PlaylistItem::SetTemporaryMetadata(const Song& metadata) {
|
||||
|
|
|
@ -128,6 +128,8 @@ class PlaylistItem : public boost::enable_shared_from_this<PlaylistItem> {
|
|||
Column_Album,
|
||||
Column_Length,
|
||||
Column_RadioService,
|
||||
Column_Beginning,
|
||||
Column_CuePath,
|
||||
};
|
||||
|
||||
virtual QVariant DatabaseValue(DatabaseColumn) const {
|
||||
|
|
|
@ -55,18 +55,27 @@ bool SongPlaylistItem::InitFromQuery(const SqlRow& query) {
|
|||
|
||||
if (!song_.is_valid())
|
||||
return false;
|
||||
|
||||
int beginning(query.value(row + 7).toInt());
|
||||
QString cue_path(query.value(row + 8).toString());
|
||||
|
||||
song_.set_beginning(beginning);
|
||||
song_.set_cue_path(cue_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant SongPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
||||
switch (column) {
|
||||
case Column_Url: return song_.filename();
|
||||
case Column_Title: return song_.title();
|
||||
case Column_Artist: return song_.artist();
|
||||
case Column_Album: return song_.album();
|
||||
case Column_Length: return song_.length();
|
||||
default: return PlaylistItem::DatabaseValue(column);
|
||||
case Column_Url: return song_.filename();
|
||||
case Column_Title: return song_.title();
|
||||
case Column_Artist: return song_.artist();
|
||||
case Column_Album: return song_.album();
|
||||
case Column_Length: return song_.length();
|
||||
case Column_Beginning: return song_.beginning();
|
||||
case Column_CuePath: return song_.cue_path();
|
||||
default: return PlaylistItem::DatabaseValue(column);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ class SongPlaylistItem : public PlaylistItem {
|
|||
SongPlaylistItem(const QString& type);
|
||||
SongPlaylistItem(const Song& song);
|
||||
|
||||
// Restores a stream- or file-related playlist item using query row.
|
||||
// If it's a file related playlist item, this will restore it's CUE
|
||||
// attributes (if any) but won't parse the CUE!
|
||||
bool InitFromQuery(const SqlRow& query);
|
||||
void Reload();
|
||||
|
||||
|
|
|
@ -197,6 +197,8 @@ MainWindow::MainWindow(
|
|||
library_ = new Library(database_, task_manager_, this);
|
||||
devices_ = new DeviceManager(database_, task_manager_, this);
|
||||
|
||||
playlist_backend_->SetLibrary(library_->backend());
|
||||
|
||||
// Initialise the UI
|
||||
ui_->setupUi(this);
|
||||
ui_->multi_loading_indicator->SetTaskManager(task_manager_);
|
||||
|
|
Loading…
Reference in New Issue