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