Remove lazy loading and FTS

Fixes #392
This commit is contained in:
Jonas Kvinge 2021-06-27 22:54:08 +02:00
parent 26d3e8371f
commit ec0baa54cb
32 changed files with 546 additions and 548 deletions

View File

@ -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 <QSqlDatabase>
#include <QSqlQuery>
int main() {
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
db.setDatabaseName(\":memory:\");
if (!db.open()) { return 1; }
QSqlQuery q(db);
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
if (!q.exec()) return 1;
}
"
SQLITE_FTS5_TEST
)
endif()
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()

View File

@ -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/)

View File

@ -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

View File

@ -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);

View File

@ -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<int>(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<int>(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 QList<QStri
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("ROWID, " + Song::kColumnSpec);
query.AddWhere("album", album);
if (!artist.isEmpty()) query.AddWhere("artist", artist);
@ -1817,15 +1770,6 @@ void CollectionBackend::DeleteAll() {
}
}
{
SqlQuery q(db);
q.prepare("DELETE FROM " + fts_table_);
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
t.Commit();
}

View File

@ -75,12 +75,13 @@ class CollectionBackendInterface : public QObject {
using AlbumList = QList<Album>;
virtual QString songs_table() const = 0;
virtual QString fts_table() const = 0;
virtual Song::Source source() const = 0;
virtual 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_;
};

View File

@ -0,0 +1,196 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QSortFilterProxyModel>
#include <QVariant>
#include <QString>
#include "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<CollectionModel*>(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<QString, QString> 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<QString, QString> &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<QString, QString> &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<QString, QString> &tags) const {
Song &metadata = item->metadata;
for (QMap<QString, QString>::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<QString, QString> &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);
}

View File

@ -0,0 +1,50 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLLECTIONFILTER_H
#define COLLECTIONFILTER_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
#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<QString, QString> &tags) const;
bool TagMatches(CollectionItem *item, const CollectionModel::GroupBy group_by, const QMap<QString, QString> &tags) const;
bool ItemMatches(CollectionModel *model, CollectionItem *item, const QMap<QString, QString> &tags, const QString &filter) const;
bool ChildrenMatches(CollectionModel *model, CollectionItem *item, const QMap<QString, QString> &tags, const QString &filter) const;
};
#endif // COLLECTIONFILTER_H

View File

@ -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("<html><head/><body><p>") +
@ -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());
}
}

View File

@ -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_;

View File

@ -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<CollectionModel*>(this)->LazyPopulate(const_cast<CollectionItem*>(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<CollectionModel::QueryResult> future = QtConcurrent::run(&CollectionModel::RunQuery, this, root_);
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(&CollectionModel::RunQuery, this);
#else
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_);
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery);
#endif
QFutureWatcher<CollectionModel::QueryResult> *watcher = new QFutureWatcher<CollectionModel::QueryResult>();
QObject::connect(watcher, &QFutureWatcher<CollectionModel::QueryResult>::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<CollectionModel::QueryResult> *watcher = static_cast<QFutureWatcher<CollectionModel::QueryResult>*>(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<int>(parent->children.count()), static_cast<int>(parent->children.count()));
if (signal) {
beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(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<QUrl> *urls, Son
switch (item->type) {
case CollectionItem::Type_Container: {
const_cast<CollectionModel*>(this)->LazyPopulate(item);
QList<CollectionItem*> 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<bool> 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<CollectionModel*>(this)->LazyPopulate(const_cast<CollectionItem*>(item), false);
for (CollectionItem *child : item->children) {
ExpandAll(child);
}

View File

@ -151,7 +151,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
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<CollectionItem> {
// 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<CollectionItem> {
}
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<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
int divider_nodes_count() const { return divider_nodes_.count(); }
@ -204,20 +203,15 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
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<CollectionItem> {
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<CollectionItem> {
CollectionBackend *backend_;
Application *app_;
CollectionDirectoryModel *dir_model_;
bool show_various_artists_;
int total_song_count_;
@ -299,12 +284,13 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
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_;

View File

@ -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);

View File

@ -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_;
};

View File

@ -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<SystemTrayIcon> 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<SystemTrayIcon> 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<SystemTrayIcon> 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<SystemTrayIcon> 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);

View File

@ -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_;

View File

@ -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<T>::hasChildren(const QModelIndex &parent) const {
return true;
}
template<typename T>
bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const {
T *item = IndexToItem(parent);
return !item->lazy_loaded;
}
template<typename T>
void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) {
T *item = IndexToItem(parent);
if (!item->lazy_loaded) {
LazyPopulate(item);
}
}
template<typename T>
void SimpleTreeModel<T>::BeginInsert(T *parent, int start, int end) {
if (end == -1) end = start;

View File

@ -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_);

View File

@ -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;

View File

@ -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());

View File

@ -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");

View File

@ -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);
}

View File

@ -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));

View File

@ -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() {}

View File

@ -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);

View File

@ -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);

View File

@ -35,7 +35,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QSortFilterProxyModel>
#include <QSslError>
#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

View File

@ -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_;

View File

@ -40,7 +40,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QSortFilterProxyModel>
#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();

View File

@ -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<SubsonicRequest> songs_request_;
std::shared_ptr<SubsonicScrobbleRequest> scrobble_request_;

View File

@ -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

View File

@ -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_;