From 300497f967ae938299a2869dfb6cb55a49af9235 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sun, 27 Jun 2021 22:54:08 +0200 Subject: [PATCH] Remove lazy loading and FTS Fixes #392 --- CMakeLists.txt | 25 +- README.md | 2 +- src/CMakeLists.txt | 3 + src/collection/collection.cpp | 3 +- src/collection/collectionbackend.cpp | 179 ++-- src/collection/collectionbackend.h | 15 +- src/collection/collectionfilter.cpp | 196 +++++ src/collection/collectionfilter.h | 50 ++ src/collection/collectionfilterwidget.cpp | 20 +- src/collection/collectionfilterwidget.h | 7 +- src/collection/collectionmodel.cpp | 960 ++++++--------------- src/collection/collectionmodel.h | 85 +- src/collection/collectionmodelupdate.cpp | 23 + src/collection/collectionmodelupdate.h | 38 + src/collection/collectionquery.cpp | 95 +- src/collection/collectionquery.h | 5 +- src/core/mainwindow.cpp | 19 +- src/core/mainwindow.h | 3 +- src/core/simpletreeitem.h | 4 - src/core/simpletreemodel.h | 26 +- src/core/song.cpp | 85 +- src/core/song.h | 9 +- src/core/songloader.cpp | 2 +- src/covermanager/albumcovermanager.cpp | 2 +- src/device/cddadevice.cpp | 2 +- src/device/connecteddevice.cpp | 1 - src/device/devicedatabasebackend.cpp | 2 +- src/internet/internetservice.h | 8 +- src/internet/internetsongsview.cpp | 5 +- src/internet/internettabsview.cpp | 13 +- src/playlist/playlistmanager.cpp | 2 +- src/qobuz/qobuzservice.cpp | 48 +- src/qobuz/qobuzservice.h | 14 +- src/radios/radiomodel.cpp | 5 - src/smartplaylists/smartplaylistsmodel.cpp | 7 +- src/subsonic/subsonicservice.cpp | 17 +- src/subsonic/subsonicservice.h | 8 +- src/tidal/tidalservice.cpp | 47 +- src/tidal/tidalservice.h | 14 +- tests/src/collectionbackend_test.cpp | 16 +- tests/src/collectionmodel_test.cpp | 36 +- 41 files changed, 911 insertions(+), 1190 deletions(-) create mode 100644 src/collection/collectionfilter.cpp create mode 100644 src/collection/collectionfilter.h create mode 100644 src/collection/collectionmodelupdate.cpp create mode 100644 src/collection/collectionmodelupdate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 74f07659..cd335879 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -476,25 +476,6 @@ if(NOT CMAKE_CROSSCOMPILING) " QT_SQLITE_TEST ) - if(QT_SQLITE_TEST) - # Check that we have sqlite3 with FTS5 - check_cxx_source_runs(" - #include - #include - 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 @@ -555,11 +536,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() diff --git a/README.md b/README.md index 38a9e0de..3bfd081e 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 89b4f097..c1f0410a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,12 +90,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 @@ -345,6 +347,7 @@ set(HEADERS collection/collectionviewcontainer.h collection/collectiondirectorymodel.h collection/collectionfilterwidget.h + collection/collectionfilter.h collection/savedgroupingmanager.h collection/groupbydialog.h diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 184f2ad3..92cfa5f9 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -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); diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 0c6bc70f..3b98c361 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -75,16 +75,13 @@ CollectionBackend::~CollectionBackend() { } -void CollectionBackend::Init(SharedPtr db, SharedPtr 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 db, SharedPtr 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() { @@ -122,6 +119,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); } @@ -614,17 +640,6 @@ 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; @@ -653,17 +668,6 @@ 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; @@ -688,17 +692,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; @@ -708,7 +701,7 @@ 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); UpdateTotalSongCountAsync(); UpdateTotalArtistCountAsync(); @@ -733,7 +726,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) { SongMap old_songs; { - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); if (!ExecCollectionQuery(&query, old_songs)) { ReportErrors(query); return; @@ -759,16 +752,6 @@ 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); @@ -794,17 +777,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; @@ -824,15 +796,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; } } @@ -840,7 +803,7 @@ 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); UpdateTotalSongCountAsync(); UpdateTotalArtistCountAsync(); @@ -853,11 +816,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()) { @@ -874,25 +836,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); @@ -925,7 +879,7 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u emit SongsDeleted(songs); } else { - emit SongsDiscovered(songs); + emit SongsAdded(songs); } UpdateTotalSongCountAsync(); @@ -939,7 +893,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); @@ -967,13 +921,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("!=")); @@ -1014,7 +968,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); @@ -1032,7 +986,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); @@ -1051,7 +1005,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); @@ -1358,7 +1312,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); @@ -1444,7 +1398,7 @@ void CollectionBackend::CompilationsNeedUpdating() { if (!deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -1495,7 +1449,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")); @@ -1586,7 +1540,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); @@ -1622,7 +1576,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart QSqlDatabase db(db_->Connect()); // Get the songs before they're updated - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1668,7 +1622,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -1684,7 +1638,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1726,7 +1680,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -1742,7 +1696,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1783,7 +1737,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -1799,7 +1753,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist); query.AddWhere(QStringLiteral("album"), album); @@ -1841,7 +1795,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -1854,7 +1808,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList for (const QString &artist : artists) { // Get the songs before they're updated - CollectionQuery query(db, songs_table_, fts_table_); + CollectionQuery query(db, songs_table_); query.SetColumnSpec(Song::kRowIdColumnSpec); query.AddWhere(QStringLiteral("album"), album); if (!artist.isEmpty()) query.AddWhere(QStringLiteral("artist"), artist); @@ -1901,7 +1855,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) { emit SongsDeleted(deleted_songs); - emit SongsDiscovered(added_songs); + emit SongsAdded(added_songs); } } @@ -2016,15 +1970,6 @@ void CollectionBackend::DeleteAll() { } } - { - SqlQuery q(db); - q.prepare(QStringLiteral("DELETE FROM ") + fts_table_); - if (!q.Exec()) { - db_->ReportErrors(q); - return; - } - } - t.Commit(); } diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h index 8cbee265..404b6316 100644 --- a/src/collection/collectionbackend.h +++ b/src/collection/collectionbackend.h @@ -80,12 +80,13 @@ class CollectionBackendInterface : public QObject { using AlbumList = QList; virtual QString songs_table() const = 0; - virtual QString fts_table() const = 0; virtual Song::Source source() const = 0; virtual SharedPtr 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 db, SharedPtr 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 db, SharedPtr 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 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(); @@ -273,8 +277,10 @@ class CollectionBackend : public CollectionBackendInterface { void DirectoryDiscovered(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(); @@ -317,7 +323,6 @@ class CollectionBackend : public CollectionBackendInterface { QString songs_table_; QString dirs_table_; QString subdirs_table_; - QString fts_table_; QThread *original_thread_; }; diff --git a/src/collection/collectionfilter.cpp b/src/collection/collectionfilter.cpp new file mode 100644 index 00000000..667f2e7c --- /dev/null +++ b/src/collection/collectionfilter.cpp @@ -0,0 +1,196 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include + +#include "collectionfilter.h" +#include "collectionmodel.h" +#include "collectionitem.h" + +CollectionFilter::CollectionFilter(QObject *parent) : QSortFilterProxyModel(parent) { + setDynamicSortFilter(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); +} + +bool CollectionFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + + CollectionModel *model = qobject_cast(sourceModel()); + if (!model) return false; + 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 = filterRegularExpression().pattern().remove(QLatin1Char('\\')); +#else + QString filter = filterRegExp().pattern(); +#endif + + if (filter.isEmpty()) return true; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList tokens(filter.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts)); +#else + QStringList tokens(filter.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts)); +#endif + + filter.clear(); + + QMap tags; + for (QString token : tokens) { + if (token.contains(QLatin1Char(':'))) { + if (Song::kColumns.contains(token.section(QLatin1Char(':'), 0, 0), Qt::CaseInsensitive)) { + QString tag = token.section(QLatin1Char(':'), 0, 0).remove(QLatin1Char(':')).trimmed(); + QString value = token.section(QLatin1Char(':'), 1, -1).remove(QLatin1Char(':')).trimmed(); + if (!tag.isEmpty() && !value.isEmpty()) { + tags.insert(tag, value); + } + } + else { + token = token.remove(QLatin1Char(':')).trimmed(); + if (!token.isEmpty()) { + if (!filter.isEmpty()) filter.append(QLatin1Char(' ')); + filter += token; + } + } + } + else { + if (!filter.isEmpty()) filter.append(QLatin1Char(' ')); + filter += token; + } + } + + if (ItemMatches(model, item, tags, filter)) return true; + + for (CollectionItem *parent = item->parent ; parent ; parent = parent->parent) { + if (ItemMatches(model, parent, tags, filter)) return true; + } + + return ChildrenMatches(model, item, tags, filter); + +} + +bool CollectionFilter::ItemMatches(CollectionModel *model, CollectionItem *item, const QMap &tags, const QString &filter) const { + + if ( + (filter.isEmpty() || item->DisplayText().contains(filter, Qt::CaseInsensitive)) + && + ( + tags.isEmpty() // If no tags were specified, only the filter needs to match. + || + (item->metadata.is_valid() && TagMatches(item, tags)) // Song node + || + (item->container_level >= 0 && item->container_level <= 2 && TagMatches(item, model->GetGroupBy()[item->container_level], tags)) // Container node + ) + ) { return true; } + + return false; + +} + +bool CollectionFilter::ChildrenMatches(CollectionModel *model, CollectionItem *item, const QMap &tags, const QString &filter) const { + + if (ItemMatches(model, item, tags, filter)) return true; + + for (CollectionItem *child : item->children) { + if (ChildrenMatches(model, child, tags, filter)) return true; + } + + return false; + +} + +bool CollectionFilter::TagMatches(CollectionItem *item, const QMap &tags) const { + + Song &metadata = item->metadata; + + for (QMap::const_iterator it = tags.begin() ; it != tags.end() ; ++it) { + QString tag = it.key().toLower(); + QString value = it.value(); + if (tag == QStringLiteral("albumartist") && metadata.effective_albumartist().contains(value, Qt::CaseInsensitive)) return true; + if (tag == QStringLiteral("artist") && metadata.artist().contains(value, Qt::CaseInsensitive)) return true; + if (tag == QStringLiteral("album") && metadata.album().contains(value, Qt::CaseInsensitive)) return true; + if (tag == QStringLiteral("title") && metadata.title().contains(value, Qt::CaseInsensitive)) return true; + } + + return false; + +} + +bool CollectionFilter::TagMatches(CollectionItem *item, const CollectionModel::GroupBy group_by, const QMap &tags) const { + + QString tag; + switch (group_by) { + case CollectionModel::GroupBy::AlbumArtist: + tag = QStringLiteral("albumartist"); + break; + case CollectionModel::GroupBy::Artist: + tag = QStringLiteral("artist"); + break; + case CollectionModel::GroupBy::Album: + case CollectionModel::GroupBy::AlbumDisc: + case CollectionModel::GroupBy::YearAlbum: + case CollectionModel::GroupBy::YearAlbumDisc: + case CollectionModel::GroupBy::OriginalYearAlbum: + case CollectionModel::GroupBy::OriginalYearAlbumDisc: + tag = QStringLiteral("album"); + break; + case CollectionModel::GroupBy::Disc: + case CollectionModel::GroupBy::Year: + case CollectionModel::GroupBy::OriginalYear: + break; + case CollectionModel::GroupBy::Genre: + tag = QStringLiteral("genre"); + break; + case CollectionModel::GroupBy::Composer: + tag = QStringLiteral("composer"); + break; + case CollectionModel::GroupBy::Performer: + tag = QStringLiteral("performer"); + break; + case CollectionModel::GroupBy::Grouping: + tag = QStringLiteral("grouping"); + break; + case CollectionModel::GroupBy::FileType: + tag = QStringLiteral("filetype"); + break; + case CollectionModel::GroupBy::Format: + case CollectionModel::GroupBy::Bitdepth: + case CollectionModel::GroupBy::Samplerate: + case CollectionModel::GroupBy::Bitrate: + case CollectionModel::GroupBy::None: + case CollectionModel::GroupBy::GroupByCount: + break; + } + + QString value; + if (!tag.isEmpty() && tags.contains(tag)) { + value = tags[tag]; + } + + return !value.isEmpty() && item->DisplayText().contains(value, Qt::CaseInsensitive); + +} diff --git a/src/collection/collectionfilter.h b/src/collection/collectionfilter.h new file mode 100644 index 00000000..f02c2784 --- /dev/null +++ b/src/collection/collectionfilter.h @@ -0,0 +1,50 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef COLLECTIONFILTER_H +#define COLLECTIONFILTER_H + +#include "config.h" + +#include +#include +#include +#include + +#include "collectionmodel.h" + +class CollectionItem; + +class CollectionFilter : public QSortFilterProxyModel { + Q_OBJECT + + public: + explicit CollectionFilter(QObject *parent = nullptr); + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + private: + bool TagMatches(CollectionItem *item, const QMap &tags) const; + bool TagMatches(CollectionItem *item, const CollectionModel::GroupBy group_by, const QMap &tags) const; + bool ItemMatches(CollectionModel *model, CollectionItem *item, const QMap &tags, const QString &filter) const; + bool ChildrenMatches(CollectionModel *model, CollectionItem *item, const QMap &tags, const QString &filter) const; +}; + +#endif // COLLECTIONFILTER_H diff --git a/src/collection/collectionfilterwidget.cpp b/src/collection/collectionfilterwidget.cpp index 94a5f742..4dd51579 100644 --- a/src/collection/collectionfilterwidget.cpp +++ b/src/collection/collectionfilterwidget.cpp @@ -50,6 +50,8 @@ #include "core/settings.h" #include "collectionfilteroptions.h" #include "collectionmodel.h" +#include "collectionfilter.h" +#include "collectionquery.h" #include "savedgroupingmanager.h" #include "collectionfilterwidget.h" #include "groupbydialog.h" @@ -62,6 +64,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) : QWidget(parent), ui_(new Ui_CollectionFilterWidget), model_(nullptr), + filter_(nullptr), group_by_dialog_(new GroupByDialog(this)), groupings_manager_(nullptr), filter_age_menu_(nullptr), @@ -74,8 +77,8 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) ui_->setupUi(this); - QString available_fields = Song::kFtsColumns.join(QStringLiteral(", ")).replace(QRegularExpression(QStringLiteral("\\bfts")), QLatin1String("")); - available_fields += QStringLiteral(", ") + Song::kNumericalColumns.join(QStringLiteral(", ")); + QString available_fields = Song::kTextSearchColumns.join(QStringLiteral(", ")); + available_fields += QStringLiteral(", ") + Song::kNumericalSearchColumns.join(QStringLiteral(", ")); ui_->search_field->setToolTip( QStringLiteral("

") + @@ -156,7 +159,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; } -void CollectionFilterWidget::Init(CollectionModel *model) { +void CollectionFilterWidget::Init(CollectionModel *model, CollectionFilter *filter) { if (model_) { QObject::disconnect(model_, nullptr, this, nullptr); @@ -169,6 +172,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) { } model_ = model; + filter_ = filter; // Connect signals QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged); @@ -217,6 +221,10 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) { } +void CollectionFilterWidget::setFilter(CollectionFilter *filter) { + filter_ = filter; +} + void CollectionFilterWidget::ReloadSettings() { Settings s; @@ -518,9 +526,6 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) { void CollectionFilterWidget::FilterTextChanged(const QString &text) { - // Searching with one or two characters can be very expensive on the database even with FTS, - // so if there are a large number of songs in the database introduce a small delay before actually filtering the model, - // so if the user is typing the first few characters of something it will be quicker. const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000); if (delay) { @@ -535,9 +540,8 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) { void CollectionFilterWidget::FilterDelayTimeout() { - emit Filter(ui_->search_field->text()); if (filter_applies_to_model_) { - model_->SetFilterText(ui_->search_field->text()); + filter_->setFilterFixedString(ui_->search_field->text()); } } diff --git a/src/collection/collectionfilterwidget.h b/src/collection/collectionfilterwidget.h index fd07b84d..c89c5672 100644 --- a/src/collection/collectionfilterwidget.h +++ b/src/collection/collectionfilterwidget.h @@ -41,6 +41,7 @@ class QKeyEvent; class GroupByDialog; class SavedGroupingManager; +class CollectionFilter; class Ui_CollectionFilterWidget; class CollectionFilterWidget : public QWidget { @@ -58,7 +59,9 @@ class CollectionFilterWidget : public QWidget { AlwaysDelayed, }; - void Init(CollectionModel *model); + void Init(CollectionModel *model, CollectionFilter *filter); + + void setFilter(CollectionFilter *filter); static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent); @@ -94,7 +97,6 @@ class CollectionFilterWidget : public QWidget { void UpPressed(); void DownPressed(); void ReturnPressed(); - void Filter(const QString &text); protected: void keyReleaseEvent(QKeyEvent *e) override; @@ -115,6 +117,7 @@ class CollectionFilterWidget : public QWidget { private: Ui_CollectionFilterWidget *ui_; CollectionModel *model_; + CollectionFilter *filter_; GroupByDialog *group_by_dialog_; SavedGroupingManager *groupings_manager_; diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp index 43f90269..ad438a02 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -1,7 +1,5 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify @@ -52,6 +50,7 @@ #include #include #include +#include #include "core/scoped_ptr.h" #include "core/shared_ptr.h" @@ -64,11 +63,11 @@ #include "core/settings.h" #include "collectionfilteroptions.h" #include "collectionquery.h" -#include "collectionqueryoptions.h" #include "collectionbackend.h" #include "collectiondirectorymodel.h" #include "collectionitem.h" #include "collectionmodel.h" +#include "collectionmodelupdate.h" #include "playlist/playlistmanager.h" #include "playlist/songmimedata.h" #include "covermanager/albumcoverloaderoptions.h" @@ -77,7 +76,10 @@ #include "settings/collectionsettingspage.h" const int CollectionModel::kPrettyCoverSize = 32; -const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache"; +namespace { +constexpr char kPixmapDiskCacheDir[] = "pixmapcache"; +constexpr char kVariousArtists[] = "Various artists"; +} // namespace QNetworkDiskCache *CollectionModel::sIconCache = nullptr; @@ -98,9 +100,8 @@ CollectionModel::CollectionModel(SharedPtr backend, Applicati use_pretty_covers_(true), show_dividers_(true), use_disk_cache_(false), - use_lazy_loading_(true) { - - root_->lazy_loaded = true; + timer_reset_(new QTimer(this)), + timer_update_(new QTimer(this)) { group_by_[0] = GroupBy::AlbumArtist; group_by_[1] = GroupBy::AlbumDisc; @@ -122,19 +123,28 @@ CollectionModel::CollectionModel(SharedPtr backend, Applicati QObject::connect(app_, &Application::ClearPixmapDiskCache, this, &CollectionModel::ClearDiskCache); } - QObject::connect(&*backend_, &CollectionBackend::SongsDiscovered, this, &CollectionModel::SongsDiscovered); - QObject::connect(&*backend_, &CollectionBackend::SongsDeleted, this, &CollectionModel::SongsDeleted); - QObject::connect(&*backend_, &CollectionBackend::DatabaseReset, this, &CollectionModel::Reset); + QObject::connect(&*backend_, &CollectionBackend::SongsAdded, this, &CollectionModel::SongsAdded); + QObject::connect(&*backend_, &CollectionBackend::SongsDeleted, this, &CollectionModel::SongsRemoved); + QObject::connect(&*backend_, &CollectionBackend::SongsChanged, this, &CollectionModel::SongsChanged); + QObject::connect(&*backend_, &CollectionBackend::DatabaseReset, this, &CollectionModel::ScheduleReset); QObject::connect(&*backend_, &CollectionBackend::TotalSongCountUpdated, this, &CollectionModel::TotalSongCountUpdatedSlot); QObject::connect(&*backend_, &CollectionBackend::TotalArtistCountUpdated, this, &CollectionModel::TotalArtistCountUpdatedSlot); QObject::connect(&*backend_, &CollectionBackend::TotalAlbumCountUpdated, this, &CollectionModel::TotalAlbumCountUpdatedSlot); - QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &CollectionModel::SongsSlightlyChanged); - QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &CollectionModel::SongsSlightlyChanged); + QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &CollectionModel::SongsChanged); + QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &CollectionModel::SongsChanged); backend_->UpdateTotalSongCountAsync(); backend_->UpdateTotalArtistCountAsync(); backend_->UpdateTotalAlbumCountAsync(); + timer_reset_->setSingleShot(true); + timer_reset_->setInterval(300); + QObject::connect(timer_reset_, &QTimer::timeout, this, &CollectionModel::Reload); + + timer_update_->setSingleShot(false); + timer_update_->setInterval(20); + QObject::connect(timer_update_, &QTimer::timeout, this, &CollectionModel::ProcessUpdate); + ReloadSettings(); } @@ -149,29 +159,67 @@ CollectionModel::~CollectionModel() { } -void CollectionModel::set_pretty_covers(const bool use_pretty_covers) { +void CollectionModel::Init() { + ScheduleReset(); +} - if (use_pretty_covers != use_pretty_covers_) { - use_pretty_covers_ = use_pretty_covers; - Reset(); +void CollectionModel::Reset() { + ScheduleReset(); +} + +void CollectionModel::Clear() { + + if (root_) { + delete root_; + root_ = nullptr; } + song_nodes_.clear(); + container_nodes_[0].clear(); + container_nodes_[1].clear(); + container_nodes_[2].clear(); + divider_nodes_.clear(); + pending_art_.clear(); + pending_cache_keys_.clear(); } -void CollectionModel::set_show_dividers(const bool show_dividers) { +void CollectionModel::BeginReset() { - if (show_dividers != show_dividers_) { - show_dividers_ = show_dividers; - Reset(); - } + beginResetModel(); + Clear(); + root_ = new CollectionItem(this); } -void CollectionModel::set_sort_skips_articles(const bool sort_skips_articles) { +void CollectionModel::EndReset() { - if (sort_skips_articles != sort_skips_articles_) { - sort_skips_articles_ = sort_skips_articles; - Reset(); + endResetModel(); + +} + +void CollectionModel::Reload() { + + BeginReset(); + + // Show a loading indicator in the model. + CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_); + loading->display_text = tr("Loading..."); + + // Show a loading indicator in the status bar too. + if (app_) { + init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); + } + + EndReset(); + + StartLoadSongsFromSql(); + +} + +void CollectionModel::ScheduleReset() { + + if (!timer_reset_->isActive()) { + timer_reset_->start(); } } @@ -200,37 +248,112 @@ void CollectionModel::ReloadSettings() { } -void CollectionModel::Init(const bool async) { +void CollectionModel::set_pretty_covers(const bool use_pretty_covers) { - if (!root_) return; - - if (async) { - // Show a loading indicator in the model. - CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_); - loading->display_text = tr("Loading..."); - loading->lazy_loaded = true; - beginResetModel(); - endResetModel(); - - // Show a loading indicator in the status bar too. - if (app_) { - init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); - } - - ResetAsync(); - } - else { - Reset(); + if (use_pretty_covers != use_pretty_covers_) { + use_pretty_covers_ = use_pretty_covers; + ScheduleReset(); } } -void CollectionModel::SongsDiscovered(const SongList &songs) { +void CollectionModel::set_show_dividers(const bool show_dividers) { - if (!root_) return; + if (show_dividers != show_dividers_) { + show_dividers_ = show_dividers; + ScheduleReset(); + } + +} + +void CollectionModel::set_sort_skips_articles(const bool sort_skips_articles) { + + if (sort_skips_articles != sort_skips_articles_) { + sort_skips_articles_ = sort_skips_articles; + ScheduleReset(); + } + +} + +void CollectionModel::SongsAdded(const SongList &songs) { + + ScheduleUpdate(CollectionModelUpdate::Type::Add, songs); + +} + +void CollectionModel::SongsRemoved(const SongList &songs) { + + ScheduleUpdate(CollectionModelUpdate::Type::Remove, songs); + +} + +void CollectionModel::SongsChanged(const SongList &songs) { + + ScheduleUpdate(CollectionModelUpdate::Type::ReAddOrUpdate, songs); + +} + +void CollectionModel::SongsUpdated(const SongList &songs) { + + ScheduleUpdate(CollectionModelUpdate::Type::Update, songs); + +} + +void CollectionModel::ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs) { + + for (qint64 i = 0; i < songs.count(); i += 400LL) { + const qint64 number = std::min(songs.count() - i, 400LL); + const SongList songs_to_queue = songs.mid(i, number); + updates_.enqueue(CollectionModelUpdate(type, songs_to_queue)); + } + + if (!timer_update_->isActive()) { + timer_update_->start(); + } + +} + +void CollectionModel::ProcessUpdate() { + + if (updates_.isEmpty()) { + timer_update_->stop(); + return; + } + + const CollectionModelUpdate update = updates_.dequeue(); + + if (updates_.isEmpty()) { + timer_update_->stop(); + } + + switch (update.type) { + case CollectionModelUpdate::Type::Add: + AddSongs(update.songs); + break; + case CollectionModelUpdate::Type::Remove: + RemoveSongs(update.songs); + break; + case CollectionModelUpdate::Type::ReAddOrUpdate: + ReAddOrUpdate(update.songs); + break; + case CollectionModelUpdate::Type::Update: + UpdateSongs(update.songs); + break; + } + +} + +void CollectionModel::AddSongs(const SongList &songs) { for (const Song &song : songs) { + if (songs_.contains(song.id())) { + songs_[song.id()] = song; + } + else { + songs_.insert(song.id(), song); + } + // Sanity check to make sure we don't add songs that are outside the user's filter if (!filter_options_.Matches(song)) continue; @@ -274,24 +397,64 @@ void CollectionModel::SongsDiscovered(const SongList &songs) { } - // If we just created the damn thing then we don't need to continue into it any further because it'll get lazy-loaded properly later. - if (!container->lazy_loaded && use_lazy_loading_) break; } - if (!container->lazy_loaded && use_lazy_loading_) continue; - // We've gone all the way down to the deepest level and everything was already lazy loaded, so now we have to create the song in the container. song_nodes_.insert(song.id(), ItemFromSong(GroupBy::None, separate_albums_by_grouping_, true, false, container, song, -1)); } } -void CollectionModel::SongsSlightlyChanged(const SongList &songs) { +void CollectionModel::ReAddOrUpdate(const SongList &songs) { + + SongList songs_added; + SongList songs_removed; + SongList songs_updated; - // This is called if there was a minor change to the songs that will not normally require the collection to be restructured. - // We can just update our internal cache of Song objects without worrying about resetting the model. for (const Song &song : songs) { - if (song_nodes_.contains(song.id())) { - song_nodes_[song.id()]->metadata = song; + if (!song_nodes_.contains(song.id())) { + qLog(Error) << "Song does not exist in model" << song.effective_albumartist() << song.effective_album() << song.title(); + continue; + } + const Song &metadata = song_nodes_[song.id()]->metadata; + bool container_key_changed = false; + for (int i = 0; i < 3; ++i) { + if (ContainerKey(group_by_[i], separate_albums_by_grouping_, song) != ContainerKey(group_by_[i], separate_albums_by_grouping_, metadata)) { + container_key_changed = true; + } + } + if (container_key_changed) { + songs_removed << metadata; + songs_added << song; + } + else { + songs_updated << song; + } + } + + SongsUpdated(songs_updated); + SongsRemoved(songs_removed); + SongsAdded(songs_added); + +} + +void CollectionModel::UpdateSongs(const SongList &songs) { + + for (const Song &song : songs) { + if (songs_.contains(song.id())) { + songs_[song.id()] = song; + } + if (!song_nodes_.contains(song.id())) { + qLog(Error) << "Song does not exist in model" << song.effective_albumartist() << song.effective_album() << song.title(); + continue; + } + CollectionItem *item = song_nodes_[song.id()]; + const Song &metadata = item->metadata; + const bool data_changed = !IsCollectionMetadataEqual(song, metadata); + item->metadata = song; + if (data_changed) { + const QModelIndex idx = ItemToIndex(item); + if (!idx.isValid()) return; + emit dataChanged(idx, idx); } } @@ -306,8 +469,8 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(const bool signal, parent->compilation_artist_node_ = new CollectionItem(CollectionItem::Type_Container, parent); parent->compilation_artist_node_->compilation_artist_node_ = nullptr; if (parent != root_ && !parent->key.isEmpty()) parent->compilation_artist_node_->key.append(parent->key); - parent->compilation_artist_node_->key.append(tr("Various artists")); - parent->compilation_artist_node_->display_text = tr("Various artists"); + parent->compilation_artist_node_->key.append(tr(kVariousArtists)); + parent->compilation_artist_node_->display_text = tr(kVariousArtists); parent->compilation_artist_node_->sort_text = QStringLiteral(" various"); parent->compilation_artist_node_->container_level = parent->container_level + 1; @@ -431,7 +594,7 @@ QString CollectionModel::DividerKey(const GroupBy group_by, CollectionItem *item case GroupBy::Disc: case GroupBy::Genre: case GroupBy::Format: - case GroupBy::FileType:{ + case GroupBy::FileType: { QChar c = item->sort_text[0]; if (c.isDigit()) return QStringLiteral("0"); if (c == QLatin1Char(' ')) return QString(); @@ -488,7 +651,7 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin case GroupBy::Genre: case GroupBy::FileType: case GroupBy::Format: - if (key == QLatin1String("0")) return QStringLiteral("0-9"); + if (key == QStringLiteral("0")) return QStringLiteral("0-9"); return key.toUpper(); case GroupBy::YearAlbum: @@ -519,19 +682,23 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin case GroupBy::GroupByCount: break; } + qLog(Error) << "Unknown GroupBy" << group_by << "for divider key" << key; + return QString(); } -void CollectionModel::SongsDeleted(const SongList &songs) { - - if (!root_) return; +void CollectionModel::RemoveSongs(const SongList &songs) { // Delete the actual song nodes first, keeping track of each parent so we might check to see if they're empty later. QSet parents; for (const Song &song : songs) { + if (songs_.contains(song.id())) { + songs_[song.id()] = song; + } + if (song_nodes_.contains(song.id())) { CollectionItem *node = song_nodes_[song.id()]; @@ -543,13 +710,6 @@ void CollectionModel::SongsDeleted(const SongList &songs) { endRemoveRows(); } - else { - // If we get here it means some of the songs we want to delete haven't been lazy-loaded yet. - // This is bad, because it would mean that to clean up empty parents we would need to lazy-load them all individually to see if they're empty. - // This can take a very long time, so better to just reset the model and be done with it. - Reset(); - return; - } } // Now delete empty parents @@ -589,7 +749,7 @@ void CollectionModel::SongsDeleted(const SongList &songs) { // Remove from pending art loading for (QMap::iterator it = pending_art_.begin(); it != pending_art_.end();) { if (it.value().first == node) { - it = pending_art_.erase(it); // clazy:exclude=strict-iterators + it = pending_art_.erase(it); } else { ++it; @@ -807,10 +967,6 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const return item->metadata.artist(); case Role_Editable:{ - if (!item->lazy_loaded) { - const_cast(this)->LazyPopulate(const_cast(item), true); - } - if (item->type == CollectionItem::Type_Container) { // If we have even one non editable item as a child, we ourselves are not available for edit if (item->children.isEmpty()) { @@ -841,165 +997,59 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const } -bool CollectionModel::HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options) { +void CollectionModel::StartLoadSongsFromSql() { - CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), filter_options); - q.SetColumnSpec(query_options.column_spec()); - for (const CollectionQueryOptions::Where &where_clauses : query_options.where_clauses()) { - q.AddWhere(where_clauses.column, where_clauses.value, where_clauses.op); - } - q.AddCompilationRequirement(true); - q.SetLimit(1); + songs_.clear(); - if (!q.Exec()) { - backend_->ReportErrors(q); - return false; - } - - return q.Next(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QFuture future = QtConcurrent::run(&CollectionModel::LoadSongsFromSql, this, filter_options_); +#else + QFuture future = QtConcurrent::run(this, &CollectionModel::LoadSongsFromSql, filter_options_); +#endif + QFutureWatcher *watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcher::finished, this, &CollectionModel::LoadSongsFromSqlFinished); + watcher->setFuture(future); } -CollectionQueryOptions CollectionModel::PrepareQuery(CollectionItem *parent) { +SongList CollectionModel::LoadSongsFromSql(const CollectionFilterOptions &filter_options) { - // Information about what we want the children to be - const int child_level = parent == root_ ? 0 : parent->container_level + 1; - const GroupBy child_group_by = child_level >= 3 ? GroupBy::None : group_by_[child_level]; + SongList songs; - CollectionQueryOptions query_options; - - // Initialize the query. child_group_by says what type of thing we want (artists, songs, etc.) - SetQueryColumnSpec(child_group_by, separate_albums_by_grouping_, &query_options); - - // Walk up through the item's parents adding filters as necessary - for (CollectionItem *p = parent; p && p->type == CollectionItem::Type_Container; p = p->parent) { - AddQueryWhere(group_by_[p->container_level], separate_albums_by_grouping_, p, &query_options); - } - - // Artists GroupBy is special - we don't want compilation albums appearing - if (show_various_artists_ && IsArtistGroupBy(child_group_by)) { - query_options.set_query_have_compilations(true); - } - - return query_options; - -} - -CollectionModel::QueryResult CollectionModel::RunQuery(const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options) { - - QMutexLocker l(backend_->db()->Mutex()); - - QueryResult result; { - + QMutexLocker l(backend_->db()->Mutex()); QSqlDatabase db(backend_->db()->Connect()); - // Add the special Various artists node - if (query_options.query_have_compilations() && HasCompilations(db, filter_options, query_options)) { - result.create_va = true; - } - - CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), filter_options); - q.SetColumnSpec(query_options.column_spec()); - for (const CollectionQueryOptions::Where &where_clauses : query_options.where_clauses()) { - q.AddWhere(where_clauses.column, where_clauses.value, where_clauses.op); - } - - if (result.create_va) { - q.AddCompilationRequirement(false); - } - else if (query_options.compilation_requirement() != CollectionQueryOptions::CompilationRequirement::None) { - q.AddCompilationRequirement(query_options.compilation_requirement() == CollectionQueryOptions::CompilationRequirement::On); - } - + CollectionQuery q(db, backend_->songs_table(), filter_options); + q.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec); if (q.Exec()) { while (q.Next()) { - result.rows << SqlRow(q); + Song song; + song.InitFromQuery(q, true); + songs << song; } } else { backend_->ReportErrors(q); } - } if (QThread::currentThread() != thread() && QThread::currentThread() != backend_->thread()) { backend_->db()->Close(); } - return result; + return songs; } -void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::QueryResult &result, const bool signal) { +void CollectionModel::LoadSongsFromSqlFinished() { - if (!root_) return; - - // Information about what we want the children to be - int child_level = parent == root_ ? 0 : parent->container_level + 1; - GroupBy child_group_by = child_level >= 3 ? GroupBy::None : group_by_[child_level]; - - if (result.create_va && parent->compilation_artist_node_ == nullptr) { - CreateCompilationArtistNode(signal, parent); - } - - // Step through the results - for (const SqlRow &row : result.rows) { - // Create the item - it will get inserted into the model here - CollectionItem *item = ItemFromQuery(child_group_by, separate_albums_by_grouping_, signal, child_level == 0, parent, row, child_level); - - // Save a pointer to it for later - if (child_group_by == GroupBy::None) { - song_nodes_.insert(item->metadata.id(), item); - } - else { - container_nodes_[child_level].insert(item->key, item); - } - } - -} - -void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) { - - if (!root_) return; - - if (parent->lazy_loaded) return; - parent->lazy_loaded = true; - - CollectionQueryOptions query_options = PrepareQuery(parent); - QueryResult result = RunQuery(filter_options_, query_options); - PostQuery(parent, result, signal); - -} - -void CollectionModel::ResetAsync() { - - if (!root_) return; - - CollectionQueryOptions query_options = PrepareQuery(root_); - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QFuture future = QtConcurrent::run(&CollectionModel::RunQuery, this, filter_options_, query_options); -#else - QFuture future = QtConcurrent::run(this, &CollectionModel::RunQuery, filter_options_, query_options); -#endif - QFutureWatcher *watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcher::finished, this, &CollectionModel::ResetAsyncQueryFinished); - watcher->setFuture(future); - -} - -void CollectionModel::ResetAsyncQueryFinished() { - - if (!root_) return; - - QFutureWatcher *watcher = static_cast*>(sender()); - const struct QueryResult result = watcher->result(); + QFutureWatcher *watcher = static_cast*>(sender()); + const SongList songs = watcher->result(); watcher->deleteLater(); BeginReset(); - root_->lazy_loaded = true; - - PostQuery(root_, result, false); + SongsAdded(songs); + EndReset(); if (init_task_id_ != -1) { if (app_) { @@ -1008,245 +1058,6 @@ void CollectionModel::ResetAsyncQueryFinished() { init_task_id_ = -1; } - endResetModel(); - -} - -void CollectionModel::Clear() { - - if (root_) { - delete root_; - root_ = nullptr; - } - song_nodes_.clear(); - container_nodes_[0].clear(); - container_nodes_[1].clear(); - container_nodes_[2].clear(); - divider_nodes_.clear(); - pending_art_.clear(); - pending_cache_keys_.clear(); - -} - -void CollectionModel::BeginReset() { - - beginResetModel(); - Clear(); - - root_ = new CollectionItem(this); - root_->compilation_artist_node_ = nullptr; - root_->lazy_loaded = false; - -} - -void CollectionModel::Reset() { - - BeginReset(); - - // Populate top level - LazyPopulate(root_, false); - - endResetModel(); - -} - -void CollectionModel::SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options) { - - // Say what group_by of thing we want to get back from the database. - switch (group_by) { - case GroupBy::AlbumArtist: - query_options->set_column_spec(QStringLiteral("DISTINCT effective_albumartist")); - break; - case GroupBy::Artist: - query_options->set_column_spec(QStringLiteral("DISTINCT artist")); - break; - case GroupBy::Album:{ - QString query(QStringLiteral("DISTINCT album, album_id")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::AlbumDisc:{ - QString query(QStringLiteral("DISTINCT album, album_id, disc")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::YearAlbum:{ - QString query(QStringLiteral("DISTINCT year, album, album_id")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::YearAlbumDisc:{ - QString query(QStringLiteral("DISTINCT year, album, album_id, disc")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::OriginalYearAlbum:{ - QString query(QStringLiteral("DISTINCT year, originalyear, album, album_id")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::OriginalYearAlbumDisc:{ - QString query(QStringLiteral("DISTINCT year, originalyear, album, album_id, disc")); - if (separate_albums_by_grouping) query.append(QStringLiteral(", grouping")); - query_options->set_column_spec(query); - break; - } - case GroupBy::Disc: - query_options->set_column_spec(QStringLiteral("DISTINCT disc")); - break; - case GroupBy::Year: - query_options->set_column_spec(QStringLiteral("DISTINCT year")); - break; - case GroupBy::OriginalYear: - query_options->set_column_spec(QStringLiteral("DISTINCT effective_originalyear")); - break; - case GroupBy::Genre: - query_options->set_column_spec(QStringLiteral("DISTINCT genre")); - break; - case GroupBy::Composer: - query_options->set_column_spec(QStringLiteral("DISTINCT composer")); - break; - case GroupBy::Performer: - query_options->set_column_spec(QStringLiteral("DISTINCT performer")); - break; - case GroupBy::Grouping: - query_options->set_column_spec(QStringLiteral("DISTINCT grouping")); - break; - case GroupBy::FileType: - query_options->set_column_spec(QStringLiteral("DISTINCT filetype")); - break; - case GroupBy::Format: - query_options->set_column_spec(QStringLiteral("DISTINCT filetype, samplerate, bitdepth")); - break; - case GroupBy::Samplerate: - query_options->set_column_spec(QStringLiteral("DISTINCT samplerate")); - break; - case GroupBy::Bitdepth: - query_options->set_column_spec(QStringLiteral("DISTINCT bitdepth")); - break; - case GroupBy::Bitrate: - query_options->set_column_spec(QStringLiteral("DISTINCT bitrate")); - break; - case GroupBy::None: - case GroupBy::GroupByCount: - query_options->set_column_spec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec); - break; - } - -} - -void CollectionModel::AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options) { - - // Say how we want the query to be filtered. This is done once for each parent going up the tree. - - switch (group_by) { - case GroupBy::AlbumArtist: - if (IsCompilationArtistNode(item)) { - query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::On); - } - else { - // Don't duplicate compilations outside the Various artists node - query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off); - query_options->AddWhere(QStringLiteral("effective_albumartist"), item->metadata.effective_albumartist()); - } - break; - case GroupBy::Artist: - if (IsCompilationArtistNode(item)) { - query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::On); - } - else { - // Don't duplicate compilations outside the Various artists node - query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off); - query_options->AddWhere(QStringLiteral("artist"), item->metadata.artist()); - } - break; - case GroupBy::Album: - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::AlbumDisc: - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::YearAlbum: - query_options->AddWhere(QStringLiteral("year"), item->metadata.year()); - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::YearAlbumDisc: - query_options->AddWhere(QStringLiteral("year"), item->metadata.year()); - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::OriginalYearAlbum: - query_options->AddWhere(QStringLiteral("year"), item->metadata.year()); - query_options->AddWhere(QStringLiteral("originalyear"), item->metadata.originalyear()); - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::OriginalYearAlbumDisc: - query_options->AddWhere(QStringLiteral("year"), item->metadata.year()); - query_options->AddWhere(QStringLiteral("originalyear"), item->metadata.originalyear()); - query_options->AddWhere(QStringLiteral("album"), item->metadata.album()); - query_options->AddWhere(QStringLiteral("album_id"), item->metadata.album_id()); - query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc()); - if (separate_albums_by_grouping) query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::Disc: - query_options->AddWhere(QStringLiteral("disc"), item->metadata.disc()); - break; - case GroupBy::Year: - query_options->AddWhere(QStringLiteral("year"), item->metadata.year()); - break; - case GroupBy::OriginalYear: - query_options->AddWhere(QStringLiteral("effective_originalyear"), item->metadata.effective_originalyear()); - break; - case GroupBy::Genre: - query_options->AddWhere(QStringLiteral("genre"), item->metadata.genre()); - break; - case GroupBy::Composer: - query_options->AddWhere(QStringLiteral("composer"), item->metadata.composer()); - break; - case GroupBy::Performer: - query_options->AddWhere(QStringLiteral("performer"), item->metadata.performer()); - break; - case GroupBy::Grouping: - query_options->AddWhere(QStringLiteral("grouping"), item->metadata.grouping()); - break; - case GroupBy::FileType: - query_options->AddWhere(QStringLiteral("filetype"), static_cast(item->metadata.filetype())); - break; - case GroupBy::Format: - query_options->AddWhere(QStringLiteral("filetype"), static_cast(item->metadata.filetype())); - query_options->AddWhere(QStringLiteral("samplerate"), item->metadata.samplerate()); - query_options->AddWhere(QStringLiteral("bitdepth"), item->metadata.bitdepth()); - break; - case GroupBy::Samplerate: - query_options->AddWhere(QStringLiteral("samplerate"), item->metadata.samplerate()); - break; - case GroupBy::Bitdepth: - query_options->AddWhere(QStringLiteral("bitdepth"), item->metadata.bitdepth()); - break; - case GroupBy::Bitrate: - query_options->AddWhere(QStringLiteral("bitrate"), item->metadata.bitrate()); - break; - case GroupBy::None: - case GroupBy::GroupByCount: - qLog(Error) << "Unknown GroupBy" << group_by << "used in filter"; - break; - } } @@ -1265,205 +1076,6 @@ CollectionItem *CollectionModel::InitItem(const GroupBy group_by, const bool sig } -CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level) { - - CollectionItem *item = InitItem(group_by, signal, parent, container_level); - - if (parent != root_ && !parent->key.isEmpty()) { - item->key = parent->key + QLatin1Char('-'); - } - - switch (group_by) { - case GroupBy::AlbumArtist:{ - item->metadata.set_albumartist(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.albumartist()); - item->sort_text = SortTextForArtist(item->metadata.albumartist(), sort_skips_articles_); - break; - } - case GroupBy::Artist:{ - item->metadata.set_artist(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.artist()); - item->sort_text = SortTextForArtist(item->metadata.artist(), sort_skips_articles_); - break; - } - case GroupBy::Album:{ - item->metadata.set_album(row.value(0).toString()); - item->metadata.set_album_id(row.value(1).toString()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(2).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.album()); - item->sort_text = SortTextForArtist(item->metadata.album(), sort_skips_articles_); - break; - } - case GroupBy::AlbumDisc:{ - item->metadata.set_album(row.value(0).toString()); - item->metadata.set_album_id(row.value(1).toString()); - item->metadata.set_disc(row.value(2).toInt()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(3).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc()); - item->sort_text = item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); - break; - } - case GroupBy::YearAlbum:{ - item->metadata.set_year(row.value(0).toInt()); - item->metadata.set_album(row.value(1).toString()); - item->metadata.set_album_id(row.value(2).toString()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(3).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album()); - item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album(); - break; - } - case GroupBy::YearAlbumDisc:{ - item->metadata.set_year(row.value(0).toInt()); - item->metadata.set_album(row.value(1).toString()); - item->metadata.set_album_id(row.value(2).toString()); - item->metadata.set_disc(row.value(3).toInt()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(4).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc()); - item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); - break; - } - case GroupBy::OriginalYearAlbum:{ - item->metadata.set_year(row.value(0).toInt()); - item->metadata.set_originalyear(row.value(1).toInt()); - item->metadata.set_album(row.value(2).toString()); - item->metadata.set_album_id(row.value(3).toString()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(4).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album()); - item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album(); - break; - } - case GroupBy::OriginalYearAlbumDisc:{ - item->metadata.set_year(row.value(0).toInt()); - item->metadata.set_originalyear(row.value(1).toInt()); - item->metadata.set_album(row.value(2).toString()); - item->metadata.set_album_id(row.value(3).toString()); - item->metadata.set_disc(row.value(4).toInt()); - if (separate_albums_by_grouping) item->metadata.set_grouping(row.value(5).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc()); - item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc())); - break; - } - case GroupBy::Disc:{ - item->metadata.set_disc(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int disc = std::max(0, row.value(0).toInt()); - item->display_text = PrettyDisc(disc); - item->sort_text = SortTextForNumber(disc); - break; - } - case GroupBy::Year:{ - item->metadata.set_year(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int year = std::max(0, item->metadata.year()); - item->display_text = QString::number(year); - item->sort_text = SortTextForNumber(year) + QLatin1Char(' '); - break; - } - case GroupBy::OriginalYear:{ - item->metadata.set_originalyear(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int year = std::max(0, item->metadata.originalyear()); - item->display_text = QString::number(year); - item->sort_text = SortTextForNumber(year) + QLatin1Char(' '); - break; - } - case GroupBy::Genre:{ - item->metadata.set_genre(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.genre()); - item->sort_text = SortTextForArtist(item->metadata.genre(), sort_skips_articles_); - break; - } - case GroupBy::Composer:{ - item->metadata.set_composer(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.composer()); - item->sort_text = SortTextForArtist(item->metadata.composer(), sort_skips_articles_); - break; - } - case GroupBy::Performer:{ - item->metadata.set_performer(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.performer()); - item->sort_text = SortTextForArtist(item->metadata.performer(), sort_skips_articles_); - break; - } - case GroupBy::Grouping:{ - item->metadata.set_grouping(row.value(0).toString()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = TextOrUnknown(item->metadata.grouping()); - item->sort_text = SortTextForArtist(item->metadata.grouping(), sort_skips_articles_); - break; - } - case GroupBy::FileType:{ - item->metadata.set_filetype(static_cast(row.value(0).toInt())); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - item->display_text = item->metadata.TextForFiletype(); - item->sort_text = item->metadata.TextForFiletype(); - break; - } - case GroupBy::Format:{ - item->metadata.set_filetype(static_cast(row.value(0).toInt())); - item->metadata.set_samplerate(row.value(1).toInt()); - item->metadata.set_bitdepth(row.value(2).toInt()); - QString key = ContainerKey(group_by, separate_albums_by_grouping, item->metadata); - item->key.append(key); - item->display_text = key; - item->sort_text = key; - break; - } - case GroupBy::Samplerate:{ - item->metadata.set_samplerate(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int samplerate = std::max(0, item->metadata.samplerate()); - item->display_text = QString::number(samplerate); - item->sort_text = SortTextForNumber(samplerate) + QLatin1Char(' '); - break; - } - case GroupBy::Bitdepth:{ - item->metadata.set_bitdepth(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int bitdepth = std::max(0, item->metadata.bitdepth()); - item->display_text = QString::number(bitdepth); - item->sort_text = SortTextForNumber(bitdepth) + QLatin1Char(' '); - break; - } - case GroupBy::Bitrate:{ - item->metadata.set_bitrate(row.value(0).toInt()); - item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata)); - const int bitrate = std::max(0, item->metadata.bitrate()); - item->display_text = QString::number(bitrate); - item->sort_text = SortTextForNumber(bitrate) + QLatin1Char(' '); - break; - } - case GroupBy::None: - case GroupBy::GroupByCount: - item->metadata.InitFromQuery(row, true); - item->key.append(TextOrUnknown(item->metadata.title())); - item->display_text = item->metadata.TitleWithCompilationArtist(); - if (item->container_level == 1 && !IsAlbumGroupBy(group_by_[0])) { - item->sort_text = SortText(item->metadata.title()); - } - else { - item->sort_text = SortTextForSong(item->metadata); - } - break; - } - - FinishItem(group_by, signal, create_divider, parent, item); - - return item; - -} - CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level) { CollectionItem *item = InitItem(group_by, signal, parent, container_level); @@ -1659,7 +1271,6 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool } FinishItem(group_by, signal, create_divider, parent, item); - if (s.url().scheme() == QStringLiteral("cdda")) item->lazy_loaded = true; return item; @@ -1667,10 +1278,6 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item) { - if (!root_) return; - - if (group_by == GroupBy::None) item->lazy_loaded = true; - if (signal) { endInsertRows(); } @@ -1691,7 +1298,6 @@ void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, cons divider->key = divider_key; divider->display_text = DividerDisplayText(group_by, divider_key); divider->sort_text = divider_key + QStringLiteral(" "); - divider->lazy_loaded = true; divider_nodes_[divider_key] = divider; @@ -1861,8 +1467,8 @@ bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem qint64 CollectionModel::MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default) { - qint64 size = s->value(QLatin1String(size_id), cache_size_default).toInt(); - int unit = s->value(QLatin1String(size_unit_id), static_cast(CollectionSettingsPage::CacheSizeUnit::MB)).toInt() + 1; + qint64 size = s->value(size_id, cache_size_default).toInt(); + int unit = s->value(size_unit_id, static_cast(CollectionSettingsPage::CacheSizeUnit::MB)).toInt() + 1; do { size *= 1024; @@ -1876,9 +1482,7 @@ qint64 CollectionModel::MaximumCacheSize(Settings *s, const char *size_id, const void CollectionModel::GetChildSongs(CollectionItem *item, QList *urls, SongList *songs, QSet *song_ids) const { switch (item->type) { - case CollectionItem::Type_Container:{ - const_cast(this)->LazyPopulate(item); - + case CollectionItem::Type_Container: { QList children = item->children; std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, std::placeholders::_1, std::placeholders::_2)); @@ -1919,27 +1523,17 @@ SongList CollectionModel::GetChildSongs(const QModelIndex &idx) const { return GetChildSongs(QModelIndexList() << idx); } -void CollectionModel::SetFilterMode(CollectionFilterOptions::FilterMode filter_mode) { +void CollectionModel::SetFilterMode(const CollectionFilterOptions::FilterMode filter_mode) { + filter_options_.set_filter_mode(filter_mode); - ResetAsync(); + ScheduleReset(); + } void CollectionModel::SetFilterAge(const int filter_age) { + filter_options_.set_max_age(filter_age); - ResetAsync(); -} - -void CollectionModel::SetFilterText(const QString &filter_text) { - filter_options_.set_filter_text(filter_text); - ResetAsync(); -} - -bool CollectionModel::canFetchMore(const QModelIndex &parent) const { - - if (!parent.isValid()) return false; - - CollectionItem *item = IndexToItem(parent); - return !item->lazy_loaded; + ScheduleReset(); } @@ -1950,7 +1544,8 @@ void CollectionModel::SetGroupBy(const Grouping g, const std::optional sep separate_albums_by_grouping_ = separate_albums_by_grouping.value(); } - ResetAsync(); + ScheduleReset(); + emit GroupingChanged(g, separate_albums_by_grouping_); } @@ -1982,7 +1577,6 @@ CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int i) { } - void CollectionModel::TotalSongCountUpdatedSlot(const int count) { total_song_count_ = count; @@ -2013,13 +1607,33 @@ void CollectionModel::ExpandAll(CollectionItem *item) const { if (!root_) return; if (!item) item = root_; - const_cast(this)->LazyPopulate(item, false); + for (CollectionItem *child : item->children) { ExpandAll(child); } } +bool CollectionModel::IsCollectionMetadataEqual(const Song &song1, const Song &song2) { + + return song1.title() == song2.title() && + song1.album() == song2.album() && + song1.artist() == song2.artist() && + song1.albumartist() == song2.albumartist() && + song1.track() == song2.track() && + song1.disc() == song2.disc() && + song1.year() == song2.year() && + song1.originalyear() == song2.originalyear() && + song1.genre() == song2.genre() && + song1.compilation() == song2.compilation() && + song1.composer() == song2.composer() && + song1.performer() == song2.performer() && + song1.grouping() == song2.grouping() && + song1.bitrate() == song2.bitrate() && + song1.samplerate() == song2.samplerate() && + song1.bitdepth() == song2.bitdepth(); +} + QDataStream &operator<<(QDataStream &s, const CollectionModel::Grouping g) { s << static_cast(g.first) << static_cast(g.second) << static_cast(g.third); return s; diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h index e1d4066d..d31196cf 100644 --- a/src/collection/collectionmodel.h +++ b/src/collection/collectionmodel.h @@ -1,7 +1,5 @@ /* * Strawberry Music Player - * This file was part of Clementine. - * Copyright 2010, David Sansome * Copyright 2018-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify @@ -44,6 +42,7 @@ #include #include #include +#include #include "core/shared_ptr.h" #include "core/simpletreemodel.h" @@ -51,10 +50,11 @@ #include "core/sqlrow.h" #include "covermanager/albumcoverloaderoptions.h" #include "covermanager/albumcoverloaderresult.h" +#include "collectionmodelupdate.h" #include "collectionfilteroptions.h" -#include "collectionqueryoptions.h" #include "collectionitem.h" +class QTimer; class Settings; class Application; @@ -69,7 +69,6 @@ class CollectionModel : public SimpleTreeModel { ~CollectionModel() override; static const int kPrettyCoverSize; - static const char *kPixmapDiskCacheDir; enum Role { Role_Type = Qt::UserRole + 1, @@ -126,10 +125,8 @@ class CollectionModel : public SimpleTreeModel { }; struct QueryResult { - QueryResult() : create_va(false) {} - - SqlRowList rows; - bool create_va; + QueryResult() {} + SongList songs; }; SharedPtr backend() const { return backend_; } @@ -153,7 +150,6 @@ class CollectionModel : public SimpleTreeModel { Qt::ItemFlags flags(const QModelIndex &idx) const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; - bool canFetchMore(const QModelIndex &parent) const override; // Whether or not to use album cover art, if it exists, in the collection view void set_pretty_covers(const bool use_pretty_covers); @@ -165,6 +161,9 @@ class CollectionModel : public SimpleTreeModel { // Whether to skip articles such as “The” when sorting artist names void set_sort_skips_articles(const bool sort_skips_articles); + void Init(); + void Reset(); + // Reload settings. void ReloadSettings(); @@ -188,8 +187,6 @@ class CollectionModel : public SimpleTreeModel { } static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy::Album || group_by == GroupBy::YearAlbum || group_by == GroupBy::AlbumDisc || group_by == GroupBy::YearAlbumDisc || group_by == GroupBy::OriginalYearAlbum || group_by == GroupBy::OriginalYearAlbumDisc; } - void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; } - QMap container_nodes(const int i) { return container_nodes_[i]; } QList song_nodes() const { return song_nodes_.values(); } int divider_nodes_count() const { return divider_nodes_.count(); } @@ -208,63 +205,45 @@ class CollectionModel : public SimpleTreeModel { void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping); public slots: - void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode); + void SetFilterMode(const CollectionFilterOptions::FilterMode filter_mode); void SetFilterAge(const int filter_age); - void SetFilterText(const QString &filter_text); - void Init(const bool async = true); - void Reset(); - void ResetAsync(); - - void SongsDiscovered(const SongList &songs); - - protected: - void LazyPopulate(CollectionItem *item) override { LazyPopulate(item, true); } - void LazyPopulate(CollectionItem *parent, const bool signal); + void SongsAdded(const SongList &songs); + void SongsRemoved(const SongList &songs); + void SongsChanged(const SongList &songs); + void SongsUpdated(const SongList &songs); private slots: + void ScheduleReset(); + void Reload(); + // From CollectionBackend - void SongsDeleted(const SongList &songs); - void SongsSlightlyChanged(const SongList &songs); void TotalSongCountUpdatedSlot(const int count); void TotalArtistCountUpdatedSlot(const int count); void TotalAlbumCountUpdatedSlot(const int count); static void ClearDiskCache(); - // Called after ResetAsync - void ResetAsyncQueryFinished(); + void LoadSongsFromSqlFinished(); void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); - private: - // Provides some optimizations for loading the list of items in the root. - // This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread. - CollectionQueryOptions PrepareQuery(CollectionItem *parent); - QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions()); - void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal); + void ProcessUpdate(); - bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options); + private: + void StartLoadSongsFromSql(); + SongList LoadSongsFromSql(const CollectionFilterOptions &filter_options = CollectionFilterOptions()); void Clear(); void BeginReset(); + void EndReset(); - // Functions for working with queries and creating items. - // When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items. - // Filters are added for each parent item, restricting the songs returned to a particular album or artist for example. - static void SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options); - static void AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options); - - // Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend. - CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level); + CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level); CollectionItem *ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level); + void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item); // The "Various Artists" node is an annoying special case. CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent); - // Helpers for ItemFromQuery and ItemFromSong - CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level); - void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item); - static QString DividerKey(const GroupBy group_by, CollectionItem *item); static QString DividerDisplayText(const GroupBy group_by, const QString &key); @@ -277,6 +256,15 @@ class CollectionModel : public SimpleTreeModel { bool CompareItems(const CollectionItem *a, const CollectionItem *b) const; static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default); + void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs); + + void AddSongs(const SongList &songs); + void RemoveSongs(const SongList &songs); + void UpdateSongs(const SongList &songs); + void ReAddOrUpdate(const SongList &songs); + + static bool IsCollectionMetadataEqual(const Song &song1, const Song &song2); + private: SharedPtr backend_; Application *app_; @@ -293,6 +281,7 @@ class CollectionModel : public SimpleTreeModel { bool separate_albums_by_grouping_; // Keyed on database ID + QMap songs_; QMap song_nodes_; // Keyed on whatever the key is for that level - artist, album, year, etc. @@ -313,13 +302,17 @@ class CollectionModel : public SimpleTreeModel { bool use_pretty_covers_; bool show_dividers_; bool use_disk_cache_; - bool use_lazy_loading_; AlbumCoverLoaderOptions::Types cover_types_; using ItemAndCacheKey = QPair; QMap pending_art_; QSet pending_cache_keys_; + + QTimer *timer_reset_; + QTimer *timer_update_; + + QQueue updates_; }; Q_DECLARE_METATYPE(CollectionModel::Grouping) diff --git a/src/collection/collectionmodelupdate.cpp b/src/collection/collectionmodelupdate.cpp new file mode 100644 index 00000000..0a8d60ac --- /dev/null +++ b/src/collection/collectionmodelupdate.cpp @@ -0,0 +1,23 @@ +/* + * Strawberry Music Player + * Copyright 2023, Jonas Kvinge + * + * 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 . + * + */ + +#include "collectionmodelupdate.h" + +CollectionModelUpdate::CollectionModelUpdate(const Type &_type, const SongList &_songs) + : type(_type), songs(_songs) {} diff --git a/src/collection/collectionmodelupdate.h b/src/collection/collectionmodelupdate.h new file mode 100644 index 00000000..01071286 --- /dev/null +++ b/src/collection/collectionmodelupdate.h @@ -0,0 +1,38 @@ +/* + * Strawberry Music Player + * Copyright 2023, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef COLLECTIONMODELUPDATE_H +#define COLLECTIONMODELUPDATE_H + +#include "core/song.h" + +class CollectionModelUpdate { + public: + enum class Type { + Add, + Remove, + ReAddOrUpdate, + Update, + }; + explicit CollectionModelUpdate(const Type &_type, const SongList &_songs); + Type type; + SongList songs; +}; + +#endif // COLLECTIONMODELUPDATE_H diff --git a/src/collection/collectionquery.cpp b/src/collection/collectionquery.cpp index bee96b8b..5a1220fa 100644 --- a/src/collection/collectionquery.cpp +++ b/src/collection/collectionquery.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2024, Jonas Kvinge + * Copyright 2018-2021, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ #include #include #include +#include #include "core/sqlquery.h" #include "core/song.h" @@ -38,78 +39,13 @@ #include "collectionfilteroptions.h" #include "utilities/searchparserutils.h" -CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options) +CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options) : SqlQuery(db), songs_table_(songs_table), - fts_table_(fts_table), include_unavailable_(false), - join_with_fts_(false), duplicates_only_(false), limit_(-1) { - if (!filter_options.filter_text().isEmpty()) { - // We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5: - // 1) Append * to all tokens. - // 2) Prefix "fts" to column names. - // 3) Remove colons which don't correspond to column names. - - // Split on whitespace - QString filter_text = filter_options.filter_text().replace(QRegularExpression(QStringLiteral(":\\s+")), QStringLiteral(":")); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts)); -#else - QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts)); -#endif - QString query; - for (QString token : tokens) { - token.remove(QLatin1Char('(')) - .remove(QLatin1Char(')')) - .remove(QLatin1Char('"')) - .replace(QLatin1Char('-'), QLatin1Char(' ')); - - if (token.contains(QLatin1Char(':'))) { - const QString columntoken = token.section(QLatin1Char(':'), 0, 0); - QString subtoken = token.section(QLatin1Char(':'), 1, -1).replace(QLatin1String(":"), QLatin1String(" ")).trimmed(); - if (subtoken.isEmpty()) continue; - if (Song::kFtsColumns.contains(QLatin1String("fts") + columntoken, Qt::CaseInsensitive)) { - if (!query.isEmpty()) query.append(QLatin1String(" ")); - query += QStringLiteral("fts") + columntoken + QStringLiteral(":\"") + subtoken + QStringLiteral("\"*"); - } - else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) { - QString comparator = RemoveSqlOperator(subtoken); - if (columntoken.compare(QLatin1String("rating"), Qt::CaseInsensitive) == 0) { - AddWhereRating(subtoken, comparator); - } - else if (columntoken.compare(QLatin1String("length"), Qt::CaseInsensitive) == 0) { - // Time is saved in nanoseconds, so add 9 0's - QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + QStringLiteral("000000000"); - AddWhere(columntoken, parsedTime, comparator); - } - else { - AddWhere(columntoken, subtoken, comparator); - } - } - // Not a valid filter, remove - else { - token = token.replace(QLatin1String(":"), QLatin1String(" ")).trimmed(); - if (!token.isEmpty()) { - if (!query.isEmpty()) query.append(QLatin1Char(' ')); - query += QLatin1Char('\"') + token + QStringLiteral("\"*"); - } - } - } - else { - if (!query.isEmpty()) query.append(QLatin1Char(' ')); - query += QLatin1Char('\"') + token + QStringLiteral("\"*"); - } - } - if (!query.isEmpty()) { - where_clauses_ << QStringLiteral("fts.%fts_table_noprefix MATCH ?"); - bound_values_ << query; - join_with_fts_ = true; - } - } - if (filter_options.max_age() != -1) { qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age(); @@ -117,12 +53,6 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta bound_values_ << cutoff; } - // TODO: Currently you cannot use any FilterMode other than All and FTS at the same time. - // Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time. - // The query takes about 20 seconds on my machine then. Why? - // Untagged mode could work with additional filtering but I'm disabling it just to be consistent - // this way filtering is available only in the All mode. - // Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes. duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates; if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) { @@ -152,7 +82,7 @@ QString CollectionQuery::RemoveSqlOperator(QString &token) { void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) { // Ignore 'literal' for IN - if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) { + if (op.compare(QStringLiteral("IN"), Qt::CaseInsensitive) == 0) { QStringList values = value.toStringList(); QStringList final_values; final_values.reserve(values.count()); @@ -161,7 +91,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con bound_values_ << single_value; } - where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QStringLiteral(","))); + where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QLatin1Char(','))); } else { // Do integers inline - sqlite seems to get confused when you pass integers to bound parameters @@ -231,8 +161,6 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) { void CollectionQuery::AddCompilationRequirement(const bool compilation) { // The unary + is added to prevent sqlite from using the index idx_comp_artist. - // When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance - where_clauses_ << QStringLiteral("+compilation_effective = %1").arg(compilation ? 1 : 0); } @@ -248,14 +176,7 @@ QString CollectionQuery::GetInnerQuery() const { bool CollectionQuery::Exec() { - QString sql; - - if (join_with_fts_) { - sql = QStringLiteral("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_); - } - else { - sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery()); - } + QString sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery()); QStringList where_clauses(where_clauses_); if (!include_unavailable_) { @@ -268,9 +189,7 @@ bool CollectionQuery::Exec() { if (limit_ != -1) sql += QStringLiteral(" LIMIT ") + QString::number(limit_); - sql.replace(QLatin1String("%songs_table"), songs_table_); - sql.replace(QLatin1String("%fts_table_noprefix"), fts_table_.section(QLatin1Char('.'), -1, -1)); - sql.replace(QLatin1String("%fts_table"), fts_table_); + sql.replace(QStringLiteral("%songs_table"), songs_table_); if (!QSqlQuery::prepare(sql)) return false; diff --git a/src/collection/collectionquery.h b/src/collection/collectionquery.h index ce705303..ea65f3c7 100644 --- a/src/collection/collectionquery.h +++ b/src/collection/collectionquery.h @@ -36,7 +36,7 @@ class CollectionQuery : public SqlQuery { public: - explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions()); + explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions()); QVariant Value(const int column) const; QVariant value(const int column) const { return Value(column); } @@ -51,7 +51,6 @@ class CollectionQuery : public SqlQuery { QStringList where_clauses() const { return where_clauses_; } QVariantList bound_values() const { return bound_values_; } bool include_unavailable() const { return include_unavailable_; } - bool join_with_fts() const { return join_with_fts_; } bool duplicates_only() const { return duplicates_only_; } int limit() const { return limit_; } @@ -83,7 +82,6 @@ class CollectionQuery : public SqlQuery { QSqlDatabase db_; QString songs_table_; - QString fts_table_; QString column_spec_; QString order_by_; @@ -91,7 +89,6 @@ class CollectionQuery : public SqlQuery { QVariantList bound_values_; bool include_unavailable_; - bool join_with_fts_; bool duplicates_only_; int limit_; }; diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index a46e7b17..da28b34a 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -131,10 +131,11 @@ #include "collection/collection.h" #include "collection/collectionbackend.h" #include "collection/collectiondirectorymodel.h" +#include "collection/collectionviewcontainer.h" #include "collection/collectionfilterwidget.h" +#include "collection/collectionfilter.h" #include "collection/collectionmodel.h" #include "collection/collectionview.h" -#include "collection/collectionviewcontainer.h" #include "playlist/playlist.h" #include "playlist/playlistbackend.h" #include "playlist/playlistcontainer.h" @@ -335,7 +336,7 @@ MainWindow::MainWindow(Application *app, SharedPtr tray_icon, OS playlist_add_to_another_(nullptr), playlistitem_actions_separator_(nullptr), playlist_rescan_songs_(nullptr), - collection_sort_model_(new QSortFilterProxyModel(this)), + collection_filter_(new CollectionFilter(this)), track_position_timer_(new QTimer(this)), track_slider_timer_(new QTimer(this)), keep_running_(false), @@ -418,11 +419,11 @@ MainWindow::MainWindow(Application *app, SharedPtr tray_icon, OS // Models qLog(Debug) << "Creating models"; - collection_sort_model_->setSourceModel(app_->collection()->model()); - collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - collection_sort_model_->setDynamicSortFilter(true); - collection_sort_model_->setSortLocaleAware(true); - collection_sort_model_->sort(0); + collection_filter_->setSourceModel(app_->collection()->model()); + collection_filter_->setSortRole(CollectionModel::Role_SortText); + collection_filter_->setDynamicSortFilter(true); + collection_filter_->setSortLocaleAware(true); + collection_filter_->sort(0); qLog(Debug) << "Creating models finished"; @@ -432,7 +433,7 @@ MainWindow::MainWindow(Application *app, SharedPtr tray_icon, OS ui_->playlist->view()->Init(app_); - collection_view_->view()->setModel(collection_sort_model_); + collection_view_->view()->setModel(collection_filter_); collection_view_->view()->SetApplication(app_); #ifndef Q_OS_WIN device_view_->view()->SetApplication(app_); @@ -692,7 +693,7 @@ MainWindow::MainWindow(Application *app, SharedPtr tray_icon, OS QAction *collection_config_action = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure collection..."), this); QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig); collection_view_->filter_widget()->SetSettingsGroup(QLatin1String(CollectionSettingsPage::kSettingsGroup)); - collection_view_->filter_widget()->Init(app_->collection()->model()); + collection_view_->filter_widget()->Init(app_->collection()->model(), collection_filter_); QAction *separator = new QAction(this); separator->setSeparator(true); diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index d8b7336e..fbfdc164 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -68,6 +68,7 @@ class AlbumCoverManager; class Application; class ContextView; class CollectionViewContainer; +class CollectionFilter; class AlbumCoverChoiceController; class CommandlineOptions; #ifndef Q_OS_WIN @@ -376,7 +377,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { QModelIndex playlist_menu_index_; - QSortFilterProxyModel *collection_sort_model_; + CollectionFilter *collection_filter_; QTimer *track_position_timer_; QTimer *track_slider_timer_; diff --git a/src/core/simpletreeitem.h b/src/core/simpletreeitem.h index 34ff1347..5387831e 100644 --- a/src/core/simpletreeitem.h +++ b/src/core/simpletreeitem.h @@ -55,7 +55,6 @@ class SimpleTreeItem { QString display_text; int row; - bool lazy_loaded; T *parent; QList children; @@ -68,7 +67,6 @@ template SimpleTreeItem::SimpleTreeItem(int _type, SimpleTreeModel *_model) : type(_type), row(0), - lazy_loaded(true), parent(nullptr), child_model(nullptr), model(_model) {} @@ -77,7 +75,6 @@ template SimpleTreeItem::SimpleTreeItem(int _type, const QString &_key, T *_parent) : type(_type), key(_key), - lazy_loaded(false), parent(_parent), child_model(nullptr), model(_parent ? _parent->model : nullptr) { @@ -90,7 +87,6 @@ SimpleTreeItem::SimpleTreeItem(int _type, const QString &_key, T *_parent) template SimpleTreeItem::SimpleTreeItem(int _type, T *_parent) : type(_type), - lazy_loaded(false), parent(_parent), child_model(nullptr), model(_parent ? _parent->model : nullptr) { diff --git a/src/core/simpletreemodel.h b/src/core/simpletreemodel.h index 8e1b4161..0f94fd4b 100644 --- a/src/core/simpletreemodel.h +++ b/src/core/simpletreemodel.h @@ -39,8 +39,6 @@ class SimpleTreeModel : public QAbstractItemModel { QModelIndex parent(const QModelIndex &idx) const override; int rowCount(const QModelIndex &parent) const override; bool hasChildren(const QModelIndex &parent) const override; - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; T *IndexToItem(const QModelIndex &idx) const; QModelIndex ItemToIndex(T *item) const; @@ -52,9 +50,6 @@ class SimpleTreeModel : public QAbstractItemModel { void EndDelete(); void EmitDataChanged(T *item); - protected: - virtual void LazyPopulate(T *item) { item->lazy_loaded = true; } - protected: T *root_; }; @@ -99,32 +94,13 @@ QModelIndex SimpleTreeModel::parent(const QModelIndex &idx) const { template int SimpleTreeModel::rowCount(const QModelIndex &parent) const { T *item = IndexToItem(parent); - if (!item) return 0; return item->children.count(); } template bool SimpleTreeModel::hasChildren(const QModelIndex &parent) const { T *item = IndexToItem(parent); - if (!item) return false; - if (item->lazy_loaded) - return !item->children.isEmpty(); - else - return true; -} - -template -bool SimpleTreeModel::canFetchMore(const QModelIndex &parent) const { - T *item = IndexToItem(parent); - return item && !item->lazy_loaded; -} - -template -void SimpleTreeModel::fetchMore(const QModelIndex &parent) { - T *item = IndexToItem(parent); - if (item && !item->lazy_loaded) { - LazyPopulate(item); - } + return !item->children.isEmpty(); } template diff --git a/src/core/song.cpp b/src/core/song.cpp index 051058d8..b98d50c8 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -149,30 +149,24 @@ const QString Song::kRowIdColumnSpec = Song::kRowIdColumns.join(QStringLiteral(" const QString Song::kBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kColumns).join(QStringLiteral(", ")); const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(QStringLiteral(", ")); -// used to indicate, what columns can be filtered numerically. Used by the CollectionQuery. -const QStringList Song::kNumericalColumns = QStringList() << QStringLiteral("year") - << QStringLiteral("length") - << QStringLiteral("samplerate") - << QStringLiteral("bitdepth") - << QStringLiteral("bitrate") - << QStringLiteral("rating") - << QStringLiteral("playcount") - << QStringLiteral("skipcount"); +const QStringList Song::kTextSearchColumns = QStringList() << QStringLiteral("title") + << QStringLiteral("album") + << QStringLiteral("artist") + << QStringLiteral("albumartist") + << QStringLiteral("composer") + << QStringLiteral("performer") + << QStringLiteral("grouping") + << QStringLiteral("genre") + << QStringLiteral("comment"); - -const QStringList Song::kFtsColumns = QStringList() << QStringLiteral("ftstitle") - << QStringLiteral("ftsalbum") - << QStringLiteral("ftsartist") - << QStringLiteral("ftsalbumartist") - << QStringLiteral("ftscomposer") - << QStringLiteral("ftsperformer") - << QStringLiteral("ftsgrouping") - << QStringLiteral("ftsgenre") - << QStringLiteral("ftscomment"); - -const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(QStringLiteral(", ")); -const QString Song::kFtsBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kFtsColumns).join(QStringLiteral(", ")); -const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(QStringLiteral(", ")); +const QStringList Song::kNumericalSearchColumns = QStringList() << QStringLiteral("year") + << QStringLiteral("length") + << QStringLiteral("samplerate") + << QStringLiteral("bitdepth") + << QStringLiteral("bitrate") + << QStringLiteral("rating") + << QStringLiteral("playcount") + << QStringLiteral("skipcount"); const Song::RegularExpressionList Song::kAlbumDisc = Song::RegularExpressionList() << QRegularExpression(QStringLiteral("\\s+-*\\s*(Disc|CD)\\s*([0-9]{1,2})$"), QRegularExpression::CaseInsensitiveOption) @@ -199,10 +193,33 @@ const Song::RegularExpressionList Song::kTitleMisc = Song::RegularExpressionList const QStringList Song::kArticles = QStringList() << QStringLiteral("the ") << QStringLiteral("a ") << QStringLiteral("an "); -const QStringList Song::kAcceptedExtensions = QStringList() << QStringLiteral("wav") << QStringLiteral("flac") << QStringLiteral("wv") << QStringLiteral("ogg") << QStringLiteral("oga") << QStringLiteral("opus") << QStringLiteral("spx") << QStringLiteral("ape") << QStringLiteral("mpc") - << QStringLiteral("mp2") << QStringLiteral("mp3") << QStringLiteral("m4a") << QStringLiteral("mp4") << QStringLiteral("aac") << QStringLiteral("asf") << QStringLiteral("asx") << QStringLiteral("wma") - << QStringLiteral("aif << aiff") << QStringLiteral("mka") << QStringLiteral("tta") << QStringLiteral("dsf") << QStringLiteral("dsd") - << QStringLiteral("ac3") << QStringLiteral("dts") << QStringLiteral("spc") << QStringLiteral("vgm"); +const QStringList Song::kAcceptedExtensions = QStringList() << QStringLiteral("wav") + << QStringLiteral("flac") + << QStringLiteral("wv") + << QStringLiteral("ogg") + << QStringLiteral("oga") + << QStringLiteral("opus") + << QStringLiteral("spx") + << QStringLiteral("ape") + << QStringLiteral("mpc") + << QStringLiteral("mp2") + << QStringLiteral("mp3") + << QStringLiteral("m4a") + << QStringLiteral("mp4") + << QStringLiteral("aac") + << QStringLiteral("asf") + << QStringLiteral("asx") + << QStringLiteral("wma") + << QStringLiteral("aif") + << QStringLiteral("aiff") + << QStringLiteral("mka") + << QStringLiteral("tta") + << QStringLiteral("dsf") + << QStringLiteral("dsd") + << QStringLiteral("ac3") + << QStringLiteral("dts") + << QStringLiteral("spc") + << QStringLiteral("vgm"); struct Song::Private : public QSharedData { @@ -1778,20 +1795,6 @@ void Song::BindToQuery(SqlQuery *query) const { } -void Song::BindToFtsQuery(SqlQuery *query) const { - - query->BindValue(QStringLiteral(":ftstitle"), d->title_); - query->BindValue(QStringLiteral(":ftsalbum"), d->album_); - query->BindValue(QStringLiteral(":ftsartist"), d->artist_); - query->BindValue(QStringLiteral(":ftsalbumartist"), d->albumartist_); - query->BindValue(QStringLiteral(":ftscomposer"), d->composer_); - query->BindValue(QStringLiteral(":ftsperformer"), d->performer_); - query->BindValue(QStringLiteral(":ftsgrouping"), d->grouping_); - query->BindValue(QStringLiteral(":ftsgenre"), d->genre_); - query->BindValue(QStringLiteral(":ftscomment"), d->comment_); - -} - #ifdef HAVE_DBUS void Song::ToXesam(QVariantMap *map) const { diff --git a/src/core/song.h b/src/core/song.h index d26fbce6..4b0ad7c1 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -119,12 +119,8 @@ class Song { static const QString kBindSpec; static const QString kUpdateSpec; - static const QStringList kNumericalColumns; - - static const QStringList kFtsColumns; - static const QString kFtsColumnSpec; - static const QString kFtsBindSpec; - static const QString kFtsUpdateSpec; + static const QStringList kTextSearchColumns; + static const QStringList kNumericalSearchColumns; using RegularExpressionList = QList; static const RegularExpressionList kAlbumDisc; @@ -439,7 +435,6 @@ class Song { // Save void BindToQuery(SqlQuery *query) const; - void BindToFtsQuery(SqlQuery *query) const; #ifdef HAVE_DBUS void ToXesam(QVariantMap *map) const; #endif diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index d527a90e..cc5cefde 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -236,7 +236,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString &filename) { QMutexLocker l(collection_backend_->db()->Mutex()); QSqlDatabase db(collection_backend_->db()->Connect()); - CollectionQuery query(db, collection_backend_->songs_table(), collection_backend_->fts_table()); + CollectionQuery query(db, collection_backend_->songs_table()); query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec); query.AddWhere(QStringLiteral("url"), url.toEncoded()); diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index 6a336fc5..23210be2 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -876,7 +876,7 @@ SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &idx) const { QMutexLocker l(collection_backend_->db()->Mutex()); QSqlDatabase db(collection_backend_->db()->Connect()); - CollectionQuery q(db, collection_backend_->songs_table(), collection_backend_->fts_table()); + CollectionQuery q(db, collection_backend_->songs_table()); q.SetColumnSpec(Song::kRowIdColumnSpec); q.AddWhere(QStringLiteral("album"), idx.data(Role_Album).toString()); q.SetOrderBy(QStringLiteral("disc, track, title")); diff --git a/src/device/cddadevice.cpp b/src/device/cddadevice.cpp index 6161f54a..5967d43b 100644 --- a/src/device/cddadevice.cpp +++ b/src/device/cddadevice.cpp @@ -41,7 +41,7 @@ CddaDevice::CddaDevice(const QUrl &url, DeviceLister *lister, const QString &uni QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsLoaded, this, &CddaDevice::SongsLoaded); QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsDurationLoaded, this, &CddaDevice::SongsLoaded); QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsMetadataLoaded, this, &CddaDevice::SongsLoaded); - QObject::connect(this, &CddaDevice::SongsDiscovered, model_, &CollectionModel::SongsDiscovered); + QObject::connect(this, &CddaDevice::SongsDiscovered, model_, &CollectionModel::SongsAdded); } diff --git a/src/device/connecteddevice.cpp b/src/device/connecteddevice.cpp index fdd14403..45228aa2 100644 --- a/src/device/connecteddevice.cpp +++ b/src/device/connecteddevice.cpp @@ -68,7 +68,6 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS app_->task_manager(), Song::Source::Device, QStringLiteral("device_%1_songs").arg(database_id), - QStringLiteral("device_%1_fts").arg(database_id), QStringLiteral("device_%1_directories").arg(database_id), QStringLiteral("device_%1_subdirectories").arg(database_id)); diff --git a/src/device/devicedatabasebackend.cpp b/src/device/devicedatabasebackend.cpp index 5effb6e4..11d54454 100644 --- a/src/device/devicedatabasebackend.cpp +++ b/src/device/devicedatabasebackend.cpp @@ -186,7 +186,7 @@ void DeviceDatabaseBackend::RemoveDevice(const int id) { { SqlQuery q(db); - q.prepare(QStringLiteral("DROP TABLE device_%1_fts").arg(id)); + q.prepare(QStringLiteral("DROP TABLE IF EXISTS device_%1_fts").arg(id)); if (!q.Exec()) { db_->ReportErrors(q); return; diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index da71c9a6..8b2b99ae 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -33,10 +33,10 @@ #include "settings/settingsdialog.h" #include "internetsearchview.h" -class QSortFilterProxyModel; class Application; class CollectionBackend; class CollectionModel; +class CollectionFilter; class InternetService : public QObject { Q_OBJECT @@ -69,9 +69,9 @@ class InternetService : public QObject { virtual CollectionModel *albums_collection_model() { return nullptr; } virtual CollectionModel *songs_collection_model() { return nullptr; } - virtual QSortFilterProxyModel *artists_collection_sort_model() { return nullptr; } - virtual QSortFilterProxyModel *albums_collection_sort_model() { return nullptr; } - virtual QSortFilterProxyModel *songs_collection_sort_model() { return nullptr; } + virtual CollectionFilter *artists_collection_filter_model() { return nullptr; } + virtual CollectionFilter *albums_collection_filter_model() { return nullptr; } + virtual CollectionFilter *songs_collection_filter_model() { return nullptr; } public slots: virtual void ShowConfig() {} diff --git a/src/internet/internetsongsview.cpp b/src/internet/internetsongsview.cpp index 94621094..0831db10 100644 --- a/src/internet/internetsongsview.cpp +++ b/src/internet/internetsongsview.cpp @@ -34,6 +34,7 @@ #include "core/iconloader.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "internetservice.h" #include "internetsongsview.h" #include "internetcollectionview.h" @@ -51,10 +52,10 @@ InternetSongsView::InternetSongsView(Application *app, InternetServicePtr servic ui_->stacked->setCurrentWidget(ui_->internetcollection_page); ui_->view->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), false); - ui_->view->setModel(service_->songs_collection_sort_model()); + ui_->view->setModel(service_->songs_collection_filter_model()); ui_->view->SetFilter(ui_->filter_widget); ui_->filter_widget->SetSettingsGroup(settings_group); - ui_->filter_widget->Init(service_->songs_collection_model()); + ui_->filter_widget->Init(service_->songs_collection_model(), service_->songs_collection_filter_model()); QAction *action_configure = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this); QObject::connect(action_configure, &QAction::triggered, this, &InternetSongsView::OpenSettingsDialog); diff --git a/src/internet/internettabsview.cpp b/src/internet/internettabsview.cpp index 0825c166..4525b5a2 100644 --- a/src/internet/internettabsview.cpp +++ b/src/internet/internettabsview.cpp @@ -38,6 +38,7 @@ #include "core/settings.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "collection/collectionfilterwidget.h" #include "internetservice.h" #include "internettabsview.h" @@ -66,11 +67,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service, if (service_->artists_collection_model()) { ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page()); ui_->artists_collection->view()->Init(app_, service_->artists_collection_backend(), service_->artists_collection_model(), true); - ui_->artists_collection->view()->setModel(service_->artists_collection_sort_model()); + ui_->artists_collection->view()->setModel(service_->artists_collection_filter_model()); ui_->artists_collection->view()->SetFilter(ui_->artists_collection->filter_widget()); ui_->artists_collection->filter_widget()->SetSettingsGroup(settings_group); ui_->artists_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("artists")); - ui_->artists_collection->filter_widget()->Init(service_->artists_collection_model()); + ui_->artists_collection->filter_widget()->Init(service_->artists_collection_model(), service_->artists_collection_filter_model()); ui_->artists_collection->filter_widget()->AddMenuAction(action_configure); QObject::connect(ui_->artists_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetArtists); @@ -98,11 +99,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service, if (service_->albums_collection_model()) { ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page()); ui_->albums_collection->view()->Init(app_, service_->albums_collection_backend(), service_->albums_collection_model(), true); - ui_->albums_collection->view()->setModel(service_->albums_collection_sort_model()); + ui_->albums_collection->view()->setModel(service_->albums_collection_filter_model()); ui_->albums_collection->view()->SetFilter(ui_->albums_collection->filter_widget()); ui_->albums_collection->filter_widget()->SetSettingsGroup(settings_group); ui_->albums_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("albums")); - ui_->albums_collection->filter_widget()->Init(service_->albums_collection_model()); + ui_->albums_collection->filter_widget()->Init(service_->albums_collection_model(), service_->albums_collection_filter_model()); ui_->albums_collection->filter_widget()->AddMenuAction(action_configure); QObject::connect(ui_->albums_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetAlbums); @@ -130,11 +131,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service, if (service_->songs_collection_model()) { ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page()); ui_->songs_collection->view()->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), true); - ui_->songs_collection->view()->setModel(service_->songs_collection_sort_model()); + ui_->songs_collection->view()->setModel(service_->songs_collection_filter_model()); ui_->songs_collection->view()->SetFilter(ui_->songs_collection->filter_widget()); ui_->songs_collection->filter_widget()->SetSettingsGroup(settings_group); ui_->songs_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("songs")); - ui_->songs_collection->filter_widget()->Init(service_->songs_collection_model()); + ui_->songs_collection->filter_widget()->Init(service_->songs_collection_model(), service_->songs_collection_filter_model()); ui_->songs_collection->filter_widget()->AddMenuAction(action_configure); QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetSongs); diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index 1bcfd95f..19291280 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -96,7 +96,7 @@ void PlaylistManager::Init(SharedPtr collection_backend, Shar parser_ = new PlaylistParser(collection_backend, this); playlist_container_ = playlist_container; - QObject::connect(&*collection_backend_, &CollectionBackend::SongsDiscovered, this, &PlaylistManager::SongsDiscovered); + QObject::connect(&*collection_backend_, &CollectionBackend::SongsAdded, this, &PlaylistManager::SongsDiscovered); QObject::connect(&*collection_backend_, &CollectionBackend::SongsStatisticsChanged, this, &PlaylistManager::SongsDiscovered); QObject::connect(&*collection_backend_, &CollectionBackend::SongsRatingChanged, this, &PlaylistManager::SongsDiscovered); diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index 2db75bbe..8038b086 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include "core/shared_ptr.h" @@ -50,6 +49,7 @@ #include "internet/internetsearchview.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "qobuzservice.h" #include "qobuzurlhandler.h" #include "qobuzbaserequest.h" @@ -75,10 +75,6 @@ constexpr char kArtistsSongsTable[] = "qobuz_artists_songs"; constexpr char kAlbumsSongsTable[] = "qobuz_albums_songs"; constexpr char kSongsTable[] = "qobuz_songs"; -constexpr char kArtistsSongsFtsTable[] = "qobuz_artists_songs_fts"; -constexpr char kAlbumsSongsFtsTable[] = "qobuz_albums_songs_fts"; -constexpr char kSongsFtsTable[] = "qobuz_songs_fts"; - } // namespace QobuzService::QobuzService(Application *app, QObject *parent) @@ -92,9 +88,9 @@ QobuzService::QobuzService(Application *app, QObject *parent) artists_collection_model_(nullptr), albums_collection_model_(nullptr), songs_collection_model_(nullptr), - artists_collection_sort_model_(new QSortFilterProxyModel(this)), - albums_collection_sort_model_(new QSortFilterProxyModel(this)), - songs_collection_sort_model_(new QSortFilterProxyModel(this)), + artists_collection_filter_model_(new CollectionFilter(this)), + albums_collection_filter_model_(new CollectionFilter(this)), + songs_collection_filter_model_(new CollectionFilter(this)), timer_search_delay_(new QTimer(this)), timer_login_attempt_(new QTimer(this)), favorite_request_(new QobuzFavoriteRequest(this, network_, this)), @@ -120,37 +116,37 @@ QobuzService::QobuzService(Application *app, QObject *parent) artists_collection_backend_ = make_shared(); artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kArtistsSongsTable), QLatin1String(kArtistsSongsFtsTable)); + artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kArtistsSongsTable)); albums_collection_backend_ = make_shared(); albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kAlbumsSongsTable), QLatin1String(kAlbumsSongsFtsTable)); + albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kAlbumsSongsTable)); songs_collection_backend_ = make_shared(); songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable)); + songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kSongsTable)); artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this); - artists_collection_sort_model_->setSourceModel(artists_collection_model_); - artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - artists_collection_sort_model_->setDynamicSortFilter(true); - artists_collection_sort_model_->setSortLocaleAware(true); - artists_collection_sort_model_->sort(0); + artists_collection_filter_model_->setSourceModel(artists_collection_model_); + artists_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + artists_collection_filter_model_->setDynamicSortFilter(true); + artists_collection_filter_model_->setSortLocaleAware(true); + artists_collection_filter_model_->sort(0); - albums_collection_sort_model_->setSourceModel(albums_collection_model_); - albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - albums_collection_sort_model_->setDynamicSortFilter(true); - albums_collection_sort_model_->setSortLocaleAware(true); - albums_collection_sort_model_->sort(0); + albums_collection_filter_model_->setSourceModel(albums_collection_model_); + albums_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + albums_collection_filter_model_->setDynamicSortFilter(true); + albums_collection_filter_model_->setSortLocaleAware(true); + albums_collection_filter_model_->sort(0); - songs_collection_sort_model_->setSourceModel(songs_collection_model_); - songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - songs_collection_sort_model_->setDynamicSortFilter(true); - songs_collection_sort_model_->setSortLocaleAware(true); - songs_collection_sort_model_->sort(0); + songs_collection_filter_model_->setSourceModel(songs_collection_model_); + songs_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + songs_collection_filter_model_->setDynamicSortFilter(true); + songs_collection_filter_model_->setSortLocaleAware(true); + songs_collection_filter_model_->sort(0); // Search diff --git a/src/qobuz/qobuzservice.h b/src/qobuz/qobuzservice.h index 5fffc628..4edd938a 100644 --- a/src/qobuz/qobuzservice.h +++ b/src/qobuz/qobuzservice.h @@ -44,7 +44,6 @@ class QTimer; class QNetworkReply; -class QSortFilterProxyModel; class Application; class NetworkAccessManager; class QobuzUrlHandler; @@ -53,6 +52,7 @@ class QobuzFavoriteRequest; class QobuzStreamURLRequest; class CollectionBackend; class CollectionModel; +class CollectionFilter; class QobuzService : public InternetService { Q_OBJECT @@ -105,9 +105,9 @@ class QobuzService : public InternetService { CollectionModel *albums_collection_model() override { return albums_collection_model_; } CollectionModel *songs_collection_model() override { return songs_collection_model_; } - QSortFilterProxyModel *artists_collection_sort_model() override { return artists_collection_sort_model_; } - QSortFilterProxyModel *albums_collection_sort_model() override { return albums_collection_sort_model_; } - QSortFilterProxyModel *songs_collection_sort_model() override { return songs_collection_sort_model_; } + CollectionFilter *artists_collection_filter_model() override { return artists_collection_filter_model_; } + CollectionFilter *albums_collection_filter_model() override { return albums_collection_filter_model_; } + CollectionFilter *songs_collection_filter_model() override { return songs_collection_filter_model_; } public slots: void ShowConfig() override; @@ -160,9 +160,9 @@ class QobuzService : public InternetService { CollectionModel *albums_collection_model_; CollectionModel *songs_collection_model_; - QSortFilterProxyModel *artists_collection_sort_model_; - QSortFilterProxyModel *albums_collection_sort_model_; - QSortFilterProxyModel *songs_collection_sort_model_; + CollectionFilter *artists_collection_filter_model_; + CollectionFilter *albums_collection_filter_model_; + CollectionFilter *songs_collection_filter_model_; QTimer *timer_search_delay_; QTimer *timer_login_attempt_; diff --git a/src/radios/radiomodel.cpp b/src/radios/radiomodel.cpp index fa15e626..7f3c01d8 100644 --- a/src/radios/radiomodel.cpp +++ b/src/radios/radiomodel.cpp @@ -45,8 +45,6 @@ RadioModel::RadioModel(Application *app, QObject *parent) : SimpleTreeModel(new RadioItem(this), parent), app_(app) { - root_->lazy_loaded = true; - if (app_) { QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &RadioModel::AlbumCoverLoaded); } @@ -154,7 +152,6 @@ void RadioModel::Reset() { pending_cache_keys_.clear(); delete root_; root_ = new RadioItem(this); - root_->lazy_loaded = true; endResetModel(); } @@ -172,7 +169,6 @@ void RadioModel::AddChannels(const RadioChannelList &channels) { item->source = channel.source; item->display_text = Song::DescriptionForSource(channel.source); item->sort_text = SortText(Song::TextForSource(channel.source)); - item->lazy_loaded = true; container_nodes_.insert(channel.source, item); endInsertRows(); container = item; @@ -183,7 +179,6 @@ void RadioModel::AddChannels(const RadioChannelList &channels) { item->display_text = channel.name; item->sort_text = SortText(Song::TextForSource(channel.source) + QStringLiteral(" - ") + channel.name); item->channel = channel; - item->lazy_loaded = true; items_ << item; endInsertRows(); } diff --git a/src/smartplaylists/smartplaylistsmodel.cpp b/src/smartplaylists/smartplaylistsmodel.cpp index c29d4a6f..e526ae18 100644 --- a/src/smartplaylists/smartplaylistsmodel.cpp +++ b/src/smartplaylists/smartplaylistsmodel.cpp @@ -51,11 +51,7 @@ const int SmartPlaylistsModel::kSmartPlaylistsVersion = 1; SmartPlaylistsModel::SmartPlaylistsModel(SharedPtr collection_backend, QObject *parent) : SimpleTreeModel(new SmartPlaylistsItem(this), parent), collection_backend_(collection_backend), - icon_(IconLoader::Load(QStringLiteral("view-media-playlist"))) { - - root_->lazy_loaded = true; - -} + icon_(IconLoader::Load(QStringLiteral("view-media-playlist"))) {} SmartPlaylistsModel::~SmartPlaylistsModel() { delete root_; } @@ -164,7 +160,6 @@ void SmartPlaylistsModel::ItemFromSmartPlaylist(const Settings &s, const bool no item->sort_text = item->display_text; item->smart_playlist_type = PlaylistGenerator::Type(s.value("type").toInt()); item->smart_playlist_data = s.value("data").toByteArray(); - item->lazy_loaded = true; if (notify) item->InsertNotify(root_); diff --git a/src/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index 0c52d41c..a991b54a 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include "core/logging.h" #include "core/shared_ptr.h" @@ -52,6 +51,7 @@ #include "utilities/randutils.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "subsonicservice.h" #include "subsonicurlhandler.h" #include "subsonicrequest.h" @@ -68,7 +68,6 @@ const char *SubsonicService::kApiVersion = "1.11.0"; namespace { constexpr char kSongsTable[] = "subsonic_songs"; -constexpr char kSongsFtsTable[] = "subsonic_songs_fts"; constexpr int kMaxRedirects = 3; } // namespace @@ -78,7 +77,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) url_handler_(new SubsonicUrlHandler(app, this)), collection_backend_(nullptr), collection_model_(nullptr), - collection_sort_model_(new QSortFilterProxyModel(this)), + collection_filter_model_(new CollectionFilter(this)), http2_(false), verify_certificate_(false), download_album_covers_(true), @@ -92,16 +91,16 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) collection_backend_ = make_shared(); collection_backend_->moveToThread(app_->database()->thread()); - collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Subsonic, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable)); + collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Subsonic, QLatin1String(kSongsTable)); // Model collection_model_ = new CollectionModel(collection_backend_, app_, this); - collection_sort_model_->setSourceModel(collection_model_); - collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - collection_sort_model_->setDynamicSortFilter(true); - collection_sort_model_->setSortLocaleAware(true); - collection_sort_model_->sort(0); + collection_filter_model_->setSourceModel(collection_model_); + collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + collection_filter_model_->setDynamicSortFilter(true); + collection_filter_model_->setSortLocaleAware(true); + collection_filter_model_->sort(0); SubsonicService::ReloadSettings(); diff --git a/src/subsonic/subsonicservice.h b/src/subsonic/subsonicservice.h index 41281057..bf021998 100644 --- a/src/subsonic/subsonicservice.h +++ b/src/subsonic/subsonicservice.h @@ -42,7 +42,6 @@ #include "internet/internetservice.h" #include "settings/subsonicsettingspage.h" -class QSortFilterProxyModel; class QNetworkReply; class Application; @@ -51,6 +50,7 @@ class SubsonicRequest; class SubsonicScrobbleRequest; class CollectionBackend; class CollectionModel; +class CollectionFilter; class SubsonicService : public InternetService { Q_OBJECT @@ -78,11 +78,11 @@ class SubsonicService : public InternetService { SharedPtr collection_backend() const { return collection_backend_; } CollectionModel *collection_model() const { return collection_model_; } - QSortFilterProxyModel *collection_sort_model() const { return collection_sort_model_; } + CollectionFilter *collection_filter_model() const { return collection_filter_model_; } SharedPtr songs_collection_backend() override { return collection_backend_; } CollectionModel *songs_collection_model() override { return collection_model_; } - QSortFilterProxyModel *songs_collection_sort_model() override { return collection_sort_model_; } + CollectionFilter *songs_collection_filter_model() override { return collection_filter_model_; } void CheckConfiguration(); void Scrobble(const QString &song_id, const bool submission, const QDateTime &time); @@ -109,7 +109,7 @@ class SubsonicService : public InternetService { SharedPtr collection_backend_; CollectionModel *collection_model_; - QSortFilterProxyModel *collection_sort_model_; + CollectionFilter *collection_filter_model_; SharedPtr songs_request_; SharedPtr scrobble_request_; diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 800b9dc1..446e1308 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -54,6 +54,7 @@ #include "internet/internetsearchview.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "tidalservice.h" #include "tidalurlhandler.h" #include "tidalbaserequest.h" @@ -85,10 +86,6 @@ constexpr char kArtistsSongsTable[] = "tidal_artists_songs"; constexpr char kAlbumsSongsTable[] = "tidal_albums_songs"; constexpr char kSongsTable[] = "tidal_songs"; -constexpr char kArtistsSongsFtsTable[] = "tidal_artists_songs_fts"; -constexpr char kAlbumsSongsFtsTable[] = "tidal_albums_songs_fts"; -constexpr char kSongsFtsTable[] = "tidal_songs_fts"; - } // namespace TidalService::TidalService(Application *app, QObject *parent) @@ -102,9 +99,9 @@ TidalService::TidalService(Application *app, QObject *parent) artists_collection_model_(nullptr), albums_collection_model_(nullptr), songs_collection_model_(nullptr), - artists_collection_sort_model_(new QSortFilterProxyModel(this)), - albums_collection_sort_model_(new QSortFilterProxyModel(this)), - songs_collection_sort_model_(new QSortFilterProxyModel(this)), + artists_collection_filter_model_(new CollectionFilter(this)), + albums_collection_filter_model_(new CollectionFilter(this)), + songs_collection_filter_model_(new CollectionFilter(this)), timer_search_delay_(new QTimer(this)), timer_login_attempt_(new QTimer(this)), timer_refresh_login_(new QTimer(this)), @@ -135,37 +132,37 @@ TidalService::TidalService(Application *app, QObject *parent) artists_collection_backend_ = make_shared(); artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kArtistsSongsTable), QLatin1String(kArtistsSongsFtsTable)); + artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kArtistsSongsTable)); albums_collection_backend_ = make_shared(); albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kAlbumsSongsTable), QLatin1String(kAlbumsSongsFtsTable)); + albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kAlbumsSongsTable)); songs_collection_backend_ = make_shared(); songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable)); + songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kSongsTable)); artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this); - artists_collection_sort_model_->setSourceModel(artists_collection_model_); - artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - artists_collection_sort_model_->setDynamicSortFilter(true); - artists_collection_sort_model_->setSortLocaleAware(true); - artists_collection_sort_model_->sort(0); + artists_collection_filter_model_->setSourceModel(artists_collection_model_); + artists_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + artists_collection_filter_model_->setDynamicSortFilter(true); + artists_collection_filter_model_->setSortLocaleAware(true); + artists_collection_filter_model_->sort(0); - albums_collection_sort_model_->setSourceModel(albums_collection_model_); - albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - albums_collection_sort_model_->setDynamicSortFilter(true); - albums_collection_sort_model_->setSortLocaleAware(true); - albums_collection_sort_model_->sort(0); + albums_collection_filter_model_->setSourceModel(albums_collection_model_); + albums_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + albums_collection_filter_model_->setDynamicSortFilter(true); + albums_collection_filter_model_->setSortLocaleAware(true); + albums_collection_filter_model_->sort(0); - songs_collection_sort_model_->setSourceModel(songs_collection_model_); - songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText); - songs_collection_sort_model_->setDynamicSortFilter(true); - songs_collection_sort_model_->setSortLocaleAware(true); - songs_collection_sort_model_->sort(0); + songs_collection_filter_model_->setSourceModel(songs_collection_model_); + songs_collection_filter_model_->setSortRole(CollectionModel::Role_SortText); + songs_collection_filter_model_->setDynamicSortFilter(true); + songs_collection_filter_model_->setSortLocaleAware(true); + songs_collection_filter_model_->sort(0); // Search diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index 11210086..8184f630 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -42,7 +42,6 @@ #include "internet/internetsearchview.h" #include "settings/tidalsettingspage.h" -class QSortFilterProxyModel; class QNetworkReply; class QTimer; @@ -54,6 +53,7 @@ class TidalFavoriteRequest; class TidalStreamURLRequest; class CollectionBackend; class CollectionModel; +class CollectionFilter; class TidalService : public InternetService { Q_OBJECT @@ -112,9 +112,9 @@ class TidalService : public InternetService { CollectionModel *albums_collection_model() override { return albums_collection_model_; } CollectionModel *songs_collection_model() override { return songs_collection_model_; } - QSortFilterProxyModel *artists_collection_sort_model() override { return artists_collection_sort_model_; } - QSortFilterProxyModel *albums_collection_sort_model() override { return albums_collection_sort_model_; } - QSortFilterProxyModel *songs_collection_sort_model() override { return songs_collection_sort_model_; } + CollectionFilter *artists_collection_filter_model() override { return artists_collection_filter_model_; } + CollectionFilter *albums_collection_filter_model() override { return albums_collection_filter_model_; } + CollectionFilter *songs_collection_filter_model() override { return songs_collection_filter_model_; } public slots: void ShowConfig() override; @@ -172,9 +172,9 @@ class TidalService : public InternetService { CollectionModel *albums_collection_model_; CollectionModel *songs_collection_model_; - QSortFilterProxyModel *artists_collection_sort_model_; - QSortFilterProxyModel *albums_collection_sort_model_; - QSortFilterProxyModel *songs_collection_sort_model_; + CollectionFilter *artists_collection_filter_model_; + CollectionFilter *albums_collection_filter_model_; + CollectionFilter *songs_collection_filter_model_; QTimer *timer_search_delay_; QTimer *timer_login_attempt_; diff --git a/tests/src/collectionbackend_test.cpp b/tests/src/collectionbackend_test.cpp index 6ceca18e..31a2a9d7 100644 --- a/tests/src/collectionbackend_test.cpp +++ b/tests/src/collectionbackend_test.cpp @@ -47,7 +47,7 @@ class CollectionBackendTest : public ::testing::Test { void SetUp() override { database_.reset(new MemoryDatabase(nullptr)); backend_ = make_unique(); - backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable)); + backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable)); } static Song MakeDummySong(int directory_id) { @@ -132,7 +132,7 @@ class SingleSong : public CollectionBackendTest { } void AddDummySong() { - QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsAdded); QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted); // Add the song @@ -266,7 +266,7 @@ TEST_F(SingleSong, UpdateSong) { new_song.set_title(QStringLiteral("A different title")); QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted); - QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsAdded); backend_->AddOrUpdateSongs(SongList() << new_song); @@ -389,7 +389,7 @@ TEST_F(TestUrls, TestUrls) { } - QSignalSpy spy(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy spy(&*backend_, &CollectionBackend::SongsAdded); backend_->AddOrUpdateSongs(songs); if (HasFatalFailure()) return; @@ -474,7 +474,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) { } - QSignalSpy spy(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy spy(&*backend_, &CollectionBackend::SongsAdded); backend_->UpdateSongsBySongID(songs); @@ -495,7 +495,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) { SongMap songs; { QSqlDatabase db(database_->Connect()); - CollectionQuery query(db, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable)); + CollectionQuery query(db, QLatin1String(SCollection::kSongsTable)); EXPECT_TRUE(backend_->ExecCollectionQuery(&query, songs)); } @@ -512,7 +512,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) { } { // Remove some songs - QSignalSpy spy1(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy spy1(&*backend_, &CollectionBackend::SongsAdded); QSignalSpy spy2(&*backend_, &CollectionBackend::SongsDeleted); SongMap songs; @@ -558,7 +558,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) { { // Update some songs QSignalSpy spy1(&*backend_, &CollectionBackend::SongsDeleted); - QSignalSpy spy2(&*backend_, &CollectionBackend::SongsDiscovered); + QSignalSpy spy2(&*backend_, &CollectionBackend::SongsAdded); SongMap songs; diff --git a/tests/src/collectionmodel_test.cpp b/tests/src/collectionmodel_test.cpp index b59b045c..a475ac25 100644 --- a/tests/src/collectionmodel_test.cpp +++ b/tests/src/collectionmodel_test.cpp @@ -54,7 +54,7 @@ class CollectionModelTest : public ::testing::Test { void SetUp() override { database_ = make_shared(nullptr); backend_ = make_shared(); - backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable)); + backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable)); model_ = make_unique(backend_, nullptr); added_dir_ = false; @@ -108,7 +108,7 @@ TEST_F(CollectionModelTest, WithInitialArtists) { AddSong(QStringLiteral("Title"), QStringLiteral("Artist 1"), QStringLiteral("Album"), 123); AddSong(QStringLiteral("Title"), QStringLiteral("Artist 2"), QStringLiteral("Album"), 123); AddSong(QStringLiteral("Title"), QStringLiteral("Foo"), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); ASSERT_EQ(5, model_sorted_->rowCount(QModelIndex())); EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString()); @@ -128,7 +128,7 @@ TEST_F(CollectionModelTest, CompilationAlbums) { song.set_ctime(0); AddSong(song); - model_->Init(false); + model_->Init(); model_->fetchMore(model_->index(0, 0)); ASSERT_EQ(1, model_->rowCount(QModelIndex())); @@ -150,7 +150,7 @@ TEST_F(CollectionModelTest, NumericHeaders) { AddSong(QStringLiteral("Title"), QStringLiteral("2artist"), QStringLiteral("Album"), 123); AddSong(QStringLiteral("Title"), QStringLiteral("0artist"), QStringLiteral("Album"), 123); AddSong(QStringLiteral("Title"), QStringLiteral("zartist"), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); ASSERT_EQ(6, model_sorted_->rowCount(QModelIndex())); EXPECT_EQ(QStringLiteral("0-9"), model_sorted_->index(0, 0, QModelIndex()).data().toString()); @@ -166,7 +166,7 @@ TEST_F(CollectionModelTest, MixedCaseHeaders) { AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); AddSong(QStringLiteral("Title"), QStringLiteral("artist"), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); ASSERT_EQ(3, model_sorted_->rowCount(QModelIndex())); EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString()); @@ -178,7 +178,7 @@ TEST_F(CollectionModelTest, MixedCaseHeaders) { TEST_F(CollectionModelTest, UnknownArtists) { AddSong(QStringLiteral("Title"), QLatin1String(""), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); model_->fetchMore(model_->index(0, 0)); ASSERT_EQ(1, model_->rowCount(QModelIndex())); @@ -194,7 +194,7 @@ TEST_F(CollectionModelTest, UnknownAlbums) { AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QLatin1String(""), 123); AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); model_->fetchMore(model_->index(0, 0)); QModelIndex artist_index = model_->index(0, 0, QModelIndex()); @@ -228,7 +228,7 @@ TEST_F(CollectionModelTest, VariousArtistSongs) { for (int i=0 ; i < 4 ; ++i) AddSong(songs[i]); - model_->Init(false); + model_->Init(); QModelIndex artist_index = model_->index(0, 0, QModelIndex()); model_->fetchMore(artist_index); @@ -250,7 +250,7 @@ TEST_F(CollectionModelTest, RemoveSongsLazyLoaded) { Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1); Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2); AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); - model_->Init(false); + model_->Init(); // Lazy load the items QModelIndex artist_index = model_->index(0, 0, QModelIndex()); @@ -283,7 +283,7 @@ TEST_F(CollectionModelTest, RemoveSongsNotLazyLoaded) { Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1); Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2); - model_->Init(false); + model_->Init(); // Remove the first two songs QSignalSpy spy_preremove(&*model_, &CollectionModel::rowsAboutToBeRemoved); @@ -303,7 +303,7 @@ TEST_F(CollectionModelTest, RemoveEmptyAlbums) { Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album 1"), 123); one.set_id(1); Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); two.set_id(2); Song three = AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); three.set_id(3); - model_->Init(false); + model_->Init(); QModelIndex artist_index = model_->index(0, 0, QModelIndex()); model_->fetchMore(artist_index); @@ -328,7 +328,7 @@ TEST_F(CollectionModelTest, RemoveEmptyAlbums) { TEST_F(CollectionModelTest, RemoveEmptyArtists) { Song one = AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1); - model_->Init(false); + model_->Init(); // Lazy load the items QModelIndex artist_index = model_->index(0, 0, QModelIndex()); @@ -351,8 +351,8 @@ TEST_F(CollectionModelTest, RemoveEmptyArtists) { // Test to check that the container nodes are created identical and unique all through the model with all possible collection groupings. // model1 - Nodes are created from a complete reset done through lazy-loading. -// model2 - Initial container nodes are created in SongsDiscovered. -// model3 - All container nodes are created in SongsDiscovered. +// model2 - Initial container nodes are created in SongsAdded. +// model3 - All container nodes are created in SongsAdded. // WARNING: This test can take up to 30 minutes to complete. #if 0 @@ -567,9 +567,9 @@ TEST_F(CollectionModelTest, TestContainerNodes) { backend1 = make_unique(); backend2= make_unique(); backend3 = make_unique(); - backend1->Init(database1.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); - backend2->Init(database2.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); - backend3->Init(database3.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); + backend1->Init(database1.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); + backend2->Init(database2.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); + backend3->Init(database3.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); model1 = make_unique(backend1.get(), nullptr); model2 = make_unique(backend2.get(), nullptr); model3 = make_unique(backend3.get(), nullptr); @@ -596,7 +596,7 @@ TEST_F(CollectionModelTest, TestContainerNodes) { ASSERT_EQ(model2->song_nodes().count(), 0); ASSERT_EQ(model3->song_nodes().count(), songs.count()); - model1->Init(false); + model1->Init(); model1->ExpandAll(); model2->ExpandAll();