Store the playlist in the database. This is still quite slow.
This commit is contained in:
parent
dc782cfa5b
commit
af07b5becd
@ -78,5 +78,6 @@
|
|||||||
<file>schema-6.sql</file>
|
<file>schema-6.sql</file>
|
||||||
<file>list-add.png</file>
|
<file>list-add.png</file>
|
||||||
<file>document-save.png</file>
|
<file>document-save.png</file>
|
||||||
|
<file>schema-7.sql</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
26
data/schema-7.sql
Normal file
26
data/schema-7.sql
Normal file
@ -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;
|
||||||
|
|
@ -76,6 +76,8 @@ set(CLEMENTINE-SOURCES
|
|||||||
stickyslider.cpp
|
stickyslider.cpp
|
||||||
commandlineoptions.cpp
|
commandlineoptions.cpp
|
||||||
settingsprovider.cpp
|
settingsprovider.cpp
|
||||||
|
libraryplaylistitem.cpp
|
||||||
|
scopedtransaction.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Header files that have Q_OBJECT in
|
# Header files that have Q_OBJECT in
|
||||||
|
@ -505,7 +505,7 @@ void Library::InitQuery(GroupBy type, LibraryQuery* q) {
|
|||||||
q->SetColumnSpec("DISTINCT albumartist");
|
q->SetColumnSpec("DISTINCT albumartist");
|
||||||
break;
|
break;
|
||||||
case GroupBy_None:
|
case GroupBy_None:
|
||||||
q->SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
|
q->SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "librarybackend.h"
|
#include "librarybackend.h"
|
||||||
#include "libraryquery.h"
|
#include "libraryquery.h"
|
||||||
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@ -32,7 +33,7 @@
|
|||||||
|
|
||||||
|
|
||||||
const char* LibraryBackend::kDatabaseName = "clementine.db";
|
const char* LibraryBackend::kDatabaseName = "clementine.db";
|
||||||
const int LibraryBackend::kSchemaVersion = 6;
|
const int LibraryBackend::kSchemaVersion = 7;
|
||||||
|
|
||||||
int (*LibraryBackend::_sqlite3_create_function) (
|
int (*LibraryBackend::_sqlite3_create_function) (
|
||||||
sqlite3*, const char*, int, int, void*,
|
sqlite3*, const char*, int, int, void*,
|
||||||
@ -359,7 +360,7 @@ void LibraryBackend::RemoveDirectory(const Directory& dir) {
|
|||||||
// Remove songs first
|
// Remove songs first
|
||||||
DeleteSongs(FindSongsInDirectory(dir.id));
|
DeleteSongs(FindSongsInDirectory(dir.id));
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
|
|
||||||
// Delete the subdirs that were in this directory
|
// Delete the subdirs that were in this directory
|
||||||
QSqlQuery q("DELETE FROM subdirectories WHERE directory = :id", db);
|
QSqlQuery q("DELETE FROM subdirectories WHERE directory = :id", db);
|
||||||
@ -375,13 +376,13 @@ void LibraryBackend::RemoveDirectory(const Directory& dir) {
|
|||||||
|
|
||||||
emit DirectoryDeleted(dir);
|
emit DirectoryDeleted(dir);
|
||||||
|
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList LibraryBackend::FindSongsInDirectory(int id) {
|
SongList LibraryBackend::FindSongsInDirectory(int id) {
|
||||||
QSqlDatabase db(Connect());
|
QSqlDatabase db(Connect());
|
||||||
|
|
||||||
QSqlQuery q("SELECT ROWID, " + QString(Song::kColumnSpec) +
|
QSqlQuery q("SELECT ROWID, " + Song::kColumnSpec +
|
||||||
" FROM songs WHERE directory = :directory", db);
|
" FROM songs WHERE directory = :directory", db);
|
||||||
q.bindValue(":directory", id);
|
q.bindValue(":directory", id);
|
||||||
q.exec();
|
q.exec();
|
||||||
@ -407,7 +408,7 @@ void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
|
|||||||
QSqlQuery delete_query("DELETE FROM subdirectories"
|
QSqlQuery delete_query("DELETE FROM subdirectories"
|
||||||
" WHERE directory = :id AND path = :path", db);
|
" WHERE directory = :id AND path = :path", db);
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
foreach (const Subdirectory& subdir, subdirs) {
|
foreach (const Subdirectory& subdir, subdirs) {
|
||||||
if (subdir.mtime == 0) {
|
if (subdir.mtime == 0) {
|
||||||
// Delete the subdirectory
|
// Delete the subdirectory
|
||||||
@ -437,7 +438,7 @@ void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
|
void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
|
||||||
@ -446,13 +447,13 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
|
|||||||
QSqlQuery check_dir(
|
QSqlQuery check_dir(
|
||||||
"SELECT ROWID FROM directories WHERE ROWID = :id", db);
|
"SELECT ROWID FROM directories WHERE ROWID = :id", db);
|
||||||
QSqlQuery add_song(
|
QSqlQuery add_song(
|
||||||
"INSERT INTO songs (" + QString(Song::kColumnSpec) + ")"
|
"INSERT INTO songs (" + Song::kColumnSpec + ")"
|
||||||
" VALUES (" + QString(Song::kBindSpec) + ")", db);
|
" VALUES (" + Song::kBindSpec + ")", db);
|
||||||
QSqlQuery update_song(
|
QSqlQuery update_song(
|
||||||
"UPDATE songs SET " + QString(Song::kUpdateSpec) +
|
"UPDATE songs SET " + Song::kUpdateSpec +
|
||||||
" WHERE ROWID = :id", db);
|
" WHERE ROWID = :id", db);
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
|
|
||||||
SongList added_songs;
|
SongList added_songs;
|
||||||
SongList deleted_songs;
|
SongList deleted_songs;
|
||||||
@ -495,7 +496,7 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty())
|
if (!deleted_songs.isEmpty())
|
||||||
emit SongsDeleted(deleted_songs);
|
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);
|
QSqlQuery q("UPDATE songs SET mtime = :mtime WHERE ROWID = :id", db);
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
foreach (const Song& song, songs) {
|
foreach (const Song& song, songs) {
|
||||||
q.bindValue(":mtime", song.mtime());
|
q.bindValue(":mtime", song.mtime());
|
||||||
q.bindValue(":id", song.id());
|
q.bindValue(":id", song.id());
|
||||||
q.exec();
|
q.exec();
|
||||||
CheckErrors(q.lastError());
|
CheckErrors(q.lastError());
|
||||||
}
|
}
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibraryBackend::DeleteSongs(const SongList &songs) {
|
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);
|
QSqlQuery q("DELETE FROM songs WHERE ROWID = :id", db);
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
foreach (const Song& song, songs) {
|
foreach (const Song& song, songs) {
|
||||||
q.bindValue(":id", song.id());
|
q.bindValue(":id", song.id());
|
||||||
q.exec();
|
q.exec();
|
||||||
CheckErrors(q.lastError());
|
CheckErrors(q.lastError());
|
||||||
}
|
}
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
|
|
||||||
emit SongsDeleted(songs);
|
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) {
|
SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, const QueryOptions& opt) {
|
||||||
LibraryQuery query(opt);
|
LibraryQuery query(opt);
|
||||||
query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere("artist", artist);
|
query.AddWhere("artist", artist);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
@ -583,7 +584,7 @@ SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, c
|
|||||||
Song LibraryBackend::GetSongById(int id) {
|
Song LibraryBackend::GetSongById(int id) {
|
||||||
QSqlDatabase db(Connect());
|
QSqlDatabase db(Connect());
|
||||||
|
|
||||||
QSqlQuery q("SELECT ROWID, " + QString(Song::kColumnSpec) + " FROM songs"
|
QSqlQuery q("SELECT ROWID, " + Song::kColumnSpec + " FROM songs"
|
||||||
" WHERE ROWID = :id", db);
|
" WHERE ROWID = :id", db);
|
||||||
q.bindValue(":id", id);
|
q.bindValue(":id", id);
|
||||||
q.exec();
|
q.exec();
|
||||||
@ -612,7 +613,7 @@ LibraryBackend::AlbumList LibraryBackend::GetCompilationAlbums(const QueryOption
|
|||||||
|
|
||||||
SongList LibraryBackend::GetCompilationSongs(const QString& album, const QueryOptions& opt) {
|
SongList LibraryBackend::GetCompilationSongs(const QString& album, const QueryOptions& opt) {
|
||||||
LibraryQuery query(opt);
|
LibraryQuery query(opt);
|
||||||
query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
query.AddCompilationRequirement(true);
|
query.AddCompilationRequirement(true);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
@ -670,13 +671,13 @@ void LibraryBackend::UpdateCompilations() {
|
|||||||
" SET sampler = :sampler,"
|
" SET sampler = :sampler,"
|
||||||
" effective_compilation = ((compilation OR :sampler OR forced_compilation_on) AND NOT forced_compilation_off) + 0"
|
" effective_compilation = ((compilation OR :sampler OR forced_compilation_on) AND NOT forced_compilation_off) + 0"
|
||||||
" WHERE album = :album", db);
|
" 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);
|
" WHERE album = :album AND sampler = :sampler", db);
|
||||||
|
|
||||||
SongList deleted_songs;
|
SongList deleted_songs;
|
||||||
SongList added_songs;
|
SongList added_songs;
|
||||||
|
|
||||||
db.transaction();
|
ScopedTransaction transaction(&db);
|
||||||
|
|
||||||
QMap<QString, CompilationInfo>::const_iterator it = compilation_info.constBegin();
|
QMap<QString, CompilationInfo>::const_iterator it = compilation_info.constBegin();
|
||||||
for ( ; it != compilation_info.constEnd() ; ++it) {
|
for ( ; it != compilation_info.constEnd() ; ++it) {
|
||||||
@ -695,7 +696,7 @@ void LibraryBackend::UpdateCompilations() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.commit();
|
transaction.Commit();
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) {
|
if (!deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
@ -821,7 +822,7 @@ void LibraryBackend::ForceCompilation(const QString& artist, const QString& albu
|
|||||||
|
|
||||||
// Get the songs before they're updated
|
// Get the songs before they're updated
|
||||||
LibraryQuery query;
|
LibraryQuery query;
|
||||||
query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
if (!artist.isNull())
|
if (!artist.isNull())
|
||||||
query.AddWhere("artist", artist);
|
query.AddWhere("artist", artist);
|
||||||
@ -872,3 +873,71 @@ void LibraryBackend::ForceCompilation(const QString& artist, const QString& albu
|
|||||||
bool LibraryBackend::ExecQuery(LibraryQuery *q) {
|
bool LibraryBackend::ExecQuery(LibraryQuery *q) {
|
||||||
return !CheckErrors(q->Exec(Connect()));
|
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();
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "directory.h"
|
#include "directory.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
#include "libraryquery.h"
|
#include "libraryquery.h"
|
||||||
|
#include "playlistitem.h"
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
@ -52,6 +53,12 @@ class LibraryBackendInterface : public QObject {
|
|||||||
};
|
};
|
||||||
typedef QList<Album> AlbumList;
|
typedef QList<Album> AlbumList;
|
||||||
|
|
||||||
|
struct Playlist {
|
||||||
|
int id;
|
||||||
|
QString name;
|
||||||
|
};
|
||||||
|
typedef QList<Playlist> PlaylistList;
|
||||||
|
|
||||||
virtual void Stop() {};
|
virtual void Stop() {};
|
||||||
|
|
||||||
// Get a list of directories in the library. Emits DirectoriesDiscovered.
|
// 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
|
// Counts the songs in the library. Emits TotalSongCountUpdated
|
||||||
virtual void UpdateTotalSongCountAsync() = 0;
|
virtual void UpdateTotalSongCountAsync() = 0;
|
||||||
|
|
||||||
|
// Functions for getting songs
|
||||||
virtual SongList FindSongsInDirectory(int id) = 0;
|
virtual SongList FindSongsInDirectory(int id) = 0;
|
||||||
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
|
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
|
||||||
|
|
||||||
@ -78,12 +86,19 @@ class LibraryBackendInterface : public QObject {
|
|||||||
|
|
||||||
virtual Song GetSongById(int id) = 0;
|
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 AddDirectory(const QString& path) = 0;
|
||||||
virtual void RemoveDirectory(const Directory& dir) = 0;
|
virtual void RemoveDirectory(const Directory& dir) = 0;
|
||||||
|
|
||||||
|
// Update compilation flags on songs
|
||||||
virtual void UpdateCompilationsAsync() = 0;
|
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:
|
public slots:
|
||||||
virtual void LoadDirectories() = 0;
|
virtual void LoadDirectories() = 0;
|
||||||
@ -151,6 +166,10 @@ class LibraryBackend : public LibraryBackendInterface {
|
|||||||
|
|
||||||
bool ExecQuery(LibraryQuery* q);
|
bool ExecQuery(LibraryQuery* q);
|
||||||
|
|
||||||
|
PlaylistList GetAllPlaylists();
|
||||||
|
PlaylistItemList GetPlaylistItems(int playlist);
|
||||||
|
void SavePlaylist(int playlist, const PlaylistItemList& items);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void LoadDirectories();
|
void LoadDirectories();
|
||||||
void UpdateTotalSongCount();
|
void UpdateTotalSongCount();
|
||||||
|
52
src/libraryplaylistitem.cpp
Normal file
52
src/libraryplaylistitem.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libraryplaylistitem.h"
|
||||||
|
|
||||||
|
#include <QtDebug>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
43
src/libraryplaylistitem.h
Normal file
43
src/libraryplaylistitem.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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
|
@ -120,8 +120,6 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
|
|||||||
library_sort_model_->setDynamicSortFilter(true);
|
library_sort_model_->setDynamicSortFilter(true);
|
||||||
library_sort_model_->sort(0);
|
library_sort_model_->sort(0);
|
||||||
|
|
||||||
playlist_->Restore();
|
|
||||||
|
|
||||||
playlist_->IgnoreSorting(true);
|
playlist_->IgnoreSorting(true);
|
||||||
ui_.playlist->setModel(playlist_);
|
ui_.playlist->setModel(playlist_);
|
||||||
ui_.playlist->setItemDelegates(library_);
|
ui_.playlist->setItemDelegates(library_);
|
||||||
@ -226,6 +224,8 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
|
|||||||
connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished()));
|
connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished()));
|
||||||
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackendInterface>)),
|
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackendInterface>)),
|
||||||
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackendInterface>)));
|
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackendInterface>)));
|
||||||
|
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackendInterface>)),
|
||||||
|
playlist_, SLOT(SetBackend(boost::shared_ptr<LibraryBackendInterface>)));
|
||||||
|
|
||||||
// Age filters
|
// Age filters
|
||||||
QActionGroup* filter_age_group = new QActionGroup(this);
|
QActionGroup* filter_age_group = new QActionGroup(this);
|
||||||
@ -546,7 +546,7 @@ void MainWindow::AddLibraryItemToPlaylist(const QModelIndex& index) {
|
|||||||
idx = library_sort_model_->mapToSource(idx);
|
idx = library_sort_model_->mapToSource(idx);
|
||||||
|
|
||||||
QModelIndex first_song =
|
QModelIndex first_song =
|
||||||
playlist_->InsertSongs(library_->GetChildSongs(idx));
|
playlist_->InsertLibraryItems(library_->GetChildSongs(idx));
|
||||||
|
|
||||||
if (first_song.isValid() && player_->GetState() != Engine::Playing)
|
if (first_song.isValid() && player_->GetState() != Engine::Playing)
|
||||||
player_->PlayAt(first_song.row(), Engine::First);
|
player_->PlayAt(first_song.row(), Engine::First);
|
||||||
|
@ -331,27 +331,16 @@ inline void AddMetadata(const QString& key, int metadata, QVariantMap* map) {
|
|||||||
|
|
||||||
QVariantMap Player::GetMetadata(const PlaylistItem& item) const {
|
QVariantMap Player::GetMetadata(const PlaylistItem& item) const {
|
||||||
QVariantMap ret;
|
QVariantMap ret;
|
||||||
if (item.type() == PlaylistItem::Type_Song) {
|
|
||||||
const Song& song = item.Metadata();
|
const Song& song = item.Metadata();
|
||||||
if (song.is_valid()) {
|
|
||||||
AddMetadata("location", item.Url().toString(), &ret);
|
AddMetadata("location", item.Url().toString(), &ret);
|
||||||
AddMetadata("title", song.PrettyTitle(), &ret);
|
AddMetadata("title", song.PrettyTitle(), &ret);
|
||||||
AddMetadata("artist", song.artist(), &ret);
|
AddMetadata("artist", song.artist(), &ret);
|
||||||
AddMetadata("album", song.album(), &ret);
|
AddMetadata("album", song.album(), &ret);
|
||||||
AddMetadata("time", song.length(), &ret);
|
AddMetadata("time", song.length(), &ret);
|
||||||
AddMetadata("tracknumber", song.track(), &ret);
|
AddMetadata("tracknumber", song.track(), &ret);
|
||||||
}
|
|
||||||
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap Player::GetMetadata() const {
|
QVariantMap Player::GetMetadata() const {
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#include "radioplaylistitem.h"
|
#include "radioplaylistitem.h"
|
||||||
#include "radiomodel.h"
|
#include "radiomodel.h"
|
||||||
#include "savedradio.h"
|
#include "savedradio.h"
|
||||||
|
#include "librarybackend.h"
|
||||||
|
#include "libraryplaylistitem.h"
|
||||||
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
@ -334,7 +336,7 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
|
|||||||
|
|
||||||
if (const SongMimeData* song_data = qobject_cast<const SongMimeData*>(data)) {
|
if (const SongMimeData* song_data = qobject_cast<const SongMimeData*>(data)) {
|
||||||
// Dragged from the library
|
// Dragged from the library
|
||||||
InsertSongs(song_data->songs, row);
|
InsertLibraryItems(song_data->songs, row);
|
||||||
} else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) {
|
} else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) {
|
||||||
// Dragged from the Radio pane
|
// Dragged from the Radio pane
|
||||||
InsertRadioStations(radio_data->items, row);
|
InsertRadioStations(radio_data->items, row);
|
||||||
@ -457,6 +459,14 @@ QModelIndex Playlist::InsertItems(const QList<PlaylistItem*>& items, int after)
|
|||||||
return index(start, 0);
|
return index(start, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QModelIndex Playlist::InsertLibraryItems(const SongList& songs, int after) {
|
||||||
|
QList<PlaylistItem*> items;
|
||||||
|
foreach (const Song& song, songs) {
|
||||||
|
items << new LibraryPlaylistItem(song);
|
||||||
|
}
|
||||||
|
return InsertItems(items, after);
|
||||||
|
}
|
||||||
|
|
||||||
QModelIndex Playlist::InsertSongs(const SongList& songs, int after) {
|
QModelIndex Playlist::InsertSongs(const SongList& songs, int after) {
|
||||||
QList<PlaylistItem*> items;
|
QList<PlaylistItem*> items;
|
||||||
foreach (const Song& song, songs) {
|
foreach (const Song& song, songs) {
|
||||||
@ -625,37 +635,34 @@ void Playlist::SetCurrentIsPaused(bool paused) {
|
|||||||
index(current_item_.row(), ColumnCount));
|
index(current_item_.row(), ColumnCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Playlist::SetBackend(boost::shared_ptr<LibraryBackendInterface> backend) {
|
||||||
|
backend_ = backend;
|
||||||
|
|
||||||
|
Restore();
|
||||||
|
}
|
||||||
|
|
||||||
void Playlist::Save() const {
|
void Playlist::Save() const {
|
||||||
settings_->beginWriteArray("items", items_.count());
|
if (!backend_)
|
||||||
for (int i=0 ; i<items_.count() ; ++i) {
|
return;
|
||||||
settings_->setArrayIndex(i);
|
|
||||||
settings_->setValue("type", items_.at(i)->type_string());
|
backend_->SavePlaylist(1, items_);
|
||||||
items_.at(i)->Save(settings_.get());
|
|
||||||
}
|
|
||||||
settings_->endArray();
|
|
||||||
|
|
||||||
settings_->setValue("last_index", last_played_index());
|
settings_->setValue("last_index", last_played_index());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::Restore() {
|
void Playlist::Restore() {
|
||||||
|
if (!backend_)
|
||||||
|
return;
|
||||||
|
|
||||||
qDeleteAll(items_);
|
qDeleteAll(items_);
|
||||||
items_.clear();
|
items_.clear();
|
||||||
virtual_items_.clear();
|
virtual_items_.clear();
|
||||||
|
|
||||||
int count = settings_->beginReadArray("items");
|
items_ = backend_->GetPlaylistItems(1);
|
||||||
for (int i=0 ; i<count ; ++i) {
|
|
||||||
settings_->setArrayIndex(i);
|
|
||||||
QString type(settings_->value("type").toString());
|
|
||||||
|
|
||||||
PlaylistItem* item = PlaylistItem::NewFromType(type);
|
for (int i=0 ; i<items_.count() ; ++i) {
|
||||||
if (!item)
|
virtual_items_ << i;
|
||||||
continue;
|
};
|
||||||
|
|
||||||
item->Restore(*settings_.get());
|
|
||||||
items_ << item;
|
|
||||||
virtual_items_ << virtual_items_.count();
|
|
||||||
}
|
|
||||||
settings_->endArray();
|
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
#include "playlistitem.h"
|
#include "playlistitem.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
#include "radioitem.h"
|
#include "radioitem.h"
|
||||||
@ -27,6 +29,7 @@
|
|||||||
#include "settingsprovider.h"
|
#include "settingsprovider.h"
|
||||||
|
|
||||||
class RadioService;
|
class RadioService;
|
||||||
|
class LibraryBackendInterface;
|
||||||
|
|
||||||
class Playlist : public QAbstractListModel {
|
class Playlist : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -102,7 +105,8 @@ class Playlist : public QAbstractListModel {
|
|||||||
void set_scrobbled(bool v) { has_scrobbled_ = v; }
|
void set_scrobbled(bool v) { has_scrobbled_ = v; }
|
||||||
|
|
||||||
// Changing the playlist
|
// Changing the playlist
|
||||||
QModelIndex InsertItems(const QList<PlaylistItem*>& 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 InsertSongs(const SongList& items, int after = -1);
|
||||||
QModelIndex InsertRadioStations(const QList<RadioItem*>& items, int after = -1);
|
QModelIndex InsertRadioStations(const QList<RadioItem*>& items, int after = -1);
|
||||||
QModelIndex InsertStreamUrls(const QList<QUrl>& urls, int after = -1);
|
QModelIndex InsertStreamUrls(const QList<QUrl>& urls, int after = -1);
|
||||||
@ -126,6 +130,8 @@ class Playlist : public QAbstractListModel {
|
|||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void SetBackend(boost::shared_ptr<LibraryBackendInterface>);
|
||||||
|
|
||||||
void set_current_index(int index);
|
void set_current_index(int index);
|
||||||
void Paused();
|
void Paused();
|
||||||
void Playing();
|
void Playing();
|
||||||
@ -155,7 +161,9 @@ class Playlist : public QAbstractListModel {
|
|||||||
private:
|
private:
|
||||||
boost::scoped_ptr<SettingsProvider> settings_;
|
boost::scoped_ptr<SettingsProvider> settings_;
|
||||||
|
|
||||||
QList<PlaylistItem*> items_;
|
boost::shared_ptr<LibraryBackendInterface> backend_;
|
||||||
|
|
||||||
|
PlaylistItemList items_;
|
||||||
QList<int> virtual_items_; // Contains the indices into items_ in the order
|
QList<int> virtual_items_; // Contains the indices into items_ in the order
|
||||||
// that they will be played.
|
// that they will be played.
|
||||||
|
|
||||||
|
@ -17,25 +17,31 @@
|
|||||||
#include "playlistitem.h"
|
#include "playlistitem.h"
|
||||||
#include "songplaylistitem.h"
|
#include "songplaylistitem.h"
|
||||||
#include "radioplaylistitem.h"
|
#include "radioplaylistitem.h"
|
||||||
|
#include "libraryplaylistitem.h"
|
||||||
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
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) {
|
PlaylistItem* PlaylistItem::NewFromType(const QString& type) {
|
||||||
if (type == "Song")
|
if (type == "Library")
|
||||||
return new SongPlaylistItem;
|
return new LibraryPlaylistItem(type);
|
||||||
|
if (type == "Stream" || type == "File")
|
||||||
|
return new SongPlaylistItem(type);
|
||||||
if (type == "Radio")
|
if (type == "Radio")
|
||||||
return new RadioPlaylistItem;
|
return new RadioPlaylistItem(type);
|
||||||
|
|
||||||
qWarning() << "Invalid PlaylistItem type:" << type;
|
qWarning() << "Invalid PlaylistItem type:" << type;
|
||||||
return NULL;
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,20 +21,16 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
class Song;
|
class Song;
|
||||||
class SettingsProvider;
|
|
||||||
|
class QSqlQuery;
|
||||||
|
|
||||||
class PlaylistItem {
|
class PlaylistItem {
|
||||||
public:
|
public:
|
||||||
PlaylistItem() {}
|
PlaylistItem(const QString& type) : type_(type) {}
|
||||||
virtual ~PlaylistItem() {}
|
virtual ~PlaylistItem() {}
|
||||||
|
|
||||||
static PlaylistItem* NewFromType(const QString& type);
|
static PlaylistItem* NewFromType(const QString& type);
|
||||||
|
|
||||||
enum Type {
|
|
||||||
Type_Song,
|
|
||||||
Type_Radio,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Option {
|
enum Option {
|
||||||
Default = 0x00,
|
Default = 0x00,
|
||||||
|
|
||||||
@ -45,13 +41,12 @@ class PlaylistItem {
|
|||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Options, Option);
|
Q_DECLARE_FLAGS(Options, Option);
|
||||||
|
|
||||||
virtual Type type() const = 0;
|
virtual QString type() const { return type_; }
|
||||||
QString type_string() const;
|
|
||||||
|
|
||||||
virtual Options options() const { return Default; }
|
virtual Options options() const { return Default; }
|
||||||
|
|
||||||
virtual void Save(SettingsProvider* settings) const = 0;
|
virtual void InitFromQuery(const QSqlQuery& query) = 0;
|
||||||
virtual void Restore(const SettingsProvider& settings) = 0;
|
void BindToQuery(QSqlQuery* query) const;
|
||||||
virtual void Reload() {}
|
virtual void Reload() {}
|
||||||
|
|
||||||
virtual Song Metadata() const = 0;
|
virtual Song Metadata() const = 0;
|
||||||
@ -69,7 +64,24 @@ class PlaylistItem {
|
|||||||
|
|
||||||
virtual void SetTemporaryMetadata(const Song& metadata) {Q_UNUSED(metadata)}
|
virtual void SetTemporaryMetadata(const Song& metadata) {Q_UNUSED(metadata)}
|
||||||
virtual void ClearTemporaryMetadata() {}
|
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<PlaylistItem*> PlaylistItemList;
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::Options);
|
Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::Options);
|
||||||
|
|
||||||
|
@ -21,15 +21,18 @@
|
|||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
RadioPlaylistItem::RadioPlaylistItem()
|
RadioPlaylistItem::RadioPlaylistItem(const QString& type)
|
||||||
: service_(NULL)
|
: PlaylistItem(type),
|
||||||
|
service_(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
|
RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
|
||||||
const QString& title, const QString& artist)
|
const QString& title, const QString& artist)
|
||||||
: service_(service),
|
: PlaylistItem("Radio"),
|
||||||
|
service_(service),
|
||||||
url_(url),
|
url_(url),
|
||||||
title_(title),
|
title_(title),
|
||||||
artist_(artist)
|
artist_(artist)
|
||||||
@ -37,22 +40,30 @@ RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
|
|||||||
InitMetadata();
|
InitMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RadioPlaylistItem::Save(SettingsProvider* settings) const {
|
void RadioPlaylistItem::InitFromQuery(const QSqlQuery &query) {
|
||||||
settings->setValue("service", service_->name());
|
// The song table gets joined first, plus one for the song ROWID
|
||||||
settings->setValue("url", url_.toString());
|
const int row = Song::kColumns.count() + 1;
|
||||||
settings->setValue("title", title_);
|
|
||||||
settings->setValue("artist", artist_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadioPlaylistItem::Restore(const SettingsProvider& settings) {
|
url_ = query.value(row + 1).toString();
|
||||||
service_ = RadioModel::ServiceByName(settings.value("service").toString());
|
title_ = query.value(row + 2).toString();
|
||||||
url_ = settings.value("url").toString();
|
artist_ = query.value(row + 3).toString();
|
||||||
title_ = settings.value("title").toString();
|
QString service(query.value(row + 6).toString());
|
||||||
artist_ = settings.value("artist").toString();
|
|
||||||
|
service_ = RadioModel::ServiceByName(service);
|
||||||
|
|
||||||
InitMetadata();
|
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() {
|
void RadioPlaylistItem::InitMetadata() {
|
||||||
if (!service_)
|
if (!service_)
|
||||||
metadata_.set_title(QApplication::translate("RadioPlaylistItem", "Radio service couldn't be loaded :-("));
|
metadata_.set_title(QApplication::translate("RadioPlaylistItem", "Radio service couldn't be loaded :-("));
|
||||||
|
@ -26,15 +26,14 @@ class RadioService;
|
|||||||
|
|
||||||
class RadioPlaylistItem : public PlaylistItem {
|
class RadioPlaylistItem : public PlaylistItem {
|
||||||
public:
|
public:
|
||||||
RadioPlaylistItem();
|
RadioPlaylistItem(const QString& type);
|
||||||
RadioPlaylistItem(RadioService* service, const QUrl& url,
|
RadioPlaylistItem(RadioService* service, const QUrl& url,
|
||||||
const QString& title, const QString& artist);
|
const QString& title, const QString& artist);
|
||||||
|
|
||||||
Type type() const { return Type_Radio; }
|
|
||||||
Options options() const;
|
Options options() const;
|
||||||
|
|
||||||
void Save(SettingsProvider* settings) const;
|
void InitFromQuery(const QSqlQuery &query);
|
||||||
void Restore(const SettingsProvider& settings);
|
void BindToQuery(QSqlQuery *query) const;
|
||||||
|
|
||||||
Song Metadata() const;
|
Song Metadata() const;
|
||||||
|
|
||||||
@ -46,6 +45,9 @@ class RadioPlaylistItem : public PlaylistItem {
|
|||||||
void SetTemporaryMetadata(const Song& metadata);
|
void SetTemporaryMetadata(const Song& metadata);
|
||||||
void ClearTemporaryMetadata();
|
void ClearTemporaryMetadata();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QVariant DatabaseValue(DatabaseColumn) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitMetadata();
|
void InitMetadata();
|
||||||
|
|
||||||
|
43
src/scopedtransaction.cpp
Normal file
43
src/scopedtransaction.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
39
src/scopedtransaction.h
Normal file
39
src/scopedtransaction.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SCOPEDTRANSACTION_H
|
||||||
|
#define SCOPEDTRANSACTION_H
|
||||||
|
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
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
|
54
src/song.cpp
54
src/song.cpp
@ -53,35 +53,35 @@ using boost::scoped_ptr;
|
|||||||
#include "engines/enginebase.h"
|
#include "engines/enginebase.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
|
|
||||||
const char* Song::kColumnSpec =
|
static QStringList Prepend(const QString& text, const QStringList& list) {
|
||||||
"title, album, artist, albumartist, composer, "
|
QStringList ret(list);
|
||||||
"track, disc, bpm, year, genre, comment, compilation, "
|
for (int i=0 ; i<ret.count() ; ++i)
|
||||||
"length, bitrate, samplerate, directory, filename, "
|
ret[i].prepend(text);
|
||||||
"mtime, ctime, filesize, sampler, art_automatic, art_manual, "
|
return ret;
|
||||||
"filetype, playcount, lastplayed, rating, forced_compilation_on, "
|
}
|
||||||
"forced_compilation_off, effective_compilation";
|
|
||||||
|
static QStringList Updateify(const QStringList& list) {
|
||||||
|
QStringList ret(list);
|
||||||
|
for (int i=0 ; i<ret.count() ; ++i)
|
||||||
|
ret[i].prepend(ret[i] + " = :");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList Song::kColumns = QStringList()
|
||||||
|
<< "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";
|
||||||
|
|
||||||
|
const QString Song::kColumnSpec = Song::kColumns.join(", ");
|
||||||
|
const QString Song::kJoinSpec = Prepend("songs.", Song::kColumns).join(", ");
|
||||||
|
const QString Song::kBindSpec = Prepend(":", Song::kColumns).join(", ");
|
||||||
|
const QString Song::kUpdateSpec = Updateify(Song::kColumns).join(", ");
|
||||||
|
|
||||||
const char* Song::kBindSpec =
|
|
||||||
":title, :album, :artist, :albumartist, :composer, "
|
|
||||||
":track, :disc, :bpm, :year, :genre, :comment, :compilation, "
|
|
||||||
":length, :bitrate, :samplerate, :directory_id, :filename, "
|
|
||||||
":mtime, :ctime, :filesize, :sampler, :art_automatic, :art_manual, "
|
|
||||||
":filetype, :playcount, :lastplayed, :rating, :forced_compilation_on, "
|
|
||||||
":forced_compilation_off, :effective_compilation";
|
|
||||||
|
|
||||||
const char* Song::kUpdateSpec =
|
|
||||||
"title = :title, album = :album, artist = :artist, "
|
|
||||||
"albumartist = :albumartist, composer = :composer, track = :track, "
|
|
||||||
"disc = :disc, bpm = :bpm, year = :year, genre = :genre, "
|
|
||||||
"comment = :comment, compilation = :compilation, length = :length, "
|
|
||||||
"bitrate = :bitrate, samplerate = :samplerate, "
|
|
||||||
"directory = :directory_id, filename = :filename, mtime = :mtime, "
|
|
||||||
"ctime = :ctime, filesize = :filesize, sampler = :sampler, "
|
|
||||||
"art_automatic = :art_automatic, art_manual = :art_manual, "
|
|
||||||
"filetype = :filetype, playcount = :playcount, lastplayed = :lastplayed, "
|
|
||||||
"rating = :rating, forced_compilation_on = :forced_compilation_on, "
|
|
||||||
"forced_compilation_off = :forced_compilation_off, "
|
|
||||||
"effective_compilation = :effective_compilation";
|
|
||||||
|
|
||||||
static TagLib::String QStringToTaglibString(const QString& s);
|
static TagLib::String QStringToTaglibString(const QString& s);
|
||||||
|
|
||||||
|
@ -56,9 +56,11 @@ class Song {
|
|||||||
Song(const Song& other);
|
Song(const Song& other);
|
||||||
Song(FileRefFactory* factory);
|
Song(FileRefFactory* factory);
|
||||||
|
|
||||||
static const char* kColumnSpec;
|
static const QStringList kColumns;
|
||||||
static const char* kBindSpec;
|
static const QString kColumnSpec;
|
||||||
static const char* kUpdateSpec;
|
static const QString kJoinSpec;
|
||||||
|
static const QString kBindSpec;
|
||||||
|
static const QString kUpdateSpec;
|
||||||
|
|
||||||
// Don't change these values - they're stored in the database
|
// Don't change these values - they're stored in the database
|
||||||
enum FileType {
|
enum FileType {
|
||||||
|
@ -15,74 +15,56 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "songplaylistitem.h"
|
#include "songplaylistitem.h"
|
||||||
#include "settingsprovider.h"
|
|
||||||
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
SongPlaylistItem::SongPlaylistItem()
|
SongPlaylistItem::SongPlaylistItem(const QString& type)
|
||||||
|
: PlaylistItem(type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
SongPlaylistItem::SongPlaylistItem(const Song& song)
|
SongPlaylistItem::SongPlaylistItem(const Song& song)
|
||||||
: song_(song)
|
: PlaylistItem(song.filetype() == Song::Type_Stream ? "Stream" : "File"),
|
||||||
|
song_(song)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongPlaylistItem::Save(SettingsProvider* settings) const {
|
void SongPlaylistItem::InitFromQuery(const QSqlQuery &query) {
|
||||||
settings->setValue("filename", song_.filename());
|
// The song table gets joined first, plus one for the song ROWID
|
||||||
settings->setValue("art_automatic", song_.art_automatic());
|
const int row = Song::kColumns.count() + 1;
|
||||||
settings->setValue("art_manual", song_.art_manual());
|
|
||||||
|
|
||||||
if (song_.filetype() == Song::Type_Stream) {
|
QString filename(query.value(row + 1).toString());
|
||||||
SaveStream(settings);
|
|
||||||
} else {
|
|
||||||
SaveFile(settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SongPlaylistItem::SaveFile(SettingsProvider* settings) const {
|
if (type() == "Stream") {
|
||||||
settings->setValue("stream", false);
|
QString title(query.value(row + 2).toString());
|
||||||
settings->setValue("library_directory", song_.directory_id());
|
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;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_filename(filename);
|
||||||
song_.set_filetype(Song::Type_Stream);
|
song_.set_filetype(Song::Type_Stream);
|
||||||
|
|
||||||
song_.Init(settings.value("title", "Unknown").toString(),
|
song_.Init(title, artist, album, length);
|
||||||
settings.value("artist", "Unknown").toString(),
|
} else {
|
||||||
settings.value("album", "Unknown").toString(),
|
song_.InitFromFile(filename, -1);
|
||||||
settings.value("length", -1).toInt());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl SongPlaylistItem::Url() const {
|
QUrl SongPlaylistItem::Url() const {
|
||||||
|
@ -22,25 +22,20 @@
|
|||||||
|
|
||||||
class SongPlaylistItem : public PlaylistItem {
|
class SongPlaylistItem : public PlaylistItem {
|
||||||
public:
|
public:
|
||||||
SongPlaylistItem();
|
SongPlaylistItem(const QString& type);
|
||||||
SongPlaylistItem(const Song& song);
|
SongPlaylistItem(const Song& song);
|
||||||
|
|
||||||
Type type() const { return Type_Song; }
|
void InitFromQuery(const QSqlQuery &query);
|
||||||
|
|
||||||
void Save(SettingsProvider* settings) const;
|
|
||||||
void Restore(const SettingsProvider& settings);
|
|
||||||
void Reload();
|
void Reload();
|
||||||
|
|
||||||
Song Metadata() const { return song_; }
|
Song Metadata() const { return song_; }
|
||||||
|
|
||||||
QUrl Url() const;
|
QUrl Url() const;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void SaveFile(SettingsProvider* settings) const;
|
QVariant DatabaseValue(DatabaseColumn) const;
|
||||||
void SaveStream(SettingsProvider* settings) const;
|
|
||||||
|
|
||||||
void RestoreFile(const SettingsProvider& settings);
|
private:
|
||||||
void RestoreStream(const SettingsProvider& settings);
|
|
||||||
Song song_;
|
Song song_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
|
||||||
MockPlaylistItem::MockPlaylistItem() {
|
MockPlaylistItem::MockPlaylistItem()
|
||||||
EXPECT_CALL(*this, Save(_))
|
: PlaylistItem("DummyType")
|
||||||
.WillRepeatedly(Return());
|
{
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,10 @@ class MockPlaylistItem : public PlaylistItem {
|
|||||||
public:
|
public:
|
||||||
MockPlaylistItem();
|
MockPlaylistItem();
|
||||||
|
|
||||||
MOCK_CONST_METHOD0(type,
|
|
||||||
Type());
|
|
||||||
MOCK_CONST_METHOD0(options,
|
MOCK_CONST_METHOD0(options,
|
||||||
Options());
|
Options());
|
||||||
MOCK_CONST_METHOD1(Save,
|
MOCK_METHOD1(InitFromQuery,
|
||||||
void(SettingsProvider* settings));
|
void(const QSqlQuery& settings));
|
||||||
MOCK_METHOD1(Restore,
|
|
||||||
void(const SettingsProvider& settings));
|
|
||||||
MOCK_METHOD0(Reload,
|
MOCK_METHOD0(Reload,
|
||||||
void());
|
void());
|
||||||
MOCK_CONST_METHOD0(Metadata,
|
MOCK_CONST_METHOD0(Metadata,
|
||||||
@ -49,6 +45,8 @@ class MockPlaylistItem : public PlaylistItem {
|
|||||||
void(const Song& metadata));
|
void(const Song& metadata));
|
||||||
MOCK_METHOD0(ClearTemporaryMetadata,
|
MOCK_METHOD0(ClearTemporaryMetadata,
|
||||||
void());
|
void());
|
||||||
|
MOCK_METHOD1(DatabaseValue,
|
||||||
|
QVariant(DatabaseColumn));
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MOCK_PLAYLISTITEM_H
|
#endif // MOCK_PLAYLISTITEM_H
|
||||||
|
@ -47,8 +47,6 @@ class PlaylistTest : public ::testing::Test {
|
|||||||
metadata.Init(title, artist, album, length);
|
metadata.Init(title, artist, album, length);
|
||||||
|
|
||||||
MockPlaylistItem* ret = new MockPlaylistItem;
|
MockPlaylistItem* ret = new MockPlaylistItem;
|
||||||
EXPECT_CALL(*ret, type())
|
|
||||||
.WillRepeatedly(Return(PlaylistItem::Type_Song));
|
|
||||||
EXPECT_CALL(*ret, Metadata())
|
EXPECT_CALL(*ret, Metadata())
|
||||||
.WillRepeatedly(Return(metadata));
|
.WillRepeatedly(Return(metadata));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user