diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cfcafaf..7ad9d462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -488,23 +488,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() endif() # Set up definitions @@ -563,11 +546,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 50445fd0..671a06d5 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,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 5.9 or higher (or Qt 6) 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 e6476227..0b4fffcd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,7 @@ set(SOURCES collection/collectionviewcontainer.cpp collection/collectiondirectorymodel.cpp collection/collectionfilterwidget.cpp + collection/collectionfilter.cpp collection/collectionplaylistitem.cpp collection/collectionquery.cpp collection/savedgroupingmanager.cpp @@ -310,6 +311,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 d4fefb9b..12362455 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -69,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, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable); + backend_->Init(app->database(), app->task_manager(), Song::Source_Collection, kSongsTable, kDirsTable, kSubdirsTable); model_ = new CollectionModel(backend_, app_, this); diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 45c9e32b..46d50359 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -66,16 +66,13 @@ CollectionBackend::CollectionBackend(QObject *parent) } -void CollectionBackend::Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table, const QString &subdirs_table) { - +void CollectionBackend::Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table) { db_ = db; task_manager_ = task_manager; source_ = source; songs_table_ = songs_table; dirs_table_ = dirs_table; subdirs_table_ = subdirs_table; - fts_table_ = fts_table; - } void CollectionBackend::Close() { @@ -115,6 +112,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(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(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, "LoadDirectories", Qt::QueuedConnection); } @@ -580,17 +606,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { } } - { - SqlQuery q(db); - q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_)); - song.BindToFtsQuery(&q); - q.BindValue(":id", song.id()); - if (!q.Exec()) { - db_->ReportErrors(q); - return; - } - } - deleted_songs << old_song; added_songs << song; @@ -619,17 +634,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { } } - { - SqlQuery q(db); - q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_)); - new_song.BindToFtsQuery(&q); - q.BindValue(":id", new_song.id()); - if (!q.Exec()) { - db_->ReportErrors(q); - return; - } - } - deleted_songs << old_song; added_songs << new_song; @@ -654,17 +658,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { if (id == -1) return; - { // Add to the FTS index - SqlQuery q(db); - q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_)); - q.BindValue(":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; @@ -699,7 +692,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; @@ -725,16 +718,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) { return; } } - { - SqlQuery q(db); - q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_)); - new_song.BindToFtsQuery(&q); - q.BindValue(":id", old_song.id()); - if (!q.Exec()) { - db_->ReportErrors(q); - return; - } - } deleted_songs << old_song; Song new_song_copy(new_song); @@ -760,17 +743,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) { if (id == -1) return; - { // Add to the FTS index - SqlQuery q(db); - q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_)); - q.BindValue(":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; @@ -790,15 +762,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) { return; } } - { - SqlQuery q(db); - q.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_)); - q.BindValue(":id", old_song.id()); - if (!q.Exec()) { - db_->ReportErrors(q); - return; - } - } deleted_songs << old_song; } } @@ -819,11 +782,10 @@ void CollectionBackend::UpdateMTimesOnly(const SongList &songs) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - SqlQuery q(db); - q.prepare(QString("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_)); - ScopedTransaction transaction(&db); for (const Song &song : songs) { + SqlQuery q(db); + q.prepare(QString("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_)); q.BindValue(":mtime", song.mtime()); q.BindValue(":id", song.id()); if (!q.Exec()) { @@ -840,25 +802,17 @@ void CollectionBackend::DeleteSongs(const SongList &songs) { QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - SqlQuery remove(db); - remove.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_)); - SqlQuery remove_fts(db); - remove_fts.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_)); - ScopedTransaction transaction(&db); for (const Song &song : songs) { - remove.BindValue(":id", song.id()); - if (!remove.Exec()) { - db_->ReportErrors(remove); - return; - } - - remove_fts.BindValue(":id", song.id()); - if (!remove_fts.Exec()) { - db_->ReportErrors(remove_fts); + SqlQuery q(db); + q.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_)); + q.BindValue(":id", song.id()); + if (!q.Exec()) { + db_->ReportErrors(q); return; } } + transaction.Commit(); emit SongsDeleted(songs); @@ -874,14 +828,13 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - SqlQuery remove(db); - remove.prepare(QString("UPDATE %1 SET unavailable = %2 WHERE ROWID = :id").arg(songs_table_).arg(static_cast(unavailable))); - ScopedTransaction transaction(&db); for (const Song &song : songs) { - remove.BindValue(":id", song.id()); - if (!remove.Exec()) { - db_->ReportErrors(remove); + SqlQuery q(db); + q.prepare(QString("UPDATE %1 SET unavailable = %2 WHERE ROWID = :id").arg(songs_table_).arg(static_cast(unavailable))); + q.BindValue(":id", song.id()); + if (!q.Exec()) { + db_->ReportErrors(q); return; } } @@ -905,7 +858,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - CollectionQuery query(db, songs_table_, fts_table_, opt); + CollectionQuery query(db, songs_table_, opt); query.SetColumnSpec("DISTINCT " + column); query.AddCompilationRequirement(false); @@ -933,13 +886,13 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt) 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("DISTINCT albumartist"); query.AddCompilationRequirement(false); query.AddWhere("album", "", "!="); // Albums with no 'albumartist' (extract 'artist'): - CollectionQuery query2(db, songs_table_, fts_table_, opt); + CollectionQuery query2(db, songs_table_, opt); query2.SetColumnSpec("DISTINCT artist"); query2.AddCompilationRequirement(false); query2.AddWhere("album", "", "!="); @@ -980,7 +933,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("effective_albumartist", effective_albumartist); @@ -998,7 +951,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("effective_albumartist", effective_albumartist); query.AddWhere("album", album); @@ -1017,7 +970,7 @@ SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOpt 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("album", album); @@ -1294,7 +1247,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Quer QMutexLocker l(db_->Mutex()); QSqlDatabase db(db_->Connect()); - CollectionQuery query(db, songs_table_, fts_table_, opt); + CollectionQuery query(db, songs_table_, opt); query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.AddCompilationRequirement(true); query.AddWhere("album", album); @@ -1431,7 +1384,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("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path"); query.SetOrderBy("effective_albumartist, album, url"); @@ -1516,7 +1469,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective ret.album = album; ret.album_artist = effective_albumartist; - CollectionQuery query(db, songs_table_, fts_table_, QueryOptions()); + CollectionQuery query(db, songs_table_, QueryOptions()); query.SetColumnSpec("art_automatic, art_manual, url"); if (!effective_albumartist.isEmpty()) { query.AddWhere("effective_albumartist", effective_albumartist); @@ -1550,7 +1503,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis 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("ROWID, " + Song::kColumnSpec); query.AddWhere("effective_albumartist", effective_albumartist); query.AddWhere("album", album); @@ -1617,7 +1570,7 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar 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("ROWID, " + Song::kColumnSpec); query.AddWhere("effective_albumartist", effective_albumartist); query.AddWhere("album", album); @@ -1680,7 +1633,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QListReportErrors(q); - return; - } - } - t.Commit(); } diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h index d4a6ac8d..8a398c41 100644 --- a/src/collection/collectionbackend.h +++ b/src/collection/collectionbackend.h @@ -75,12 +75,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 Database *db() const = 0; + virtual void GetAllSongsAsync(const int id = 0) = 0; + // Get a list of directories in the collection. Emits DirectoriesDiscovered. virtual void LoadDirectoriesAsync() = 0; @@ -134,7 +135,8 @@ class CollectionBackend : public CollectionBackendInterface { Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr); - void Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString()); + void Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString()); + void Close(); void ExitAsync(); @@ -146,10 +148,11 @@ class CollectionBackend : public CollectionBackendInterface { Database *db() const override { return db_; } QString songs_table() const override { return songs_table_; } - QString fts_table() const override { return fts_table_; } QString dirs_table() const { return dirs_table_; } QString subdirs_table() const { return subdirs_table_; } + void GetAllSongsAsync(const int id = 0) override; + // Get a list of directories in the collection. Emits DirectoriesDiscovered. void LoadDirectoriesAsync() override; @@ -219,6 +222,7 @@ class CollectionBackend : public CollectionBackendInterface { public slots: void Exit(); + void GetAllSongs(const int id); void LoadDirectories(); void UpdateTotalSongCount(); void UpdateTotalArtistCount(); @@ -253,6 +257,7 @@ class CollectionBackend : public CollectionBackendInterface { void DirectoryDiscovered(Directory, SubdirectoryList); void DirectoryDeleted(Directory); + void GotSongs(SongList, int); void SongsDiscovered(SongList); void SongsDeleted(SongList); void SongsStatisticsChanged(SongList); @@ -297,7 +302,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..3d651c7e --- /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('\\'); +#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("\\s+"), Qt::SkipEmptyParts)); +#else + QStringList tokens(filter.split(QRegularExpression("\\s+"), QString::SkipEmptyParts)); +#endif + + filter.clear(); + + QMap tags; + for (QString token : tokens) { + if (token.contains(':')) { + if (Song::kColumns.contains(token.section(':', 0, 0), Qt::CaseInsensitive)) { + QString tag = token.section(':', 0, 0).remove(':').trimmed(); + QString value = token.section(':', 1, -1).remove(':').trimmed(); + if (!tag.isEmpty() && !value.isEmpty()) { + tags.insert(tag, value); + } + } + else { + token = token.remove(':').trimmed(); + if (!token.isEmpty()) { + if (!filter.isEmpty()) filter.append(" "); + filter += token; + } + } + } + else { + if (!filter.isEmpty()) filter.append(" "); + 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 == "albumartist" && metadata.effective_albumartist().contains(value, Qt::CaseInsensitive)) return true; + if (tag == "artist" && metadata.artist().contains(value, Qt::CaseInsensitive)) return true; + if (tag == "album" && metadata.album().contains(value, Qt::CaseInsensitive)) return true; + if (tag == "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 = "albumartist"; + break; + case CollectionModel::GroupBy_Artist: + tag = "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 = "album"; + break; + case CollectionModel::GroupBy_Disc: + case CollectionModel::GroupBy_Year: + case CollectionModel::GroupBy_OriginalYear: + break; + case CollectionModel::GroupBy_Genre: + tag = "genre"; + break; + case CollectionModel::GroupBy_Composer: + tag = "composer"; + break; + case CollectionModel::GroupBy_Performer: + tag = "performer"; + break; + case CollectionModel::GroupBy_Grouping: + tag = "grouping"; + break; + case CollectionModel::GroupBy_FileType: + tag = "filetype"; + break; + case CollectionModel::GroupBy_Format: + case CollectionModel::GroupBy_Bitdepth: + case CollectionModel::GroupBy_Samplerate: + case CollectionModel::GroupBy_Bitrate: + case CollectionModel::GroupBy_None: + case CollectionModel::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 3731572e..e2349f5d 100644 --- a/src/collection/collectionfilterwidget.cpp +++ b/src/collection/collectionfilterwidget.cpp @@ -47,6 +47,7 @@ #include "core/song.h" #include "core/logging.h" #include "collectionmodel.h" +#include "collectionfilter.h" #include "collectionquery.h" #include "savedgroupingmanager.h" #include "collectionfilterwidget.h" @@ -65,13 +66,14 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent) group_by_menu_(nullptr), collection_menu_(nullptr), group_by_group_(nullptr), + filter_(nullptr), filter_delay_(new QTimer(this)), filter_applies_to_model_(true), delay_behaviour_(DelayedOnLargeLibraries) { ui_->setupUi(this); - QString available_fields = Song::kFtsColumns.join(", ").replace(QRegularExpression("\\bfts"), ""); + QString available_fields = Song::kSearchColumns.join(", "); ui_->search_field->setToolTip( QString("

") + @@ -144,7 +146,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); @@ -157,6 +159,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) { } model_ = model; + filter_ = filter; // Connect signals QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged); @@ -204,6 +207,10 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) { } +void CollectionFilterWidget::setFilter(CollectionFilter *filter) { + filter_ = filter; +} + void CollectionFilterWidget::ReloadSettings() { QSettings s; @@ -505,9 +512,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_ == AlwaysDelayed) || (delay_behaviour_ == DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000); if (delay) { @@ -522,9 +526,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 422b20ab..baa86474 100644 --- a/src/collection/collectionfilterwidget.h +++ b/src/collection/collectionfilterwidget.h @@ -42,6 +42,7 @@ class QKeyEvent; class GroupByDialog; class SavedGroupingManager; +class CollectionFilter; class Ui_CollectionFilterWidget; class CollectionFilterWidget : public QWidget { @@ -59,7 +60,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); @@ -95,7 +98,6 @@ class CollectionFilterWidget : public QWidget { void UpPressed(); void DownPressed(); void ReturnPressed(); - void Filter(QString text); protected: void keyReleaseEvent(QKeyEvent *e) override; @@ -116,6 +118,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 f98e6c62..1b7759bd 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -88,13 +88,12 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q separate_albums_by_grouping_(false), artist_icon_(IconLoader::Load("folder-sound")), album_icon_(IconLoader::Load("cdcase")), + init_id_(-1), + next_init_id_(0), init_task_id_(-1), use_pretty_covers_(true), show_dividers_(true), - use_disk_cache_(false), - use_lazy_loading_(true) { - - root_->lazy_loaded = true; + use_disk_cache_(false) { group_by_[0] = GroupBy_AlbumArtist; group_by_[1] = GroupBy_AlbumDisc; @@ -122,6 +121,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q QObject::connect(app_, &Application::ClearPixmapDiskCache, this, &CollectionModel::ClearDiskCache); } + QObject::connect(backend_, &CollectionBackend::GotSongs, this, &CollectionModel::ResetAsyncFinished); QObject::connect(backend_, &CollectionBackend::SongsDiscovered, this, &CollectionModel::SongsDiscovered); QObject::connect(backend_, &CollectionBackend::SongsDeleted, this, &CollectionModel::SongsDeleted); QObject::connect(backend_, &CollectionBackend::DatabaseReset, this, &CollectionModel::Reset); @@ -182,27 +182,22 @@ void CollectionModel::ReloadSettings() { } -void CollectionModel::Init(const bool async) { +void CollectionModel::Init() { - 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(); + init_id_ = ++next_init_id_; + BeginReset(); + // Show a loading indicator in the model. + CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_); + loading->display_text = tr("Loading..."); + 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(); + // Show a loading indicator in the status bar too. + if (app_ && init_task_id_ == -1) { + init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs")); } + ResetAsync(); + } void CollectionModel::SongsDiscovered(const SongList &songs) { @@ -252,10 +247,7 @@ 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)); @@ -519,13 +511,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 @@ -771,10 +756,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()) { @@ -807,7 +788,7 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const bool CollectionModel::HasCompilations(const QSqlDatabase &db, const CollectionQuery &query) { - CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), query_options_); + CollectionQuery q(db, backend_->songs_table(), query_options_); q.SetColumnSpec(query.column_spec()); q.SetOrderBy(query.order_by()); @@ -827,42 +808,15 @@ bool CollectionModel::HasCompilations(const QSqlDatabase &db, const CollectionQu } -CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) { +CollectionModel::QueryResult CollectionModel::RunQuery() { QueryResult result; - // 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]; - - // Initialize the query. child_group_by says what type of thing we want (artists, songs, etc.) - { QMutexLocker l(backend_->db()->Mutex()); QSqlDatabase db(backend_->db()->Connect()); - - CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), query_options_); - InitQuery(child_group_by, separate_albums_by_grouping_, &q); - - // Walk up through the item's parents adding filters as necessary - CollectionItem *p = parent; - while (p && p->type == CollectionItem::Type_Container) { - FilterQuery(group_by_[p->container_level], separate_albums_by_grouping_, p, &q); - p = p->parent; - } - - // Artists GroupBy is special - we don't want compilation albums appearing - if (IsArtistGroupBy(child_group_by)) { - // Add the special Various artists node - if (show_various_artists_ && HasCompilations(db, q)) { - result.create_va = true; - } - - // Don't show compilations again outside the Various artists node - q.AddCompilationRequirement(false); - } - - // Execute the query + CollectionQuery q(db, backend_->songs_table(), query_options_); + q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); if (q.Exec()) { while (q.Next()) { result.rows << SqlRow(q); @@ -871,7 +825,6 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) { else { backend_->ReportErrors(q); } - } if (QThread::currentThread() != thread() && QThread::currentThread() != backend_->thread()) { @@ -882,48 +835,65 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) { } -void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::QueryResult &result, const bool signal) { - - // 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); - } +void CollectionModel::PostQuery(const CollectionModel::QueryResult &result) { // 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); + Song song; + song.InitFromQuery(row, true); + + // Sanity check to make sure we don't add songs that are outside the user's filter + if (!query_options_.Matches(song)) continue; + + // Hey, we've already got that one! + if (song_nodes_.contains(song.id())) continue; + + // Before we can add each song we need to make sure the required container items already exist in the tree. + // These depend on which "group by" settings the user has on the collection. + // Eg. if the user grouped by artist and album, we would need to make sure nodes for the song's artist and album were already in the tree. + + // Find parent containers in the tree + CollectionItem *container = root_; + QString key; + for (int i = 0; i < 3; ++i) { + GroupBy type = group_by_[i]; + if (type == GroupBy_None) break; + + if (!key.isEmpty()) key.append("-"); + + // Special case: if the song is a compilation and the current GroupBy level is Artists, then we want the Various Artists node :( + if (IsArtistGroupBy(type) && song.is_compilation()) { + if (container->compilation_artist_node_ == nullptr) { + CreateCompilationArtistNode(false, container); + } + container = container->compilation_artist_node_; + key = container->key; + } + else { + // Otherwise find the proper container at this level based on the item's key + key.append(ContainerKey(type, song)); + + // Does it exist already? + if (!container_nodes_[i].contains(key)) { + // Create the container + container_nodes_[i].insert(key, ItemFromSong(type, false, i == 0, container, song, i)); + } + container = container_nodes_[i][key]; + } + } + song_nodes_.insert(song.id(), ItemFromSong(GroupBy_None, false, false, container, song, -1)); } } -void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) { - - if (parent->lazy_loaded) return; - parent->lazy_loaded = true; - - QueryResult result = RunQuery(parent); - PostQuery(parent, result, signal); - -} - void CollectionModel::ResetAsync() { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QFuture future = QtConcurrent::run(&CollectionModel::RunQuery, this, root_); + QFuture future = QtConcurrent::run(&CollectionModel::RunQuery, this); #else - QFuture future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_); + QFuture future = QtConcurrent::run(this, &CollectionModel::RunQuery); #endif QFutureWatcher *watcher = new QFutureWatcher(); QObject::connect(watcher, &QFutureWatcher::finished, this, &CollectionModel::ResetAsyncQueryFinished); @@ -931,6 +901,23 @@ void CollectionModel::ResetAsync() { } +void CollectionModel::ResetAsyncFinished(const SongList &songs, const int id) { + + if (id != init_id_) return; + + BeginReset(); + endResetModel(); + SongsDiscovered(songs); + + if (init_task_id_ != -1) { + if (app_) { + app_->task_manager()->SetTaskFinished(init_task_id_); + } + init_task_id_ = -1; + } + +} + void CollectionModel::ResetAsyncQueryFinished() { QFutureWatcher *watcher = static_cast*>(sender()); @@ -942,9 +929,8 @@ void CollectionModel::ResetAsyncQueryFinished() { } BeginReset(); - root_->lazy_loaded = true; - PostQuery(root_, result, false); + PostQuery(result); if (init_task_id_ != -1) { if (app_) { @@ -971,21 +957,17 @@ void CollectionModel::BeginReset() { root_ = new CollectionItem(this); root_->compilation_artist_node_ = nullptr; - root_->lazy_loaded = false; } void CollectionModel::Reset() { BeginReset(); - - // Populate top level - LazyPopulate(root_, false); - endResetModel(); } +<<<<<<< HEAD void CollectionModel::InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q) { // Say what group_by of thing we want to get back from the database. @@ -1190,7 +1172,9 @@ CollectionItem *CollectionModel::InitItem(const GroupBy group_by, const bool sig CollectionItem::Type item_type = group_by == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container; - if (signal) beginInsertRows(ItemToIndex(parent), static_cast(parent->children.count()), static_cast(parent->children.count())); + if (signal) { + beginInsertRows(ItemToIndex(parent), static_cast(parent->children.count()), static_cast(parent->children.count())); + } // Initialize the item depending on what type it's meant to be CollectionItem *item = new CollectionItem(item_type, parent); @@ -1595,7 +1579,6 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool } FinishItem(group_by, signal, create_divider, parent, item); - if (s.url().scheme() == "cdda") item->lazy_loaded = true; return item; @@ -1603,8 +1586,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 (group_by == GroupBy_None) item->lazy_loaded = true; - if (signal) { endInsertRows(); } @@ -1625,7 +1606,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 + " "; - divider->lazy_loaded = true; divider_nodes_[divider_key] = divider; @@ -1809,8 +1789,6 @@ void CollectionModel::GetChildSongs(CollectionItem *item, QList *urls, Son switch (item->type) { case CollectionItem::Type_Container: { - const_cast(this)->LazyPopulate(item); - QList children = item->children; std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, std::placeholders::_1, std::placeholders::_2)); @@ -1852,28 +1830,18 @@ SongList CollectionModel::GetChildSongs(const QModelIndex &idx) const { } void CollectionModel::SetFilterAge(const int age) { - query_options_.set_max_age(age); - ResetAsync(); -} -void CollectionModel::SetFilterText(const QString &text) { - query_options_.set_filter(text); - ResetAsync(); + query_options_.set_max_age(age); + + Init(); } void CollectionModel::SetFilterQueryMode(QueryOptions::QueryMode query_mode) { + query_options_.set_query_mode(query_mode); - ResetAsync(); -} - -bool CollectionModel::canFetchMore(const QModelIndex &parent) const { - - if (!parent.isValid()) return false; - - CollectionItem *item = IndexToItem(parent); - return !item->lazy_loaded; + Init(); } @@ -1884,7 +1852,8 @@ void CollectionModel::SetGroupBy(const Grouping g, const std::optional sep separate_albums_by_grouping_ = separate_albums_by_grouping.value(); } - ResetAsync(); + Init(); + emit GroupingChanged(g, separate_albums_by_grouping_); } @@ -1945,7 +1914,6 @@ void CollectionModel::ClearDiskCache() { void CollectionModel::ExpandAll(CollectionItem *item) const { if (!item) item = root_; - const_cast(this)->LazyPopulate(const_cast(item), false); for (CollectionItem *child : item->children) { ExpandAll(child); } diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h index 2d38a579..19f65ba3 100644 --- a/src/collection/collectionmodel.h +++ b/src/collection/collectionmodel.h @@ -151,7 +151,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); @@ -163,6 +162,8 @@ class CollectionModel : public SimpleTreeModel { // Reload settings. void ReloadSettings(); + void ResetAsync(); + // Utility functions for manipulating text static QString TextOrUnknown(const QString &text); static QString PrettyYearAlbum(const int year, const QString &album); @@ -183,8 +184,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(); } @@ -204,20 +203,15 @@ class CollectionModel : public SimpleTreeModel { public slots: void SetFilterAge(const int age); - void SetFilterText(const QString &text); void SetFilterQueryMode(QueryOptions::QueryMode query_mode); - void Init(const bool async = true); + void Init(); 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); - private slots: + void ResetAsyncFinished(const SongList &songs, const int id = 0); // From CollectionBackend void SongsDeleted(const SongList &songs); void SongsSlightlyChanged(const SongList &songs); @@ -232,29 +226,19 @@ class CollectionModel : public SimpleTreeModel { void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); private: - // Provides some optimisations 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. - QueryResult RunQuery(CollectionItem *parent); - void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal); + QueryResult RunQuery(); + void PostQuery(const QueryResult &result); bool HasCompilations(const QSqlDatabase &db, const CollectionQuery &query); void BeginReset(); - // 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 InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q); - static void FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q); - // 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 *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 *ItemFromSong(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level); // 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); @@ -273,6 +257,7 @@ class CollectionModel : public SimpleTreeModel { CollectionBackend *backend_; Application *app_; CollectionDirectoryModel *dir_model_; + bool show_various_artists_; int total_song_count_; @@ -299,12 +284,13 @@ class CollectionModel : public SimpleTreeModel { static QNetworkDiskCache *sIconCache; + int init_id_; + int next_init_id_; int init_task_id_; bool use_pretty_covers_; bool show_dividers_; bool use_disk_cache_; - bool use_lazy_loading_; AlbumCoverLoaderOptions cover_loader_options_; diff --git a/src/collection/collectionquery.cpp b/src/collection/collectionquery.cpp index 15d58907..466b7440 100644 --- a/src/collection/collectionquery.cpp +++ b/src/collection/collectionquery.cpp @@ -40,66 +40,13 @@ QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {} -CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options) +CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QueryOptions &options) : QSqlQuery(db), songs_table_(songs_table), - fts_table_(fts_table), include_unavailable_(false), - join_with_fts_(false), duplicates_only_(false), limit_(-1) { - if (!options.filter().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 -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QStringList tokens(options.filter().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts)); -#else - QStringList tokens(options.filter().split(QRegularExpression("\\s+"), QString::SkipEmptyParts)); -#endif - QString query; - for (QString token : tokens) { - token.remove('('); - token.remove(')'); - token.remove('"'); - token.replace('-', ' '); - - if (token.contains(':')) { - // Only prefix fts if the token is a valid column name. - if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), Qt::CaseInsensitive)) { - // Account for multiple colons. - QString columntoken = token.section(':', 0, 0, QString::SectionIncludeTrailingSep); - QString subtoken = token.section(':', 1, -1); - subtoken.replace(":", " "); - subtoken = subtoken.trimmed(); - if (!subtoken.isEmpty()) { - if (!query.isEmpty()) query.append(" "); - query += "fts" + columntoken + "\"" + subtoken + "\"*"; - } - } - else { - token.replace(":", " "); - token = token.trimmed(); - if (!query.isEmpty()) query.append(" "); - query += "\"" + token + "\"*"; - } - } - else { - if (!query.isEmpty()) query.append(" "); - query += "\"" + token + "\"*"; - } - } - if (!query.isEmpty()) { - where_clauses_ << "fts.%fts_table_noprefix MATCH ?"; - bound_values_ << query; - join_with_fts_ = true; - } - } - if (options.max_age() != -1) { qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - options.max_age(); @@ -107,12 +54,6 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta bound_values_ << cutoff; } - // TODO: Currently you cannot use any QueryMode 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_ = options.query_mode() == QueryOptions::QueryMode_Duplicates; if (options.query_mode() == QueryOptions::QueryMode_Untagged) { @@ -181,22 +122,13 @@ void CollectionQuery::AddWhereArtist(const QVariant &value) { 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_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0); } bool CollectionQuery::Exec() { - QString sql; - - if (join_with_fts_) { - sql = QString("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_); - } - else { - sql = QString("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery()); - } + QString sql = QString("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery()); QStringList where_clauses(where_clauses_); if (!include_unavailable_) { @@ -210,8 +142,6 @@ bool CollectionQuery::Exec() { if (limit_ != -1) sql += " LIMIT " + QString::number(limit_); sql.replace("%songs_table", songs_table_); - sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1)); - sql.replace("%fts_table", fts_table_); prepare(sql); diff --git a/src/collection/collectionquery.h b/src/collection/collectionquery.h index 20c4c198..a2c431c1 100644 --- a/src/collection/collectionquery.h +++ b/src/collection/collectionquery.h @@ -40,7 +40,6 @@ struct QueryOptions { // - use the all songs table // - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table // - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty - // Please note that additional filtering based on FTS table (the filter attribute) won't work in Duplicates and Untagged modes. enum QueryMode { QueryMode_All, QueryMode_Duplicates, @@ -51,12 +50,6 @@ struct QueryOptions { bool Matches(const Song &song) const; - QString filter() const { return filter_; } - void set_filter(const QString &filter) { - filter_ = filter; - query_mode_ = QueryMode_All; - } - int max_age() const { return max_age_; } void set_max_age(int max_age) { max_age_ = max_age; } @@ -74,7 +67,7 @@ struct QueryOptions { class CollectionQuery : public QSqlQuery { public: - explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options = QueryOptions()); + explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QueryOptions &options = QueryOptions()); // Sets contents of SELECT clause on the query (list of columns to get). void SetColumnSpec(const QString &spec) { column_spec_ = spec; } @@ -103,7 +96,6 @@ class CollectionQuery : public QSqlQuery { 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_; } @@ -112,7 +104,6 @@ class CollectionQuery : public QSqlQuery { QSqlDatabase db_; QString songs_table_; - QString fts_table_; QString column_spec_; QString order_by_; @@ -120,7 +111,6 @@ class CollectionQuery : public QSqlQuery { 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 c19828d7..18348e08 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -123,11 +123,12 @@ #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/collectionquery.h" #include "collection/collectionview.h" -#include "collection/collectionviewcontainer.h" #include "playlist/playlist.h" #include "playlist/playlistbackend.h" #include "playlist/playlistcontainer.h" @@ -325,7 +326,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic 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), @@ -405,11 +406,11 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic // 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"; @@ -419,7 +420,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic 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_); @@ -679,7 +680,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr tray_ic QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this); QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig); collection_view_->filter_widget()->SetSettingsGroup(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 bdacfbbb..2b3537bb 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -69,6 +69,7 @@ class AlbumCoverManager; class Application; class ContextView; class CollectionViewContainer; +class CollectionFilter; class AlbumCoverChoiceController; class CommandlineOptions; #ifndef Q_OS_WIN @@ -371,7 +372,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/simpletreemodel.h b/src/core/simpletreemodel.h index 773962b6..03b1fc6a 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_; }; @@ -111,20 +106,6 @@ bool SimpleTreeModel::hasChildren(const QModelIndex &parent) const { return true; } -template -bool SimpleTreeModel::canFetchMore(const QModelIndex &parent) const { - T *item = IndexToItem(parent); - return !item->lazy_loaded; -} - -template -void SimpleTreeModel::fetchMore(const QModelIndex &parent) { - T *item = IndexToItem(parent); - if (!item->lazy_loaded) { - LazyPopulate(item); - } -} - template void SimpleTreeModel::BeginInsert(T *parent, int start, int end) { if (end == -1) end = start; diff --git a/src/core/song.cpp b/src/core/song.cpp index 1d155af5..17b2143f 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -126,19 +126,15 @@ const QString Song::kColumnSpec = Song::kColumns.join(", "); const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", "); const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(", "); -const QStringList Song::kFtsColumns = QStringList() << "ftstitle" - << "ftsalbum" - << "ftsartist" - << "ftsalbumartist" - << "ftscomposer" - << "ftsperformer" - << "ftsgrouping" - << "ftsgenre" - << "ftscomment"; - -const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", "); -const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", "); -const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(", "); +const QStringList Song::kSearchColumns = QStringList() << "title" + << "album" + << "artist" + << "albumartist" + << "composer" + << "performer" + << "grouping" + << "genre" + << "comment"; const QString Song::kManuallyUnsetCover = "(unset)"; const QString Song::kEmbeddedCover = "(embedded)"; @@ -1343,20 +1339,6 @@ void Song::BindToQuery(SqlQuery *query) const { } -void Song::BindToFtsQuery(SqlQuery *query) const { - - query->BindValue(":ftstitle", d->title_); - query->BindValue(":ftsalbum", d->album_); - query->BindValue(":ftsartist", d->artist_); - query->BindValue(":ftsalbumartist", d->albumartist_); - query->BindValue(":ftscomposer", d->composer_); - query->BindValue(":ftsperformer", d->performer_); - query->BindValue(":ftsgrouping", d->grouping_); - query->BindValue(":ftsgenre", d->genre_); - query->BindValue(":ftscomment", d->comment_); - -} - QString Song::PrettyTitle() const { QString title(d->title_); diff --git a/src/core/song.h b/src/core/song.h index 03ba5220..79fb70be 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -120,10 +120,7 @@ class Song { static const QString kBindSpec; static const QString kUpdateSpec; - static const QStringList kFtsColumns; - static const QString kFtsColumnSpec; - static const QString kFtsBindSpec; - static const QString kFtsUpdateSpec; + static const QStringList kSearchColumns; static const QString kManuallyUnsetCover; static const QString kEmbeddedCover; @@ -192,7 +189,6 @@ class Song { // Save void BindToQuery(SqlQuery *query) const; - void BindToFtsQuery(SqlQuery *query) const; void ToXesam(QVariantMap *map) const; void ToProtobuf(spb::tagreader::SongMetadata *pb) const; diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index 42fc654a..1d3dae4d 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_->db()->Mutex()); QSqlDatabase db(collection_->db()->Connect()); - CollectionQuery query(db, collection_->songs_table(), collection_->fts_table()); + CollectionQuery query(db, collection_->songs_table()); query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec); query.AddWhere("url", url.toEncoded()); diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index e63301f0..30522637 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -894,7 +894,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("ROWID," + Song::kColumnSpec); q.AddWhere("album", idx.data(Role_Album).toString()); q.SetOrderBy("disc, track, title"); diff --git a/src/device/connecteddevice.cpp b/src/device/connecteddevice.cpp index 63029a1e..a02c9a33 100644 --- a/src/device/connecteddevice.cpp +++ b/src/device/connecteddevice.cpp @@ -63,7 +63,6 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS app_->task_manager(), Song::Source_Device, QString("device_%1_songs").arg(database_id), - QString("device_%1_fts").arg(database_id), QString("device_%1_directories").arg(database_id), QString("device_%1_subdirectories").arg(database_id)); @@ -167,4 +166,3 @@ void ConnectedDevice::BackendTotalSongCountUpdated(int count) { song_count_ = count; emit SongCountUpdated(count); } - diff --git a/src/device/devicedatabasebackend.cpp b/src/device/devicedatabasebackend.cpp index 48a6dd13..85c9978c 100644 --- a/src/device/devicedatabasebackend.cpp +++ b/src/device/devicedatabasebackend.cpp @@ -171,7 +171,7 @@ void DeviceDatabaseBackend::RemoveDevice(const int id) { // Remove the songs tables for the device db.exec(QString("DROP TABLE device_%1_songs").arg(id)); - db.exec(QString("DROP TABLE device_%1_fts").arg(id)); + db.exec(QString("DROP TABLE IF EXISTS device_%1_fts").arg(id)); db.exec(QString("DROP TABLE device_%1_directories").arg(id)); db.exec(QString("DROP TABLE device_%1_subdirectories").arg(id)); diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index 3003ba72..aefd1d86 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -32,10 +32,10 @@ #include "settings/settingsdialog.h" #include "internetsearchview.h" -class QSortFilterProxyModel; class Application; class CollectionBackend; class CollectionModel; +class CollectionFilter; class InternetService : public QObject { Q_OBJECT @@ -68,9 +68,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 b768e5ea..b647a993 100644 --- a/src/internet/internetsongsview.cpp +++ b/src/internet/internetsongsview.cpp @@ -51,10 +51,10 @@ InternetSongsView::InternetSongsView(Application *app, InternetService *service, 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("configure"), tr("Configure %1...").arg(Song::TextForSource(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 9f096b1c..10a78316 100644 --- a/src/internet/internettabsview.cpp +++ b/src/internet/internettabsview.cpp @@ -37,6 +37,7 @@ #include "core/iconloader.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "collection/collectionfilterwidget.h" #include "internetservice.h" #include "internettabsview.h" @@ -65,11 +66,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, c 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("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); @@ -97,11 +98,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, c 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("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); @@ -129,11 +130,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, c 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("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/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index 6892c434..2255fda7 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include "core/application.h" @@ -48,6 +47,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" @@ -67,10 +67,6 @@ const char *QobuzService::kArtistsSongsTable = "qobuz_artists_songs"; const char *QobuzService::kAlbumsSongsTable = "qobuz_albums_songs"; const char *QobuzService::kSongsTable = "qobuz_songs"; -const char *QobuzService::kArtistsSongsFtsTable = "qobuz_artists_songs_fts"; -const char *QobuzService::kAlbumsSongsFtsTable = "qobuz_albums_songs_fts"; -const char *QobuzService::kSongsFtsTable = "qobuz_songs_fts"; - QobuzService::QobuzService(Application *app, QObject *parent) : InternetService(Song::Source_Qobuz, "Qobuz", "qobuz", QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page_Qobuz, app, parent), app_(app), @@ -82,9 +78,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)), @@ -110,37 +106,37 @@ QobuzService::QobuzService(Application *app, QObject *parent) artists_collection_backend_ = new CollectionBackend(); artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kArtistsSongsTable, kArtistsSongsFtsTable); + artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kArtistsSongsTable); albums_collection_backend_ = new CollectionBackend(); albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kAlbumsSongsTable, kAlbumsSongsFtsTable); + albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kAlbumsSongsTable); songs_collection_backend_ = new CollectionBackend(); songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kSongsTable, kSongsFtsTable); + songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, 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 82287af2..35631c7b 100644 --- a/src/qobuz/qobuzservice.h +++ b/src/qobuz/qobuzservice.h @@ -43,7 +43,6 @@ class QTimer; class QNetworkReply; -class QSortFilterProxyModel; class Application; class NetworkAccessManager; class QobuzUrlHandler; @@ -52,6 +51,7 @@ class QobuzFavoriteRequest; class QobuzStreamURLRequest; class CollectionBackend; class CollectionModel; +class CollectionFilter; class QobuzService : public InternetService { Q_OBJECT @@ -103,9 +103,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; @@ -158,10 +158,6 @@ class QobuzService : public InternetService { static const char *kAlbumsSongsTable; static const char *kSongsTable; - static const char *kArtistsSongsFtsTable; - static const char *kAlbumsSongsFtsTable; - static const char *kSongsFtsTable; - Application *app_; NetworkAccessManager *network_; QobuzUrlHandler *url_handler_; @@ -174,9 +170,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/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index 6441bfa6..2bfb0e85 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include "core/utilities.h" #include "core/application.h" @@ -51,6 +50,7 @@ #include "core/song.h" #include "collection/collectionbackend.h" #include "collection/collectionmodel.h" +#include "collection/collectionfilter.h" #include "subsonicservice.h" #include "subsonicurlhandler.h" #include "subsonicrequest.h" @@ -62,7 +62,6 @@ const Song::Source SubsonicService::kSource = Song::Source_Subsonic; const char *SubsonicService::kClientName = "Strawberry"; const char *SubsonicService::kApiVersion = "1.11.0"; const char *SubsonicService::kSongsTable = "subsonic_songs"; -const char *SubsonicService::kSongsFtsTable = "subsonic_songs_fts"; const int SubsonicService::kMaxRedirects = 3; SubsonicService::SubsonicService(Application *app, QObject *parent) @@ -71,7 +70,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), @@ -84,16 +83,16 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) collection_backend_ = new CollectionBackend(); collection_backend_->moveToThread(app_->database()->thread()); - collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Subsonic, kSongsTable, kSongsFtsTable); + collection_backend_->Init(app_->database(),app->task_manager(), Song::Source_Subsonic, 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 c7408d4b..339d9952 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 { CollectionBackend *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_; } CollectionBackend *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); @@ -104,7 +104,6 @@ class SubsonicService : public InternetService { void PingError(const QString &error = QString(), const QVariant &debug = QVariant()); static const char *kSongsTable; - static const char *kSongsFtsTable; static const int kMaxRedirects; Application *app_; @@ -113,7 +112,7 @@ class SubsonicService : public InternetService { CollectionBackend *collection_backend_; CollectionModel *collection_model_; - QSortFilterProxyModel *collection_sort_model_; + CollectionFilter *collection_filter_model_; std::shared_ptr songs_request_; std::shared_ptr scrobble_request_; diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 9ffedf5c..1a510187 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -52,6 +52,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" @@ -75,10 +76,6 @@ const char *TidalService::kArtistsSongsTable = "tidal_artists_songs"; const char *TidalService::kAlbumsSongsTable = "tidal_albums_songs"; const char *TidalService::kSongsTable = "tidal_songs"; -const char *TidalService::kArtistsSongsFtsTable = "tidal_artists_songs_fts"; -const char *TidalService::kAlbumsSongsFtsTable = "tidal_albums_songs_fts"; -const char *TidalService::kSongsFtsTable = "tidal_songs_fts"; - using namespace std::chrono_literals; TidalService::TidalService(Application *app, QObject *parent) @@ -92,9 +89,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)), @@ -125,37 +122,37 @@ TidalService::TidalService(Application *app, QObject *parent) artists_collection_backend_ = new CollectionBackend(); artists_collection_backend_->moveToThread(app_->database()->thread()); - artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kArtistsSongsTable, kArtistsSongsFtsTable); + artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kArtistsSongsTable); albums_collection_backend_ = new CollectionBackend(); albums_collection_backend_->moveToThread(app_->database()->thread()); - albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kAlbumsSongsTable, kAlbumsSongsFtsTable); + albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kAlbumsSongsTable); songs_collection_backend_ = new CollectionBackend(); songs_collection_backend_->moveToThread(app_->database()->thread()); - songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kSongsTable, kSongsFtsTable); + songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, 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 04941e1f..e01c8ff9 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -43,7 +43,6 @@ #include "internet/internetsearchview.h" #include "settings/tidalsettingspage.h" -class QSortFilterProxyModel; class QNetworkReply; class QTimer; @@ -55,6 +54,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; @@ -175,10 +175,6 @@ class TidalService : public InternetService { static const char *kAlbumsSongsTable; static const char *kSongsTable; - static const char *kArtistsSongsFtsTable; - static const char *kAlbumsSongsFtsTable; - static const char *kSongsFtsTable; - Application *app_; NetworkAccessManager *network_; TidalUrlHandler *url_handler_; @@ -191,9 +187,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_;