parent
8e4b4d6e41
commit
5663b46055
|
@ -487,25 +487,6 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||
"
|
||||
QT_SQLITE_TEST
|
||||
)
|
||||
if(QT_SQLITE_TEST)
|
||||
# Check that we have sqlite3 with FTS5
|
||||
check_cxx_source_runs("
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
int main() {
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
||||
db.setDatabaseName(\":memory:\");
|
||||
if (!db.open()) { return 1; }
|
||||
QSqlQuery q(db);
|
||||
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
|
||||
if (!q.exec()) return 1;
|
||||
}
|
||||
"
|
||||
SQLITE_FTS5_TEST
|
||||
)
|
||||
endif()
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
endif()
|
||||
|
||||
# Set up definitions
|
||||
|
@ -564,11 +545,7 @@ if(QT_VERSION_MAJOR EQUAL 5)
|
|||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
if(QT_SQLITE_TEST)
|
||||
if(NOT SQLITE_FTS5_TEST)
|
||||
message(WARNING "sqlite must be enabled with FTS5. See: https://www.sqlite.org/fts5.html")
|
||||
endif()
|
||||
else()
|
||||
if(NOT QT_SQLITE_TEST)
|
||||
message(WARNING "The Qt sqlite driver test failed.")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -76,8 +76,13 @@ To build Strawberry from source you need the following installed on your system
|
|||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
||||
* [Boost](https://www.boost.org/)
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
<<<<<<< HEAD
|
||||
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||
=======
|
||||
* [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](https://www.sqlite.org)
|
||||
>>>>>>> ec0baa54 (Remove lazy loading and FTS)
|
||||
* [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/)
|
||||
|
|
|
@ -89,6 +89,7 @@ set(SOURCES
|
|||
collection/collectiondirectorymodel.cpp
|
||||
collection/collectionfilteroptions.cpp
|
||||
collection/collectionfilterwidget.cpp
|
||||
collection/collectionfilter.cpp
|
||||
collection/collectionplaylistitem.cpp
|
||||
collection/collectionquery.cpp
|
||||
collection/collectionqueryoptions.cpp
|
||||
|
@ -342,6 +343,7 @@ set(HEADERS
|
|||
collection/collectionviewcontainer.h
|
||||
collection/collectiondirectorymodel.h
|
||||
collection/collectionfilterwidget.h
|
||||
collection/collectionfilter.h
|
||||
collection/savedgroupingmanager.h
|
||||
collection/groupbydialog.h
|
||||
|
||||
|
|
|
@ -70,7 +70,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);
|
||||
|
||||
|
|
|
@ -74,16 +74,13 @@ CollectionBackend::~CollectionBackend() {
|
|||
|
||||
}
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
db_ = db;
|
||||
task_manager_ = task_manager;
|
||||
source_ = source;
|
||||
songs_table_ = songs_table;
|
||||
dirs_table_ = dirs_table;
|
||||
subdirs_table_ = subdirs_table;
|
||||
fts_table_ = fts_table;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::Close() {
|
||||
|
@ -121,6 +118,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, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -613,17 +639,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
song.BindToFtsQuery(&q);
|
||||
q.BindValue(":id", song.id());
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deleted_songs << old_song;
|
||||
added_songs << song;
|
||||
|
||||
|
@ -652,17 +667,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
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;
|
||||
|
||||
|
@ -687,17 +691,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, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
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;
|
||||
|
@ -732,7 +725,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;
|
||||
|
@ -758,16 +751,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
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);
|
||||
|
@ -793,17 +776,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, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
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;
|
||||
|
@ -823,15 +795,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;
|
||||
}
|
||||
}
|
||||
|
@ -852,11 +815,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()) {
|
||||
|
@ -873,25 +835,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);
|
||||
|
@ -907,14 +861,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;
|
||||
}
|
||||
}
|
||||
|
@ -938,7 +891,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const CollectionFil
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, filter_options);
|
||||
CollectionQuery query(db, songs_table_, filter_options);
|
||||
query.SetColumnSpec("DISTINCT " + column);
|
||||
query.AddCompilationRequirement(false);
|
||||
|
||||
|
@ -966,13 +919,13 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOpt
|
|||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Albums with 'albumartist' field set:
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec("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", "", "!=");
|
||||
|
@ -1013,7 +966,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);
|
||||
|
||||
|
@ -1031,7 +984,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);
|
||||
|
@ -1050,7 +1003,7 @@ SongList CollectionBackend::GetSongsByAlbum(const QString &album, const Collecti
|
|||
QSqlDatabase db(db_->Connect());
|
||||
QMutexLocker l(db_->Mutex());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere("album", album);
|
||||
|
||||
|
@ -1327,7 +1280,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Coll
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
CollectionQuery query(db, songs_table_, opt);
|
||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
query.AddCompilationRequirement(true);
|
||||
query.AddWhere("album", album);
|
||||
|
@ -1464,7 +1417,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, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset");
|
||||
query.SetOrderBy("effective_albumartist, album, url");
|
||||
|
||||
|
@ -1555,7 +1508,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||
ret.album = album;
|
||||
ret.album_artist = effective_albumartist;
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_, CollectionFilterOptions());
|
||||
query.SetColumnSpec("url, art_embedded, art_automatic, art_manual, art_unset");
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
|
@ -1591,7 +1544,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
|||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
@ -1653,7 +1606,8 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
@ -1711,7 +1665,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
@ -1768,7 +1722,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
|||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
CollectionQuery query(db, songs_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
@ -1823,7 +1777,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);
|
||||
|
@ -1985,15 +1939,6 @@ void CollectionBackend::DeleteAll() {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare("DELETE FROM " + fts_table_);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
t.Commit();
|
||||
}
|
||||
|
||||
|
|
|
@ -80,12 +80,13 @@ class CollectionBackendInterface : public QObject {
|
|||
using AlbumList = QList<Album>;
|
||||
|
||||
virtual QString songs_table() const = 0;
|
||||
virtual QString fts_table() const = 0;
|
||||
|
||||
virtual Song::Source source() const = 0;
|
||||
|
||||
virtual SharedPtr<Database> db() const = 0;
|
||||
|
||||
virtual void GetAllSongsAsync(const int id = 0) = 0;
|
||||
|
||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||
virtual void LoadDirectoriesAsync() = 0;
|
||||
|
||||
|
@ -144,7 +145,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
|
||||
~CollectionBackend();
|
||||
|
||||
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
||||
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
||||
|
||||
void Close();
|
||||
|
||||
void ExitAsync();
|
||||
|
@ -156,10 +158,11 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
SharedPtr<Database> db() const override { return db_; }
|
||||
|
||||
QString songs_table() const override { return songs_table_; }
|
||||
QString fts_table() const override { return fts_table_; }
|
||||
QString dirs_table() const { return dirs_table_; }
|
||||
QString subdirs_table() const { return subdirs_table_; }
|
||||
|
||||
void GetAllSongsAsync(const int id = 0) override;
|
||||
|
||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||
void LoadDirectoriesAsync() override;
|
||||
|
||||
|
@ -233,6 +236,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
|
||||
public slots:
|
||||
void Exit();
|
||||
void GetAllSongs(const int id);
|
||||
void LoadDirectories();
|
||||
void UpdateTotalSongCount();
|
||||
void UpdateTotalArtistCount();
|
||||
|
@ -271,6 +275,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||
|
||||
void GotSongs(const SongList &songs, const int id);
|
||||
void SongsDiscovered(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
@ -315,7 +320,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
QString songs_table_;
|
||||
QString dirs_table_;
|
||||
QString subdirs_table_;
|
||||
QString fts_table_;
|
||||
QThread *original_thread_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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::GroupBy::GroupByCount:
|
||||
break;
|
||||
}
|
||||
|
||||
QString value;
|
||||
if (!tag.isEmpty() && tags.contains(tag)) {
|
||||
value = tags[tag];
|
||||
}
|
||||
|
||||
return !value.isEmpty() && item->DisplayText().contains(value, Qt::CaseInsensitive);
|
||||
|
||||
}
|
|
@ -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
|
|
@ -48,6 +48,8 @@
|
|||
#include "core/logging.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "collectionfilter.h"
|
||||
#include "collectionquery.h"
|
||||
#include "savedgroupingmanager.h"
|
||||
#include "collectionfilterwidget.h"
|
||||
#include "groupbydialog.h"
|
||||
|
@ -66,14 +68,15 @@ 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_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegularExpression("\\bfts"), "");
|
||||
available_fields += QString(", ") + Song::kNumericalColumns.join(", ");
|
||||
QString available_fields = Song::kTextSearchColumns.join(", ");
|
||||
available_fields += QString(", ") + Song::kNumericalSearchColumns.join(", ");
|
||||
|
||||
ui_->search_field->setToolTip(
|
||||
QString("<html><head/><body><p>") +
|
||||
|
@ -154,7 +157,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);
|
||||
|
@ -167,6 +170,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
|
|||
}
|
||||
|
||||
model_ = model;
|
||||
filter_ = filter;
|
||||
|
||||
// Connect signals
|
||||
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
|
||||
|
@ -215,6 +219,10 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) {
|
|||
|
||||
}
|
||||
|
||||
void CollectionFilterWidget::setFilter(CollectionFilter *filter) {
|
||||
filter_ = filter;
|
||||
}
|
||||
|
||||
void CollectionFilterWidget::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
|
@ -516,9 +524,6 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
|||
|
||||
void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
||||
|
||||
// Searching with one or two characters can be very expensive on the database even with FTS,
|
||||
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
||||
// so if the user is typing the first few characters of something it will be quicker.
|
||||
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||
|
||||
if (delay) {
|
||||
|
@ -533,9 +538,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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class QKeyEvent;
|
|||
|
||||
class GroupByDialog;
|
||||
class SavedGroupingManager;
|
||||
class CollectionFilter;
|
||||
class Ui_CollectionFilterWidget;
|
||||
|
||||
class CollectionFilterWidget : public QWidget {
|
||||
|
@ -58,7 +59,9 @@ class CollectionFilterWidget : public QWidget {
|
|||
AlwaysDelayed,
|
||||
};
|
||||
|
||||
void Init(CollectionModel *model);
|
||||
void Init(CollectionModel *model, CollectionFilter *filter);
|
||||
|
||||
void setFilter(CollectionFilter *filter);
|
||||
|
||||
static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent);
|
||||
|
||||
|
@ -94,7 +97,6 @@ class CollectionFilterWidget : public QWidget {
|
|||
void UpPressed();
|
||||
void DownPressed();
|
||||
void ReturnPressed();
|
||||
void Filter(const QString &text);
|
||||
|
||||
protected:
|
||||
void keyReleaseEvent(QKeyEvent *e) override;
|
||||
|
@ -115,6 +117,7 @@ class CollectionFilterWidget : public QWidget {
|
|||
private:
|
||||
Ui_CollectionFilterWidget *ui_;
|
||||
CollectionModel *model_;
|
||||
CollectionFilter *filter_;
|
||||
|
||||
GroupByDialog *group_by_dialog_;
|
||||
SavedGroupingManager *groupings_manager_;
|
||||
|
|
|
@ -92,13 +92,12 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
|||
separate_albums_by_grouping_(false),
|
||||
artist_icon_(IconLoader::Load("folder-sound")),
|
||||
album_icon_(IconLoader::Load("cdcase")),
|
||||
init_id_(-1),
|
||||
next_init_id_(-1),
|
||||
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;
|
||||
|
@ -120,6 +119,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
|
|||
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);
|
||||
|
@ -187,27 +187,21 @@ 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();
|
||||
// Show a loading indicator in the model.
|
||||
CollectionItem *loading = new CollectionItem(CollectionItem::Type_LoadingIndicator, root_);
|
||||
loading->display_text = tr("Loading...");
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
|
||||
// Show a loading indicator in the status bar too.
|
||||
if (app_) {
|
||||
init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs"));
|
||||
}
|
||||
|
||||
ResetAsync();
|
||||
}
|
||||
else {
|
||||
Reset();
|
||||
// Show a loading indicator in the status bar too.
|
||||
if (app_) {
|
||||
init_task_id_ = app_->task_manager()->StartTask(tr("Loading songs"));
|
||||
}
|
||||
|
||||
ResetAsync();
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::SongsDiscovered(const SongList &songs) {
|
||||
|
@ -257,10 +251,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));
|
||||
|
@ -524,13 +515,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
|
||||
|
@ -788,10 +772,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()) {
|
||||
|
@ -824,7 +804,7 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
|
|||
|
||||
bool CollectionModel::HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options) {
|
||||
|
||||
CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), filter_options);
|
||||
CollectionQuery q(db, backend_->songs_table(), filter_options);
|
||||
q.SetColumnSpec(query_options.column_spec());
|
||||
for (const CollectionQueryOptions::Where &where_clauses : query_options.where_clauses()) {
|
||||
q.AddWhere(where_clauses.column, where_clauses.value, where_clauses.op);
|
||||
|
@ -849,14 +829,6 @@ CollectionQueryOptions CollectionModel::PrepareQuery(CollectionItem *parent) {
|
|||
|
||||
CollectionQueryOptions query_options;
|
||||
|
||||
// Initialize the query. child_group_by says what type of thing we want (artists, songs, etc.)
|
||||
SetQueryColumnSpec(child_group_by, separate_albums_by_grouping_, &query_options);
|
||||
|
||||
// Walk up through the item's parents adding filters as necessary
|
||||
for (CollectionItem *p = parent; p && p->type == CollectionItem::Type_Container; p = p->parent) {
|
||||
AddQueryWhere(group_by_[p->container_level], separate_albums_by_grouping_, p, &query_options);
|
||||
}
|
||||
|
||||
// Artists GroupBy is special - we don't want compilation albums appearing
|
||||
if (show_various_artists_ && IsArtistGroupBy(child_group_by)) {
|
||||
query_options.set_query_have_compilations(true);
|
||||
|
@ -868,30 +840,12 @@ CollectionQueryOptions CollectionModel::PrepareQuery(CollectionItem *parent) {
|
|||
|
||||
CollectionModel::QueryResult CollectionModel::RunQuery(const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options) {
|
||||
|
||||
QMutexLocker l(backend_->db()->Mutex());
|
||||
|
||||
QueryResult result;
|
||||
{
|
||||
|
||||
QMutexLocker l(backend_->db()->Mutex());
|
||||
QSqlDatabase db(backend_->db()->Connect());
|
||||
// Add the special Various artists node
|
||||
if (query_options.query_have_compilations() && HasCompilations(db, filter_options, query_options)) {
|
||||
result.create_va = true;
|
||||
}
|
||||
|
||||
CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), filter_options);
|
||||
q.SetColumnSpec(query_options.column_spec());
|
||||
for (const CollectionQueryOptions::Where &where_clauses : query_options.where_clauses()) {
|
||||
q.AddWhere(where_clauses.column, where_clauses.value, where_clauses.op);
|
||||
}
|
||||
|
||||
if (result.create_va) {
|
||||
q.AddCompilationRequirement(false);
|
||||
}
|
||||
else if (query_options.compilation_requirement() != CollectionQueryOptions::CompilationRequirement::None) {
|
||||
q.AddCompilationRequirement(query_options.compilation_requirement() == CollectionQueryOptions::CompilationRequirement::On);
|
||||
}
|
||||
|
||||
CollectionQuery q(db, backend_->songs_table(), filter_options);
|
||||
q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
if (q.Exec()) {
|
||||
while (q.Next()) {
|
||||
result.rows << SqlRow(q);
|
||||
|
@ -900,7 +854,6 @@ CollectionModel::QueryResult CollectionModel::RunQuery(const CollectionFilterOpt
|
|||
else {
|
||||
backend_->ReportErrors(q);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != thread() && QThread::currentThread() != backend_->thread()) {
|
||||
|
@ -911,43 +864,59 @@ CollectionModel::QueryResult CollectionModel::RunQuery(const CollectionFilterOpt
|
|||
|
||||
}
|
||||
|
||||
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 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 (!filter_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, separate_albums_by_grouping_, song));
|
||||
|
||||
// Does it exist already?
|
||||
if (!container_nodes_[i].contains(key)) {
|
||||
// Create the container
|
||||
container_nodes_[i].insert(key, ItemFromSong(type, separate_albums_by_grouping_, false, i == 0, container, song, i));
|
||||
}
|
||||
container = container_nodes_[i][key];
|
||||
}
|
||||
|
||||
}
|
||||
song_nodes_.insert(song.id(), ItemFromSong(GroupBy::None, separate_albums_by_grouping_, false, false, container, song, -1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) {
|
||||
|
||||
if (parent->lazy_loaded) return;
|
||||
parent->lazy_loaded = true;
|
||||
|
||||
CollectionQueryOptions query_options = PrepareQuery(parent);
|
||||
QueryResult result = RunQuery(filter_options_, query_options);
|
||||
PostQuery(parent, result, signal);
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::ResetAsync() {
|
||||
|
||||
CollectionQueryOptions query_options = PrepareQuery(root_);
|
||||
|
@ -963,6 +932,24 @@ 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());
|
||||
|
@ -972,7 +959,7 @@ void CollectionModel::ResetAsyncQueryFinished() {
|
|||
BeginReset();
|
||||
root_->lazy_loaded = true;
|
||||
|
||||
PostQuery(root_, result, false);
|
||||
PostQuery(result);
|
||||
|
||||
if (init_task_id_ != -1) {
|
||||
if (app_) {
|
||||
|
@ -1007,213 +994,10 @@ void CollectionModel::Reset() {
|
|||
|
||||
BeginReset();
|
||||
|
||||
// Populate top level
|
||||
LazyPopulate(root_, false);
|
||||
|
||||
endResetModel();
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options) {
|
||||
|
||||
// Say what group_by of thing we want to get back from the database.
|
||||
switch (group_by) {
|
||||
case GroupBy::AlbumArtist:
|
||||
query_options->set_column_spec("DISTINCT effective_albumartist");
|
||||
break;
|
||||
case GroupBy::Artist:
|
||||
query_options->set_column_spec("DISTINCT artist");
|
||||
break;
|
||||
case GroupBy::Album:{
|
||||
QString query("DISTINCT album, album_id");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::AlbumDisc:{
|
||||
QString query("DISTINCT album, album_id, disc");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::YearAlbum:{
|
||||
QString query("DISTINCT year, album, album_id");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::YearAlbumDisc:{
|
||||
QString query("DISTINCT year, album, album_id, disc");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbum:{
|
||||
QString query("DISTINCT year, originalyear, album, album_id");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbumDisc:{
|
||||
QString query("DISTINCT year, originalyear, album, album_id, disc");
|
||||
if (separate_albums_by_grouping) query.append(", grouping");
|
||||
query_options->set_column_spec(query);
|
||||
break;
|
||||
}
|
||||
case GroupBy::Disc:
|
||||
query_options->set_column_spec("DISTINCT disc");
|
||||
break;
|
||||
case GroupBy::Year:
|
||||
query_options->set_column_spec("DISTINCT year");
|
||||
break;
|
||||
case GroupBy::OriginalYear:
|
||||
query_options->set_column_spec("DISTINCT effective_originalyear");
|
||||
break;
|
||||
case GroupBy::Genre:
|
||||
query_options->set_column_spec("DISTINCT genre");
|
||||
break;
|
||||
case GroupBy::Composer:
|
||||
query_options->set_column_spec("DISTINCT composer");
|
||||
break;
|
||||
case GroupBy::Performer:
|
||||
query_options->set_column_spec("DISTINCT performer");
|
||||
break;
|
||||
case GroupBy::Grouping:
|
||||
query_options->set_column_spec("DISTINCT grouping");
|
||||
break;
|
||||
case GroupBy::FileType:
|
||||
query_options->set_column_spec("DISTINCT filetype");
|
||||
break;
|
||||
case GroupBy::Format:
|
||||
query_options->set_column_spec("DISTINCT filetype, samplerate, bitdepth");
|
||||
break;
|
||||
case GroupBy::Samplerate:
|
||||
query_options->set_column_spec("DISTINCT samplerate");
|
||||
break;
|
||||
case GroupBy::Bitdepth:
|
||||
query_options->set_column_spec("DISTINCT bitdepth");
|
||||
break;
|
||||
case GroupBy::Bitrate:
|
||||
query_options->set_column_spec("DISTINCT bitrate");
|
||||
break;
|
||||
case GroupBy::None:
|
||||
case GroupBy::GroupByCount:
|
||||
query_options->set_column_spec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options) {
|
||||
|
||||
// Say how we want the query to be filtered. This is done once for each parent going up the tree.
|
||||
|
||||
switch (group_by) {
|
||||
case GroupBy::AlbumArtist:
|
||||
if (IsCompilationArtistNode(item)) {
|
||||
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::On);
|
||||
}
|
||||
else {
|
||||
// Don't duplicate compilations outside the Various artists node
|
||||
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off);
|
||||
query_options->AddWhere("effective_albumartist", item->metadata.effective_albumartist());
|
||||
}
|
||||
break;
|
||||
case GroupBy::Artist:
|
||||
if (IsCompilationArtistNode(item)) {
|
||||
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::On);
|
||||
}
|
||||
else {
|
||||
// Don't duplicate compilations outside the Various artists node
|
||||
query_options->set_compilation_requirement(CollectionQueryOptions::CompilationRequirement::Off);
|
||||
query_options->AddWhere("artist", item->metadata.artist());
|
||||
}
|
||||
break;
|
||||
case GroupBy::Album:
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::AlbumDisc:
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
query_options->AddWhere("disc", item->metadata.disc());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::YearAlbum:
|
||||
query_options->AddWhere("year", item->metadata.year());
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::YearAlbumDisc:
|
||||
query_options->AddWhere("year", item->metadata.year());
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
query_options->AddWhere("disc", item->metadata.disc());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::OriginalYearAlbum:
|
||||
query_options->AddWhere("year", item->metadata.year());
|
||||
query_options->AddWhere("originalyear", item->metadata.originalyear());
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::OriginalYearAlbumDisc:
|
||||
query_options->AddWhere("year", item->metadata.year());
|
||||
query_options->AddWhere("originalyear", item->metadata.originalyear());
|
||||
query_options->AddWhere("album", item->metadata.album());
|
||||
query_options->AddWhere("album_id", item->metadata.album_id());
|
||||
query_options->AddWhere("disc", item->metadata.disc());
|
||||
if (separate_albums_by_grouping) query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::Disc:
|
||||
query_options->AddWhere("disc", item->metadata.disc());
|
||||
break;
|
||||
case GroupBy::Year:
|
||||
query_options->AddWhere("year", item->metadata.year());
|
||||
break;
|
||||
case GroupBy::OriginalYear:
|
||||
query_options->AddWhere("effective_originalyear", item->metadata.effective_originalyear());
|
||||
break;
|
||||
case GroupBy::Genre:
|
||||
query_options->AddWhere("genre", item->metadata.genre());
|
||||
break;
|
||||
case GroupBy::Composer:
|
||||
query_options->AddWhere("composer", item->metadata.composer());
|
||||
break;
|
||||
case GroupBy::Performer:
|
||||
query_options->AddWhere("performer", item->metadata.performer());
|
||||
break;
|
||||
case GroupBy::Grouping:
|
||||
query_options->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy::FileType:
|
||||
query_options->AddWhere("filetype", static_cast<int>(item->metadata.filetype()));
|
||||
break;
|
||||
case GroupBy::Format:
|
||||
query_options->AddWhere("filetype", static_cast<int>(item->metadata.filetype()));
|
||||
query_options->AddWhere("samplerate", item->metadata.samplerate());
|
||||
query_options->AddWhere("bitdepth", item->metadata.bitdepth());
|
||||
break;
|
||||
case GroupBy::Samplerate:
|
||||
query_options->AddWhere("samplerate", item->metadata.samplerate());
|
||||
break;
|
||||
case GroupBy::Bitdepth:
|
||||
query_options->AddWhere("bitdepth", item->metadata.bitdepth());
|
||||
break;
|
||||
case GroupBy::Bitrate:
|
||||
query_options->AddWhere("bitrate", item->metadata.bitrate());
|
||||
break;
|
||||
case GroupBy::None:
|
||||
case GroupBy::GroupByCount:
|
||||
qLog(Error) << "Unknown GroupBy" << group_by << "used in filter";
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CollectionItem *CollectionModel::InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level) {
|
||||
|
||||
CollectionItem::Type item_type = group_by == GroupBy::None ? CollectionItem::Type_Song : CollectionItem::Type_Container;
|
||||
|
@ -1229,205 +1013,6 @@ CollectionItem *CollectionModel::InitItem(const GroupBy group_by, const bool sig
|
|||
|
||||
}
|
||||
|
||||
CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level) {
|
||||
|
||||
CollectionItem *item = InitItem(group_by, signal, parent, container_level);
|
||||
|
||||
if (parent != root_ && !parent->key.isEmpty()) {
|
||||
item->key = parent->key + "-";
|
||||
}
|
||||
|
||||
switch (group_by) {
|
||||
case GroupBy::AlbumArtist:{
|
||||
item->metadata.set_albumartist(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.albumartist());
|
||||
item->sort_text = SortTextForArtist(item->metadata.albumartist());
|
||||
break;
|
||||
}
|
||||
case GroupBy::Artist:{
|
||||
item->metadata.set_artist(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.artist());
|
||||
item->sort_text = SortTextForArtist(item->metadata.artist());
|
||||
break;
|
||||
}
|
||||
case GroupBy::Album:{
|
||||
item->metadata.set_album(row.value(0).toString());
|
||||
item->metadata.set_album_id(row.value(1).toString());
|
||||
item->metadata.set_grouping(row.value(2).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.album());
|
||||
item->sort_text = SortTextForArtist(item->metadata.album());
|
||||
break;
|
||||
}
|
||||
case GroupBy::AlbumDisc:{
|
||||
item->metadata.set_album(row.value(0).toString());
|
||||
item->metadata.set_album_id(row.value(1).toString());
|
||||
item->metadata.set_disc(row.value(2).toInt());
|
||||
item->metadata.set_grouping(row.value(3).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc());
|
||||
item->sort_text = item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||
break;
|
||||
}
|
||||
case GroupBy::YearAlbum:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->metadata.set_album(row.value(1).toString());
|
||||
item->metadata.set_album_id(row.value(2).toString());
|
||||
item->metadata.set_grouping(row.value(3).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album());
|
||||
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album();
|
||||
break;
|
||||
}
|
||||
case GroupBy::YearAlbumDisc:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->metadata.set_album(row.value(1).toString());
|
||||
item->metadata.set_album_id(row.value(2).toString());
|
||||
item->metadata.set_disc(row.value(3).toInt());
|
||||
item->metadata.set_grouping(row.value(4).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc());
|
||||
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||
break;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbum:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->metadata.set_originalyear(row.value(1).toInt());
|
||||
item->metadata.set_album(row.value(2).toString());
|
||||
item->metadata.set_album_id(row.value(3).toString());
|
||||
item->metadata.set_grouping(row.value(4).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album());
|
||||
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album();
|
||||
break;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbumDisc:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->metadata.set_originalyear(row.value(1).toInt());
|
||||
item->metadata.set_album(row.value(2).toString());
|
||||
item->metadata.set_album_id(row.value(3).toString());
|
||||
item->metadata.set_disc(row.value(4).toInt());
|
||||
item->metadata.set_grouping(row.value(5).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc());
|
||||
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||
break;
|
||||
}
|
||||
case GroupBy::Disc:{
|
||||
item->metadata.set_disc(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int disc = std::max(0, row.value(0).toInt());
|
||||
item->display_text = PrettyDisc(disc);
|
||||
item->sort_text = SortTextForNumber(disc);
|
||||
break;
|
||||
}
|
||||
case GroupBy::Year:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int year = std::max(0, item->metadata.year());
|
||||
item->display_text = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
}
|
||||
case GroupBy::OriginalYear:{
|
||||
item->metadata.set_originalyear(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int year = std::max(0, item->metadata.originalyear());
|
||||
item->display_text = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
}
|
||||
case GroupBy::Genre:{
|
||||
item->metadata.set_genre(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.genre());
|
||||
item->sort_text = SortTextForArtist(item->metadata.genre());
|
||||
break;
|
||||
}
|
||||
case GroupBy::Composer:{
|
||||
item->metadata.set_composer(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.composer());
|
||||
item->sort_text = SortTextForArtist(item->metadata.composer());
|
||||
break;
|
||||
}
|
||||
case GroupBy::Performer:{
|
||||
item->metadata.set_performer(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.performer());
|
||||
item->sort_text = SortTextForArtist(item->metadata.performer());
|
||||
break;
|
||||
}
|
||||
case GroupBy::Grouping:{
|
||||
item->metadata.set_grouping(row.value(0).toString());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = TextOrUnknown(item->metadata.grouping());
|
||||
item->sort_text = SortTextForArtist(item->metadata.grouping());
|
||||
break;
|
||||
}
|
||||
case GroupBy::FileType:{
|
||||
item->metadata.set_filetype(static_cast<Song::FileType>(row.value(0).toInt()));
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
item->display_text = item->metadata.TextForFiletype();
|
||||
item->sort_text = item->metadata.TextForFiletype();
|
||||
break;
|
||||
}
|
||||
case GroupBy::Format:{
|
||||
item->metadata.set_filetype(static_cast<Song::FileType>(row.value(0).toInt()));
|
||||
item->metadata.set_samplerate(row.value(1).toInt());
|
||||
item->metadata.set_bitdepth(row.value(2).toInt());
|
||||
QString key = ContainerKey(group_by, separate_albums_by_grouping, item->metadata);
|
||||
item->key.append(key);
|
||||
item->display_text = key;
|
||||
item->sort_text = key;
|
||||
break;
|
||||
}
|
||||
case GroupBy::Samplerate:{
|
||||
item->metadata.set_samplerate(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int samplerate = std::max(0, item->metadata.samplerate());
|
||||
item->display_text = QString::number(samplerate);
|
||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||
break;
|
||||
}
|
||||
case GroupBy::Bitdepth:{
|
||||
item->metadata.set_bitdepth(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int bitdepth = std::max(0, item->metadata.bitdepth());
|
||||
item->display_text = QString::number(bitdepth);
|
||||
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||
break;
|
||||
}
|
||||
case GroupBy::Bitrate:{
|
||||
item->metadata.set_bitrate(row.value(0).toInt());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
const int bitrate = std::max(0, item->metadata.bitrate());
|
||||
item->display_text = QString::number(bitrate);
|
||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||
break;
|
||||
}
|
||||
case GroupBy::None:
|
||||
case GroupBy::GroupByCount:
|
||||
item->metadata.InitFromQuery(row, true);
|
||||
item->key.append(TextOrUnknown(item->metadata.title()));
|
||||
item->display_text = item->metadata.TitleWithCompilationArtist();
|
||||
if (item->container_level == 1 && !IsAlbumGroupBy(group_by_[0])) {
|
||||
item->sort_text = SortText(item->metadata.title());
|
||||
}
|
||||
else {
|
||||
item->sort_text = SortTextForSong(item->metadata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
FinishItem(group_by, signal, create_divider, parent, item);
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level) {
|
||||
|
||||
CollectionItem *item = InitItem(group_by, signal, parent, container_level);
|
||||
|
@ -1837,8 +1422,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));
|
||||
|
||||
|
@ -1894,15 +1477,6 @@ void CollectionModel::SetFilterText(const QString &filter_text) {
|
|||
ResetAsync();
|
||||
}
|
||||
|
||||
bool CollectionModel::canFetchMore(const QModelIndex &parent) const {
|
||||
|
||||
if (!parent.isValid()) return false;
|
||||
|
||||
CollectionItem *item = IndexToItem(parent);
|
||||
return !item->lazy_loaded;
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::SetGroupBy(const Grouping g, const std::optional<bool> separate_albums_by_grouping) {
|
||||
|
||||
group_by_ = g;
|
||||
|
@ -1971,7 +1545,7 @@ 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);
|
||||
}
|
||||
|
|
|
@ -153,7 +153,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);
|
||||
|
@ -185,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(); }
|
||||
|
@ -209,17 +206,14 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||
void SetFilterAge(const int filter_age);
|
||||
void SetFilterText(const QString &filter_text);
|
||||
|
||||
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);
|
||||
|
@ -238,26 +232,18 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
||||
CollectionQueryOptions PrepareQuery(CollectionItem *parent);
|
||||
QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions());
|
||||
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
||||
void PostQuery(const QueryResult &result);
|
||||
|
||||
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
||||
|
||||
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 SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options);
|
||||
static void AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options);
|
||||
|
||||
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
||||
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
||||
CollectionItem *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);
|
||||
|
||||
// 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);
|
||||
|
||||
|
@ -303,12 +289,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::Types cover_types_;
|
||||
|
||||
|
|
|
@ -38,78 +38,13 @@
|
|||
#include "collectionfilteroptions.h"
|
||||
#include "utilities/searchparserutils.h"
|
||||
|
||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options)
|
||||
: QSqlQuery(db),
|
||||
songs_table_(songs_table),
|
||||
fts_table_(fts_table),
|
||||
include_unavailable_(false),
|
||||
join_with_fts_(false),
|
||||
duplicates_only_(false),
|
||||
limit_(-1) {
|
||||
|
||||
if (!filter_options.filter_text().isEmpty()) {
|
||||
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
||||
// 1) Append * to all tokens.
|
||||
// 2) Prefix "fts" to column names.
|
||||
// 3) Remove colons which don't correspond to column names.
|
||||
|
||||
// Split on whitespace
|
||||
QString filter_text = filter_options.filter_text().replace(QRegularExpression(":\\s+"), ":");
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
||||
#else
|
||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||
#endif
|
||||
QString query;
|
||||
for (QString token : tokens) {
|
||||
token.remove('(')
|
||||
.remove(')')
|
||||
.remove('"')
|
||||
.replace('-', ' ');
|
||||
|
||||
if (token.contains(':')) {
|
||||
const QString columntoken = token.section(':', 0, 0);
|
||||
QString subtoken = token.section(':', 1, -1).replace(":", " ").trimmed();
|
||||
if (subtoken.isEmpty()) continue;
|
||||
if (Song::kFtsColumns.contains("fts" + columntoken, Qt::CaseInsensitive)) {
|
||||
if (!query.isEmpty()) query.append(" ");
|
||||
query += "fts" + columntoken + ":\"" + subtoken + "\"*";
|
||||
}
|
||||
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
|
||||
QString comparator = RemoveSqlOperator(subtoken);
|
||||
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
|
||||
AddWhereRating(subtoken, comparator);
|
||||
}
|
||||
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
|
||||
// Time is saved in nanoseconds, so add 9 0's
|
||||
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
|
||||
AddWhere(columntoken, parsedTime, comparator);
|
||||
}
|
||||
else {
|
||||
AddWhere(columntoken, subtoken, comparator);
|
||||
}
|
||||
}
|
||||
// Not a valid filter, remove
|
||||
else {
|
||||
token = token.replace(":", " ").trimmed();
|
||||
if (!token.isEmpty()) {
|
||||
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 (filter_options.max_age() != -1) {
|
||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||
|
||||
|
@ -117,12 +52,6 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||
bound_values_ << cutoff;
|
||||
}
|
||||
|
||||
// TODO: Currently you cannot use any FilterMode other than All and FTS at the same time.
|
||||
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
||||
// The query takes about 20 seconds on my machine then. Why?
|
||||
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
||||
// this way filtering is available only in the All mode.
|
||||
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
||||
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
||||
|
||||
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
||||
|
@ -231,8 +160,6 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
|
|||
|
||||
void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
||||
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
|
||||
// When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance
|
||||
|
||||
where_clauses_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0);
|
||||
|
||||
}
|
||||
|
@ -248,14 +175,7 @@ QString CollectionQuery::GetInnerQuery() const {
|
|||
|
||||
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_) {
|
||||
|
@ -269,8 +189,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_);
|
||||
|
||||
if (!QSqlQuery::prepare(sql)) return false;
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
class CollectionQuery : public QSqlQuery {
|
||||
public:
|
||||
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||
|
||||
QVariant Value(const int column) const;
|
||||
QVariant value(const int column) const { return Value(column); }
|
||||
|
@ -50,7 +50,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_; }
|
||||
|
||||
|
@ -82,7 +81,6 @@ class CollectionQuery : public QSqlQuery {
|
|||
|
||||
QSqlDatabase db_;
|
||||
QString songs_table_;
|
||||
QString fts_table_;
|
||||
|
||||
QString column_spec_;
|
||||
QString order_by_;
|
||||
|
@ -90,7 +88,6 @@ class CollectionQuery : public QSqlQuery {
|
|||
QVariantList bound_values_;
|
||||
|
||||
bool include_unavailable_;
|
||||
bool join_with_fts_;
|
||||
bool duplicates_only_;
|
||||
int limit_;
|
||||
};
|
||||
|
|
|
@ -126,10 +126,11 @@
|
|||
#include "collection/collection.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectiondirectorymodel.h"
|
||||
#include "collection/collectionviewcontainer.h"
|
||||
#include "collection/collectionfilterwidget.h"
|
||||
#include "collection/collectionfilter.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionview.h"
|
||||
#include "collection/collectionviewcontainer.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistbackend.h"
|
||||
#include "playlist/playlistcontainer.h"
|
||||
|
@ -328,7 +329,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
|||
playlist_add_to_another_(nullptr),
|
||||
playlistitem_actions_separator_(nullptr),
|
||||
playlist_rescan_songs_(nullptr),
|
||||
collection_sort_model_(new QSortFilterProxyModel(this)),
|
||||
collection_filter_(new CollectionFilter(this)),
|
||||
track_position_timer_(new QTimer(this)),
|
||||
track_slider_timer_(new QTimer(this)),
|
||||
keep_running_(false),
|
||||
|
@ -408,11 +409,11 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
|||
|
||||
// Models
|
||||
qLog(Debug) << "Creating models";
|
||||
collection_sort_model_->setSourceModel(app_->collection()->model());
|
||||
collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
||||
collection_sort_model_->setDynamicSortFilter(true);
|
||||
collection_sort_model_->setSortLocaleAware(true);
|
||||
collection_sort_model_->sort(0);
|
||||
collection_filter_->setSourceModel(app_->collection()->model());
|
||||
collection_filter_->setSortRole(CollectionModel::Role_SortText);
|
||||
collection_filter_->setDynamicSortFilter(true);
|
||||
collection_filter_->setSortLocaleAware(true);
|
||||
collection_filter_->sort(0);
|
||||
|
||||
qLog(Debug) << "Creating models finished";
|
||||
|
||||
|
@ -422,7 +423,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
|||
|
||||
ui_->playlist->view()->Init(app_);
|
||||
|
||||
collection_view_->view()->setModel(collection_sort_model_);
|
||||
collection_view_->view()->setModel(collection_filter_);
|
||||
collection_view_->view()->SetApplication(app_);
|
||||
#ifndef Q_OS_WIN
|
||||
device_view_->view()->SetApplication(app_);
|
||||
|
@ -682,7 +683,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
|||
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);
|
||||
|
|
|
@ -67,6 +67,7 @@ class AlbumCoverManager;
|
|||
class Application;
|
||||
class ContextView;
|
||||
class CollectionViewContainer;
|
||||
class CollectionFilter;
|
||||
class AlbumCoverChoiceController;
|
||||
class CommandlineOptions;
|
||||
#ifndef Q_OS_WIN
|
||||
|
@ -369,7 +370,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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -142,30 +142,24 @@ 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(", ");
|
||||
|
||||
// used to indicate, what columns can be filtered numerically. Used by the CollectionQuery.
|
||||
const QStringList Song::kNumericalColumns = QStringList() << "year"
|
||||
<< "length"
|
||||
<< "samplerate"
|
||||
<< "bitdepth"
|
||||
<< "bitrate"
|
||||
<< "rating"
|
||||
<< "playcount"
|
||||
<< "skipcount";
|
||||
const QStringList Song::kTextSearchColumns = QStringList() << "title"
|
||||
<< "album"
|
||||
<< "artist"
|
||||
<< "albumartist"
|
||||
<< "composer"
|
||||
<< "performer"
|
||||
<< "grouping"
|
||||
<< "genre"
|
||||
<< "comment";
|
||||
|
||||
|
||||
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::kNumericalSearchColumns = QStringList() << "year"
|
||||
<< "length"
|
||||
<< "samplerate"
|
||||
<< "bitdepth"
|
||||
<< "bitrate"
|
||||
<< "rating"
|
||||
<< "playcount"
|
||||
<< "skipcount";
|
||||
|
||||
const QRegularExpression Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpression Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|([0-9]{1,4}) *Remaster|Explicit) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
|
||||
|
@ -1712,20 +1706,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_);
|
||||
|
||||
}
|
||||
|
||||
void Song::ToXesam(QVariantMap *map) const {
|
||||
|
||||
using mpris::AddMetadata;
|
||||
|
|
|
@ -116,12 +116,8 @@ class Song {
|
|||
static const QString kBindSpec;
|
||||
static const QString kUpdateSpec;
|
||||
|
||||
static const QStringList kNumericalColumns;
|
||||
|
||||
static const QStringList kFtsColumns;
|
||||
static const QString kFtsColumnSpec;
|
||||
static const QString kFtsBindSpec;
|
||||
static const QString kFtsUpdateSpec;
|
||||
static const QStringList kTextSearchColumns;
|
||||
static const QStringList kNumericalSearchColumns;
|
||||
|
||||
static const QRegularExpression kAlbumRemoveDisc;
|
||||
static const QRegularExpression kAlbumRemoveMisc;
|
||||
|
@ -429,7 +425,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;
|
||||
|
||||
|
|
|
@ -236,7 +236,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
|
|||
QMutexLocker l(collection_backend_->db()->Mutex());
|
||||
QSqlDatabase db(collection_backend_->db()->Connect());
|
||||
|
||||
CollectionQuery query(db, collection_backend_->songs_table(), collection_backend_->fts_table());
|
||||
CollectionQuery query(db, collection_backend_->songs_table());
|
||||
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("url", url.toEncoded());
|
||||
|
||||
|
|
|
@ -875,7 +875,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");
|
||||
|
|
|
@ -68,7 +68,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));
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ void DeviceDatabaseBackend::RemoveDevice(const int id) {
|
|||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("DROP TABLE device_%1_fts").arg(id));
|
||||
q.prepare(QString("DROP TABLE IF EXISTS device_%1_fts").arg(id));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
#include "settings/settingsdialog.h"
|
||||
#include "internetsearchview.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilter;
|
||||
|
||||
class InternetService : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -69,9 +69,9 @@ class InternetService : public QObject {
|
|||
virtual CollectionModel *albums_collection_model() { return nullptr; }
|
||||
virtual CollectionModel *songs_collection_model() { return nullptr; }
|
||||
|
||||
virtual QSortFilterProxyModel *artists_collection_sort_model() { return nullptr; }
|
||||
virtual QSortFilterProxyModel *albums_collection_sort_model() { return nullptr; }
|
||||
virtual QSortFilterProxyModel *songs_collection_sort_model() { return nullptr; }
|
||||
virtual CollectionFilter *artists_collection_filter_model() { return nullptr; }
|
||||
virtual CollectionFilter *albums_collection_filter_model() { return nullptr; }
|
||||
virtual CollectionFilter *songs_collection_filter_model() { return nullptr; }
|
||||
|
||||
public slots:
|
||||
virtual void ShowConfig() {}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "core/iconloader.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionfilter.h"
|
||||
#include "internetservice.h"
|
||||
#include "internetsongsview.h"
|
||||
#include "internetcollectionview.h"
|
||||
|
@ -51,10 +52,10 @@ InternetSongsView::InternetSongsView(Application *app, InternetServicePtr servic
|
|||
|
||||
ui_->stacked->setCurrentWidget(ui_->internetcollection_page);
|
||||
ui_->view->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), false);
|
||||
ui_->view->setModel(service_->songs_collection_sort_model());
|
||||
ui_->view->setModel(service_->songs_collection_filter_model());
|
||||
ui_->view->SetFilter(ui_->filter_widget);
|
||||
ui_->filter_widget->SetSettingsGroup(settings_group);
|
||||
ui_->filter_widget->Init(service_->songs_collection_model());
|
||||
ui_->filter_widget->Init(service_->songs_collection_model(), service_->songs_collection_filter_model());
|
||||
|
||||
QAction *action_configure = new QAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this);
|
||||
QObject::connect(action_configure, &QAction::triggered, this, &InternetSongsView::OpenSettingsDialog);
|
||||
|
|
|
@ -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, InternetServicePtr service,
|
|||
if (service_->artists_collection_model()) {
|
||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page());
|
||||
ui_->artists_collection->view()->Init(app_, service_->artists_collection_backend(), service_->artists_collection_model(), true);
|
||||
ui_->artists_collection->view()->setModel(service_->artists_collection_sort_model());
|
||||
ui_->artists_collection->view()->setModel(service_->artists_collection_filter_model());
|
||||
ui_->artists_collection->view()->SetFilter(ui_->artists_collection->filter_widget());
|
||||
ui_->artists_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->artists_collection->filter_widget()->SetSettingsPrefix("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, InternetServicePtr service,
|
|||
if (service_->albums_collection_model()) {
|
||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page());
|
||||
ui_->albums_collection->view()->Init(app_, service_->albums_collection_backend(), service_->albums_collection_model(), true);
|
||||
ui_->albums_collection->view()->setModel(service_->albums_collection_sort_model());
|
||||
ui_->albums_collection->view()->setModel(service_->albums_collection_filter_model());
|
||||
ui_->albums_collection->view()->SetFilter(ui_->albums_collection->filter_widget());
|
||||
ui_->albums_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->albums_collection->filter_widget()->SetSettingsPrefix("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, InternetServicePtr service,
|
|||
if (service_->songs_collection_model()) {
|
||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page());
|
||||
ui_->songs_collection->view()->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), true);
|
||||
ui_->songs_collection->view()->setModel(service_->songs_collection_sort_model());
|
||||
ui_->songs_collection->view()->setModel(service_->songs_collection_filter_model());
|
||||
ui_->songs_collection->view()->SetFilter(ui_->songs_collection->filter_widget());
|
||||
ui_->songs_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||
ui_->songs_collection->filter_widget()->SetSettingsPrefix("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);
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QSslError>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
|
@ -49,6 +48,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"
|
||||
|
@ -72,10 +72,6 @@ constexpr char QobuzService::kArtistsSongsTable[] = "qobuz_artists_songs";
|
|||
constexpr char QobuzService::kAlbumsSongsTable[] = "qobuz_albums_songs";
|
||||
constexpr char QobuzService::kSongsTable[] = "qobuz_songs";
|
||||
|
||||
constexpr char QobuzService::kArtistsSongsFtsTable[] = "qobuz_artists_songs_fts";
|
||||
constexpr char QobuzService::kAlbumsSongsFtsTable[] = "qobuz_albums_songs_fts";
|
||||
constexpr 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),
|
||||
|
@ -87,9 +83,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)),
|
||||
|
@ -115,37 +111,37 @@ QobuzService::QobuzService(Application *app, QObject *parent)
|
|||
|
||||
artists_collection_backend_ = make_shared<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_ = make_shared<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_ = make_shared<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
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
|
||||
class QTimer;
|
||||
class QNetworkReply;
|
||||
class QSortFilterProxyModel;
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class QobuzUrlHandler;
|
||||
|
@ -53,6 +52,7 @@ class QobuzFavoriteRequest;
|
|||
class QobuzStreamURLRequest;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilter;
|
||||
|
||||
class QobuzService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
@ -104,9 +104,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;
|
||||
|
@ -156,10 +156,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_;
|
||||
SharedPtr<NetworkAccessManager> network_;
|
||||
QobuzUrlHandler *url_handler_;
|
||||
|
@ -172,9 +168,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_;
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/shared_ptr.h"
|
||||
|
@ -51,6 +50,7 @@
|
|||
#include "utilities/randutils.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionfilter.h"
|
||||
#include "subsonicservice.h"
|
||||
#include "subsonicurlhandler.h"
|
||||
#include "subsonicrequest.h"
|
||||
|
@ -65,7 +65,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)
|
||||
|
@ -74,7 +73,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),
|
||||
|
@ -87,16 +86,16 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
|
|||
|
||||
collection_backend_ = make_shared<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();
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
#include "internet/internetservice.h"
|
||||
#include "settings/subsonicsettingspage.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
class QNetworkReply;
|
||||
|
||||
class Application;
|
||||
|
@ -51,6 +50,7 @@ class SubsonicRequest;
|
|||
class SubsonicScrobbleRequest;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilter;
|
||||
|
||||
class SubsonicService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
@ -78,11 +78,11 @@ class SubsonicService : public InternetService {
|
|||
|
||||
SharedPtr<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_; }
|
||||
|
||||
SharedPtr<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 {
|
|||
|
||||
SharedPtr<CollectionBackend> collection_backend_;
|
||||
CollectionModel *collection_model_;
|
||||
QSortFilterProxyModel *collection_sort_model_;
|
||||
CollectionFilter *collection_filter_model_;
|
||||
|
||||
SharedPtr<SubsonicRequest> songs_request_;
|
||||
SharedPtr<SubsonicScrobbleRequest> scrobble_request_;
|
||||
|
|
|
@ -53,6 +53,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"
|
||||
|
@ -82,10 +83,6 @@ constexpr char TidalService::kArtistsSongsTable[] = "tidal_artists_songs";
|
|||
constexpr char TidalService::kAlbumsSongsTable[] = "tidal_albums_songs";
|
||||
constexpr char TidalService::kSongsTable[] = "tidal_songs";
|
||||
|
||||
constexpr char TidalService::kArtistsSongsFtsTable[] = "tidal_artists_songs_fts";
|
||||
constexpr char TidalService::kAlbumsSongsFtsTable[] = "tidal_albums_songs_fts";
|
||||
constexpr char TidalService::kSongsFtsTable[] = "tidal_songs_fts";
|
||||
|
||||
TidalService::TidalService(Application *app, QObject *parent)
|
||||
: InternetService(Song::Source::Tidal, "Tidal", "tidal", TidalSettingsPage::kSettingsGroup, SettingsDialog::Page::Tidal, app, parent),
|
||||
app_(app),
|
||||
|
@ -97,9 +94,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)),
|
||||
|
@ -130,37 +127,37 @@ TidalService::TidalService(Application *app, QObject *parent)
|
|||
|
||||
artists_collection_backend_ = make_shared<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_ = make_shared<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_ = make_shared<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
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
#include "internet/internetsearchview.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
class QNetworkReply;
|
||||
class QTimer;
|
||||
|
||||
|
@ -54,6 +53,7 @@ class TidalFavoriteRequest;
|
|||
class TidalStreamURLRequest;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
class CollectionFilter;
|
||||
|
||||
class TidalService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
@ -111,9 +111,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;
|
||||
|
@ -171,10 +171,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_;
|
||||
SharedPtr<NetworkAccessManager> network_;
|
||||
TidalUrlHandler *url_handler_;
|
||||
|
@ -187,9 +183,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_;
|
||||
|
|
Loading…
Reference in New Issue