parent
ea1e4541c0
commit
e477449cd4
|
@ -467,25 +467,6 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||
"
|
||||
QT_SQLITE_TEST
|
||||
)
|
||||
if(QT_SQLITE_TEST)
|
||||
# Check that we have sqlite3 with FTS5
|
||||
check_cxx_source_runs("
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
int main() {
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
||||
db.setDatabaseName(\":memory:\");
|
||||
if (!db.open()) { return 1; }
|
||||
QSqlQuery q(db);
|
||||
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
|
||||
if (!q.exec()) return 1;
|
||||
}
|
||||
"
|
||||
SQLITE_FTS5_TEST
|
||||
)
|
||||
endif()
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
endif()
|
||||
|
||||
# Set up definitions
|
||||
|
@ -546,11 +527,7 @@ if(QT_VERSION_MAJOR EQUAL 5)
|
|||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
if(QT_SQLITE_TEST)
|
||||
if(NOT SQLITE_FTS5_TEST)
|
||||
message(WARNING "sqlite must be enabled with FTS5. See: https://www.sqlite.org/fts5.html")
|
||||
endif()
|
||||
else()
|
||||
if(NOT QT_SQLITE_TEST)
|
||||
message(WARNING "The Qt sqlite driver test failed.")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -76,7 +76,7 @@ To build Strawberry from source you need the following installed on your system
|
|||
* [Boost](https://www.boost.org/)
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||
* [SQLite 3.9 or newer](https://www.sqlite.org)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
|
|
|
@ -92,12 +92,14 @@ set(SOURCES
|
|||
collection/collectiondirectorymodel.cpp
|
||||
collection/collectionfilteroptions.cpp
|
||||
collection/collectionfilterwidget.cpp
|
||||
collection/collectionfilter.cpp
|
||||
collection/collectionplaylistitem.cpp
|
||||
collection/collectionquery.cpp
|
||||
collection/collectionqueryoptions.cpp
|
||||
collection/savedgroupingmanager.cpp
|
||||
collection/groupbydialog.cpp
|
||||
collection/collectiontask.cpp
|
||||
collection/collectionmodelupdate.cpp
|
||||
|
||||
playlist/playlist.cpp
|
||||
playlist/playlistbackend.cpp
|
||||
|
@ -349,6 +351,7 @@ set(HEADERS
|
|||
collection/collectionviewcontainer.h
|
||||
collection/collectiondirectorymodel.h
|
||||
collection/collectionfilterwidget.h
|
||||
collection/collectionfilter.h
|
||||
collection/savedgroupingmanager.h
|
||||
collection/groupbydialog.h
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
using std::make_shared;
|
||||
|
||||
const char *SCollection::kSongsTable = "songs";
|
||||
const char *SCollection::kFtsTable = "songs_fts";
|
||||
const char *SCollection::kDirsTable = "directories";
|
||||
const char *SCollection::kSubdirsTable = "subdirectories";
|
||||
|
||||
|
@ -70,7 +69,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||
backend()->moveToThread(app->database()->thread());
|
||||
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
|
||||
|
||||
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kFtsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
|
||||
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
|
||||
|
||||
model_ = new CollectionModel(backend_, app_, this);
|
||||
|
||||
|
|
|
@ -76,16 +76,13 @@ CollectionBackend::~CollectionBackend() {
|
|||
|
||||
}
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
db_ = db;
|
||||
task_manager_ = task_manager;
|
||||
source_ = source;
|
||||
songs_table_ = songs_table;
|
||||
dirs_table_ = dirs_table;
|
||||
subdirs_table_ = subdirs_table;
|
||||
fts_table_ = fts_table;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::Close() {
|
||||
|
@ -123,6 +120,35 @@ void CollectionBackend::ReportErrors(const CollectionQuery &query) {
|
|||
|
||||
}
|
||||
|
||||
void CollectionBackend::GetAllSongsAsync(const int id) {
|
||||
metaObject()->invokeMethod(this, "GetAllSongs", Qt::QueuedConnection, Q_ARG(int, id));
|
||||
}
|
||||
|
||||
void CollectionBackend::GetAllSongs(const int id) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.setForwardOnly(true);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
if (!q.exec()) {
|
||||
db_->ReportErrors(q);
|
||||
emit GotSongs(SongList(), id);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList songs;
|
||||
while (q.next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
|
||||
emit GotSongs(songs, id);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::LoadDirectoriesAsync() {
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -596,7 +622,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
ScopedTransaction transaction(&db);
|
||||
|
||||
SongList added_songs;
|
||||
SongList deleted_songs;
|
||||
SongList changed_songs;
|
||||
|
||||
for (const Song &song : songs) {
|
||||
|
||||
|
@ -633,19 +659,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
song.BindToFtsQuery(&q);
|
||||
q.BindValue(QStringLiteral(":id"), song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deleted_songs << old_song;
|
||||
added_songs << song;
|
||||
changed_songs << song;
|
||||
|
||||
continue;
|
||||
|
||||
|
@ -672,19 +686,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
new_song.BindToFtsQuery(&q);
|
||||
q.BindValue(QStringLiteral(":id"), new_song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deleted_songs << old_song;
|
||||
added_songs << new_song;
|
||||
changed_songs << new_song;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -707,17 +709,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
|
||||
if (id == -1) return;
|
||||
|
||||
{ // Add to the FTS index
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
q.BindValue(QStringLiteral(":id"), id);
|
||||
song.BindToFtsQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Song song_copy(song);
|
||||
song_copy.set_id(id);
|
||||
added_songs << song_copy;
|
||||
|
@ -726,8 +717,8 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
|
||||
transaction.Commit();
|
||||
|
||||
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
||||
if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs);
|
||||
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) emit SongsChanged(changed_songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
|
@ -748,11 +739,12 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
ScopedTransaction transaction(&db);
|
||||
|
||||
SongList added_songs;
|
||||
SongList changed_songs;
|
||||
SongList deleted_songs;
|
||||
|
||||
SongMap old_songs;
|
||||
{
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_);
|
||||
if (!ExecCollectionQuery(&query, old_songs)) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
|
@ -778,21 +770,10 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
new_song.BindToFtsQuery(&q);
|
||||
q.BindValue(QStringLiteral(":id"), old_song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deleted_songs << old_song;
|
||||
Song new_song_copy(new_song);
|
||||
new_song_copy.set_id(old_song.id());
|
||||
added_songs << new_song_copy;
|
||||
changed_songs << new_song_copy;
|
||||
|
||||
}
|
||||
|
||||
|
@ -813,17 +794,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
|
||||
if (id == -1) return;
|
||||
|
||||
{ // Add to the FTS index
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
q.BindValue(QStringLiteral(":id"), id);
|
||||
new_song.BindToFtsQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Song new_song_copy(new_song);
|
||||
new_song_copy.set_id(id);
|
||||
added_songs << new_song_copy;
|
||||
|
@ -843,15 +813,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_));
|
||||
q.BindValue(QStringLiteral(":id"), old_song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
deleted_songs << old_song;
|
||||
}
|
||||
}
|
||||
|
@ -859,7 +820,8 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
transaction.Commit();
|
||||
|
||||
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
||||
if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs);
|
||||
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||
if (!changed_songs.isEmpty()) emit SongsChanged(changed_songs);
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
UpdateTotalArtistCountAsync();
|
||||
|
@ -872,11 +834,10 @@ void CollectionBackend::UpdateMTimesOnly(const SongList &songs) {
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_));
|
||||
|
||||
ScopedTransaction transaction(&db);
|
||||
for (const Song &song : songs) {
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":mtime"), song.mtime());
|
||||
q.BindValue(QStringLiteral(":id"), song.id());
|
||||
if (!q.Exec()) {
|
||||
|
@ -893,25 +854,17 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery remove(db);
|
||||
remove.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_));
|
||||
SqlQuery remove_fts(db);
|
||||
remove_fts.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_));
|
||||
|
||||
ScopedTransaction transaction(&db);
|
||||
for (const Song &song : songs) {
|
||||
remove.BindValue(QStringLiteral(":id"), song.id());
|
||||
if (!remove.Exec()) {
|
||||
db_->ReportErrors(remove);
|
||||
return;
|
||||
}
|
||||
|
||||
remove_fts.BindValue(QStringLiteral(":id"), song.id());
|
||||
if (!remove_fts.Exec()) {
|
||||
db_->ReportErrors(remove_fts);
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":id"), song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
emit SongsDeleted(songs);
|
||||
|
@ -944,7 +897,7 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u
|
|||
emit SongsDeleted(songs);
|
||||
}
|
||||
else {
|
||||
emit SongsDiscovered(songs);
|
||||
emit SongsAdded(songs);
|
||||
}
|
||||
|
||||
UpdateTotalSongCountAsync();
|
||||
|
@ -958,7 +911,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const CollectionFil
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, filter_options);
|
||||
CollectionQuery query(db, songs_table_, filter_options);
|
||||
query.SetColumnSpec(QStringLiteral("DISTINCT ") + column);
|
||||
query.AddCompilationRequirement(false);
|
||||
|
||||
|
@ -986,13 +939,13 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOpt
|
|||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Albums with 'albumartist' field set:
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec(QStringLiteral("DISTINCT albumartist"));
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||
|
||||
// Albums with no 'albumartist' (extract 'artist'):
|
||||
CollectionQuery query2(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query2(db, songs_table_, opt);
|
||||
query2.SetColumnSpec(QStringLiteral("DISTINCT artist"));
|
||||
query2.AddCompilationRequirement(false);
|
||||
query2.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||
|
@ -1033,7 +986,7 @@ SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist,
|
|||
QSqlDatabase db(db_->Connect());
|
||||
QMutexLocker l(db_->Mutex());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
|
||||
|
@ -1051,7 +1004,7 @@ SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist,
|
|||
QSqlDatabase db(db_->Connect());
|
||||
QMutexLocker l(db_->Mutex());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
@ -1070,7 +1023,7 @@ SongList CollectionBackend::GetSongsByAlbum(const QString &album, const Collecti
|
|||
QSqlDatabase db(db_->Connect());
|
||||
QMutexLocker l(db_->Mutex());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
||||
|
@ -1207,11 +1160,10 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, const qint64 beginning) {
|
|||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
|
||||
q.BindValue(QStringLiteral(":url1"), url);
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString());
|
||||
q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded());
|
||||
q.BindValue(QStringLiteral(":url1"), url.toString(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url3"), url.toEncoded(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":beginning"), beginning);
|
||||
|
||||
if (!q.Exec()) {
|
||||
|
@ -1237,11 +1189,10 @@ Song CollectionBackend::GetSongByUrlAndTrack(const QUrl &url, const int track) {
|
|||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND track = :track AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
|
||||
q.BindValue(QStringLiteral(":url1"), url);
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString());
|
||||
q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded());
|
||||
q.BindValue(QStringLiteral(":url1"), url.toString(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url3"), url.toEncoded(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":track"), track);
|
||||
|
||||
if (!q.Exec()) {
|
||||
|
@ -1267,23 +1218,21 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url, const bool unavailabl
|
|||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
|
||||
q.BindValue(QStringLiteral(":url1"), url);
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString());
|
||||
q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded());
|
||||
q.BindValue(QStringLiteral(":url1"), url.toString(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url3"), url.toEncoded(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":unavailable"), (unavailable ? 1 : 0));
|
||||
|
||||
SongList songs;
|
||||
if (q.Exec()) {
|
||||
while (q.next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
}
|
||||
while (q.next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
|
||||
return songs;
|
||||
|
@ -1377,7 +1326,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Coll
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
|
||||
query.AddCompilationRequirement(true);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
@ -1434,8 +1383,7 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
|||
}
|
||||
|
||||
// Now mark the songs that we think are in compilations
|
||||
SongList deleted_songs;
|
||||
SongList added_songs;
|
||||
SongList changed_songs;
|
||||
|
||||
ScopedTransaction transaction(&db);
|
||||
|
||||
|
@ -1448,12 +1396,12 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
|||
for (const QUrl &url : info.urls) {
|
||||
if (info.artists.count() > 1) { // This directory+album is a compilation.
|
||||
if (info.has_not_compilation_detected > 0) { // Run updates if any of the songs is not marked as compilations.
|
||||
UpdateCompilations(db, deleted_songs, added_songs, url, true);
|
||||
UpdateCompilations(db, changed_songs, url, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (info.has_compilation_detected > 0) {
|
||||
UpdateCompilations(db, deleted_songs, added_songs, url, false);
|
||||
UpdateCompilations(db, changed_songs, url, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1461,29 +1409,27 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
|||
|
||||
transaction.Commit();
|
||||
|
||||
if (!deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!changed_songs.isEmpty()) {
|
||||
emit SongsChanged(changed_songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected) {
|
||||
bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &changed_songs, const QUrl &url, const bool compilation_detected) {
|
||||
|
||||
{ // Get song, so we can tell the model its updated
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("SELECT %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||
q.BindValue(QStringLiteral(":url1"), url);
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString());
|
||||
q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded());
|
||||
q.BindValue(QStringLiteral(":url1"), url.toString(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url3"), url.toEncoded(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded(QUrl::FullyEncoded));
|
||||
if (q.Exec()) {
|
||||
while (q.next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
deleted_songs << song;
|
||||
song.set_compilation_detected(compilation_detected);
|
||||
added_songs << song;
|
||||
changed_songs << song;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -1496,10 +1442,10 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del
|
|||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET compilation_detected = :compilation_detected, compilation_effective = ((compilation OR :compilation_detected OR compilation_on) AND NOT compilation_off) + 0 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":compilation_detected"), static_cast<int>(compilation_detected));
|
||||
q.BindValue(QStringLiteral(":url1"), url);
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString());
|
||||
q.BindValue(QStringLiteral(":url3"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded());
|
||||
q.BindValue(QStringLiteral(":url1"), url.toString(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url2"), url.toString(QUrl::FullyEncoded));
|
||||
q.BindValue(QStringLiteral(":url3"), url.toEncoded(QUrl::FullyDecoded));
|
||||
q.BindValue(QStringLiteral(":url4"), url.toEncoded(QUrl::FullyEncoded));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return false;
|
||||
|
@ -1514,7 +1460,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec(QStringLiteral("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset"));
|
||||
query.SetOrderBy(QStringLiteral("effective_albumartist, album, url"));
|
||||
|
||||
|
@ -1605,7 +1551,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||
ret.album = album;
|
||||
ret.album_artist = effective_albumartist;
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec(QStringLiteral("url, art_embedded, art_automatic, art_manual, art_unset"));
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
|
@ -1640,54 +1586,37 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_embedded"), art_embedded ? 1 : 0);
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
SongList songs;
|
||||
{
|
||||
CollectionQuery q(db, songs_table_);
|
||||
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
q.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
q.AddWhere(QStringLiteral("album"), album);
|
||||
if (!q.Exec()) {
|
||||
ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
while (q.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql = QStringLiteral("UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_);
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(sql);
|
||||
q.BindValue(QStringLiteral(":art_embedded"), art_embedded ? 1 : 0);
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now get the updated songs
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1703,49 +1632,37 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_manual"), art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : QLatin1String(""));
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
SongList songs;
|
||||
{
|
||||
CollectionQuery q(db, songs_table_);
|
||||
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
q.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
q.AddWhere(QStringLiteral("album"), album);
|
||||
if (!q.Exec()) {
|
||||
ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
while (q.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_manual"), art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : QLatin1String(""));
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1761,48 +1678,36 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
SongList songs;
|
||||
{
|
||||
CollectionQuery q(db, songs_table_);
|
||||
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
q.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
q.AddWhere(QStringLiteral("album"), album);
|
||||
if (!q.Exec()) {
|
||||
ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
while (q.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1818,49 +1723,37 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_unset"), art_unset ? 1 : 0);
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
SongList songs;
|
||||
{
|
||||
CollectionQuery q(db, songs_table_);
|
||||
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
q.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||
q.AddWhere(QStringLiteral("album"), album);
|
||||
if (!q.Exec()) {
|
||||
ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
while (q.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(QStringLiteral(":art_unset"), art_unset ? 1 : 0);
|
||||
q.BindValue(QStringLiteral(":effective_albumartist"), effective_albumartist);
|
||||
q.BindValue(QStringLiteral(":album"), album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1869,25 +1762,9 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
|||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
SongList deleted_songs, added_songs;
|
||||
SongList songs;
|
||||
|
||||
for (const QString &artist : artists) {
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
if (!artist.isEmpty()) query.AddWhere(QStringLiteral("artist"), artist);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql(QStringLiteral("UPDATE %1 SET compilation_on = :compilation_on, compilation_off = :compilation_off, compilation_effective = ((compilation OR compilation_detected OR :compilation_on) AND NOT :compilation_off) + 0 WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
||||
|
@ -1905,7 +1782,13 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
|||
return;
|
||||
}
|
||||
|
||||
// Now get the updated songs
|
||||
// Get the updated songs
|
||||
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||
query.AddWhere(QStringLiteral("album"), album);
|
||||
if (!artist.isEmpty()) query.AddWhere(QStringLiteral("artist"), artist);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
|
@ -1914,13 +1797,12 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
|||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
songs << song;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
if (!songs.isEmpty()) {
|
||||
emit SongsChanged(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2035,15 +1917,6 @@ void CollectionBackend::DeleteAll() {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QStringLiteral("DELETE FROM ") + fts_table_);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
|
|
|
@ -80,12 +80,13 @@ class CollectionBackendInterface : public QObject {
|
|||
using AlbumList = QList<Album>;
|
||||
|
||||
virtual QString songs_table() const = 0;
|
||||
virtual QString fts_table() const = 0;
|
||||
|
||||
virtual Song::Source source() const = 0;
|
||||
|
||||
virtual SharedPtr<Database> db() const = 0;
|
||||
|
||||
virtual void GetAllSongsAsync(const int id = 0) = 0;
|
||||
|
||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||
virtual void LoadDirectoriesAsync() = 0;
|
||||
|
||||
|
@ -145,7 +146,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
|
||||
~CollectionBackend();
|
||||
|
||||
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
||||
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
||||
|
||||
void Close();
|
||||
|
||||
void ExitAsync();
|
||||
|
@ -157,10 +159,11 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
SharedPtr<Database> db() const override { return db_; }
|
||||
|
||||
QString songs_table() const override { return songs_table_; }
|
||||
QString fts_table() const override { return fts_table_; }
|
||||
QString dirs_table() const { return dirs_table_; }
|
||||
QString subdirs_table() const { return subdirs_table_; }
|
||||
|
||||
void GetAllSongsAsync(const int id = 0) override;
|
||||
|
||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||
void LoadDirectoriesAsync() override;
|
||||
|
||||
|
@ -235,6 +238,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
|
||||
public slots:
|
||||
void Exit();
|
||||
void GetAllSongs(const int id);
|
||||
void LoadDirectories();
|
||||
void UpdateTotalSongCount();
|
||||
void UpdateTotalArtistCount();
|
||||
|
@ -275,8 +279,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
void DirectoryAdded(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||
|
||||
void SongsDiscovered(const SongList &songs);
|
||||
void GotSongs(const SongList &songs, const int id);
|
||||
void SongsAdded(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
void SongsChanged(const SongList &songs);
|
||||
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
void DatabaseReset();
|
||||
|
@ -301,7 +307,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
int has_not_compilation_detected;
|
||||
};
|
||||
|
||||
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
||||
bool UpdateCompilations(const QSqlDatabase &db, SongList &changed_songs, const QUrl &url, const bool compilation_detected);
|
||||
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||
|
@ -319,7 +325,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
QString songs_table_;
|
||||
QString dirs_table_;
|
||||
QString subdirs_table_;
|
||||
QString fts_table_;
|
||||
QThread *original_thread_;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2021-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry 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.
|
||||
*
|
||||
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "utilities/searchparserutils.h"
|
||||
|
||||
#include "collectionfilter.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "collectionitem.h"
|
||||
|
||||
const QStringList CollectionFilter::Operators = QStringList() << QStringLiteral(":")
|
||||
<< QStringLiteral("=")
|
||||
<< QStringLiteral("==")
|
||||
<< QStringLiteral("<>")
|
||||
<< QStringLiteral("<")
|
||||
<< QStringLiteral("<=")
|
||||
<< QStringLiteral(">")
|
||||
<< QStringLiteral(">=");
|
||||
|
||||
CollectionFilter::CollectionFilter(QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex &source_parent) const {
|
||||
|
||||
CollectionModel *model = qobject_cast<CollectionModel*>(sourceModel());
|
||||
if (!model) return false;
|
||||
const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
|
||||
if (!idx.isValid()) return false;
|
||||
CollectionItem *item = model->IndexToItem(idx);
|
||||
if (!item) return false;
|
||||
|
||||
if (item->type == CollectionItem::Type::LoadingIndicator) return true;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QString filter_text = filterRegularExpression().pattern().remove(QLatin1Char('\\'));
|
||||
#else
|
||||
QString filter_text = filterRegExp().pattern();
|
||||
#endif
|
||||
|
||||
if (filter_text.isEmpty()) return true;
|
||||
|
||||
filter_text = filter_text.replace(QRegularExpression(QStringLiteral("\\s*:\\s*")), QStringLiteral(":"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*=\\s*")), QStringLiteral("="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*==\\s*")), QStringLiteral("=="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<>\\s*")), QStringLiteral("<>"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<\\s*")), QStringLiteral("<"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*>\\s*")), QStringLiteral(">"))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*<=\\s*")), QStringLiteral("<="))
|
||||
.replace(QRegularExpression(QStringLiteral("\\s*>=\\s*")), QStringLiteral(">="));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
|
||||
#else
|
||||
const QStringList tokens = filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
filter_text.clear();
|
||||
|
||||
FilterList filters;
|
||||
static QRegularExpression operator_regex(QStringLiteral("(=|<[>=]?|>=?|!=)"));
|
||||
for (int i = 0; i < tokens.count(); ++i) {
|
||||
const QString &token = tokens[i];
|
||||
if (token.contains(QLatin1Char(':'))) {
|
||||
QString field = token.section(QLatin1Char(':'), 0, 0).remove(QLatin1Char(':')).trimmed();
|
||||
QString value = token.section(QLatin1Char(':'), 1, -1).remove(QLatin1Char(':')).trimmed();
|
||||
if (field.isEmpty() || value.isEmpty()) continue;
|
||||
if (Song::kTextSearchColumns.contains(field, Qt::CaseInsensitive) && value.count(QLatin1Char('"')) <= 2) {
|
||||
bool quotation_mark_start = false;
|
||||
bool quotation_mark_end = false;
|
||||
if (value.left(1) == QLatin1Char('"')) {
|
||||
value.remove(0, 1);
|
||||
quotation_mark_start = true;
|
||||
if (value.length() >= 1 && value.count(QLatin1Char('"')) == 1) {
|
||||
value = value.section(QLatin1Char(QLatin1Char('"')), 0, 0).remove(QLatin1Char('"')).trimmed();
|
||||
quotation_mark_end = true;
|
||||
}
|
||||
}
|
||||
for (int y = i + 1; y < tokens.count() && !quotation_mark_end; ++y) {
|
||||
QString next_value = tokens[y];
|
||||
if (!quotation_mark_start && ContainsOperators(next_value)) {
|
||||
break;
|
||||
}
|
||||
if (quotation_mark_start && next_value.contains(QLatin1Char('"'))) {
|
||||
next_value = next_value.section(QLatin1Char(QLatin1Char('"')), 0, 0).remove(QLatin1Char('"')).trimmed();
|
||||
quotation_mark_end = true;
|
||||
}
|
||||
value.append(QLatin1Char(' ') + next_value);
|
||||
i = y;
|
||||
}
|
||||
if (!field.isEmpty() && !value.isEmpty()) {
|
||||
filters.insert(field, Filter(field, value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (token.contains(operator_regex)) {
|
||||
QRegularExpressionMatch re_match = operator_regex.match(token);
|
||||
if (re_match.hasMatch()) {
|
||||
const QString foperator = re_match.captured(0);
|
||||
const QString field = token.section(foperator, 0, 0).remove(foperator).trimmed();
|
||||
const QString value = token.section(foperator, 1, -1).remove(foperator).trimmed();
|
||||
if (value.isEmpty()) continue;
|
||||
if (Song::kNumericalSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
if (Song::kIntSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
bool ok = false;
|
||||
const int value_int = value.toInt(&ok);
|
||||
if (ok) {
|
||||
filters.insert(field, Filter(field, value_int, foperator));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Song::kUIntSearchColumns.contains(field, Qt::CaseInsensitive)) {
|
||||
bool ok = false;
|
||||
const uint value_uint = value.toUInt(&ok);
|
||||
if (ok) {
|
||||
filters.insert(field, Filter(field, value_uint, foperator));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (field.compare(QStringLiteral("length"), Qt::CaseInsensitive) == 0) {
|
||||
filters.insert(field, Filter(field, static_cast<qint64>(Utilities::ParseSearchTime(value)) * kNsecPerSec, foperator));
|
||||
continue;
|
||||
}
|
||||
else if (field.compare(QStringLiteral("rating"), Qt::CaseInsensitive) == 0) {
|
||||
filters.insert(field, Filter(field, Utilities::ParseSearchRating(value), foperator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!filter_text.isEmpty()) filter_text.append(QLatin1Char(' '));
|
||||
filter_text += token;
|
||||
}
|
||||
|
||||
if (filter_text.isEmpty() && filters.isEmpty()) return true;
|
||||
|
||||
return ItemMatchesFilters(item, filters, filter_text);
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ItemMatchesFilters(CollectionItem *item, const FilterList &filters, const QString &filter_text) {
|
||||
|
||||
if (item->type == CollectionItem::Type::Song &&
|
||||
item->metadata.is_valid() &&
|
||||
ItemMetadataMatchesFilters(item->metadata, filters, filter_text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (CollectionItem *child : std::as_const(item->children)) {
|
||||
if (ItemMatchesFilters(child, filters, filter_text)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool CollectionFilter::ItemMetadataMatchesFilters(const Song &metadata, const FilterList &filters, const QString &filter_text) {
|
||||
|
||||
for (FilterList::const_iterator it = filters.begin() ; it != filters.end() ; ++it) {
|
||||
const QString &field = it.key();
|
||||
const Filter &filter = it.value();
|
||||