restoring CUE related files in playlist after Clementine's restart (fixes issue #68)

This commit is contained in:
Paweł Bara 2011-01-19 15:36:40 +00:00
parent d9f7b6750f
commit a575dac451
10 changed files with 121 additions and 16 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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,"

View File

@ -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_;
};

View File

@ -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) {

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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_);