diff --git a/data/data.qrc b/data/data.qrc
index 0269dd706..b5e8a9d41 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -343,6 +343,7 @@
schema/schema-42.sql
schema/schema-43.sql
schema/schema-44.sql
+ schema/schema-45.sql
schema/schema-4.sql
schema/schema-5.sql
schema/schema-6.sql
diff --git a/data/schema/jamendo.sql b/data/schema/jamendo.sql
index 42a85908f..0c914365b 100644
--- a/data/schema/jamendo.sql
+++ b/data/schema/jamendo.sql
@@ -42,11 +42,14 @@ CREATE TABLE jamendo.songs (
unavailable INTEGER DEFAULT 0,
effective_albumartist TEXT,
- etag TEXT
+ etag TEXT,
+
+ performer TEXT,
+ grouping TEXT
);
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(
- ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
+ ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment,
tokenize=unicode
);
diff --git a/data/schema/schema-45.sql b/data/schema/schema-45.sql
new file mode 100644
index 000000000..058d4599b
--- /dev/null
+++ b/data/schema/schema-45.sql
@@ -0,0 +1,24 @@
+CREATE VIRTUAL TABLE playlist_items_fts USING fts3(
+ ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
+ tokenize=unicode
+);
+
+DELETE FROM %allsongstables_fts;
+
+DROP TABLE %allsongstables_fts;
+
+ALTER TABLE %allsongstables ADD COLUMN performer TEXT;
+
+ALTER TABLE %allsongstables ADD COLUMN grouping TEXT;
+
+CREATE VIRTUAL TABLE %allsongstables_fts USING fts3(
+ ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment,
+ tokenize=unicode
+);
+
+INSERT INTO %allsongstables_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
+ SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment
+ FROM %allsongstables;
+
+UPDATE schema_version SET version=45;
+
diff --git a/ext/libclementine-tagreader/tagreader.cpp b/ext/libclementine-tagreader/tagreader.cpp
index 5cf43c789..69a7d9f29 100644
--- a/ext/libclementine-tagreader/tagreader.cpp
+++ b/ext/libclementine-tagreader/tagreader.cpp
@@ -161,6 +161,12 @@ void TagReader::ReadFile(const QString& filename,
if (!map["TCOM"].isEmpty())
Decode(map["TCOM"].front()->toString(), NULL, song->mutable_composer());
+ if (!map["TIT1"].isEmpty()) // content group
+ Decode(map["TIT1"].front()->toString(), NULL, song->mutable_grouping());
+
+ if (!map["TPE1"].isEmpty()) // ID3v2: lead performer/soloist
+ Decode(map["TPE1"].front()->toString(), NULL, song->mutable_performer());
+
if (!map["TPE2"].isEmpty()) // non-standard: Apple, Microsoft
Decode(map["TPE2"].front()->toString(), NULL, song->mutable_albumartist());
@@ -260,6 +266,9 @@ void TagReader::ReadFile(const QString& filename,
if(items.contains("\251wrt")) {
Decode(items["\251wrt"].toStringList().toString(", "), NULL, song->mutable_composer());
}
+ if(items.contains("\251grp")) {
+ Decode(items["\251grp"].toStringList().toString(" "), NULL, song->mutable_grouping());
+ }
Decode(mp4_tag->comment(), NULL, song->mutable_comment());
}
}
@@ -398,6 +407,10 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map,
pb::tagreader::SongMetadata* song) const {
if (!map["COMPOSER"].isEmpty())
Decode(map["COMPOSER"].front(), codec, song->mutable_composer());
+ if (!map["PERFORMER"].isEmpty())
+ Decode(map["PERFORMER"].front(), codec, song->mutable_performer());
+ if (!map["CONTENT GROUP"].isEmpty())
+ Decode(map["CONTENT GROUP"].front(), codec, song->mutable_grouping());
if (!map["ALBUMARTIST"].isEmpty()) {
Decode(map["ALBUMARTIST"].front(), codec, song->mutable_albumartist());
@@ -428,6 +441,8 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
const pb::tagreader::SongMetadata& song) const {
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true);
+ vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true);
+ vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true);
vorbis_comments->addField("BPM", QStringToTaglibString(song.bpm() <= 0 -1 ? QString() : QString::number(song.bpm())), true);
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true);
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
@@ -501,6 +516,8 @@ bool TagReader::SaveFile(const QString& filename,
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TBPM", song.bpm() <= 0 -1 ? QString() : QString::number(song.bpm()), tag);
SetTextFrame("TCOM", song.composer(), tag);
+ SetTextFrame("TIT1", song.grouping(), tag);
+ SetTextFrame("TPE1", song.performer(), tag);
SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
} else if (TagLib::FLAC::File* file = dynamic_cast(fileref->file())) {
@@ -511,6 +528,7 @@ bool TagReader::SaveFile(const QString& filename,
tag->itemListMap()["disk"] = TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0);
tag->itemListMap()["tmpo"] = TagLib::StringList(song.bpm() <= 0 -1 ? "0" : TagLib::String::number(song.bpm()));
tag->itemListMap()["\251wrt"] = TagLib::StringList(song.composer());
+ tag->itemListMap()["\251grp"] = TagLib::StringList(song.grouping());
tag->itemListMap()["aART"] = TagLib::StringList(song.albumartist());
tag->itemListMap()["cpil"] = TagLib::StringList(song.compilation() ? "1" : "0");
}
diff --git a/ext/libclementine-tagreader/tagreadermessages.proto b/ext/libclementine-tagreader/tagreadermessages.proto
index 5b4e4b7f4..0cd8971f7 100644
--- a/ext/libclementine-tagreader/tagreadermessages.proto
+++ b/ext/libclementine-tagreader/tagreadermessages.proto
@@ -49,6 +49,8 @@ message SongMetadata {
optional string art_automatic = 28;
optional Type type = 29;
optional string etag = 30;
+ optional string performer = 31;
+ optional string grouping = 32;
}
message ReadFileRequest {
diff --git a/src/core/database.cpp b/src/core/database.cpp
index ccbabe394..15e936f8e 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -37,7 +37,7 @@
#include
const char* Database::kDatabaseFilename = "clementine.db";
-const int Database::kSchemaVersion = 44;
+const int Database::kSchemaVersion = 45;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;
@@ -369,12 +369,16 @@ QSqlDatabase Database::Connect() {
// Find Sqlite3 functions in the Qt plugin.
StaticInit();
- QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db);
- set_fts_tokenizer.bindValue(":name", "unicode");
- set_fts_tokenizer.bindValue(":pointer", QByteArray(
- reinterpret_cast(&sFTSTokenizer), sizeof(&sFTSTokenizer)));
- if (!set_fts_tokenizer.exec()) {
- qLog(Warning) << "Couldn't register FTS3 tokenizer";
+ {
+ QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db);
+ set_fts_tokenizer.bindValue(":name", "unicode");
+ set_fts_tokenizer.bindValue(":pointer", QByteArray(
+ reinterpret_cast(&sFTSTokenizer), sizeof(&sFTSTokenizer)));
+ if (!set_fts_tokenizer.exec()) {
+ qLog(Warning) << "Couldn't register FTS3 tokenizer";
+ }
+ // Implicit invocation of ~QSqlQuery() when leaving the scope
+ // to release any remaining database locks!
}
if (db.tables().count() == 0) {
@@ -410,9 +414,8 @@ QSqlDatabase Database::Connect() {
QSqlQuery q(QString("SELECT ROWID FROM %1.sqlite_master"
" WHERE type='table'").arg(key), db);
if (!q.exec() || !q.next()) {
- ScopedTransaction t(&db);
- ExecFromFile(attached_databases_[key].schema_, db, 0);
- t.Commit();
+ q.finish();
+ ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
}
}
@@ -421,10 +424,14 @@ QSqlDatabase Database::Connect() {
void Database::UpdateMainSchema(QSqlDatabase* db) {
// Get the database's schema version
- QSqlQuery q("SELECT version FROM schema_version", *db);
int schema_version = 0;
- if (q.next())
- schema_version = q.value(0).toInt();
+ {
+ QSqlQuery q("SELECT version FROM schema_version", *db);
+ if (q.next())
+ schema_version = q.value(0).toInt();
+ // Implicit invocation of ~QSqlQuery() when leaving the scope
+ // to release any remaining database locks!
+ }
startup_schema_version_ = schema_version;
@@ -478,13 +485,12 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
filename = ":/schema/schema.sql";
else
filename = QString(":/schema/schema-%1.sql").arg(version);
-
- ScopedTransaction t(&db);
-
+
if (version == 31) {
// This version used to do a bad job of converting filenames in the songs
// table to file:// URLs. Now we do it properly here instead.
-
+ ScopedTransaction t(&db);
+
UrlEncodeFilenameColumn("songs", db);
UrlEncodeFilenameColumn("playlist_items", db);
@@ -493,30 +499,32 @@ void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
UrlEncodeFilenameColumn(table, db);
}
}
+ qLog(Debug) << "Applying database schema update" << version
+ << "from" << filename;
+ ExecSchemaCommandsFromFile(db, filename, version - 1, &t);
+ t.Commit();
+ } else {
+ qLog(Debug) << "Applying database schema update" << version
+ << "from" << filename;
+ ExecSchemaCommandsFromFile(db, filename, version - 1);
}
-
- qLog(Debug) << "Applying database schema update" << version
- << "from" << filename;
- ExecFromFile(filename, db, version - 1);
- t.Commit();
}
void Database::UrlEncodeFilenameColumn(const QString& table, QSqlDatabase& db) {
QSqlQuery select(QString("SELECT ROWID, filename FROM %1").arg(table), db);
QSqlQuery update(QString("UPDATE %1 SET filename=:filename WHERE ROWID=:id").arg(table), db);
-
select.exec();
if (CheckErrors(select)) return;
while (select.next()) {
const int rowid = select.value(0).toInt();
const QString filename = select.value(1).toString();
-
+
if (filename.isEmpty() || filename.contains("://")) {
continue;
}
-
+
const QUrl url = QUrl::fromLocalFile(filename);
-
+
update.bindValue(":filename", url.toEncoded());
update.bindValue(":id", rowid);
update.exec();
@@ -524,30 +532,50 @@ void Database::UrlEncodeFilenameColumn(const QString& table, QSqlDatabase& db) {
}
}
-void Database::ExecFromFile(const QString &filename, QSqlDatabase &db,
- int schema_version) {
+void Database::ExecSchemaCommandsFromFile(QSqlDatabase& db,
+ QString const& filename,
+ int schema_version,
+ ScopedTransaction const* outerTransaction) {
// Open and read the database schema
QFile schema_file(filename);
if (!schema_file.open(QIODevice::ReadOnly))
qFatal("Couldn't open schema file %s", filename.toUtf8().constData());
- ExecCommands(QString::fromUtf8(schema_file.readAll()), db, schema_version);
+ ExecSchemaCommands(db, QString::fromUtf8(schema_file.readAll()), schema_version, outerTransaction);
}
-void Database::ExecCommands(const QString& schema, QSqlDatabase& db,
- int schema_version) {
+void Database::ExecSchemaCommands(QSqlDatabase& db,
+ QString const& schema,
+ int schema_version,
+ ScopedTransaction const* outerTransaction) {
// Run each command
- QStringList commands(schema.split(";\n\n"));
+ QStringList const schemaCommands(schema.split(";\n\n"));
// We don't want this list to reflect possible DB schema changes
// so we initialize it before executing any statements.
- QStringList tables = SongsTables(db, schema_version);
+ // If no outer transaction is provided the song tables need to
+ // be queried before beginning an inner transaction! Otherwise
+ // DROP TABLE commands on song tables may fail due to database
+ // locks.
+ QStringList const songTables(SongsTables(db, schema_version));
- foreach (const QString& command, commands) {
+ if (0 == outerTransaction) {
+ ScopedTransaction innerTransaction(&db);
+ ExecSongTablesCommands(db, songTables, schemaCommands);
+ innerTransaction.Commit();
+ } else {
+ ExecSongTablesCommands(db, songTables, schemaCommands);
+ }
+}
+
+void Database::ExecSongTablesCommands(QSqlDatabase& db,
+ QStringList const& songTables,
+ QStringList const& commands) {
+ foreach (QString const& command, commands) {
// There are now lots of "songs" tables that need to have the same schema:
// songs, magnatune_songs, and device_*_songs. We allow a magic value
// in the schema files to update all songs tables at once.
if (command.contains(kMagicAllSongsTables)) {
- foreach (const QString& table, tables) {
+ foreach (QString const& table, songTables) {
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
QString new_command(command);
new_command.replace(kMagicAllSongsTables, table);
diff --git a/src/core/database.h b/src/core/database.h
index 1bc570d68..cc409cfe1 100644
--- a/src/core/database.h
+++ b/src/core/database.h
@@ -38,6 +38,7 @@ struct sqlite3_tokenizer_module;
}
class Application;
+class ScopedTransaction;
class Database : public QObject {
Q_OBJECT
@@ -55,8 +56,7 @@ class Database : public QObject {
QMutex* Mutex() { return &mutex_; }
void RecreateAttachedDb(const QString& database_name);
- void ExecFromFile(const QString& filename, QSqlDatabase &db, int schema_version);
- void ExecCommands(const QString& commands, QSqlDatabase &db, int schema_version);
+ void ExecSchemaCommands(QSqlDatabase &db, QString const& schema, int schema_version, ScopedTransaction const* outerTransaction = 0);
int startup_schema_version() const { return startup_schema_version_; }
int current_schema_version() const { return kSchemaVersion; }
@@ -70,6 +70,9 @@ class Database : public QObject {
private:
void UpdateMainSchema(QSqlDatabase* db);
+ void ExecSchemaCommandsFromFile(QSqlDatabase &db, QString const& filename, int schema_version, ScopedTransaction const* outerTransaction = 0);
+ void ExecSongTablesCommands(QSqlDatabase &db, QStringList const& songTables, QStringList const& commands);
+
void UpdateDatabaseSchema(int version, QSqlDatabase& db);
void UrlEncodeFilenameColumn(const QString& table, QSqlDatabase& db);
QStringList SongsTables(QSqlDatabase& db, int schema_version) const;
diff --git a/src/core/mpris1.cpp b/src/core/mpris1.cpp
index b2f63f8bf..368e284ed 100644
--- a/src/core/mpris1.cpp
+++ b/src/core/mpris1.cpp
@@ -342,6 +342,8 @@ QVariantMap Mpris1::GetMetadata(const Song& song) {
AddMetadata("audio-samplerate", song.samplerate(), &ret);
AddMetadata("bpm", song.bpm(), &ret);
AddMetadata("composer", song.composer(), &ret);
+ AddMetadata("performer", song.performer(), &ret);
+ AddMetadata("grouping", song.grouping(), &ret);
if (song.rating() != -1.0) {
AddMetadata("rating", song.rating() * 5, &ret);
}
diff --git a/src/core/organiseformat.cpp b/src/core/organiseformat.cpp
index 15e074c2c..2e932817a 100644
--- a/src/core/organiseformat.cpp
+++ b/src/core/organiseformat.cpp
@@ -27,7 +27,8 @@ const char* OrganiseFormat::kBlockPattern = "\\{([^{}]+)\\}";
const QStringList OrganiseFormat::kKnownTags = QStringList()
<< "title" << "album" << "artist" << "artistinitial" << "albumartist"
<< "composer" << "track" << "disc" << "bpm" << "year" << "genre"
- << "comment" << "length" << "bitrate" << "samplerate" << "extension";
+ << "comment" << "length" << "bitrate" << "samplerate" << "extension"
+ << "performer" << "grouping";
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
const char* OrganiseFormat::kInvalidFatCharacters = "\"*/\\:<>?|";
@@ -131,6 +132,8 @@ QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const {
else if (tag == "album") value = song.album();
else if (tag == "artist") value = song.artist();
else if (tag == "composer") value = song.composer();
+ else if (tag == "performer") value = song.performer();
+ else if (tag == "grouping") value = song.grouping();
else if (tag == "genre") value = song.genre();
else if (tag == "comment") value = song.comment();
else if (tag == "year") value = QString::number(song.year());
diff --git a/src/core/song.cpp b/src/core/song.cpp
index 6afc9459f..08af33ae3 100644
--- a/src/core/song.cpp
+++ b/src/core/song.cpp
@@ -73,7 +73,8 @@ const QStringList Song::kColumns = QStringList()
<< "art_manual" << "filetype" << "playcount" << "lastplayed" << "rating"
<< "forced_compilation_on" << "forced_compilation_off"
<< "effective_compilation" << "skipcount" << "score" << "beginning" << "length"
- << "cue_path" << "unavailable" << "effective_albumartist" << "etag";
+ << "cue_path" << "unavailable" << "effective_albumartist" << "etag"
+ << "performer" << "grouping";
const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", ");
@@ -82,7 +83,7 @@ const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(", "
const QStringList Song::kFtsColumns = QStringList()
<< "ftstitle" << "ftsalbum" << "ftsartist" << "ftsalbumartist"
- << "ftscomposer" << "ftsgenre" << "ftscomment";
+ << "ftscomposer" << "ftsperformer" << "ftsgrouping" << "ftsgenre" << "ftscomment";
const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", ");
const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", ");
@@ -103,6 +104,8 @@ struct Song::Private : public QSharedData {
QString artist_;
QString albumartist_;
QString composer_;
+ QString performer_;
+ QString grouping_;
int track_;
int disc_;
float bpm_;
@@ -234,6 +237,8 @@ const QString& Song::albumartist() const { return d->albumartist_; }
const QString& Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
const QString& Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
const QString& Song::composer() const { return d->composer_; }
+const QString& Song::performer() const { return d->performer_; }
+const QString& Song::grouping() const { return d->grouping_; }
int Song::track() const { return d->track_; }
int Song::disc() const { return d->disc_; }
float Song::bpm() const { return d->bpm_; }
@@ -281,6 +286,8 @@ void Song::set_album(const QString& v) { d->album_ = v; }
void Song::set_artist(const QString& v) { d->artist_ = v; }
void Song::set_albumartist(const QString& v) { d->albumartist_ = v; }
void Song::set_composer(const QString& v) { d->composer_ = v; }
+void Song::set_performer(const QString& v) { d->performer_ = v; }
+void Song::set_grouping(const QString& v) { d->grouping_ = v; }
void Song::set_track(int v) { d->track_ = v; }
void Song::set_disc(int v) { d->disc_ = v; }
void Song::set_bpm(float v) { d->bpm_ = v; }
@@ -396,6 +403,8 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
d->artist_ = QStringFromStdString(pb.artist());
d->albumartist_ = QStringFromStdString(pb.albumartist());
d->composer_ = QStringFromStdString(pb.composer());
+ d->performer_ = QStringFromStdString(pb.performer());
+ d->grouping_ = QStringFromStdString(pb.grouping());
d->track_ = pb.track();
d->disc_ = pb.disc();
d->bpm_ = pb.bpm();
@@ -437,6 +446,8 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
pb->set_artist(DataCommaSizeFromQString(d->artist_));
pb->set_albumartist(DataCommaSizeFromQString(d->albumartist_));
pb->set_composer(DataCommaSizeFromQString(d->composer_));
+ pb->set_performer(DataCommaSizeFromQString(d->performer_));
+ pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
pb->set_track(d->track_);
pb->set_disc(d->disc_);
pb->set_bpm(d->bpm_);
@@ -523,6 +534,10 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
d->unavailable_ = q.value(col + 35).toBool();
// effective_albumartist = 36
+ // etag = 37
+
+ d->performer_ = tostr(col + 38);
+ d->grouping_ = tostr(col + 39);
#undef tostr
#undef toint
@@ -571,6 +586,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
d->artist_ = QString::fromUtf8(track->artist);
d->albumartist_ = QString::fromUtf8(track->albumartist);
d->composer_ = QString::fromUtf8(track->composer);
+ d->grouping_ = QString::fromUtf8(track->grouping);
d->track_ = track->track_nr;
d->disc_ = track->cd_nr;
d->bpm_ = track->BPM;
@@ -608,6 +624,7 @@ void Song::InitFromLastFM(const lastfm::Track& track) {
track->artist = strdup(d->artist_.toUtf8().constData());
track->albumartist = strdup(d->albumartist_.toUtf8().constData());
track->composer = strdup(d->composer_.toUtf8().constData());
+ track->grouping = strdup(d->grouping_.toUtf8().constData());
track->track_nr = d->track_;
track->cd_nr = d->disc_;
track->BPM = d->bpm_;
@@ -1012,6 +1029,9 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":etag", strval(d->etag_));
+ query->bindValue(":performer", strval(d->performer_));
+ query->bindValue(":grouping", strval(d->grouping_));
+
#undef intval
#undef notnullintval
#undef strval
@@ -1023,6 +1043,8 @@ void Song::BindToFtsQuery(QSqlQuery *query) const {
query->bindValue(":ftsartist", d->artist_);
query->bindValue(":ftsalbumartist", d->albumartist_);
query->bindValue(":ftscomposer", d->composer_);
+ query->bindValue(":ftsperformer", d->performer_);
+ query->bindValue(":ftsgrouping", d->grouping_);
query->bindValue(":ftsgenre", d->genre_);
query->bindValue(":ftscomment", d->comment_);
}
@@ -1114,6 +1136,8 @@ bool Song::IsMetadataEqual(const Song& other) const {
d->artist_ == other.d->artist_ &&
d->albumartist_ == other.d->albumartist_ &&
d->composer_ == other.d->composer_ &&
+ d->performer_ == other.d->performer_ &&
+ d->grouping_ == other.d->grouping_ &&
d->track_ == other.d->track_ &&
d->disc_ == other.d->disc_ &&
qFuzzyCompare(d->bpm_, other.d->bpm_) &&
diff --git a/src/core/song.h b/src/core/song.h
index 240ddfd89..f5310e1ad 100644
--- a/src/core/song.h
+++ b/src/core/song.h
@@ -161,6 +161,8 @@ class Song {
// compilations, but you do for normal albums:
const QString& playlist_albumartist() const;
const QString& composer() const;
+ const QString& performer() const;
+ const QString& grouping() const;
int track() const;
int disc() const;
float bpm() const;
@@ -235,6 +237,8 @@ class Song {
void set_artist(const QString& v);
void set_albumartist(const QString& v);
void set_composer(const QString& v);
+ void set_performer(const QString& v);
+ void set_grouping(const QString& v);
void set_track(int v);
void set_disc(int v);
void set_bpm(float v);
diff --git a/src/devices/devicedatabasebackend.cpp b/src/devices/devicedatabasebackend.cpp
index 74749b8b4..a7ace7425 100644
--- a/src/devices/devicedatabasebackend.cpp
+++ b/src/devices/devicedatabasebackend.cpp
@@ -90,7 +90,7 @@ int DeviceDatabaseBackend::AddDevice(const Device& device) {
QString schema = QString::fromUtf8(schema_file.readAll());
schema.replace("%deviceid", QString::number(id));
- db_->ExecCommands(schema, db, 0);
+ db_->ExecSchemaCommands(db, schema, 0, &t);
t.Commit();
return id;
diff --git a/src/globalsearch/globalsearchmodel.cpp b/src/globalsearch/globalsearchmodel.cpp
index bd4fc9beb..d1433b228 100644
--- a/src/globalsearch/globalsearchmodel.cpp
+++ b/src/globalsearch/globalsearchmodel.cpp
@@ -125,6 +125,8 @@ QStandardItem* GlobalSearchModel::BuildContainers(
break;
case LibraryModel::GroupBy_Composer: display_text = s.composer();
+ case LibraryModel::GroupBy_Performer: display_text = s.performer();
+ case LibraryModel::GroupBy_Grouping: display_text = s.grouping();
case LibraryModel::GroupBy_Genre: if (display_text.isNull()) display_text = s.genre();
case LibraryModel::GroupBy_Album:
unique_tag = s.album_id();
diff --git a/src/library/groupbydialog.cpp b/src/library/groupbydialog.cpp
index 3ece9c15c..eea1dd448 100644
--- a/src/library/groupbydialog.cpp
+++ b/src/library/groupbydialog.cpp
@@ -36,6 +36,8 @@ GroupByDialog::GroupByDialog(QWidget *parent)
mapping_.insert(Mapping(LibraryModel::GroupBy_Genre, 6));
mapping_.insert(Mapping(LibraryModel::GroupBy_Year, 7));
mapping_.insert(Mapping(LibraryModel::GroupBy_YearAlbum, 8));
+ mapping_.insert(Mapping(LibraryModel::GroupBy_Performer, 9));
+ mapping_.insert(Mapping(LibraryModel::GroupBy_Grouping, 10));
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()),
SLOT(Reset()));
diff --git a/src/library/librarymodel.cpp b/src/library/librarymodel.cpp
index 0b3f61503..2c73fd14e 100644
--- a/src/library/librarymodel.cpp
+++ b/src/library/librarymodel.cpp
@@ -180,6 +180,8 @@ void LibraryModel::SongsDiscovered(const SongList& songs) {
case GroupBy_Album: key = song.album(); break;
case GroupBy_Artist: key = song.artist(); break;
case GroupBy_Composer: key = song.composer(); break;
+ case GroupBy_Performer: key = song.performer(); break;
+ case GroupBy_Grouping: key = song.grouping(); break;
case GroupBy_Genre: key = song.genre(); break;
case GroupBy_AlbumArtist: key = song.effective_albumartist(); break;
case GroupBy_Year:
@@ -256,6 +258,8 @@ QString LibraryModel::DividerKey(GroupBy type, LibraryItem* item) const {
case GroupBy_Album:
case GroupBy_Artist:
case GroupBy_Composer:
+ case GroupBy_Performer:
+ case GroupBy_Grouping:
case GroupBy_Genre:
case GroupBy_AlbumArtist:
case GroupBy_FileType: {
@@ -289,6 +293,8 @@ QString LibraryModel::DividerDisplayText(GroupBy type, const QString& key) const
case GroupBy_Album:
case GroupBy_Artist:
case GroupBy_Composer:
+ case GroupBy_Performer:
+ case GroupBy_Grouping:
case GroupBy_Genre:
case GroupBy_AlbumArtist:
case GroupBy_FileType:
@@ -714,6 +720,12 @@ void LibraryModel::InitQuery(GroupBy type, LibraryQuery* q) {
case GroupBy_Composer:
q->SetColumnSpec("DISTINCT composer");
break;
+ case GroupBy_Performer:
+ q->SetColumnSpec("DISTINCT performer");
+ break;
+ case GroupBy_Grouping:
+ q->SetColumnSpec("DISTINCT grouping");
+ break;
case GroupBy_YearAlbum:
q->SetColumnSpec("DISTINCT year, album");
break;
@@ -762,6 +774,12 @@ void LibraryModel::FilterQuery(GroupBy type, LibraryItem* item, LibraryQuery* q)
case GroupBy_Composer:
q->AddWhere("composer", item->key);
break;
+ case GroupBy_Performer:
+ q->AddWhere("performer", item->key);
+ break;
+ case GroupBy_Grouping:
+ q->AddWhere("grouping", item->key);
+ break;
case GroupBy_Genre:
q->AddWhere("genre", item->key);
break;
@@ -829,6 +847,8 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
break;
case GroupBy_Composer:
+ case GroupBy_Performer:
+ case GroupBy_Grouping:
case GroupBy_Genre:
case GroupBy_Album:
case GroupBy_AlbumArtist:
@@ -883,6 +903,8 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type,
break;
case GroupBy_Composer: item->key = s.composer();
+ case GroupBy_Performer: item->key = s.performer();
+ case GroupBy_Grouping: item->key = s.grouping();
case GroupBy_Genre: if (item->key.isNull()) item->key = s.genre();
case GroupBy_Album: if (item->key.isNull()) item->key = s.album();
case GroupBy_AlbumArtist: if (item->key.isNull()) item->key = s.effective_albumartist();
diff --git a/src/library/librarymodel.h b/src/library/librarymodel.h
index fb238c0ec..6fdd5e665 100644
--- a/src/library/librarymodel.h
+++ b/src/library/librarymodel.h
@@ -80,6 +80,8 @@ class LibraryModel : public SimpleTreeModel {
GroupBy_Genre = 6,
GroupBy_AlbumArtist = 7,
GroupBy_FileType = 8,
+ GroupBy_Performer = 9,
+ GroupBy_Grouping = 10,
};
struct Grouping {
diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp
index 0209c4cc8..27b2a6fe8 100644
--- a/src/playlist/playlist.cpp
+++ b/src/playlist/playlist.cpp
@@ -174,6 +174,8 @@ bool Playlist::column_is_editable(Playlist::Column column) {
case Column_Album:
case Column_AlbumArtist:
case Column_Composer:
+ case Column_Performer:
+ case Column_Grouping:
case Column_Track:
case Column_Disc:
case Column_Year:
@@ -209,6 +211,12 @@ bool Playlist::set_column_value(Song& song, Playlist::Column column,
case Column_Composer:
song.set_composer(value.toString());
break;
+ case Column_Performer:
+ song.set_performer(value.toString());
+ break;
+ case Column_Grouping:
+ song.set_grouping(value.toString());
+ break;
case Column_Track:
song.set_track(value.toInt());
break;
@@ -271,6 +279,8 @@ QVariant Playlist::data(const QModelIndex& index, int role) const {
case Column_Genre: return song.genre();
case Column_AlbumArtist: return song.playlist_albumartist();
case Column_Composer: return song.composer();
+ case Column_Performer: return song.performer();
+ case Column_Grouping: return song.grouping();
case Column_Rating: return song.rating();
case Column_PlayCount: return song.playcount();
@@ -1160,6 +1170,8 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order,
case Column_Genre: strcmp(genre);
case Column_AlbumArtist: strcmp(playlist_albumartist);
case Column_Composer: strcmp(composer);
+ case Column_Performer: strcmp(performer);
+ case Column_Grouping: strcmp(grouping);
case Column_Rating: cmp(rating);
case Column_PlayCount: cmp(playcount);
@@ -1199,6 +1211,8 @@ QString Playlist::column_name(Column column) {
case Column_Genre: return tr("Genre");
case Column_AlbumArtist: return tr("Album artist");
case Column_Composer: return tr("Composer");
+ case Column_Performer: return tr("Performer");
+ case Column_Grouping: return tr("Grouping");
case Column_Rating: return tr("Rating");
case Column_PlayCount: return tr("Play count");
diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h
index e1c655dc7..16e9de037 100644
--- a/src/playlist/playlist.h
+++ b/src/playlist/playlist.h
@@ -90,6 +90,8 @@ class Playlist : public QAbstractListModel {
Column_Album,
Column_AlbumArtist,
Column_Composer,
+ Column_Performer,
+ Column_Grouping,
Column_Length,
Column_Track,
Column_Disc,
diff --git a/src/playlist/playlistdelegates.cpp b/src/playlist/playlistdelegates.cpp
index 1dfedb9b7..752b556eb 100644
--- a/src/playlist/playlistdelegates.cpp
+++ b/src/playlist/playlistdelegates.cpp
@@ -378,6 +378,8 @@ QString TagCompletionModel::database_column(Playlist::Column column) {
case Playlist::Column_Album: return "album";
case Playlist::Column_AlbumArtist: return "albumartist";
case Playlist::Column_Composer: return "composer";
+ case Playlist::Column_Performer: return "performer";
+ case Playlist::Column_Grouping: return "grouping";
case Playlist::Column_Genre: return "genre";
default:
qLog(Warning) << "Unknown column" << column;
diff --git a/src/playlist/playlistfilter.cpp b/src/playlist/playlistfilter.cpp
index 2158972d2..d96dc0d3f 100644
--- a/src/playlist/playlistfilter.cpp
+++ b/src/playlist/playlistfilter.cpp
@@ -33,6 +33,8 @@ PlaylistFilter::PlaylistFilter(QObject *parent)
column_names_["album"] = Playlist::Column_Album;
column_names_["albumartist"] = Playlist::Column_AlbumArtist;
column_names_["composer"] = Playlist::Column_Composer;
+ column_names_["performer"] = Playlist::Column_Performer;
+ column_names_["grouping"] = Playlist::Column_Grouping;
column_names_["length"] = Playlist::Column_Length;
column_names_["track"] = Playlist::Column_Track;
column_names_["disc"] = Playlist::Column_Disc;
diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp
index d3539c5db..8281f8fce 100644
--- a/src/playlist/playlistview.cpp
+++ b/src/playlist/playlistview.cpp
@@ -192,6 +192,10 @@ void PlaylistView::SetItemDelegates(LibraryBackend* backend) {
new TagCompletionItemDelegate(this, backend, Playlist::Column_Genre));
setItemDelegateForColumn(Playlist::Column_Composer,
new TagCompletionItemDelegate(this, backend, Playlist::Column_Composer));
+ setItemDelegateForColumn(Playlist::Column_Performer,
+ new TagCompletionItemDelegate(this, backend, Playlist::Column_Performer));
+ setItemDelegateForColumn(Playlist::Column_Grouping,
+ new TagCompletionItemDelegate(this, backend, Playlist::Column_Grouping));
setItemDelegateForColumn(Playlist::Column_Length, new LengthItemDelegate(this));
setItemDelegateForColumn(Playlist::Column_Filesize, new SizeItemDelegate(this));
setItemDelegateForColumn(Playlist::Column_Filetype, new FileTypeItemDelegate(this));
@@ -286,6 +290,8 @@ void PlaylistView::LoadGeometry() {
header_->HideSection(Playlist::Column_DateModified);
header_->HideSection(Playlist::Column_AlbumArtist);
header_->HideSection(Playlist::Column_Composer);
+ header_->HideSection(Playlist::Column_Performer);
+ header_->HideSection(Playlist::Column_Grouping);
header_->HideSection(Playlist::Column_Rating);
header_->HideSection(Playlist::Column_PlayCount);
header_->HideSection(Playlist::Column_SkipCount);
diff --git a/src/smartplaylists/searchterm.cpp b/src/smartplaylists/searchterm.cpp
index 4a373d190..ddc6b59fa 100644
--- a/src/smartplaylists/searchterm.cpp
+++ b/src/smartplaylists/searchterm.cpp
@@ -250,6 +250,8 @@ QString SearchTerm::FieldColumnName(Field field) {
case Field_Album: return "album";
case Field_AlbumArtist: return "albumartist";
case Field_Composer: return "composer";
+ case Field_Performer: return "performer";
+ case Field_Grouping: return "grouping";
case Field_Genre: return "genre";
case Field_Comment: return "comment";
case Field_Filepath: return "filename";
@@ -280,6 +282,8 @@ QString SearchTerm::FieldName(Field field) {
case Field_Album: return Playlist::column_name(Playlist::Column_Album);
case Field_AlbumArtist: return Playlist::column_name(Playlist::Column_AlbumArtist);
case Field_Composer: return Playlist::column_name(Playlist::Column_Composer);
+ case Field_Performer: return Playlist::column_name(Playlist::Column_Performer);
+ case Field_Grouping: return Playlist::column_name(Playlist::Column_Grouping);
case Field_Genre: return Playlist::column_name(Playlist::Column_Genre);
case Field_Comment: return QObject::tr("Comment");
case Field_Filepath: return Playlist::column_name(Playlist::Column_Filename);
diff --git a/src/smartplaylists/searchterm.h b/src/smartplaylists/searchterm.h
index 07fbd89a6..fd628ad88 100644
--- a/src/smartplaylists/searchterm.h
+++ b/src/smartplaylists/searchterm.h
@@ -32,6 +32,8 @@ public:
Field_Album,
Field_AlbumArtist,
Field_Composer,
+ Field_Performer,
+ Field_Grouping,
Field_Length,
Field_Track,
Field_Disc,
diff --git a/src/ui/edittagdialog.cpp b/src/ui/edittagdialog.cpp
index 7e81d8c82..2eabf8770 100644
--- a/src/ui/edittagdialog.cpp
+++ b/src/ui/edittagdialog.cpp
@@ -186,6 +186,8 @@ EditTagDialog::EditTagDialog(Application* app, QWidget* parent)
new TagCompleter(app_->library_backend(), Playlist::Column_AlbumArtist, ui_->albumartist);
new TagCompleter(app_->library_backend(), Playlist::Column_Genre, ui_->genre);
new TagCompleter(app_->library_backend(), Playlist::Column_Composer, ui_->composer);
+ new TagCompleter(app_->library_backend(), Playlist::Column_Performer, ui_->performer);
+ new TagCompleter(app_->library_backend(), Playlist::Column_Grouping, ui_->grouping);
}
EditTagDialog::~EditTagDialog() {
@@ -290,6 +292,8 @@ QVariant EditTagDialog::Data::value(const Song& song, const QString& id) {
if (id == "album") return song.album();
if (id == "albumartist") return song.albumartist();
if (id == "composer") return song.composer();
+ if (id == "performer") return song.performer();
+ if (id == "grouping") return song.grouping();
if (id == "genre") return song.genre();
if (id == "comment") return song.comment();
if (id == "track") return song.track();
@@ -305,6 +309,8 @@ void EditTagDialog::Data::set_value(const QString& id, const QVariant& value) {
if (id == "album") current_.set_album(value.toString());
if (id == "albumartist") current_.set_albumartist(value.toString());
if (id == "composer") current_.set_composer(value.toString());
+ if (id == "performer") current_.set_performer(value.toString());
+ if (id == "grouping") current_.set_grouping(value.toString());
if (id == "genre") current_.set_genre(value.toString());
if (id == "comment") current_.set_comment(value.toString());
if (id == "track") current_.set_track(value.toInt());
diff --git a/src/ui/edittagdialog.ui b/src/ui/edittagdialog.ui
index 01fe91e81..5b5d8ee9a 100644
--- a/src/ui/edittagdialog.ui
+++ b/src/ui/edittagdialog.ui
@@ -779,17 +779,37 @@
-
-
+
- Genre
+ Performer
- genre
+ performer
-
-
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Grouping
+
+
+ grouping
+
+
+
+ -
+
true
@@ -799,17 +819,17 @@
-
-
+
- Comment
+ Genre
- comment
+ genre
- -
-
+
-
+
true
@@ -818,7 +838,7 @@
- -
+
-
Complete tags automatically
@@ -835,6 +855,26 @@
+ -
+
+
+ Comment
+
+
+ comment
+
+
+
+ -
+
+
+ true
+
+
+ false
+
+
+
diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp
index d960c6dd1..372d7e756 100644
--- a/src/ui/mainwindow.cpp
+++ b/src/ui/mainwindow.cpp
@@ -2289,6 +2289,7 @@ void MainWindow::HandleNotificationPreview(OSD::Behaviour type, QString line1, Q
fake.Init("Title", "Artist", "Album", 123);
fake.set_genre("Classical");
fake.set_composer("Anonymous");
+ fake.set_performer("Anonymous");
fake.set_track(1);
fake.set_disc(1);
fake.set_year(2011);
diff --git a/src/ui/notificationssettingspage.cpp b/src/ui/notificationssettingspage.cpp
index 1991a0928..ef05f15ac 100644
--- a/src/ui/notificationssettingspage.cpp
+++ b/src/ui/notificationssettingspage.cpp
@@ -48,6 +48,8 @@ NotificationsSettingsPage::NotificationsSettingsPage(SettingsDialog* dialog)
menu->addAction(ui_->action_albumartist);
menu->addAction(ui_->action_year);
menu->addAction(ui_->action_composer);
+ menu->addAction(ui_->action_performer);
+ menu->addAction(ui_->action_grouping);
menu->addAction(ui_->action_length);
menu->addAction(ui_->action_disc);
menu->addAction(ui_->action_track);
diff --git a/src/ui/notificationssettingspage.ui b/src/ui/notificationssettingspage.ui
index 5ffec8033..7cc4e52df 100644
--- a/src/ui/notificationssettingspage.ui
+++ b/src/ui/notificationssettingspage.ui
@@ -378,6 +378,22 @@
Add song composer tag
+
+
+ %performer%
+
+
+ Add song performer tag
+
+
+
+
+ %grouping%
+
+
+ Add song grouping tag
+
+
%disc%
diff --git a/src/ui/organisedialog.cpp b/src/ui/organisedialog.cpp
index cafb43278..bb786115e 100644
--- a/src/ui/organisedialog.cpp
+++ b/src/ui/organisedialog.cpp
@@ -57,6 +57,8 @@ OrganiseDialog::OrganiseDialog(TaskManager* task_manager, QWidget *parent)
tags[tr("Artist's initial")] = "artistinitial";
tags[tr("Album artist")] = "albumartist";
tags[tr("Composer")] = "composer";
+ tags[tr("Performer")] = "performer";
+ tags[tr("Grouping")] = "grouping";
tags[tr("Track")] = "track";
tags[tr("Disc")] = "disc";
tags[tr("BPM")] = "bpm";
diff --git a/src/widgets/osd.cpp b/src/widgets/osd.cpp
index 26586ec13..9e40ff1d9 100644
--- a/src/widgets/osd.cpp
+++ b/src/widgets/osd.cpp
@@ -306,6 +306,10 @@ QString OSD::ReplaceVariable(const QString& variable, const Song& song) {
return song.PrettyYear();
} else if (variable == "%composer%") {
return song.composer();
+ } else if (variable == "%performer%") {
+ return song.performer();
+ } else if (variable == "%grouping%") {
+ return song.grouping();
} else if (variable == "%length%") {
return song.PrettyLength();
} else if (variable == "%disc%") {
diff --git a/tests/organiseformat_test.cpp b/tests/organiseformat_test.cpp
index 0c333f7d1..4afb98725 100644
--- a/tests/organiseformat_test.cpp
+++ b/tests/organiseformat_test.cpp
@@ -38,6 +38,8 @@ TEST_F(OrganiseFormatTest, BasicReplace) {
song_.set_bpm(4.56);
song_.set_comment("comment");
song_.set_composer("composer");
+ song_.set_performer("performer");
+ song_.set_grouping("grouping");
song_.set_disc(789);
song_.set_genre("genre");
song_.set_length_nanosec(987 * kNsecPerSec);
@@ -47,10 +49,11 @@ TEST_F(OrganiseFormatTest, BasicReplace) {
song_.set_year(2010);
format_.set_format("%album %albumartist %bitrate %bpm %comment %composer "
+ "%performer %grouping "
"%disc %genre %length %samplerate %title %track %year");
ASSERT_TRUE(format_.IsValid());
- EXPECT_EQ("album albumartist 123 4.56 comment composer 789 genre 987 654 title 321 2010",
+ EXPECT_EQ("album albumartist 123 4.56 comment composer performer grouping 789 genre 987 654 title 321 2010",
format_.GetFilenameForSong(song_));
}