diff --git a/data/data.qrc b/data/data.qrc
index f485e9b10..63882f474 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -295,6 +295,7 @@
schema/schema-23.sql
schema/schema-24.sql
schema/schema-25.sql
+ schema/schema-26.sql
pythonstartup.py
diff --git a/data/schema/schema-26.sql b/data/schema/schema-26.sql
new file mode 100644
index 000000000..0de6687b1
--- /dev/null
+++ b/data/schema/schema-26.sql
@@ -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;
diff --git a/src/core/database.cpp b/src/core/database.cpp
index da61fb614..b9c3dc471 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -31,7 +31,7 @@
#include
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;
diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp
index 92942a2cc..af5cc82e2 100644
--- a/src/playlist/playlistbackend.cpp
+++ b/src/playlist/playlistbackend.cpp
@@ -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
+#include
+#include
#include
#include
#include
+#include
+
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 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 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 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 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 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,"
diff --git a/src/playlist/playlistbackend.h b/src/playlist/playlistbackend.h
index 7285d4565..39098792d 100644
--- a/src/playlist/playlistbackend.h
+++ b/src/playlist/playlistbackend.h
@@ -19,7 +19,9 @@
#define PLAYLISTBACKEND_H
#include
+#include
#include
+#include
#include
#include "playlistitem.h"
@@ -28,6 +30,7 @@
#include
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 cached_cues_;
+ QMutex mutex_;
+ };
+
+ PlaylistItemPtr NewSongFromQuery(const SqlRow& row, boost::shared_ptr state);
+ PlaylistItemPtr RestoreCueData(PlaylistItemPtr item, boost::shared_ptr state);
+
+ LibraryBackend* library_;
boost::shared_ptr db_;
};
diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp
index 9b0ea6df6..3d783ccd5 100644
--- a/src/playlist/playlistitem.cpp
+++ b/src/playlist/playlistitem.cpp
@@ -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) {
diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h
index 868b7c5fa..995569cf4 100644
--- a/src/playlist/playlistitem.h
+++ b/src/playlist/playlistitem.h
@@ -128,6 +128,8 @@ class PlaylistItem : public boost::enable_shared_from_this {
Column_Album,
Column_Length,
Column_RadioService,
+ Column_Beginning,
+ Column_CuePath,
};
virtual QVariant DatabaseValue(DatabaseColumn) const {
diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp
index 5cda1afb5..1d39e9e6d 100644
--- a/src/playlist/songplaylistitem.cpp
+++ b/src/playlist/songplaylistitem.cpp
@@ -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);
}
}
diff --git a/src/playlist/songplaylistitem.h b/src/playlist/songplaylistitem.h
index 9f5b6b489..f5a5595a7 100644
--- a/src/playlist/songplaylistitem.h
+++ b/src/playlist/songplaylistitem.h
@@ -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();
diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp
index 443c26eb6..2a9ddbc1a 100644
--- a/src/ui/mainwindow.cpp
+++ b/src/ui/mainwindow.cpp
@@ -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_);