diff --git a/data/data.qrc b/data/data.qrc
index 238419c70..cda15b8b8 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -78,5 +78,6 @@
schema-6.sql
list-add.png
document-save.png
+ schema-7.sql
diff --git a/data/schema-7.sql b/data/schema-7.sql
new file mode 100644
index 000000000..e183ff458
--- /dev/null
+++ b/data/schema-7.sql
@@ -0,0 +1,26 @@
+CREATE TABLE playlists (
+ name TEXT NOT NULL
+);
+
+CREATE TABLE playlist_items (
+ playlist INTEGER NOT NULL,
+ type TEXT NOT NULL, /* Library, Stream, File, or Radio */
+
+ /* Library */
+ library_id INTEGER,
+
+ /* Stream, File or Radio */
+ url TEXT,
+
+ /* Stream or Radio */
+ title TEXT,
+ artist TEXT,
+ album TEXT,
+ length INTEGER,
+
+ /* Radio */
+ radio_service TEXT
+);
+
+UPDATE schema_version SET version=7;
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2fd836c37..e141b6a16 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -76,6 +76,8 @@ set(CLEMENTINE-SOURCES
stickyslider.cpp
commandlineoptions.cpp
settingsprovider.cpp
+ libraryplaylistitem.cpp
+ scopedtransaction.cpp
)
# Header files that have Q_OBJECT in
diff --git a/src/library.cpp b/src/library.cpp
index 79edf72b1..407423b6d 100644
--- a/src/library.cpp
+++ b/src/library.cpp
@@ -505,7 +505,7 @@ void Library::InitQuery(GroupBy type, LibraryQuery* q) {
q->SetColumnSpec("DISTINCT albumartist");
break;
case GroupBy_None:
- q->SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
+ q->SetColumnSpec("ROWID, " + Song::kColumnSpec);
break;
}
}
diff --git a/src/librarybackend.cpp b/src/librarybackend.cpp
index ea394ee74..b44f2c5a4 100644
--- a/src/librarybackend.cpp
+++ b/src/librarybackend.cpp
@@ -16,6 +16,7 @@
#include "librarybackend.h"
#include "libraryquery.h"
+#include "scopedtransaction.h"
#include
#include
@@ -32,7 +33,7 @@
const char* LibraryBackend::kDatabaseName = "clementine.db";
-const int LibraryBackend::kSchemaVersion = 6;
+const int LibraryBackend::kSchemaVersion = 7;
int (*LibraryBackend::_sqlite3_create_function) (
sqlite3*, const char*, int, int, void*,
@@ -359,7 +360,7 @@ void LibraryBackend::RemoveDirectory(const Directory& dir) {
// Remove songs first
DeleteSongs(FindSongsInDirectory(dir.id));
- db.transaction();
+ ScopedTransaction transaction(&db);
// Delete the subdirs that were in this directory
QSqlQuery q("DELETE FROM subdirectories WHERE directory = :id", db);
@@ -375,13 +376,13 @@ void LibraryBackend::RemoveDirectory(const Directory& dir) {
emit DirectoryDeleted(dir);
- db.commit();
+ transaction.Commit();
}
SongList LibraryBackend::FindSongsInDirectory(int id) {
QSqlDatabase db(Connect());
- QSqlQuery q("SELECT ROWID, " + QString(Song::kColumnSpec) +
+ QSqlQuery q("SELECT ROWID, " + Song::kColumnSpec +
" FROM songs WHERE directory = :directory", db);
q.bindValue(":directory", id);
q.exec();
@@ -407,7 +408,7 @@ void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
QSqlQuery delete_query("DELETE FROM subdirectories"
" WHERE directory = :id AND path = :path", db);
- db.transaction();
+ ScopedTransaction transaction(&db);
foreach (const Subdirectory& subdir, subdirs) {
if (subdir.mtime == 0) {
// Delete the subdirectory
@@ -437,7 +438,7 @@ void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
}
}
}
- db.commit();
+ transaction.Commit();
}
void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
@@ -446,13 +447,13 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
QSqlQuery check_dir(
"SELECT ROWID FROM directories WHERE ROWID = :id", db);
QSqlQuery add_song(
- "INSERT INTO songs (" + QString(Song::kColumnSpec) + ")"
- " VALUES (" + QString(Song::kBindSpec) + ")", db);
+ "INSERT INTO songs (" + Song::kColumnSpec + ")"
+ " VALUES (" + Song::kBindSpec + ")", db);
QSqlQuery update_song(
- "UPDATE songs SET " + QString(Song::kUpdateSpec) +
+ "UPDATE songs SET " + Song::kUpdateSpec +
" WHERE ROWID = :id", db);
- db.transaction();
+ ScopedTransaction transaction(&db);
SongList added_songs;
SongList deleted_songs;
@@ -495,7 +496,7 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
}
}
- db.commit();
+ transaction.Commit();
if (!deleted_songs.isEmpty())
emit SongsDeleted(deleted_songs);
@@ -511,14 +512,14 @@ void LibraryBackend::UpdateMTimesOnly(const SongList& songs) {
QSqlQuery q("UPDATE songs SET mtime = :mtime WHERE ROWID = :id", db);
- db.transaction();
+ ScopedTransaction transaction(&db);
foreach (const Song& song, songs) {
q.bindValue(":mtime", song.mtime());
q.bindValue(":id", song.id());
q.exec();
CheckErrors(q.lastError());
}
- db.commit();
+ transaction.Commit();
}
void LibraryBackend::DeleteSongs(const SongList &songs) {
@@ -526,13 +527,13 @@ void LibraryBackend::DeleteSongs(const SongList &songs) {
QSqlQuery q("DELETE FROM songs WHERE ROWID = :id", db);
- db.transaction();
+ ScopedTransaction transaction(&db);
foreach (const Song& song, songs) {
q.bindValue(":id", song.id());
q.exec();
CheckErrors(q.lastError());
}
- db.commit();
+ transaction.Commit();
emit SongsDeleted(songs);
@@ -564,7 +565,7 @@ LibraryBackend::AlbumList LibraryBackend::GetAlbumsByArtist(const QString& artis
SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, const QueryOptions& opt) {
LibraryQuery query(opt);
- query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
+ query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddCompilationRequirement(false);
query.AddWhere("artist", artist);
query.AddWhere("album", album);
@@ -583,7 +584,7 @@ SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, c
Song LibraryBackend::GetSongById(int id) {
QSqlDatabase db(Connect());
- QSqlQuery q("SELECT ROWID, " + QString(Song::kColumnSpec) + " FROM songs"
+ QSqlQuery q("SELECT ROWID, " + Song::kColumnSpec + " FROM songs"
" WHERE ROWID = :id", db);
q.bindValue(":id", id);
q.exec();
@@ -612,7 +613,7 @@ LibraryBackend::AlbumList LibraryBackend::GetCompilationAlbums(const QueryOption
SongList LibraryBackend::GetCompilationSongs(const QString& album, const QueryOptions& opt) {
LibraryQuery query(opt);
- query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
+ query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddCompilationRequirement(true);
query.AddWhere("album", album);
@@ -670,13 +671,13 @@ void LibraryBackend::UpdateCompilations() {
" SET sampler = :sampler,"
" effective_compilation = ((compilation OR :sampler OR forced_compilation_on) AND NOT forced_compilation_off) + 0"
" WHERE album = :album", db);
- QSqlQuery find_songs("SELECT ROWID, " + QString(Song::kColumnSpec) + " FROM songs"
+ QSqlQuery find_songs("SELECT ROWID, " + Song::kColumnSpec + " FROM songs"
" WHERE album = :album AND sampler = :sampler", db);
SongList deleted_songs;
SongList added_songs;
- db.transaction();
+ ScopedTransaction transaction(&db);
QMap::const_iterator it = compilation_info.constBegin();
for ( ; it != compilation_info.constEnd() ; ++it) {
@@ -695,7 +696,7 @@ void LibraryBackend::UpdateCompilations() {
}
}
- db.commit();
+ transaction.Commit();
if (!deleted_songs.isEmpty()) {
emit SongsDeleted(deleted_songs);
@@ -821,7 +822,7 @@ void LibraryBackend::ForceCompilation(const QString& artist, const QString& albu
// Get the songs before they're updated
LibraryQuery query;
- query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
+ query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddWhere("album", album);
if (!artist.isNull())
query.AddWhere("artist", artist);
@@ -872,3 +873,71 @@ void LibraryBackend::ForceCompilation(const QString& artist, const QString& albu
bool LibraryBackend::ExecQuery(LibraryQuery *q) {
return !CheckErrors(q->Exec(Connect()));
}
+
+LibraryBackendInterface::PlaylistList LibraryBackend::GetAllPlaylists() {
+ qWarning() << "Not implemented:" << __PRETTY_FUNCTION__;
+ return PlaylistList();
+}
+
+PlaylistItemList LibraryBackend::GetPlaylistItems(int playlist) {
+ QSqlDatabase db(Connect());
+
+ PlaylistItemList ret;
+
+ QSqlQuery q("SELECT songs.ROWID, " + Song::kJoinSpec + ","
+ " p.type, p.url, p.title, p.artist, p.album, p.length,"
+ " p.radio_service"
+ " FROM playlist_items AS p"
+ " LEFT JOIN songs"
+ " ON p.library_id = songs.ROWID"
+ " WHERE p.playlist = :playlist", db);
+ q.bindValue(":playlist", playlist);
+ q.exec();
+ if (CheckErrors(q.lastError()))
+ return ret;
+
+ while (q.next()) {
+ // The song table gets joined first, plus one for the song ROWID
+ const int row = Song::kColumns.count() + 1;
+
+ PlaylistItem* item = PlaylistItem::NewFromType(q.value(row + 0).toString());
+ if (!item)
+ continue;
+
+ item->InitFromQuery(q);
+ ret << item;
+ }
+
+ return ret;
+}
+
+void LibraryBackend::SavePlaylist(int playlist, const PlaylistItemList& items) {
+ QSqlDatabase db(Connect());
+
+ 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 (:playlist, :type, :library_id, :url, :title,"
+ " :artist, :album, :length, :radio_service)", db);
+
+ clear.bindValue(":playlist", playlist);
+
+ ScopedTransaction transaction(&db);
+
+ // Clear the existing items in the playlist
+ clear.exec();
+ if (CheckErrors(clear.lastError()))
+ return;
+
+ // Save the new ones
+ foreach (const PlaylistItem* item, items) {
+ insert.bindValue(":playlist", playlist);
+ item->BindToQuery(&insert);
+
+ insert.exec();
+ CheckErrors(insert.lastError());
+ }
+
+ transaction.Commit();
+}
diff --git a/src/librarybackend.h b/src/librarybackend.h
index 842b13d62..45aaca5ba 100644
--- a/src/librarybackend.h
+++ b/src/librarybackend.h
@@ -26,6 +26,7 @@
#include "directory.h"
#include "song.h"
#include "libraryquery.h"
+#include "playlistitem.h"
#include
@@ -52,6 +53,12 @@ class LibraryBackendInterface : public QObject {
};
typedef QList AlbumList;
+ struct Playlist {
+ int id;
+ QString name;
+ };
+ typedef QList PlaylistList;
+
virtual void Stop() {};
// Get a list of directories in the library. Emits DirectoriesDiscovered.
@@ -60,6 +67,7 @@ class LibraryBackendInterface : public QObject {
// Counts the songs in the library. Emits TotalSongCountUpdated
virtual void UpdateTotalSongCountAsync() = 0;
+ // Functions for getting songs
virtual SongList FindSongsInDirectory(int id) = 0;
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
@@ -78,12 +86,19 @@ class LibraryBackendInterface : public QObject {
virtual Song GetSongById(int id) = 0;
+ virtual bool ExecQuery(LibraryQuery* q) = 0;
+
+ // Add or remove directories to the library
virtual void AddDirectory(const QString& path) = 0;
virtual void RemoveDirectory(const Directory& dir) = 0;
+ // Update compilation flags on songs
virtual void UpdateCompilationsAsync() = 0;
- virtual bool ExecQuery(LibraryQuery* q) = 0;
+ // Functions for getting playlists
+ virtual PlaylistList GetAllPlaylists() = 0;
+ virtual PlaylistItemList GetPlaylistItems(int playlist) = 0;
+ virtual void SavePlaylist(int playlist, const PlaylistItemList& items) = 0;
public slots:
virtual void LoadDirectories() = 0;
@@ -151,6 +166,10 @@ class LibraryBackend : public LibraryBackendInterface {
bool ExecQuery(LibraryQuery* q);
+ PlaylistList GetAllPlaylists();
+ PlaylistItemList GetPlaylistItems(int playlist);
+ void SavePlaylist(int playlist, const PlaylistItemList& items);
+
public slots:
void LoadDirectories();
void UpdateTotalSongCount();
diff --git a/src/libraryplaylistitem.cpp b/src/libraryplaylistitem.cpp
new file mode 100644
index 000000000..d183ae888
--- /dev/null
+++ b/src/libraryplaylistitem.cpp
@@ -0,0 +1,52 @@
+/* This file is part of Clementine.
+
+ 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 .
+*/
+
+#include "libraryplaylistitem.h"
+
+#include
+#include
+
+LibraryPlaylistItem::LibraryPlaylistItem(const QString& type)
+ : PlaylistItem(type)
+{
+}
+
+LibraryPlaylistItem::LibraryPlaylistItem(const Song& song)
+ : PlaylistItem("Library"),
+ song_(song)
+{
+}
+
+
+QUrl LibraryPlaylistItem::Url() const {
+ return QUrl::fromLocalFile(song_.filename());
+}
+
+void LibraryPlaylistItem::Reload() {
+ song_.InitFromFile(song_.filename(), song_.directory_id());
+}
+
+void LibraryPlaylistItem::InitFromQuery(const QSqlQuery &query) {
+ // Rows from the songs table come first
+ song_.InitFromQuery(query);
+}
+
+QVariant LibraryPlaylistItem::DatabaseValue(DatabaseColumn column) const {
+ switch (column) {
+ case Column_LibraryId: return song_.id();
+ default: return PlaylistItem::DatabaseValue(column);
+ }
+}
diff --git a/src/libraryplaylistitem.h b/src/libraryplaylistitem.h
new file mode 100644
index 000000000..f88d3e450
--- /dev/null
+++ b/src/libraryplaylistitem.h
@@ -0,0 +1,43 @@
+/* This file is part of Clementine.
+
+ 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 .
+*/
+
+#ifndef LIBRARYPLAYLISTITEM_H
+#define LIBRARYPLAYLISTITEM_H
+
+#include "playlistitem.h"
+#include "song.h"
+
+class LibraryPlaylistItem : public PlaylistItem {
+ public:
+ LibraryPlaylistItem(const QString& type);
+ LibraryPlaylistItem(const Song& song);
+
+ void InitFromQuery(const QSqlQuery &query);
+ void BindToQuery(QSqlQuery *query) const;
+ void Reload();
+
+ Song Metadata() const { return song_; }
+
+ QUrl Url() const;
+
+ protected:
+ QVariant DatabaseValue(DatabaseColumn column) const;
+
+ private:
+ Song song_;
+};
+
+#endif // LIBRARYPLAYLISTITEM_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 2ba0602ca..662b815a0 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -120,8 +120,6 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
library_sort_model_->setDynamicSortFilter(true);
library_sort_model_->sort(0);
- playlist_->Restore();
-
playlist_->IgnoreSorting(true);
ui_.playlist->setModel(playlist_);
ui_.playlist->setItemDelegates(library_);
@@ -226,6 +224,8 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished()));
connect(library_, SIGNAL(BackendReady(boost::shared_ptr)),
cover_manager_, SLOT(SetBackend(boost::shared_ptr)));
+ connect(library_, SIGNAL(BackendReady(boost::shared_ptr)),
+ playlist_, SLOT(SetBackend(boost::shared_ptr)));
// Age filters
QActionGroup* filter_age_group = new QActionGroup(this);
@@ -546,7 +546,7 @@ void MainWindow::AddLibraryItemToPlaylist(const QModelIndex& index) {
idx = library_sort_model_->mapToSource(idx);
QModelIndex first_song =
- playlist_->InsertSongs(library_->GetChildSongs(idx));
+ playlist_->InsertLibraryItems(library_->GetChildSongs(idx));
if (first_song.isValid() && player_->GetState() != Engine::Playing)
player_->PlayAt(first_song.row(), Engine::First);
diff --git a/src/player.cpp b/src/player.cpp
index 594fd2cec..10c5eb5a6 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -331,27 +331,16 @@ inline void AddMetadata(const QString& key, int metadata, QVariantMap* map) {
QVariantMap Player::GetMetadata(const PlaylistItem& item) const {
QVariantMap ret;
- if (item.type() == PlaylistItem::Type_Song) {
- const Song& song = item.Metadata();
- if (song.is_valid()) {
- AddMetadata("location", item.Url().toString(), &ret);
- AddMetadata("title", song.PrettyTitle(), &ret);
- AddMetadata("artist", song.artist(), &ret);
- AddMetadata("album", song.album(), &ret);
- AddMetadata("time", song.length(), &ret);
- AddMetadata("tracknumber", song.track(), &ret);
- }
- return ret;
- } else {
- AddMetadata("location", item.Url().toString(), &ret);
- const Song& song = item.Metadata();
- AddMetadata("title", song.PrettyTitle(), &ret);
- AddMetadata("artist", song.artist(), &ret);
- AddMetadata("album", song.album(), &ret);
- AddMetadata("time", song.length(), &ret);
- AddMetadata("tracknumber", song.track(), &ret);
- return ret;
- }
+
+ const Song& song = item.Metadata();
+ AddMetadata("location", item.Url().toString(), &ret);
+ AddMetadata("title", song.PrettyTitle(), &ret);
+ AddMetadata("artist", song.artist(), &ret);
+ AddMetadata("album", song.album(), &ret);
+ AddMetadata("time", song.length(), &ret);
+ AddMetadata("tracknumber", song.track(), &ret);
+
+ return ret;
}
QVariantMap Player::GetMetadata() const {
diff --git a/src/playlist.cpp b/src/playlist.cpp
index d64fd3f13..41a3d0cc6 100644
--- a/src/playlist.cpp
+++ b/src/playlist.cpp
@@ -21,6 +21,8 @@
#include "radioplaylistitem.h"
#include "radiomodel.h"
#include "savedradio.h"
+#include "librarybackend.h"
+#include "libraryplaylistitem.h"
#include
#include
@@ -334,7 +336,7 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
if (const SongMimeData* song_data = qobject_cast(data)) {
// Dragged from the library
- InsertSongs(song_data->songs, row);
+ InsertLibraryItems(song_data->songs, row);
} else if (const RadioMimeData* radio_data = qobject_cast(data)) {
// Dragged from the Radio pane
InsertRadioStations(radio_data->items, row);
@@ -457,6 +459,14 @@ QModelIndex Playlist::InsertItems(const QList& items, int after)
return index(start, 0);
}
+QModelIndex Playlist::InsertLibraryItems(const SongList& songs, int after) {
+ QList items;
+ foreach (const Song& song, songs) {
+ items << new LibraryPlaylistItem(song);
+ }
+ return InsertItems(items, after);
+}
+
QModelIndex Playlist::InsertSongs(const SongList& songs, int after) {
QList items;
foreach (const Song& song, songs) {
@@ -625,37 +635,34 @@ void Playlist::SetCurrentIsPaused(bool paused) {
index(current_item_.row(), ColumnCount));
}
+void Playlist::SetBackend(boost::shared_ptr backend) {
+ backend_ = backend;
+
+ Restore();
+}
+
void Playlist::Save() const {
- settings_->beginWriteArray("items", items_.count());
- for (int i=0 ; isetArrayIndex(i);
- settings_->setValue("type", items_.at(i)->type_string());
- items_.at(i)->Save(settings_.get());
- }
- settings_->endArray();
+ if (!backend_)
+ return;
+
+ backend_->SavePlaylist(1, items_);
settings_->setValue("last_index", last_played_index());
}
void Playlist::Restore() {
+ if (!backend_)
+ return;
+
qDeleteAll(items_);
items_.clear();
virtual_items_.clear();
- int count = settings_->beginReadArray("items");
- for (int i=0 ; isetArrayIndex(i);
- QString type(settings_->value("type").toString());
+ items_ = backend_->GetPlaylistItems(1);
- PlaylistItem* item = PlaylistItem::NewFromType(type);
- if (!item)
- continue;
-
- item->Restore(*settings_.get());
- items_ << item;
- virtual_items_ << virtual_items_.count();
- }
- settings_->endArray();
+ for (int i=0 ; i
#include
+#include
+
#include "playlistitem.h"
#include "song.h"
#include "radioitem.h"
@@ -27,6 +29,7 @@
#include "settingsprovider.h"
class RadioService;
+class LibraryBackendInterface;
class Playlist : public QAbstractListModel {
Q_OBJECT
@@ -102,7 +105,8 @@ class Playlist : public QAbstractListModel {
void set_scrobbled(bool v) { has_scrobbled_ = v; }
// Changing the playlist
- QModelIndex InsertItems(const QList& items, int after = -1);
+ QModelIndex InsertItems(const PlaylistItemList& items, int after = -1);
+ QModelIndex InsertLibraryItems(const SongList& items, int after = -1);
QModelIndex InsertSongs(const SongList& items, int after = -1);
QModelIndex InsertRadioStations(const QList& items, int after = -1);
QModelIndex InsertStreamUrls(const QList& urls, int after = -1);
@@ -126,6 +130,8 @@ class Playlist : public QAbstractListModel {
public slots:
+ void SetBackend(boost::shared_ptr);
+
void set_current_index(int index);
void Paused();
void Playing();
@@ -155,7 +161,9 @@ class Playlist : public QAbstractListModel {
private:
boost::scoped_ptr settings_;
- QList items_;
+ boost::shared_ptr backend_;
+
+ PlaylistItemList items_;
QList virtual_items_; // Contains the indices into items_ in the order
// that they will be played.
diff --git a/src/playlistitem.cpp b/src/playlistitem.cpp
index 22143ca4f..a93cba584 100644
--- a/src/playlistitem.cpp
+++ b/src/playlistitem.cpp
@@ -17,25 +17,31 @@
#include "playlistitem.h"
#include "songplaylistitem.h"
#include "radioplaylistitem.h"
+#include "libraryplaylistitem.h"
#include
-QString PlaylistItem::type_string() const {
- switch (type()) {
- case Type_Song: return "Song";
- case Type_Radio: return "Radio";
- default:
- qWarning() << "Invalid PlaylistItem type:" << type();
- return QString::null;
- }
-}
-
PlaylistItem* PlaylistItem::NewFromType(const QString& type) {
- if (type == "Song")
- return new SongPlaylistItem;
+ if (type == "Library")
+ return new LibraryPlaylistItem(type);
+ if (type == "Stream" || type == "File")
+ return new SongPlaylistItem(type);
if (type == "Radio")
- return new RadioPlaylistItem;
+ return new RadioPlaylistItem(type);
qWarning() << "Invalid PlaylistItem type:" << type;
return NULL;
}
+
+void PlaylistItem::BindToQuery(QSqlQuery* query) const {
+ query->bindValue(":type", type());
+ query->bindValue(":library_id", DatabaseValue(Column_LibraryId));
+ query->bindValue(":url", DatabaseValue(Column_Url));
+ query->bindValue(":title", DatabaseValue(Column_Title));
+ query->bindValue(":artist", DatabaseValue(Column_Artist));
+ query->bindValue(":album", DatabaseValue(Column_Album));
+ query->bindValue(":length", DatabaseValue(Column_Length));
+ query->bindValue(":radio_service", DatabaseValue(Column_RadioService));
+}
+
+
diff --git a/src/playlistitem.h b/src/playlistitem.h
index 062617d72..391efe508 100644
--- a/src/playlistitem.h
+++ b/src/playlistitem.h
@@ -21,20 +21,16 @@
#include
class Song;
-class SettingsProvider;
+
+class QSqlQuery;
class PlaylistItem {
public:
- PlaylistItem() {}
+ PlaylistItem(const QString& type) : type_(type) {}
virtual ~PlaylistItem() {}
static PlaylistItem* NewFromType(const QString& type);
- enum Type {
- Type_Song,
- Type_Radio,
- };
-
enum Option {
Default = 0x00,
@@ -45,13 +41,12 @@ class PlaylistItem {
};
Q_DECLARE_FLAGS(Options, Option);
- virtual Type type() const = 0;
- QString type_string() const;
+ virtual QString type() const { return type_; }
virtual Options options() const { return Default; }
- virtual void Save(SettingsProvider* settings) const = 0;
- virtual void Restore(const SettingsProvider& settings) = 0;
+ virtual void InitFromQuery(const QSqlQuery& query) = 0;
+ void BindToQuery(QSqlQuery* query) const;
virtual void Reload() {}
virtual Song Metadata() const = 0;
@@ -69,7 +64,24 @@ class PlaylistItem {
virtual void SetTemporaryMetadata(const Song& metadata) {Q_UNUSED(metadata)}
virtual void ClearTemporaryMetadata() {}
+
+ protected:
+ enum DatabaseColumn {
+ Column_LibraryId,
+ Column_Url,
+ Column_Title,
+ Column_Artist,
+ Column_Album,
+ Column_Length,
+ Column_RadioService,
+ };
+
+ virtual QVariant DatabaseValue(DatabaseColumn) const {
+ return QVariant(QVariant::String); }
+
+ QString type_;
};
+typedef QList PlaylistItemList;
Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::Options);
diff --git a/src/radioplaylistitem.cpp b/src/radioplaylistitem.cpp
index b9e1160f4..c54165079 100644
--- a/src/radioplaylistitem.cpp
+++ b/src/radioplaylistitem.cpp
@@ -21,15 +21,18 @@
#include
#include
+#include
-RadioPlaylistItem::RadioPlaylistItem()
- : service_(NULL)
+RadioPlaylistItem::RadioPlaylistItem(const QString& type)
+ : PlaylistItem(type),
+ service_(NULL)
{
}
RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
const QString& title, const QString& artist)
- : service_(service),
+ : PlaylistItem("Radio"),
+ service_(service),
url_(url),
title_(title),
artist_(artist)
@@ -37,22 +40,30 @@ RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
InitMetadata();
}
-void RadioPlaylistItem::Save(SettingsProvider* settings) const {
- settings->setValue("service", service_->name());
- settings->setValue("url", url_.toString());
- settings->setValue("title", title_);
- settings->setValue("artist", artist_);
-}
+void RadioPlaylistItem::InitFromQuery(const QSqlQuery &query) {
+ // The song table gets joined first, plus one for the song ROWID
+ const int row = Song::kColumns.count() + 1;
-void RadioPlaylistItem::Restore(const SettingsProvider& settings) {
- service_ = RadioModel::ServiceByName(settings.value("service").toString());
- url_ = settings.value("url").toString();
- title_ = settings.value("title").toString();
- artist_ = settings.value("artist").toString();
+ url_ = query.value(row + 1).toString();
+ title_ = query.value(row + 2).toString();
+ artist_ = query.value(row + 3).toString();
+ QString service(query.value(row + 6).toString());
+
+ service_ = RadioModel::ServiceByName(service);
InitMetadata();
}
+QVariant RadioPlaylistItem::DatabaseValue(DatabaseColumn column) const {
+ switch (column) {
+ case Column_Url: return url_.toString();
+ case Column_Title: return title_;
+ case Column_Artist: return artist_;
+ case Column_RadioService: return service_->name();
+ default: return PlaylistItem::DatabaseValue(column);
+ }
+}
+
void RadioPlaylistItem::InitMetadata() {
if (!service_)
metadata_.set_title(QApplication::translate("RadioPlaylistItem", "Radio service couldn't be loaded :-("));
diff --git a/src/radioplaylistitem.h b/src/radioplaylistitem.h
index c76b5e753..db5c76bba 100644
--- a/src/radioplaylistitem.h
+++ b/src/radioplaylistitem.h
@@ -26,15 +26,14 @@ class RadioService;
class RadioPlaylistItem : public PlaylistItem {
public:
- RadioPlaylistItem();
+ RadioPlaylistItem(const QString& type);
RadioPlaylistItem(RadioService* service, const QUrl& url,
const QString& title, const QString& artist);
- Type type() const { return Type_Radio; }
Options options() const;
- void Save(SettingsProvider* settings) const;
- void Restore(const SettingsProvider& settings);
+ void InitFromQuery(const QSqlQuery &query);
+ void BindToQuery(QSqlQuery *query) const;
Song Metadata() const;
@@ -46,6 +45,9 @@ class RadioPlaylistItem : public PlaylistItem {
void SetTemporaryMetadata(const Song& metadata);
void ClearTemporaryMetadata();
+ protected:
+ QVariant DatabaseValue(DatabaseColumn) const;
+
private:
void InitMetadata();
diff --git a/src/scopedtransaction.cpp b/src/scopedtransaction.cpp
new file mode 100644
index 000000000..f9b5739e5
--- /dev/null
+++ b/src/scopedtransaction.cpp
@@ -0,0 +1,43 @@
+/* This file is part of Clementine.
+
+ 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 .
+*/
+
+#include "scopedtransaction.h"
+
+#include
+#include
+
+ScopedTransaction::ScopedTransaction(QSqlDatabase* db)
+ : db_(db),
+ pending_(true)
+{
+}
+
+ScopedTransaction::~ScopedTransaction() {
+ if (pending_) {
+ qDebug() << __PRETTY_FUNCTION__ << "Rolling back transaction";
+ db_->rollback();
+ }
+}
+
+void ScopedTransaction::Commit() {
+ if (!pending_) {
+ qWarning() << "Tried to commit a ScopedTransaction twice";
+ return;
+ }
+
+ db_->commit();
+ pending_ = false;
+}
diff --git a/src/scopedtransaction.h b/src/scopedtransaction.h
new file mode 100644
index 000000000..230d6495d
--- /dev/null
+++ b/src/scopedtransaction.h
@@ -0,0 +1,39 @@
+/* This file is part of Clementine.
+
+ 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 .
+*/
+
+#ifndef SCOPEDTRANSACTION_H
+#define SCOPEDTRANSACTION_H
+
+#include
+
+class QSqlDatabase;
+
+// Opens a transaction on a database.
+// Rolls back the transaction if the object goes out of scope before Commit()
+// is called.
+class ScopedTransaction : boost::noncopyable {
+ public:
+ ScopedTransaction(QSqlDatabase* db);
+ ~ScopedTransaction();
+
+ void Commit();
+
+ private:
+ QSqlDatabase* db_;
+ bool pending_;
+};
+
+#endif // SCOPEDTRANSACTION_H
diff --git a/src/song.cpp b/src/song.cpp
index 84dbec841..c214ab2aa 100644
--- a/src/song.cpp
+++ b/src/song.cpp
@@ -53,35 +53,35 @@ using boost::scoped_ptr;
#include "engines/enginebase.h"
#include "albumcoverloader.h"
-const char* Song::kColumnSpec =
- "title, album, artist, albumartist, composer, "
- "track, disc, bpm, year, genre, comment, compilation, "
- "length, bitrate, samplerate, directory, filename, "
- "mtime, ctime, filesize, sampler, art_automatic, art_manual, "
- "filetype, playcount, lastplayed, rating, forced_compilation_on, "
- "forced_compilation_off, effective_compilation";
+static QStringList Prepend(const QString& text, const QStringList& list) {
+ QStringList ret(list);
+ for (int i=0 ; i
#include
#include
-SongPlaylistItem::SongPlaylistItem()
+SongPlaylistItem::SongPlaylistItem(const QString& type)
+ : PlaylistItem(type)
{
}
SongPlaylistItem::SongPlaylistItem(const Song& song)
- : song_(song)
+ : PlaylistItem(song.filetype() == Song::Type_Stream ? "Stream" : "File"),
+ song_(song)
{
}
-void SongPlaylistItem::Save(SettingsProvider* settings) const {
- settings->setValue("filename", song_.filename());
- settings->setValue("art_automatic", song_.art_automatic());
- settings->setValue("art_manual", song_.art_manual());
+void SongPlaylistItem::InitFromQuery(const QSqlQuery &query) {
+ // The song table gets joined first, plus one for the song ROWID
+ const int row = Song::kColumns.count() + 1;
- if (song_.filetype() == Song::Type_Stream) {
- SaveStream(settings);
+ QString filename(query.value(row + 1).toString());
+
+ if (type() == "Stream") {
+ QString title(query.value(row + 2).toString());
+ QString artist(query.value(row + 3).toString());
+ QString album(query.value(row + 4).toString());
+ int length(query.value(row + 5).toInt());
+ if (title.isEmpty()) title = "Unknown";
+ if (artist.isEmpty()) artist = "Unknown";
+ if (album.isEmpty()) album = "Unknown";
+ if (length == 0) length = -1;
+
+ song_.set_filename(filename);
+ song_.set_filetype(Song::Type_Stream);
+
+ song_.Init(title, artist, album, length);
} else {
- SaveFile(settings);
+ song_.InitFromFile(filename, -1);
}
}
-void SongPlaylistItem::SaveFile(SettingsProvider* settings) const {
- settings->setValue("stream", false);
- settings->setValue("library_directory", song_.directory_id());
-}
-
-void SongPlaylistItem::SaveStream(SettingsProvider* settings) const {
- settings->setValue("stream", true);
- settings->setValue("title", song_.title());
- settings->setValue("artist", song_.artist());
- settings->setValue("album", song_.album());
- settings->setValue("length", song_.length());
-}
-
-void SongPlaylistItem::Restore(const SettingsProvider& settings) {
- song_.set_art_automatic(settings.value("art_automatic").toString());
- song_.set_art_manual(settings.value("art_manual").toString());
-
- const bool stream = settings.value("stream", false).toBool();
- if (stream) {
- RestoreStream(settings);
- } else {
- RestoreFile(settings);
+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);
}
}
-void SongPlaylistItem::RestoreFile(const SettingsProvider& settings) {
- QString filename(settings.value("filename").toString());
-
- int directory_id(settings.value("library_directory", -1).toInt());
- song_.InitFromFile(filename, directory_id);
-}
-
-void SongPlaylistItem::RestoreStream(const SettingsProvider& settings) {
- QString filename(settings.value("filename").toString());
- song_.set_filename(filename);
- song_.set_filetype(Song::Type_Stream);
-
- song_.Init(settings.value("title", "Unknown").toString(),
- settings.value("artist", "Unknown").toString(),
- settings.value("album", "Unknown").toString(),
- settings.value("length", -1).toInt());
-}
-
QUrl SongPlaylistItem::Url() const {
if (QFile::exists(song_.filename())) {
return QUrl::fromLocalFile(song_.filename());
diff --git a/src/songplaylistitem.h b/src/songplaylistitem.h
index 6f9db9f68..20e6541b4 100644
--- a/src/songplaylistitem.h
+++ b/src/songplaylistitem.h
@@ -22,25 +22,20 @@
class SongPlaylistItem : public PlaylistItem {
public:
- SongPlaylistItem();
+ SongPlaylistItem(const QString& type);
SongPlaylistItem(const Song& song);
- Type type() const { return Type_Song; }
-
- void Save(SettingsProvider* settings) const;
- void Restore(const SettingsProvider& settings);
+ void InitFromQuery(const QSqlQuery &query);
void Reload();
Song Metadata() const { return song_; }
QUrl Url() const;
- private:
- void SaveFile(SettingsProvider* settings) const;
- void SaveStream(SettingsProvider* settings) const;
+ protected:
+ QVariant DatabaseValue(DatabaseColumn) const;
- void RestoreFile(const SettingsProvider& settings);
- void RestoreStream(const SettingsProvider& settings);
+ private:
Song song_;
};
diff --git a/tests/mock_playlistitem.cpp b/tests/mock_playlistitem.cpp
index 0a110c9b3..60b95977d 100644
--- a/tests/mock_playlistitem.cpp
+++ b/tests/mock_playlistitem.cpp
@@ -19,7 +19,7 @@
using ::testing::_;
using ::testing::Return;
-MockPlaylistItem::MockPlaylistItem() {
- EXPECT_CALL(*this, Save(_))
- .WillRepeatedly(Return());
+MockPlaylistItem::MockPlaylistItem()
+ : PlaylistItem("DummyType")
+{
}
diff --git a/tests/mock_playlistitem.h b/tests/mock_playlistitem.h
index eab4b2c3f..6ef5a6aa2 100644
--- a/tests/mock_playlistitem.h
+++ b/tests/mock_playlistitem.h
@@ -27,14 +27,10 @@ class MockPlaylistItem : public PlaylistItem {
public:
MockPlaylistItem();
- MOCK_CONST_METHOD0(type,
- Type());
MOCK_CONST_METHOD0(options,
Options());
- MOCK_CONST_METHOD1(Save,
- void(SettingsProvider* settings));
- MOCK_METHOD1(Restore,
- void(const SettingsProvider& settings));
+ MOCK_METHOD1(InitFromQuery,
+ void(const QSqlQuery& settings));
MOCK_METHOD0(Reload,
void());
MOCK_CONST_METHOD0(Metadata,
@@ -49,6 +45,8 @@ class MockPlaylistItem : public PlaylistItem {
void(const Song& metadata));
MOCK_METHOD0(ClearTemporaryMetadata,
void());
+ MOCK_METHOD1(DatabaseValue,
+ QVariant(DatabaseColumn));
};
#endif // MOCK_PLAYLISTITEM_H
diff --git a/tests/playlist_test.cpp b/tests/playlist_test.cpp
index adfe56d46..df024e94a 100644
--- a/tests/playlist_test.cpp
+++ b/tests/playlist_test.cpp
@@ -47,8 +47,6 @@ class PlaylistTest : public ::testing::Test {
metadata.Init(title, artist, album, length);
MockPlaylistItem* ret = new MockPlaylistItem;
- EXPECT_CALL(*ret, type())
- .WillRepeatedly(Return(PlaylistItem::Type_Song));
EXPECT_CALL(*ret, Metadata())
.WillRepeatedly(Return(metadata));