parent
176984afe0
commit
300497f967
|
@ -476,25 +476,6 @@ if(NOT CMAKE_CROSSCOMPILING)
|
||||||
"
|
"
|
||||||
QT_SQLITE_TEST
|
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()
|
endif()
|
||||||
|
|
||||||
# Set up definitions
|
# Set up definitions
|
||||||
|
@ -555,11 +536,7 @@ if(QT_VERSION_MAJOR EQUAL 5)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
if(QT_SQLITE_TEST)
|
if(NOT 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()
|
|
||||||
message(WARNING "The Qt sqlite driver test failed.")
|
message(WARNING "The Qt sqlite driver test failed.")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -76,7 +76,7 @@ To build Strawberry from source you need the following installed on your system
|
||||||
* [Boost](https://www.boost.org/)
|
* [Boost](https://www.boost.org/)
|
||||||
* [GLib](https://developer.gnome.org/glib/)
|
* [GLib](https://developer.gnome.org/glib/)
|
||||||
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
* [SQLite 3.9 or newer](https://www.sqlite.org)
|
||||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||||
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||||
|
|
|
@ -90,12 +90,14 @@ set(SOURCES
|
||||||
collection/collectiondirectorymodel.cpp
|
collection/collectiondirectorymodel.cpp
|
||||||
collection/collectionfilteroptions.cpp
|
collection/collectionfilteroptions.cpp
|
||||||
collection/collectionfilterwidget.cpp
|
collection/collectionfilterwidget.cpp
|
||||||
|
collection/collectionfilter.cpp
|
||||||
collection/collectionplaylistitem.cpp
|
collection/collectionplaylistitem.cpp
|
||||||
collection/collectionquery.cpp
|
collection/collectionquery.cpp
|
||||||
collection/collectionqueryoptions.cpp
|
collection/collectionqueryoptions.cpp
|
||||||
collection/savedgroupingmanager.cpp
|
collection/savedgroupingmanager.cpp
|
||||||
collection/groupbydialog.cpp
|
collection/groupbydialog.cpp
|
||||||
collection/collectiontask.cpp
|
collection/collectiontask.cpp
|
||||||
|
collection/collectionmodelupdate.cpp
|
||||||
|
|
||||||
playlist/playlist.cpp
|
playlist/playlist.cpp
|
||||||
playlist/playlistbackend.cpp
|
playlist/playlistbackend.cpp
|
||||||
|
@ -345,6 +347,7 @@ set(HEADERS
|
||||||
collection/collectionviewcontainer.h
|
collection/collectionviewcontainer.h
|
||||||
collection/collectiondirectorymodel.h
|
collection/collectiondirectorymodel.h
|
||||||
collection/collectionfilterwidget.h
|
collection/collectionfilterwidget.h
|
||||||
|
collection/collectionfilter.h
|
||||||
collection/savedgroupingmanager.h
|
collection/savedgroupingmanager.h
|
||||||
collection/groupbydialog.h
|
collection/groupbydialog.h
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
|
|
||||||
const char *SCollection::kSongsTable = "songs";
|
const char *SCollection::kSongsTable = "songs";
|
||||||
const char *SCollection::kFtsTable = "songs_fts";
|
|
||||||
const char *SCollection::kDirsTable = "directories";
|
const char *SCollection::kDirsTable = "directories";
|
||||||
const char *SCollection::kSubdirsTable = "subdirectories";
|
const char *SCollection::kSubdirsTable = "subdirectories";
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||||
backend()->moveToThread(app->database()->thread());
|
backend()->moveToThread(app->database()->thread());
|
||||||
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
|
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
|
||||||
|
|
||||||
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kFtsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
|
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
|
||||||
|
|
||||||
model_ = new CollectionModel(backend_, app_, this);
|
model_ = new CollectionModel(backend_, app_, this);
|
||||||
|
|
||||||
|
|
|
@ -75,16 +75,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;
|
db_ = db;
|
||||||
task_manager_ = task_manager;
|
task_manager_ = task_manager;
|
||||||
source_ = source;
|
source_ = source;
|
||||||
songs_table_ = songs_table;
|
songs_table_ = songs_table;
|
||||||
dirs_table_ = dirs_table;
|
dirs_table_ = dirs_table;
|
||||||
subdirs_table_ = subdirs_table;
|
subdirs_table_ = subdirs_table;
|
||||||
fts_table_ = fts_table;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::Close() {
|
void CollectionBackend::Close() {
|
||||||
|
@ -122,6 +119,35 @@ void CollectionBackend::ReportErrors(const CollectionQuery &query) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::GetAllSongsAsync(const int id) {
|
||||||
|
metaObject()->invokeMethod(this, "GetAllSongs", Qt::QueuedConnection, Q_ARG(int, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::GetAllSongs(const int id) {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
SqlQuery q(db);
|
||||||
|
q.setForwardOnly(true);
|
||||||
|
q.prepare(QStringLiteral("SELECT %1 FROM %2").arg(Song::kRowIdColumnSpec, songs_table_));
|
||||||
|
if (!q.exec()) {
|
||||||
|
db_->ReportErrors(q);
|
||||||
|
emit GotSongs(SongList(), id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList songs;
|
||||||
|
while (q.next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(q, true);
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit GotSongs(songs, id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionBackend::LoadDirectoriesAsync() {
|
void CollectionBackend::LoadDirectoriesAsync() {
|
||||||
QMetaObject::invokeMethod(this, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
@ -614,17 +640,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
|
||||||
song.BindToFtsQuery(&q);
|
|
||||||
q.BindValue(QStringLiteral(":id"), song.id());
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted_songs << old_song;
|
deleted_songs << old_song;
|
||||||
added_songs << song;
|
added_songs << song;
|
||||||
|
|
||||||
|
@ -653,17 +668,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
|
||||||
new_song.BindToFtsQuery(&q);
|
|
||||||
q.BindValue(QStringLiteral(":id"), new_song.id());
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted_songs << old_song;
|
deleted_songs << old_song;
|
||||||
added_songs << new_song;
|
added_songs << new_song;
|
||||||
|
|
||||||
|
@ -688,17 +692,6 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||||
|
|
||||||
if (id == -1) return;
|
if (id == -1) return;
|
||||||
|
|
||||||
{ // Add to the FTS index
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
|
||||||
q.BindValue(QStringLiteral(":id"), id);
|
|
||||||
song.BindToFtsQuery(&q);
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Song song_copy(song);
|
Song song_copy(song);
|
||||||
song_copy.set_id(id);
|
song_copy.set_id(id);
|
||||||
added_songs << song_copy;
|
added_songs << song_copy;
|
||||||
|
@ -708,7 +701,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
||||||
if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs);
|
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||||
|
|
||||||
UpdateTotalSongCountAsync();
|
UpdateTotalSongCountAsync();
|
||||||
UpdateTotalArtistCountAsync();
|
UpdateTotalArtistCountAsync();
|
||||||
|
@ -733,7 +726,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||||
|
|
||||||
SongMap old_songs;
|
SongMap old_songs;
|
||||||
{
|
{
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
if (!ExecCollectionQuery(&query, old_songs)) {
|
if (!ExecCollectionQuery(&query, old_songs)) {
|
||||||
ReportErrors(query);
|
ReportErrors(query);
|
||||||
return;
|
return;
|
||||||
|
@ -759,16 +752,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
|
||||||
new_song.BindToFtsQuery(&q);
|
|
||||||
q.BindValue(QStringLiteral(":id"), old_song.id());
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted_songs << old_song;
|
deleted_songs << old_song;
|
||||||
Song new_song_copy(new_song);
|
Song new_song_copy(new_song);
|
||||||
|
@ -794,17 +777,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||||
|
|
||||||
if (id == -1) return;
|
if (id == -1) return;
|
||||||
|
|
||||||
{ // Add to the FTS index
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
|
||||||
q.BindValue(QStringLiteral(":id"), id);
|
|
||||||
new_song.BindToFtsQuery(&q);
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Song new_song_copy(new_song);
|
Song new_song_copy(new_song);
|
||||||
new_song_copy.set_id(id);
|
new_song_copy.set_id(id);
|
||||||
added_songs << new_song_copy;
|
added_songs << new_song_copy;
|
||||||
|
@ -824,15 +796,6 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_));
|
|
||||||
q.BindValue(QStringLiteral(":id"), old_song.id());
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deleted_songs << old_song;
|
deleted_songs << old_song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -840,7 +803,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
|
||||||
if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs);
|
if (!added_songs.isEmpty()) emit SongsAdded(added_songs);
|
||||||
|
|
||||||
UpdateTotalSongCountAsync();
|
UpdateTotalSongCountAsync();
|
||||||
UpdateTotalArtistCountAsync();
|
UpdateTotalArtistCountAsync();
|
||||||
|
@ -853,11 +816,10 @@ void CollectionBackend::UpdateMTimesOnly(const SongList &songs) {
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_));
|
|
||||||
|
|
||||||
ScopedTransaction transaction(&db);
|
ScopedTransaction transaction(&db);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
|
SqlQuery q(db);
|
||||||
|
q.prepare(QStringLiteral("UPDATE %1 SET mtime = :mtime WHERE ROWID = :id").arg(songs_table_));
|
||||||
q.BindValue(QStringLiteral(":mtime"), song.mtime());
|
q.BindValue(QStringLiteral(":mtime"), song.mtime());
|
||||||
q.BindValue(QStringLiteral(":id"), song.id());
|
q.BindValue(QStringLiteral(":id"), song.id());
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
|
@ -874,25 +836,17 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
SqlQuery remove(db);
|
|
||||||
remove.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_));
|
|
||||||
SqlQuery remove_fts(db);
|
|
||||||
remove_fts.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_));
|
|
||||||
|
|
||||||
ScopedTransaction transaction(&db);
|
ScopedTransaction transaction(&db);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
remove.BindValue(QStringLiteral(":id"), song.id());
|
SqlQuery q(db);
|
||||||
if (!remove.Exec()) {
|
q.prepare(QStringLiteral("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_));
|
||||||
db_->ReportErrors(remove);
|
q.BindValue(QStringLiteral(":id"), song.id());
|
||||||
return;
|
if (!q.Exec()) {
|
||||||
}
|
db_->ReportErrors(q);
|
||||||
|
|
||||||
remove_fts.BindValue(QStringLiteral(":id"), song.id());
|
|
||||||
if (!remove_fts.Exec()) {
|
|
||||||
db_->ReportErrors(remove_fts);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
emit SongsDeleted(songs);
|
emit SongsDeleted(songs);
|
||||||
|
@ -925,7 +879,7 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u
|
||||||
emit SongsDeleted(songs);
|
emit SongsDeleted(songs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emit SongsDiscovered(songs);
|
emit SongsAdded(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTotalSongCountAsync();
|
UpdateTotalSongCountAsync();
|
||||||
|
@ -939,7 +893,7 @@ QStringList CollectionBackend::GetAll(const QString &column, const CollectionFil
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, filter_options);
|
CollectionQuery query(db, songs_table_, filter_options);
|
||||||
query.SetColumnSpec(QStringLiteral("DISTINCT ") + column);
|
query.SetColumnSpec(QStringLiteral("DISTINCT ") + column);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
|
|
||||||
|
@ -967,13 +921,13 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOpt
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
// Albums with 'albumartist' field set:
|
// Albums with 'albumartist' field set:
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.SetColumnSpec(QStringLiteral("DISTINCT albumartist"));
|
query.SetColumnSpec(QStringLiteral("DISTINCT albumartist"));
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
query.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||||
|
|
||||||
// Albums with no 'albumartist' (extract 'artist'):
|
// Albums with no 'albumartist' (extract 'artist'):
|
||||||
CollectionQuery query2(db, songs_table_, fts_table_, opt);
|
CollectionQuery query2(db, songs_table_, opt);
|
||||||
query2.SetColumnSpec(QStringLiteral("DISTINCT artist"));
|
query2.SetColumnSpec(QStringLiteral("DISTINCT artist"));
|
||||||
query2.AddCompilationRequirement(false);
|
query2.AddCompilationRequirement(false);
|
||||||
query2.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
query2.AddWhere(QStringLiteral("album"), QLatin1String(""), QStringLiteral("!="));
|
||||||
|
@ -1014,7 +968,7 @@ SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist,
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
|
|
||||||
|
@ -1032,7 +986,7 @@ SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist,
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1051,7 +1005,7 @@ SongList CollectionBackend::GetSongsByAlbum(const QString &album, const Collecti
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
|
||||||
|
@ -1358,7 +1312,7 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Coll
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
|
query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
|
||||||
query.AddCompilationRequirement(true);
|
query.AddCompilationRequirement(true);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1444,7 +1398,7 @@ void CollectionBackend::CompilationsNeedUpdating() {
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) {
|
if (!deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1495,7 +1449,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, opt);
|
||||||
query.SetColumnSpec(QStringLiteral("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset"));
|
query.SetColumnSpec(QStringLiteral("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset"));
|
||||||
query.SetOrderBy(QStringLiteral("effective_albumartist, album, url"));
|
query.SetOrderBy(QStringLiteral("effective_albumartist, album, url"));
|
||||||
|
|
||||||
|
@ -1586,7 +1540,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||||
ret.album = album;
|
ret.album = album;
|
||||||
ret.album_artist = effective_albumartist;
|
ret.album_artist = effective_albumartist;
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(QStringLiteral("url, art_embedded, art_automatic, art_manual, art_unset"));
|
query.SetColumnSpec(QStringLiteral("url, art_embedded, art_automatic, art_manual, art_unset"));
|
||||||
if (!effective_albumartist.isEmpty()) {
|
if (!effective_albumartist.isEmpty()) {
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
|
@ -1622,7 +1576,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
// Get the songs before they're updated
|
// Get the songs before they're updated
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1668,7 +1622,7 @@ void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumart
|
||||||
|
|
||||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1684,7 +1638,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1726,7 +1680,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||||
|
|
||||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1742,7 +1696,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1783,7 +1737,7 @@ void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, cons
|
||||||
|
|
||||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1799,7 +1753,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
query.AddWhere(QStringLiteral("effective_albumartist"), effective_albumartist);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
|
@ -1841,7 +1795,7 @@ void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, cons
|
||||||
|
|
||||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1854,7 +1808,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
||||||
|
|
||||||
for (const QString &artist : artists) {
|
for (const QString &artist : artists) {
|
||||||
// Get the songs before they're updated
|
// Get the songs before they're updated
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_);
|
||||||
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
query.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("album"), album);
|
query.AddWhere(QStringLiteral("album"), album);
|
||||||
if (!artist.isEmpty()) query.AddWhere(QStringLiteral("artist"), artist);
|
if (!artist.isEmpty()) query.AddWhere(QStringLiteral("artist"), artist);
|
||||||
|
@ -1901,7 +1855,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QStringList
|
||||||
|
|
||||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
emit SongsDeleted(deleted_songs);
|
emit SongsDeleted(deleted_songs);
|
||||||
emit SongsDiscovered(added_songs);
|
emit SongsAdded(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2016,15 +1970,6 @@ void CollectionBackend::DeleteAll() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
SqlQuery q(db);
|
|
||||||
q.prepare(QStringLiteral("DELETE FROM ") + fts_table_);
|
|
||||||
if (!q.Exec()) {
|
|
||||||
db_->ReportErrors(q);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Commit();
|
t.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,12 +80,13 @@ class CollectionBackendInterface : public QObject {
|
||||||
using AlbumList = QList<Album>;
|
using AlbumList = QList<Album>;
|
||||||
|
|
||||||
virtual QString songs_table() const = 0;
|
virtual QString songs_table() const = 0;
|
||||||
virtual QString fts_table() const = 0;
|
|
||||||
|
|
||||||
virtual Song::Source source() const = 0;
|
virtual Song::Source source() const = 0;
|
||||||
|
|
||||||
virtual SharedPtr<Database> db() 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.
|
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||||
virtual void LoadDirectoriesAsync() = 0;
|
virtual void LoadDirectoriesAsync() = 0;
|
||||||
|
|
||||||
|
@ -145,7 +146,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
|
|
||||||
~CollectionBackend();
|
~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 Close();
|
||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
|
@ -157,10 +159,11 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
SharedPtr<Database> db() const override { return db_; }
|
SharedPtr<Database> db() const override { return db_; }
|
||||||
|
|
||||||
QString songs_table() const override { return songs_table_; }
|
QString songs_table() const override { return songs_table_; }
|
||||||
QString fts_table() const override { return fts_table_; }
|
|
||||||
QString dirs_table() const { return dirs_table_; }
|
QString dirs_table() const { return dirs_table_; }
|
||||||
QString subdirs_table() const { return subdirs_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.
|
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||||
void LoadDirectoriesAsync() override;
|
void LoadDirectoriesAsync() override;
|
||||||
|
|
||||||
|
@ -235,6 +238,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
|
void GetAllSongs(const int id);
|
||||||
void LoadDirectories();
|
void LoadDirectories();
|
||||||
void UpdateTotalSongCount();
|
void UpdateTotalSongCount();
|
||||||
void UpdateTotalArtistCount();
|
void UpdateTotalArtistCount();
|
||||||
|
@ -273,8 +277,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||||
|
|
||||||
void SongsDiscovered(const SongList &songs);
|
void GotSongs(const SongList &songs, const int id);
|
||||||
|
void SongsAdded(const SongList &songs);
|
||||||
void SongsDeleted(const SongList &songs);
|
void SongsDeleted(const SongList &songs);
|
||||||
|
void SongsChanged(const SongList &songs);
|
||||||
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
@ -317,7 +323,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
QString songs_table_;
|
QString songs_table_;
|
||||||
QString dirs_table_;
|
QString dirs_table_;
|
||||||
QString subdirs_table_;
|
QString subdirs_table_;
|
||||||
QString fts_table_;
|
|
||||||
QThread *original_thread_;
|
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(QLatin1Char('\\'));
|
||||||
|
#else
|
||||||
|
QString filter = filterRegExp().pattern();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (filter.isEmpty()) return true;
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
QStringList tokens(filter.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts));
|
||||||
|
#else
|
||||||
|
QStringList tokens(filter.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
filter.clear();
|
||||||
|
|
||||||
|
QMap<QString, QString> tags;
|
||||||
|
for (QString token : tokens) {
|
||||||
|
if (token.contains(QLatin1Char(':'))) {
|
||||||
|
if (Song::kColumns.contains(token.section(QLatin1Char(':'), 0, 0), Qt::CaseInsensitive)) {
|
||||||
|
QString tag = token.section(QLatin1Char(':'), 0, 0).remove(QLatin1Char(':')).trimmed();
|
||||||
|
QString value = token.section(QLatin1Char(':'), 1, -1).remove(QLatin1Char(':')).trimmed();
|
||||||
|
if (!tag.isEmpty() && !value.isEmpty()) {
|
||||||
|
tags.insert(tag, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
token = token.remove(QLatin1Char(':')).trimmed();
|
||||||
|
if (!token.isEmpty()) {
|
||||||
|
if (!filter.isEmpty()) filter.append(QLatin1Char(' '));
|
||||||
|
filter += token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!filter.isEmpty()) filter.append(QLatin1Char(' '));
|
||||||
|
filter += token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ItemMatches(model, item, tags, filter)) return true;
|
||||||
|
|
||||||
|
for (CollectionItem *parent = item->parent ; parent ; parent = parent->parent) {
|
||||||
|
if (ItemMatches(model, parent, tags, filter)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChildrenMatches(model, item, tags, filter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionFilter::ItemMatches(CollectionModel *model, CollectionItem *item, const QMap<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 == QStringLiteral("albumartist") && metadata.effective_albumartist().contains(value, Qt::CaseInsensitive)) return true;
|
||||||
|
if (tag == QStringLiteral("artist") && metadata.artist().contains(value, Qt::CaseInsensitive)) return true;
|
||||||
|
if (tag == QStringLiteral("album") && metadata.album().contains(value, Qt::CaseInsensitive)) return true;
|
||||||
|
if (tag == QStringLiteral("title") && metadata.title().contains(value, Qt::CaseInsensitive)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionFilter::TagMatches(CollectionItem *item, const CollectionModel::GroupBy group_by, const QMap<QString, QString> &tags) const {
|
||||||
|
|
||||||
|
QString tag;
|
||||||
|
switch (group_by) {
|
||||||
|
case CollectionModel::GroupBy::AlbumArtist:
|
||||||
|
tag = QStringLiteral("albumartist");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Artist:
|
||||||
|
tag = QStringLiteral("artist");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Album:
|
||||||
|
case CollectionModel::GroupBy::AlbumDisc:
|
||||||
|
case CollectionModel::GroupBy::YearAlbum:
|
||||||
|
case CollectionModel::GroupBy::YearAlbumDisc:
|
||||||
|
case CollectionModel::GroupBy::OriginalYearAlbum:
|
||||||
|
case CollectionModel::GroupBy::OriginalYearAlbumDisc:
|
||||||
|
tag = QStringLiteral("album");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Disc:
|
||||||
|
case CollectionModel::GroupBy::Year:
|
||||||
|
case CollectionModel::GroupBy::OriginalYear:
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Genre:
|
||||||
|
tag = QStringLiteral("genre");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Composer:
|
||||||
|
tag = QStringLiteral("composer");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Performer:
|
||||||
|
tag = QStringLiteral("performer");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Grouping:
|
||||||
|
tag = QStringLiteral("grouping");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::FileType:
|
||||||
|
tag = QStringLiteral("filetype");
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Format:
|
||||||
|
case CollectionModel::GroupBy::Bitdepth:
|
||||||
|
case CollectionModel::GroupBy::Samplerate:
|
||||||
|
case CollectionModel::GroupBy::Bitrate:
|
||||||
|
case CollectionModel::GroupBy::None:
|
||||||
|
case CollectionModel::GroupBy::GroupByCount:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString value;
|
||||||
|
if (!tag.isEmpty() && tags.contains(tag)) {
|
||||||
|
value = tags[tag];
|
||||||
|
}
|
||||||
|
|
||||||
|
return !value.isEmpty() && item->DisplayText().contains(value, Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
@ -50,6 +50,8 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
|
#include "collectionfilter.h"
|
||||||
|
#include "collectionquery.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
#include "collectionfilterwidget.h"
|
#include "collectionfilterwidget.h"
|
||||||
#include "groupbydialog.h"
|
#include "groupbydialog.h"
|
||||||
|
@ -62,6 +64,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
ui_(new Ui_CollectionFilterWidget),
|
ui_(new Ui_CollectionFilterWidget),
|
||||||
model_(nullptr),
|
model_(nullptr),
|
||||||
|
filter_(nullptr),
|
||||||
group_by_dialog_(new GroupByDialog(this)),
|
group_by_dialog_(new GroupByDialog(this)),
|
||||||
groupings_manager_(nullptr),
|
groupings_manager_(nullptr),
|
||||||
filter_age_menu_(nullptr),
|
filter_age_menu_(nullptr),
|
||||||
|
@ -74,8 +77,8 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
QString available_fields = Song::kFtsColumns.join(QStringLiteral(", ")).replace(QRegularExpression(QStringLiteral("\\bfts")), QLatin1String(""));
|
QString available_fields = Song::kTextSearchColumns.join(QStringLiteral(", "));
|
||||||
available_fields += QStringLiteral(", ") + Song::kNumericalColumns.join(QStringLiteral(", "));
|
available_fields += QStringLiteral(", ") + Song::kNumericalSearchColumns.join(QStringLiteral(", "));
|
||||||
|
|
||||||
ui_->search_field->setToolTip(
|
ui_->search_field->setToolTip(
|
||||||
QStringLiteral("<html><head/><body><p>") +
|
QStringLiteral("<html><head/><body><p>") +
|
||||||
|
@ -156,7 +159,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
|
|
||||||
CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; }
|
CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; }
|
||||||
|
|
||||||
void CollectionFilterWidget::Init(CollectionModel *model) {
|
void CollectionFilterWidget::Init(CollectionModel *model, CollectionFilter *filter) {
|
||||||
|
|
||||||
if (model_) {
|
if (model_) {
|
||||||
QObject::disconnect(model_, nullptr, this, nullptr);
|
QObject::disconnect(model_, nullptr, this, nullptr);
|
||||||
|
@ -169,6 +172,7 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
|
||||||
}
|
}
|
||||||
|
|
||||||
model_ = model;
|
model_ = model;
|
||||||
|
filter_ = filter;
|
||||||
|
|
||||||
// Connect signals
|
// Connect signals
|
||||||
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
|
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
|
||||||
|
@ -217,6 +221,10 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionFilterWidget::setFilter(CollectionFilter *filter) {
|
||||||
|
filter_ = filter;
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::ReloadSettings() {
|
void CollectionFilterWidget::ReloadSettings() {
|
||||||
|
|
||||||
Settings s;
|
Settings s;
|
||||||
|
@ -518,9 +526,6 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
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);
|
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||||
|
|
||||||
if (delay) {
|
if (delay) {
|
||||||
|
@ -535,9 +540,8 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
||||||
|
|
||||||
void CollectionFilterWidget::FilterDelayTimeout() {
|
void CollectionFilterWidget::FilterDelayTimeout() {
|
||||||
|
|
||||||
emit Filter(ui_->search_field->text());
|
|
||||||
if (filter_applies_to_model_) {
|
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 GroupByDialog;
|
||||||
class SavedGroupingManager;
|
class SavedGroupingManager;
|
||||||
|
class CollectionFilter;
|
||||||
class Ui_CollectionFilterWidget;
|
class Ui_CollectionFilterWidget;
|
||||||
|
|
||||||
class CollectionFilterWidget : public QWidget {
|
class CollectionFilterWidget : public QWidget {
|
||||||
|
@ -58,7 +59,9 @@ class CollectionFilterWidget : public QWidget {
|
||||||
AlwaysDelayed,
|
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);
|
static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent);
|
||||||
|
|
||||||
|
@ -94,7 +97,6 @@ class CollectionFilterWidget : public QWidget {
|
||||||
void UpPressed();
|
void UpPressed();
|
||||||
void DownPressed();
|
void DownPressed();
|
||||||
void ReturnPressed();
|
void ReturnPressed();
|
||||||
void Filter(const QString &text);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyReleaseEvent(QKeyEvent *e) override;
|
void keyReleaseEvent(QKeyEvent *e) override;
|
||||||
|
@ -115,6 +117,7 @@ class CollectionFilterWidget : public QWidget {
|
||||||
private:
|
private:
|
||||||
Ui_CollectionFilterWidget *ui_;
|
Ui_CollectionFilterWidget *ui_;
|
||||||
CollectionModel *model_;
|
CollectionModel *model_;
|
||||||
|
CollectionFilter *filter_;
|
||||||
|
|
||||||
GroupByDialog *group_by_dialog_;
|
GroupByDialog *group_by_dialog_;
|
||||||
SavedGroupingManager *groupings_manager_;
|
SavedGroupingManager *groupings_manager_;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
@ -44,6 +42,7 @@
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
|
#include <QQueue>
|
||||||
|
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
#include "core/simpletreemodel.h"
|
#include "core/simpletreemodel.h"
|
||||||
|
@ -51,10 +50,11 @@
|
||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
#include "collectionmodelupdate.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionqueryoptions.h"
|
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
|
|
||||||
|
class QTimer;
|
||||||
class Settings;
|
class Settings;
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
@ -69,7 +69,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
~CollectionModel() override;
|
~CollectionModel() override;
|
||||||
|
|
||||||
static const int kPrettyCoverSize;
|
static const int kPrettyCoverSize;
|
||||||
static const char *kPixmapDiskCacheDir;
|
|
||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
Role_Type = Qt::UserRole + 1,
|
Role_Type = Qt::UserRole + 1,
|
||||||
|
@ -126,10 +125,8 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QueryResult {
|
struct QueryResult {
|
||||||
QueryResult() : create_va(false) {}
|
QueryResult() {}
|
||||||
|
SongList songs;
|
||||||
SqlRowList rows;
|
|
||||||
bool create_va;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
||||||
|
@ -153,7 +150,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
Qt::ItemFlags flags(const QModelIndex &idx) const override;
|
Qt::ItemFlags flags(const QModelIndex &idx) const override;
|
||||||
QStringList mimeTypes() const override;
|
QStringList mimeTypes() const override;
|
||||||
QMimeData *mimeData(const QModelIndexList &indexes) 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
|
// Whether or not to use album cover art, if it exists, in the collection view
|
||||||
void set_pretty_covers(const bool use_pretty_covers);
|
void set_pretty_covers(const bool use_pretty_covers);
|
||||||
|
@ -165,6 +161,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
// Whether to skip articles such as “The” when sorting artist names
|
// Whether to skip articles such as “The” when sorting artist names
|
||||||
void set_sort_skips_articles(const bool sort_skips_articles);
|
void set_sort_skips_articles(const bool sort_skips_articles);
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
void Reset();
|
||||||
|
|
||||||
// Reload settings.
|
// Reload settings.
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
|
@ -188,8 +187,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; }
|
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]; }
|
QMap<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
|
||||||
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
|
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
|
||||||
int divider_nodes_count() const { return divider_nodes_.count(); }
|
int divider_nodes_count() const { return divider_nodes_.count(); }
|
||||||
|
@ -208,63 +205,45 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
void SetFilterMode(const CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void SetFilterAge(const int filter_age);
|
void SetFilterAge(const int filter_age);
|
||||||
void SetFilterText(const QString &filter_text);
|
|
||||||
|
|
||||||
void Init(const bool async = true);
|
void SongsAdded(const SongList &songs);
|
||||||
void Reset();
|
void SongsRemoved(const SongList &songs);
|
||||||
void ResetAsync();
|
void SongsChanged(const SongList &songs);
|
||||||
|
void SongsUpdated(const SongList &songs);
|
||||||
void SongsDiscovered(const SongList &songs);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void LazyPopulate(CollectionItem *item) override { LazyPopulate(item, true); }
|
|
||||||
void LazyPopulate(CollectionItem *parent, const bool signal);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void ScheduleReset();
|
||||||
|
void Reload();
|
||||||
|
|
||||||
// From CollectionBackend
|
// From CollectionBackend
|
||||||
void SongsDeleted(const SongList &songs);
|
|
||||||
void SongsSlightlyChanged(const SongList &songs);
|
|
||||||
void TotalSongCountUpdatedSlot(const int count);
|
void TotalSongCountUpdatedSlot(const int count);
|
||||||
void TotalArtistCountUpdatedSlot(const int count);
|
void TotalArtistCountUpdatedSlot(const int count);
|
||||||
void TotalAlbumCountUpdatedSlot(const int count);
|
void TotalAlbumCountUpdatedSlot(const int count);
|
||||||
static void ClearDiskCache();
|
static void ClearDiskCache();
|
||||||
|
|
||||||
// Called after ResetAsync
|
void LoadSongsFromSqlFinished();
|
||||||
void ResetAsyncQueryFinished();
|
|
||||||
|
|
||||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
private:
|
void ProcessUpdate();
|
||||||
// Provides some optimizations for loading the list of items in the root.
|
|
||||||
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
|
||||||
CollectionQueryOptions PrepareQuery(CollectionItem *parent);
|
|
||||||
QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions());
|
|
||||||
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
|
||||||
|
|
||||||
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
private:
|
||||||
|
void StartLoadSongsFromSql();
|
||||||
|
SongList LoadSongsFromSql(const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
void BeginReset();
|
void BeginReset();
|
||||||
|
void EndReset();
|
||||||
|
|
||||||
// Functions for working with queries and creating items.
|
CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level);
|
||||||
// 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);
|
CollectionItem *ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level);
|
||||||
|
void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
|
||||||
|
|
||||||
// The "Various Artists" node is an annoying special case.
|
// The "Various Artists" node is an annoying special case.
|
||||||
CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent);
|
CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent);
|
||||||
|
|
||||||
// Helpers for ItemFromQuery and ItemFromSong
|
|
||||||
CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level);
|
|
||||||
void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
|
|
||||||
|
|
||||||
static QString DividerKey(const GroupBy group_by, CollectionItem *item);
|
static QString DividerKey(const GroupBy group_by, CollectionItem *item);
|
||||||
static QString DividerDisplayText(const GroupBy group_by, const QString &key);
|
static QString DividerDisplayText(const GroupBy group_by, const QString &key);
|
||||||
|
|
||||||
|
@ -277,6 +256,15 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||||
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
||||||
|
|
||||||
|
void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs);
|
||||||
|
|
||||||
|
void AddSongs(const SongList &songs);
|
||||||
|
void RemoveSongs(const SongList &songs);
|
||||||
|
void UpdateSongs(const SongList &songs);
|
||||||
|
void ReAddOrUpdate(const SongList &songs);
|
||||||
|
|
||||||
|
static bool IsCollectionMetadataEqual(const Song &song1, const Song &song2);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedPtr<CollectionBackend> backend_;
|
SharedPtr<CollectionBackend> backend_;
|
||||||
Application *app_;
|
Application *app_;
|
||||||
|
@ -293,6 +281,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
bool separate_albums_by_grouping_;
|
bool separate_albums_by_grouping_;
|
||||||
|
|
||||||
// Keyed on database ID
|
// Keyed on database ID
|
||||||
|
QMap<int, Song> songs_;
|
||||||
QMap<int, CollectionItem*> song_nodes_;
|
QMap<int, CollectionItem*> song_nodes_;
|
||||||
|
|
||||||
// Keyed on whatever the key is for that level - artist, album, year, etc.
|
// Keyed on whatever the key is for that level - artist, album, year, etc.
|
||||||
|
@ -313,13 +302,17 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
bool use_pretty_covers_;
|
bool use_pretty_covers_;
|
||||||
bool show_dividers_;
|
bool show_dividers_;
|
||||||
bool use_disk_cache_;
|
bool use_disk_cache_;
|
||||||
bool use_lazy_loading_;
|
|
||||||
|
|
||||||
AlbumCoverLoaderOptions::Types cover_types_;
|
AlbumCoverLoaderOptions::Types cover_types_;
|
||||||
|
|
||||||
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||||
QSet<QString> pending_cache_keys_;
|
QSet<QString> pending_cache_keys_;
|
||||||
|
|
||||||
|
QTimer *timer_reset_;
|
||||||
|
QTimer *timer_update_;
|
||||||
|
|
||||||
|
QQueue<CollectionModelUpdate> updates_;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(CollectionModel::Grouping)
|
Q_DECLARE_METATYPE(CollectionModel::Grouping)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, 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 "collectionmodelupdate.h"
|
||||||
|
|
||||||
|
CollectionModelUpdate::CollectionModelUpdate(const Type &_type, const SongList &_songs)
|
||||||
|
: type(_type), songs(_songs) {}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, 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 COLLECTIONMODELUPDATE_H
|
||||||
|
#define COLLECTIONMODELUPDATE_H
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
|
||||||
|
class CollectionModelUpdate {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
ReAddOrUpdate,
|
||||||
|
Update,
|
||||||
|
};
|
||||||
|
explicit CollectionModelUpdate(const Type &_type, const SongList &_songs);
|
||||||
|
Type type;
|
||||||
|
SongList songs;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONMODELUPDATE_H
|
|
@ -2,7 +2,7 @@
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
|
||||||
#include "core/sqlquery.h"
|
#include "core/sqlquery.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
@ -38,78 +39,13 @@
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "utilities/searchparserutils.h"
|
#include "utilities/searchparserutils.h"
|
||||||
|
|
||||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options)
|
||||||
: SqlQuery(db),
|
: SqlQuery(db),
|
||||||
songs_table_(songs_table),
|
songs_table_(songs_table),
|
||||||
fts_table_(fts_table),
|
|
||||||
include_unavailable_(false),
|
include_unavailable_(false),
|
||||||
join_with_fts_(false),
|
|
||||||
duplicates_only_(false),
|
duplicates_only_(false),
|
||||||
limit_(-1) {
|
limit_(-1) {
|
||||||
|
|
||||||
if (!filter_options.filter_text().isEmpty()) {
|
|
||||||
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
|
||||||
// 1) Append * to all tokens.
|
|
||||||
// 2) Prefix "fts" to column names.
|
|
||||||
// 3) Remove colons which don't correspond to column names.
|
|
||||||
|
|
||||||
// Split on whitespace
|
|
||||||
QString filter_text = filter_options.filter_text().replace(QRegularExpression(QStringLiteral(":\\s+")), QStringLiteral(":"));
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts));
|
|
||||||
#else
|
|
||||||
QStringList tokens(filter_text.split(QRegularExpression(QStringLiteral("\\s+")), QString::SkipEmptyParts));
|
|
||||||
#endif
|
|
||||||
QString query;
|
|
||||||
for (QString token : tokens) {
|
|
||||||
token.remove(QLatin1Char('('))
|
|
||||||
.remove(QLatin1Char(')'))
|
|
||||||
.remove(QLatin1Char('"'))
|
|
||||||
.replace(QLatin1Char('-'), QLatin1Char(' '));
|
|
||||||
|
|
||||||
if (token.contains(QLatin1Char(':'))) {
|
|
||||||
const QString columntoken = token.section(QLatin1Char(':'), 0, 0);
|
|
||||||
QString subtoken = token.section(QLatin1Char(':'), 1, -1).replace(QLatin1String(":"), QLatin1String(" ")).trimmed();
|
|
||||||
if (subtoken.isEmpty()) continue;
|
|
||||||
if (Song::kFtsColumns.contains(QLatin1String("fts") + columntoken, Qt::CaseInsensitive)) {
|
|
||||||
if (!query.isEmpty()) query.append(QLatin1String(" "));
|
|
||||||
query += QStringLiteral("fts") + columntoken + QStringLiteral(":\"") + subtoken + QStringLiteral("\"*");
|
|
||||||
}
|
|
||||||
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
|
|
||||||
QString comparator = RemoveSqlOperator(subtoken);
|
|
||||||
if (columntoken.compare(QLatin1String("rating"), Qt::CaseInsensitive) == 0) {
|
|
||||||
AddWhereRating(subtoken, comparator);
|
|
||||||
}
|
|
||||||
else if (columntoken.compare(QLatin1String("length"), Qt::CaseInsensitive) == 0) {
|
|
||||||
// Time is saved in nanoseconds, so add 9 0's
|
|
||||||
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + QStringLiteral("000000000");
|
|
||||||
AddWhere(columntoken, parsedTime, comparator);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AddWhere(columntoken, subtoken, comparator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Not a valid filter, remove
|
|
||||||
else {
|
|
||||||
token = token.replace(QLatin1String(":"), QLatin1String(" ")).trimmed();
|
|
||||||
if (!token.isEmpty()) {
|
|
||||||
if (!query.isEmpty()) query.append(QLatin1Char(' '));
|
|
||||||
query += QLatin1Char('\"') + token + QStringLiteral("\"*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!query.isEmpty()) query.append(QLatin1Char(' '));
|
|
||||||
query += QLatin1Char('\"') + token + QStringLiteral("\"*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!query.isEmpty()) {
|
|
||||||
where_clauses_ << QStringLiteral("fts.%fts_table_noprefix MATCH ?");
|
|
||||||
bound_values_ << query;
|
|
||||||
join_with_fts_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter_options.max_age() != -1) {
|
if (filter_options.max_age() != -1) {
|
||||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||||
|
|
||||||
|
@ -117,12 +53,6 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||||
bound_values_ << cutoff;
|
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;
|
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
||||||
|
|
||||||
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
||||||
|
@ -152,7 +82,7 @@ QString CollectionQuery::RemoveSqlOperator(QString &token) {
|
||||||
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
// Ignore 'literal' for IN
|
// Ignore 'literal' for IN
|
||||||
if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) {
|
if (op.compare(QStringLiteral("IN"), Qt::CaseInsensitive) == 0) {
|
||||||
QStringList values = value.toStringList();
|
QStringList values = value.toStringList();
|
||||||
QStringList final_values;
|
QStringList final_values;
|
||||||
final_values.reserve(values.count());
|
final_values.reserve(values.count());
|
||||||
|
@ -161,7 +91,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
||||||
bound_values_ << single_value;
|
bound_values_ << single_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QStringLiteral(",")));
|
where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QLatin1Char(',')));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||||
|
@ -231,8 +161,6 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
||||||
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
|
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
|
||||||
// When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance
|
|
||||||
|
|
||||||
where_clauses_ << QStringLiteral("+compilation_effective = %1").arg(compilation ? 1 : 0);
|
where_clauses_ << QStringLiteral("+compilation_effective = %1").arg(compilation ? 1 : 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -248,14 +176,7 @@ QString CollectionQuery::GetInnerQuery() const {
|
||||||
|
|
||||||
bool CollectionQuery::Exec() {
|
bool CollectionQuery::Exec() {
|
||||||
|
|
||||||
QString sql;
|
QString sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery());
|
||||||
|
|
||||||
if (join_with_fts_) {
|
|
||||||
sql = QStringLiteral("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList where_clauses(where_clauses_);
|
QStringList where_clauses(where_clauses_);
|
||||||
if (!include_unavailable_) {
|
if (!include_unavailable_) {
|
||||||
|
@ -268,9 +189,7 @@ bool CollectionQuery::Exec() {
|
||||||
|
|
||||||
if (limit_ != -1) sql += QStringLiteral(" LIMIT ") + QString::number(limit_);
|
if (limit_ != -1) sql += QStringLiteral(" LIMIT ") + QString::number(limit_);
|
||||||
|
|
||||||
sql.replace(QLatin1String("%songs_table"), songs_table_);
|
sql.replace(QStringLiteral("%songs_table"), songs_table_);
|
||||||
sql.replace(QLatin1String("%fts_table_noprefix"), fts_table_.section(QLatin1Char('.'), -1, -1));
|
|
||||||
sql.replace(QLatin1String("%fts_table"), fts_table_);
|
|
||||||
|
|
||||||
if (!QSqlQuery::prepare(sql)) return false;
|
if (!QSqlQuery::prepare(sql)) return false;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
class CollectionQuery : public SqlQuery {
|
class CollectionQuery : public SqlQuery {
|
||||||
public:
|
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;
|
||||||
QVariant value(const int column) const { return Value(column); }
|
QVariant value(const int column) const { return Value(column); }
|
||||||
|
@ -51,7 +51,6 @@ class CollectionQuery : public SqlQuery {
|
||||||
QStringList where_clauses() const { return where_clauses_; }
|
QStringList where_clauses() const { return where_clauses_; }
|
||||||
QVariantList bound_values() const { return bound_values_; }
|
QVariantList bound_values() const { return bound_values_; }
|
||||||
bool include_unavailable() const { return include_unavailable_; }
|
bool include_unavailable() const { return include_unavailable_; }
|
||||||
bool join_with_fts() const { return join_with_fts_; }
|
|
||||||
bool duplicates_only() const { return duplicates_only_; }
|
bool duplicates_only() const { return duplicates_only_; }
|
||||||
int limit() const { return limit_; }
|
int limit() const { return limit_; }
|
||||||
|
|
||||||
|
@ -83,7 +82,6 @@ class CollectionQuery : public SqlQuery {
|
||||||
|
|
||||||
QSqlDatabase db_;
|
QSqlDatabase db_;
|
||||||
QString songs_table_;
|
QString songs_table_;
|
||||||
QString fts_table_;
|
|
||||||
|
|
||||||
QString column_spec_;
|
QString column_spec_;
|
||||||
QString order_by_;
|
QString order_by_;
|
||||||
|
@ -91,7 +89,6 @@ class CollectionQuery : public SqlQuery {
|
||||||
QVariantList bound_values_;
|
QVariantList bound_values_;
|
||||||
|
|
||||||
bool include_unavailable_;
|
bool include_unavailable_;
|
||||||
bool join_with_fts_;
|
|
||||||
bool duplicates_only_;
|
bool duplicates_only_;
|
||||||
int limit_;
|
int limit_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -131,10 +131,11 @@
|
||||||
#include "collection/collection.h"
|
#include "collection/collection.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectiondirectorymodel.h"
|
#include "collection/collectiondirectorymodel.h"
|
||||||
|
#include "collection/collectionviewcontainer.h"
|
||||||
#include "collection/collectionfilterwidget.h"
|
#include "collection/collectionfilterwidget.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "collection/collectionview.h"
|
#include "collection/collectionview.h"
|
||||||
#include "collection/collectionviewcontainer.h"
|
|
||||||
#include "playlist/playlist.h"
|
#include "playlist/playlist.h"
|
||||||
#include "playlist/playlistbackend.h"
|
#include "playlist/playlistbackend.h"
|
||||||
#include "playlist/playlistcontainer.h"
|
#include "playlist/playlistcontainer.h"
|
||||||
|
@ -335,7 +336,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||||
playlist_add_to_another_(nullptr),
|
playlist_add_to_another_(nullptr),
|
||||||
playlistitem_actions_separator_(nullptr),
|
playlistitem_actions_separator_(nullptr),
|
||||||
playlist_rescan_songs_(nullptr),
|
playlist_rescan_songs_(nullptr),
|
||||||
collection_sort_model_(new QSortFilterProxyModel(this)),
|
collection_filter_(new CollectionFilter(this)),
|
||||||
track_position_timer_(new QTimer(this)),
|
track_position_timer_(new QTimer(this)),
|
||||||
track_slider_timer_(new QTimer(this)),
|
track_slider_timer_(new QTimer(this)),
|
||||||
keep_running_(false),
|
keep_running_(false),
|
||||||
|
@ -418,11 +419,11 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
qLog(Debug) << "Creating models";
|
qLog(Debug) << "Creating models";
|
||||||
collection_sort_model_->setSourceModel(app_->collection()->model());
|
collection_filter_->setSourceModel(app_->collection()->model());
|
||||||
collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
collection_filter_->setSortRole(CollectionModel::Role_SortText);
|
||||||
collection_sort_model_->setDynamicSortFilter(true);
|
collection_filter_->setDynamicSortFilter(true);
|
||||||
collection_sort_model_->setSortLocaleAware(true);
|
collection_filter_->setSortLocaleAware(true);
|
||||||
collection_sort_model_->sort(0);
|
collection_filter_->sort(0);
|
||||||
|
|
||||||
qLog(Debug) << "Creating models finished";
|
qLog(Debug) << "Creating models finished";
|
||||||
|
|
||||||
|
@ -432,7 +433,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||||
|
|
||||||
ui_->playlist->view()->Init(app_);
|
ui_->playlist->view()->Init(app_);
|
||||||
|
|
||||||
collection_view_->view()->setModel(collection_sort_model_);
|
collection_view_->view()->setModel(collection_filter_);
|
||||||
collection_view_->view()->SetApplication(app_);
|
collection_view_->view()->SetApplication(app_);
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
device_view_->view()->SetApplication(app_);
|
device_view_->view()->SetApplication(app_);
|
||||||
|
@ -692,7 +693,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||||
QAction *collection_config_action = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure collection..."), this);
|
QAction *collection_config_action = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure collection..."), this);
|
||||||
QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig);
|
QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig);
|
||||||
collection_view_->filter_widget()->SetSettingsGroup(QLatin1String(CollectionSettingsPage::kSettingsGroup));
|
collection_view_->filter_widget()->SetSettingsGroup(QLatin1String(CollectionSettingsPage::kSettingsGroup));
|
||||||
collection_view_->filter_widget()->Init(app_->collection()->model());
|
collection_view_->filter_widget()->Init(app_->collection()->model(), collection_filter_);
|
||||||
|
|
||||||
QAction *separator = new QAction(this);
|
QAction *separator = new QAction(this);
|
||||||
separator->setSeparator(true);
|
separator->setSeparator(true);
|
||||||
|
|
|
@ -68,6 +68,7 @@ class AlbumCoverManager;
|
||||||
class Application;
|
class Application;
|
||||||
class ContextView;
|
class ContextView;
|
||||||
class CollectionViewContainer;
|
class CollectionViewContainer;
|
||||||
|
class CollectionFilter;
|
||||||
class AlbumCoverChoiceController;
|
class AlbumCoverChoiceController;
|
||||||
class CommandlineOptions;
|
class CommandlineOptions;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
|
@ -376,7 +377,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
|
|
||||||
QModelIndex playlist_menu_index_;
|
QModelIndex playlist_menu_index_;
|
||||||
|
|
||||||
QSortFilterProxyModel *collection_sort_model_;
|
CollectionFilter *collection_filter_;
|
||||||
|
|
||||||
QTimer *track_position_timer_;
|
QTimer *track_position_timer_;
|
||||||
QTimer *track_slider_timer_;
|
QTimer *track_slider_timer_;
|
||||||
|
|
|
@ -55,7 +55,6 @@ class SimpleTreeItem {
|
||||||
QString display_text;
|
QString display_text;
|
||||||
|
|
||||||
int row;
|
int row;
|
||||||
bool lazy_loaded;
|
|
||||||
|
|
||||||
T *parent;
|
T *parent;
|
||||||
QList<T*> children;
|
QList<T*> children;
|
||||||
|
@ -68,7 +67,6 @@ template<typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T> *_model)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T> *_model)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
row(0),
|
row(0),
|
||||||
lazy_loaded(true),
|
|
||||||
parent(nullptr),
|
parent(nullptr),
|
||||||
child_model(nullptr),
|
child_model(nullptr),
|
||||||
model(_model) {}
|
model(_model) {}
|
||||||
|
@ -77,7 +75,6 @@ template<typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString &_key, T *_parent)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString &_key, T *_parent)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
key(_key),
|
key(_key),
|
||||||
lazy_loaded(false),
|
|
||||||
parent(_parent),
|
parent(_parent),
|
||||||
child_model(nullptr),
|
child_model(nullptr),
|
||||||
model(_parent ? _parent->model : nullptr) {
|
model(_parent ? _parent->model : nullptr) {
|
||||||
|
@ -90,7 +87,6 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString &_key, T *_parent)
|
||||||
template<typename T>
|
template<typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, T *_parent)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, T *_parent)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
lazy_loaded(false),
|
|
||||||
parent(_parent),
|
parent(_parent),
|
||||||
child_model(nullptr),
|
child_model(nullptr),
|
||||||
model(_parent ? _parent->model : nullptr) {
|
model(_parent ? _parent->model : nullptr) {
|
||||||
|
|
|
@ -39,8 +39,6 @@ class SimpleTreeModel : public QAbstractItemModel {
|
||||||
QModelIndex parent(const QModelIndex &idx) const override;
|
QModelIndex parent(const QModelIndex &idx) const override;
|
||||||
int rowCount(const QModelIndex &parent) const override;
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
bool hasChildren(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;
|
T *IndexToItem(const QModelIndex &idx) const;
|
||||||
QModelIndex ItemToIndex(T *item) const;
|
QModelIndex ItemToIndex(T *item) const;
|
||||||
|
@ -52,9 +50,6 @@ class SimpleTreeModel : public QAbstractItemModel {
|
||||||
void EndDelete();
|
void EndDelete();
|
||||||
void EmitDataChanged(T *item);
|
void EmitDataChanged(T *item);
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void LazyPopulate(T *item) { item->lazy_loaded = true; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
T *root_;
|
T *root_;
|
||||||
};
|
};
|
||||||
|
@ -99,32 +94,13 @@ QModelIndex SimpleTreeModel<T>::parent(const QModelIndex &idx) const {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const {
|
int SimpleTreeModel<T>::rowCount(const QModelIndex &parent) const {
|
||||||
T *item = IndexToItem(parent);
|
T *item = IndexToItem(parent);
|
||||||
if (!item) return 0;
|
|
||||||
return item->children.count();
|
return item->children.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
|
bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
|
||||||
T *item = IndexToItem(parent);
|
T *item = IndexToItem(parent);
|
||||||
if (!item) return false;
|
return !item->children.isEmpty();
|
||||||
if (item->lazy_loaded)
|
|
||||||
return !item->children.isEmpty();
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
bool SimpleTreeModel<T>::canFetchMore(const QModelIndex &parent) const {
|
|
||||||
T *item = IndexToItem(parent);
|
|
||||||
return item && !item->lazy_loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void SimpleTreeModel<T>::fetchMore(const QModelIndex &parent) {
|
|
||||||
T *item = IndexToItem(parent);
|
|
||||||
if (item && !item->lazy_loaded) {
|
|
||||||
LazyPopulate(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -149,30 +149,24 @@ const QString Song::kRowIdColumnSpec = Song::kRowIdColumns.join(QStringLiteral("
|
||||||
const QString Song::kBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kColumns).join(QStringLiteral(", "));
|
const QString Song::kBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kColumns).join(QStringLiteral(", "));
|
||||||
const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(QStringLiteral(", "));
|
const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(QStringLiteral(", "));
|
||||||
|
|
||||||
// used to indicate, what columns can be filtered numerically. Used by the CollectionQuery.
|
const QStringList Song::kTextSearchColumns = QStringList() << QStringLiteral("title")
|
||||||
const QStringList Song::kNumericalColumns = QStringList() << QStringLiteral("year")
|
<< QStringLiteral("album")
|
||||||
<< QStringLiteral("length")
|
<< QStringLiteral("artist")
|
||||||
<< QStringLiteral("samplerate")
|
<< QStringLiteral("albumartist")
|
||||||
<< QStringLiteral("bitdepth")
|
<< QStringLiteral("composer")
|
||||||
<< QStringLiteral("bitrate")
|
<< QStringLiteral("performer")
|
||||||
<< QStringLiteral("rating")
|
<< QStringLiteral("grouping")
|
||||||
<< QStringLiteral("playcount")
|
<< QStringLiteral("genre")
|
||||||
<< QStringLiteral("skipcount");
|
<< QStringLiteral("comment");
|
||||||
|
|
||||||
|
const QStringList Song::kNumericalSearchColumns = QStringList() << QStringLiteral("year")
|
||||||
const QStringList Song::kFtsColumns = QStringList() << QStringLiteral("ftstitle")
|
<< QStringLiteral("length")
|
||||||
<< QStringLiteral("ftsalbum")
|
<< QStringLiteral("samplerate")
|
||||||
<< QStringLiteral("ftsartist")
|
<< QStringLiteral("bitdepth")
|
||||||
<< QStringLiteral("ftsalbumartist")
|
<< QStringLiteral("bitrate")
|
||||||
<< QStringLiteral("ftscomposer")
|
<< QStringLiteral("rating")
|
||||||
<< QStringLiteral("ftsperformer")
|
<< QStringLiteral("playcount")
|
||||||
<< QStringLiteral("ftsgrouping")
|
<< QStringLiteral("skipcount");
|
||||||
<< QStringLiteral("ftsgenre")
|
|
||||||
<< QStringLiteral("ftscomment");
|
|
||||||
|
|
||||||
const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(QStringLiteral(", "));
|
|
||||||
const QString Song::kFtsBindSpec = Utilities::Prepend(QStringLiteral(":"), Song::kFtsColumns).join(QStringLiteral(", "));
|
|
||||||
const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(QStringLiteral(", "));
|
|
||||||
|
|
||||||
const Song::RegularExpressionList Song::kAlbumDisc = Song::RegularExpressionList()
|
const Song::RegularExpressionList Song::kAlbumDisc = Song::RegularExpressionList()
|
||||||
<< QRegularExpression(QStringLiteral("\\s+-*\\s*(Disc|CD)\\s*([0-9]{1,2})$"), QRegularExpression::CaseInsensitiveOption)
|
<< QRegularExpression(QStringLiteral("\\s+-*\\s*(Disc|CD)\\s*([0-9]{1,2})$"), QRegularExpression::CaseInsensitiveOption)
|
||||||
|
@ -199,10 +193,33 @@ const Song::RegularExpressionList Song::kTitleMisc = Song::RegularExpressionList
|
||||||
|
|
||||||
const QStringList Song::kArticles = QStringList() << QStringLiteral("the ") << QStringLiteral("a ") << QStringLiteral("an ");
|
const QStringList Song::kArticles = QStringList() << QStringLiteral("the ") << QStringLiteral("a ") << QStringLiteral("an ");
|
||||||
|
|
||||||
const QStringList Song::kAcceptedExtensions = QStringList() << QStringLiteral("wav") << QStringLiteral("flac") << QStringLiteral("wv") << QStringLiteral("ogg") << QStringLiteral("oga") << QStringLiteral("opus") << QStringLiteral("spx") << QStringLiteral("ape") << QStringLiteral("mpc")
|
const QStringList Song::kAcceptedExtensions = QStringList() << QStringLiteral("wav")
|
||||||
<< QStringLiteral("mp2") << QStringLiteral("mp3") << QStringLiteral("m4a") << QStringLiteral("mp4") << QStringLiteral("aac") << QStringLiteral("asf") << QStringLiteral("asx") << QStringLiteral("wma")
|
<< QStringLiteral("flac")
|
||||||
<< QStringLiteral("aif << aiff") << QStringLiteral("mka") << QStringLiteral("tta") << QStringLiteral("dsf") << QStringLiteral("dsd")
|
<< QStringLiteral("wv")
|
||||||
<< QStringLiteral("ac3") << QStringLiteral("dts") << QStringLiteral("spc") << QStringLiteral("vgm");
|
<< QStringLiteral("ogg")
|
||||||
|
<< QStringLiteral("oga")
|
||||||
|
<< QStringLiteral("opus")
|
||||||
|
<< QStringLiteral("spx")
|
||||||
|
<< QStringLiteral("ape")
|
||||||
|
<< QStringLiteral("mpc")
|
||||||
|
<< QStringLiteral("mp2")
|
||||||
|
<< QStringLiteral("mp3")
|
||||||
|
<< QStringLiteral("m4a")
|
||||||
|
<< QStringLiteral("mp4")
|
||||||
|
<< QStringLiteral("aac")
|
||||||
|
<< QStringLiteral("asf")
|
||||||
|
<< QStringLiteral("asx")
|
||||||
|
<< QStringLiteral("wma")
|
||||||
|
<< QStringLiteral("aif")
|
||||||
|
<< QStringLiteral("aiff")
|
||||||
|
<< QStringLiteral("mka")
|
||||||
|
<< QStringLiteral("tta")
|
||||||
|
<< QStringLiteral("dsf")
|
||||||
|
<< QStringLiteral("dsd")
|
||||||
|
<< QStringLiteral("ac3")
|
||||||
|
<< QStringLiteral("dts")
|
||||||
|
<< QStringLiteral("spc")
|
||||||
|
<< QStringLiteral("vgm");
|
||||||
|
|
||||||
struct Song::Private : public QSharedData {
|
struct Song::Private : public QSharedData {
|
||||||
|
|
||||||
|
@ -1778,20 +1795,6 @@ void Song::BindToQuery(SqlQuery *query) const {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Song::BindToFtsQuery(SqlQuery *query) const {
|
|
||||||
|
|
||||||
query->BindValue(QStringLiteral(":ftstitle"), d->title_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsalbum"), d->album_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsartist"), d->artist_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsalbumartist"), d->albumartist_);
|
|
||||||
query->BindValue(QStringLiteral(":ftscomposer"), d->composer_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsperformer"), d->performer_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsgrouping"), d->grouping_);
|
|
||||||
query->BindValue(QStringLiteral(":ftsgenre"), d->genre_);
|
|
||||||
query->BindValue(QStringLiteral(":ftscomment"), d->comment_);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
void Song::ToXesam(QVariantMap *map) const {
|
void Song::ToXesam(QVariantMap *map) const {
|
||||||
|
|
||||||
|
|
|
@ -119,12 +119,8 @@ class Song {
|
||||||
static const QString kBindSpec;
|
static const QString kBindSpec;
|
||||||
static const QString kUpdateSpec;
|
static const QString kUpdateSpec;
|
||||||
|
|
||||||
static const QStringList kNumericalColumns;
|
static const QStringList kTextSearchColumns;
|
||||||
|
static const QStringList kNumericalSearchColumns;
|
||||||
static const QStringList kFtsColumns;
|
|
||||||
static const QString kFtsColumnSpec;
|
|
||||||
static const QString kFtsBindSpec;
|
|
||||||
static const QString kFtsUpdateSpec;
|
|
||||||
|
|
||||||
using RegularExpressionList = QList<QRegularExpression>;
|
using RegularExpressionList = QList<QRegularExpression>;
|
||||||
static const RegularExpressionList kAlbumDisc;
|
static const RegularExpressionList kAlbumDisc;
|
||||||
|
@ -439,7 +435,6 @@ class Song {
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
void BindToQuery(SqlQuery *query) const;
|
void BindToQuery(SqlQuery *query) const;
|
||||||
void BindToFtsQuery(SqlQuery *query) const;
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
void ToXesam(QVariantMap *map) const;
|
void ToXesam(QVariantMap *map) const;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -236,7 +236,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
|
||||||
QMutexLocker l(collection_backend_->db()->Mutex());
|
QMutexLocker l(collection_backend_->db()->Mutex());
|
||||||
QSqlDatabase db(collection_backend_->db()->Connect());
|
QSqlDatabase db(collection_backend_->db()->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, collection_backend_->songs_table(), collection_backend_->fts_table());
|
CollectionQuery query(db, collection_backend_->songs_table());
|
||||||
query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
|
query.SetColumnSpec(QStringLiteral("%songs_table.ROWID, ") + Song::kColumnSpec);
|
||||||
query.AddWhere(QStringLiteral("url"), url.toEncoded());
|
query.AddWhere(QStringLiteral("url"), url.toEncoded());
|
||||||
|
|
||||||
|
|
|
@ -876,7 +876,7 @@ SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &idx) const {
|
||||||
QMutexLocker l(collection_backend_->db()->Mutex());
|
QMutexLocker l(collection_backend_->db()->Mutex());
|
||||||
QSqlDatabase db(collection_backend_->db()->Connect());
|
QSqlDatabase db(collection_backend_->db()->Connect());
|
||||||
|
|
||||||
CollectionQuery q(db, collection_backend_->songs_table(), collection_backend_->fts_table());
|
CollectionQuery q(db, collection_backend_->songs_table());
|
||||||
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
q.SetColumnSpec(Song::kRowIdColumnSpec);
|
||||||
q.AddWhere(QStringLiteral("album"), idx.data(Role_Album).toString());
|
q.AddWhere(QStringLiteral("album"), idx.data(Role_Album).toString());
|
||||||
q.SetOrderBy(QStringLiteral("disc, track, title"));
|
q.SetOrderBy(QStringLiteral("disc, track, title"));
|
||||||
|
|
|
@ -41,7 +41,7 @@ CddaDevice::CddaDevice(const QUrl &url, DeviceLister *lister, const QString &uni
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsLoaded, this, &CddaDevice::SongsLoaded);
|
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsLoaded, this, &CddaDevice::SongsLoaded);
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsDurationLoaded, this, &CddaDevice::SongsLoaded);
|
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsDurationLoaded, this, &CddaDevice::SongsLoaded);
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsMetadataLoaded, this, &CddaDevice::SongsLoaded);
|
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsMetadataLoaded, this, &CddaDevice::SongsLoaded);
|
||||||
QObject::connect(this, &CddaDevice::SongsDiscovered, model_, &CollectionModel::SongsDiscovered);
|
QObject::connect(this, &CddaDevice::SongsDiscovered, model_, &CollectionModel::SongsAdded);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS
|
||||||
app_->task_manager(),
|
app_->task_manager(),
|
||||||
Song::Source::Device,
|
Song::Source::Device,
|
||||||
QStringLiteral("device_%1_songs").arg(database_id),
|
QStringLiteral("device_%1_songs").arg(database_id),
|
||||||
QStringLiteral("device_%1_fts").arg(database_id),
|
|
||||||
QStringLiteral("device_%1_directories").arg(database_id),
|
QStringLiteral("device_%1_directories").arg(database_id),
|
||||||
QStringLiteral("device_%1_subdirectories").arg(database_id));
|
QStringLiteral("device_%1_subdirectories").arg(database_id));
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,7 @@ void DeviceDatabaseBackend::RemoveDevice(const int id) {
|
||||||
|
|
||||||
{
|
{
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QStringLiteral("DROP TABLE device_%1_fts").arg(id));
|
q.prepare(QStringLiteral("DROP TABLE IF EXISTS device_%1_fts").arg(id));
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
db_->ReportErrors(q);
|
db_->ReportErrors(q);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -33,10 +33,10 @@
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "internetsearchview.h"
|
#include "internetsearchview.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
|
||||||
class Application;
|
class Application;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
|
class CollectionFilter;
|
||||||
|
|
||||||
class InternetService : public QObject {
|
class InternetService : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -69,9 +69,9 @@ class InternetService : public QObject {
|
||||||
virtual CollectionModel *albums_collection_model() { return nullptr; }
|
virtual CollectionModel *albums_collection_model() { return nullptr; }
|
||||||
virtual CollectionModel *songs_collection_model() { return nullptr; }
|
virtual CollectionModel *songs_collection_model() { return nullptr; }
|
||||||
|
|
||||||
virtual QSortFilterProxyModel *artists_collection_sort_model() { return nullptr; }
|
virtual CollectionFilter *artists_collection_filter_model() { return nullptr; }
|
||||||
virtual QSortFilterProxyModel *albums_collection_sort_model() { return nullptr; }
|
virtual CollectionFilter *albums_collection_filter_model() { return nullptr; }
|
||||||
virtual QSortFilterProxyModel *songs_collection_sort_model() { return nullptr; }
|
virtual CollectionFilter *songs_collection_filter_model() { return nullptr; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void ShowConfig() {}
|
virtual void ShowConfig() {}
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "internetservice.h"
|
#include "internetservice.h"
|
||||||
#include "internetsongsview.h"
|
#include "internetsongsview.h"
|
||||||
#include "internetcollectionview.h"
|
#include "internetcollectionview.h"
|
||||||
|
@ -51,10 +52,10 @@ InternetSongsView::InternetSongsView(Application *app, InternetServicePtr servic
|
||||||
|
|
||||||
ui_->stacked->setCurrentWidget(ui_->internetcollection_page);
|
ui_->stacked->setCurrentWidget(ui_->internetcollection_page);
|
||||||
ui_->view->Init(app_, service_->songs_collection_backend(), service_->songs_collection_model(), false);
|
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_->view->SetFilter(ui_->filter_widget);
|
||||||
ui_->filter_widget->SetSettingsGroup(settings_group);
|
ui_->filter_widget->SetSettingsGroup(settings_group);
|
||||||
ui_->filter_widget->Init(service_->songs_collection_model());
|
ui_->filter_widget->Init(service_->songs_collection_model(), service_->songs_collection_filter_model());
|
||||||
|
|
||||||
QAction *action_configure = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this);
|
QAction *action_configure = new QAction(IconLoader::Load(QStringLiteral("configure")), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this);
|
||||||
QObject::connect(action_configure, &QAction::triggered, this, &InternetSongsView::OpenSettingsDialog);
|
QObject::connect(action_configure, &QAction::triggered, this, &InternetSongsView::OpenSettingsDialog);
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "collection/collectionfilterwidget.h"
|
#include "collection/collectionfilterwidget.h"
|
||||||
#include "internetservice.h"
|
#include "internetservice.h"
|
||||||
#include "internettabsview.h"
|
#include "internettabsview.h"
|
||||||
|
@ -66,11 +67,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service,
|
||||||
if (service_->artists_collection_model()) {
|
if (service_->artists_collection_model()) {
|
||||||
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page());
|
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()->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->view()->SetFilter(ui_->artists_collection->filter_widget());
|
||||||
ui_->artists_collection->filter_widget()->SetSettingsGroup(settings_group);
|
ui_->artists_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||||
ui_->artists_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("artists"));
|
ui_->artists_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("artists"));
|
||||||
ui_->artists_collection->filter_widget()->Init(service_->artists_collection_model());
|
ui_->artists_collection->filter_widget()->Init(service_->artists_collection_model(), service_->artists_collection_filter_model());
|
||||||
ui_->artists_collection->filter_widget()->AddMenuAction(action_configure);
|
ui_->artists_collection->filter_widget()->AddMenuAction(action_configure);
|
||||||
|
|
||||||
QObject::connect(ui_->artists_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetArtists);
|
QObject::connect(ui_->artists_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetArtists);
|
||||||
|
@ -98,11 +99,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service,
|
||||||
if (service_->albums_collection_model()) {
|
if (service_->albums_collection_model()) {
|
||||||
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page());
|
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()->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->view()->SetFilter(ui_->albums_collection->filter_widget());
|
||||||
ui_->albums_collection->filter_widget()->SetSettingsGroup(settings_group);
|
ui_->albums_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||||
ui_->albums_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("albums"));
|
ui_->albums_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("albums"));
|
||||||
ui_->albums_collection->filter_widget()->Init(service_->albums_collection_model());
|
ui_->albums_collection->filter_widget()->Init(service_->albums_collection_model(), service_->albums_collection_filter_model());
|
||||||
ui_->albums_collection->filter_widget()->AddMenuAction(action_configure);
|
ui_->albums_collection->filter_widget()->AddMenuAction(action_configure);
|
||||||
|
|
||||||
QObject::connect(ui_->albums_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetAlbums);
|
QObject::connect(ui_->albums_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetAlbums);
|
||||||
|
@ -130,11 +131,11 @@ InternetTabsView::InternetTabsView(Application *app, InternetServicePtr service,
|
||||||
if (service_->songs_collection_model()) {
|
if (service_->songs_collection_model()) {
|
||||||
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page());
|
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()->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->view()->SetFilter(ui_->songs_collection->filter_widget());
|
||||||
ui_->songs_collection->filter_widget()->SetSettingsGroup(settings_group);
|
ui_->songs_collection->filter_widget()->SetSettingsGroup(settings_group);
|
||||||
ui_->songs_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("songs"));
|
ui_->songs_collection->filter_widget()->SetSettingsPrefix(QStringLiteral("songs"));
|
||||||
ui_->songs_collection->filter_widget()->Init(service_->songs_collection_model());
|
ui_->songs_collection->filter_widget()->Init(service_->songs_collection_model(), service_->songs_collection_filter_model());
|
||||||
ui_->songs_collection->filter_widget()->AddMenuAction(action_configure);
|
ui_->songs_collection->filter_widget()->AddMenuAction(action_configure);
|
||||||
|
|
||||||
QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetSongs);
|
QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetSongs);
|
||||||
|
|
|
@ -96,7 +96,7 @@ void PlaylistManager::Init(SharedPtr<CollectionBackend> collection_backend, Shar
|
||||||
parser_ = new PlaylistParser(collection_backend, this);
|
parser_ = new PlaylistParser(collection_backend, this);
|
||||||
playlist_container_ = playlist_container;
|
playlist_container_ = playlist_container;
|
||||||
|
|
||||||
QObject::connect(&*collection_backend_, &CollectionBackend::SongsDiscovered, this, &PlaylistManager::SongsDiscovered);
|
QObject::connect(&*collection_backend_, &CollectionBackend::SongsAdded, this, &PlaylistManager::SongsDiscovered);
|
||||||
QObject::connect(&*collection_backend_, &CollectionBackend::SongsStatisticsChanged, this, &PlaylistManager::SongsDiscovered);
|
QObject::connect(&*collection_backend_, &CollectionBackend::SongsStatisticsChanged, this, &PlaylistManager::SongsDiscovered);
|
||||||
QObject::connect(&*collection_backend_, &CollectionBackend::SongsRatingChanged, this, &PlaylistManager::SongsDiscovered);
|
QObject::connect(&*collection_backend_, &CollectionBackend::SongsRatingChanged, this, &PlaylistManager::SongsDiscovered);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QSslError>
|
#include <QSslError>
|
||||||
|
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
|
@ -50,6 +49,7 @@
|
||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "qobuzservice.h"
|
#include "qobuzservice.h"
|
||||||
#include "qobuzurlhandler.h"
|
#include "qobuzurlhandler.h"
|
||||||
#include "qobuzbaserequest.h"
|
#include "qobuzbaserequest.h"
|
||||||
|
@ -75,10 +75,6 @@ constexpr char kArtistsSongsTable[] = "qobuz_artists_songs";
|
||||||
constexpr char kAlbumsSongsTable[] = "qobuz_albums_songs";
|
constexpr char kAlbumsSongsTable[] = "qobuz_albums_songs";
|
||||||
constexpr char kSongsTable[] = "qobuz_songs";
|
constexpr char kSongsTable[] = "qobuz_songs";
|
||||||
|
|
||||||
constexpr char kArtistsSongsFtsTable[] = "qobuz_artists_songs_fts";
|
|
||||||
constexpr char kAlbumsSongsFtsTable[] = "qobuz_albums_songs_fts";
|
|
||||||
constexpr char kSongsFtsTable[] = "qobuz_songs_fts";
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QobuzService::QobuzService(Application *app, QObject *parent)
|
QobuzService::QobuzService(Application *app, QObject *parent)
|
||||||
|
@ -92,9 +88,9 @@ QobuzService::QobuzService(Application *app, QObject *parent)
|
||||||
artists_collection_model_(nullptr),
|
artists_collection_model_(nullptr),
|
||||||
albums_collection_model_(nullptr),
|
albums_collection_model_(nullptr),
|
||||||
songs_collection_model_(nullptr),
|
songs_collection_model_(nullptr),
|
||||||
artists_collection_sort_model_(new QSortFilterProxyModel(this)),
|
artists_collection_filter_model_(new CollectionFilter(this)),
|
||||||
albums_collection_sort_model_(new QSortFilterProxyModel(this)),
|
albums_collection_filter_model_(new CollectionFilter(this)),
|
||||||
songs_collection_sort_model_(new QSortFilterProxyModel(this)),
|
songs_collection_filter_model_(new CollectionFilter(this)),
|
||||||
timer_search_delay_(new QTimer(this)),
|
timer_search_delay_(new QTimer(this)),
|
||||||
timer_login_attempt_(new QTimer(this)),
|
timer_login_attempt_(new QTimer(this)),
|
||||||
favorite_request_(new QobuzFavoriteRequest(this, network_, this)),
|
favorite_request_(new QobuzFavoriteRequest(this, network_, this)),
|
||||||
|
@ -120,37 +116,37 @@ QobuzService::QobuzService(Application *app, QObject *parent)
|
||||||
|
|
||||||
artists_collection_backend_ = make_shared<CollectionBackend>();
|
artists_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
artists_collection_backend_->moveToThread(app_->database()->thread());
|
artists_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kArtistsSongsTable), QLatin1String(kArtistsSongsFtsTable));
|
artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kArtistsSongsTable));
|
||||||
|
|
||||||
albums_collection_backend_ = make_shared<CollectionBackend>();
|
albums_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
albums_collection_backend_->moveToThread(app_->database()->thread());
|
albums_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kAlbumsSongsTable), QLatin1String(kAlbumsSongsFtsTable));
|
albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kAlbumsSongsTable));
|
||||||
|
|
||||||
songs_collection_backend_ = make_shared<CollectionBackend>();
|
songs_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
songs_collection_backend_->moveToThread(app_->database()->thread());
|
songs_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable));
|
songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Qobuz, QLatin1String(kSongsTable));
|
||||||
|
|
||||||
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
|
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
|
||||||
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
|
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
|
||||||
songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this);
|
songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this);
|
||||||
|
|
||||||
artists_collection_sort_model_->setSourceModel(artists_collection_model_);
|
artists_collection_filter_model_->setSourceModel(artists_collection_model_);
|
||||||
artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
artists_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
artists_collection_sort_model_->setDynamicSortFilter(true);
|
artists_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
artists_collection_sort_model_->setSortLocaleAware(true);
|
artists_collection_filter_model_->setSortLocaleAware(true);
|
||||||
artists_collection_sort_model_->sort(0);
|
artists_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
albums_collection_sort_model_->setSourceModel(albums_collection_model_);
|
albums_collection_filter_model_->setSourceModel(albums_collection_model_);
|
||||||
albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
albums_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
albums_collection_sort_model_->setDynamicSortFilter(true);
|
albums_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
albums_collection_sort_model_->setSortLocaleAware(true);
|
albums_collection_filter_model_->setSortLocaleAware(true);
|
||||||
albums_collection_sort_model_->sort(0);
|
albums_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
songs_collection_sort_model_->setSourceModel(songs_collection_model_);
|
songs_collection_filter_model_->setSourceModel(songs_collection_model_);
|
||||||
songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
songs_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
songs_collection_sort_model_->setDynamicSortFilter(true);
|
songs_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
songs_collection_sort_model_->setSortLocaleAware(true);
|
songs_collection_filter_model_->setSortLocaleAware(true);
|
||||||
songs_collection_sort_model_->sort(0);
|
songs_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QSortFilterProxyModel;
|
|
||||||
class Application;
|
class Application;
|
||||||
class NetworkAccessManager;
|
class NetworkAccessManager;
|
||||||
class QobuzUrlHandler;
|
class QobuzUrlHandler;
|
||||||
|
@ -53,6 +52,7 @@ class QobuzFavoriteRequest;
|
||||||
class QobuzStreamURLRequest;
|
class QobuzStreamURLRequest;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
|
class CollectionFilter;
|
||||||
|
|
||||||
class QobuzService : public InternetService {
|
class QobuzService : public InternetService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -105,9 +105,9 @@ class QobuzService : public InternetService {
|
||||||
CollectionModel *albums_collection_model() override { return albums_collection_model_; }
|
CollectionModel *albums_collection_model() override { return albums_collection_model_; }
|
||||||
CollectionModel *songs_collection_model() override { return songs_collection_model_; }
|
CollectionModel *songs_collection_model() override { return songs_collection_model_; }
|
||||||
|
|
||||||
QSortFilterProxyModel *artists_collection_sort_model() override { return artists_collection_sort_model_; }
|
CollectionFilter *artists_collection_filter_model() override { return artists_collection_filter_model_; }
|
||||||
QSortFilterProxyModel *albums_collection_sort_model() override { return albums_collection_sort_model_; }
|
CollectionFilter *albums_collection_filter_model() override { return albums_collection_filter_model_; }
|
||||||
QSortFilterProxyModel *songs_collection_sort_model() override { return songs_collection_sort_model_; }
|
CollectionFilter *songs_collection_filter_model() override { return songs_collection_filter_model_; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void ShowConfig() override;
|
void ShowConfig() override;
|
||||||
|
@ -160,9 +160,9 @@ class QobuzService : public InternetService {
|
||||||
CollectionModel *albums_collection_model_;
|
CollectionModel *albums_collection_model_;
|
||||||
CollectionModel *songs_collection_model_;
|
CollectionModel *songs_collection_model_;
|
||||||
|
|
||||||
QSortFilterProxyModel *artists_collection_sort_model_;
|
CollectionFilter *artists_collection_filter_model_;
|
||||||
QSortFilterProxyModel *albums_collection_sort_model_;
|
CollectionFilter *albums_collection_filter_model_;
|
||||||
QSortFilterProxyModel *songs_collection_sort_model_;
|
CollectionFilter *songs_collection_filter_model_;
|
||||||
|
|
||||||
QTimer *timer_search_delay_;
|
QTimer *timer_search_delay_;
|
||||||
QTimer *timer_login_attempt_;
|
QTimer *timer_login_attempt_;
|
||||||
|
|
|
@ -45,8 +45,6 @@ RadioModel::RadioModel(Application *app, QObject *parent)
|
||||||
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent),
|
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent),
|
||||||
app_(app) {
|
app_(app) {
|
||||||
|
|
||||||
root_->lazy_loaded = true;
|
|
||||||
|
|
||||||
if (app_) {
|
if (app_) {
|
||||||
QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &RadioModel::AlbumCoverLoaded);
|
QObject::connect(&*app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &RadioModel::AlbumCoverLoaded);
|
||||||
}
|
}
|
||||||
|
@ -154,7 +152,6 @@ void RadioModel::Reset() {
|
||||||
pending_cache_keys_.clear();
|
pending_cache_keys_.clear();
|
||||||
delete root_;
|
delete root_;
|
||||||
root_ = new RadioItem(this);
|
root_ = new RadioItem(this);
|
||||||
root_->lazy_loaded = true;
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -172,7 +169,6 @@ void RadioModel::AddChannels(const RadioChannelList &channels) {
|
||||||
item->source = channel.source;
|
item->source = channel.source;
|
||||||
item->display_text = Song::DescriptionForSource(channel.source);
|
item->display_text = Song::DescriptionForSource(channel.source);
|
||||||
item->sort_text = SortText(Song::TextForSource(channel.source));
|
item->sort_text = SortText(Song::TextForSource(channel.source));
|
||||||
item->lazy_loaded = true;
|
|
||||||
container_nodes_.insert(channel.source, item);
|
container_nodes_.insert(channel.source, item);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
container = item;
|
container = item;
|
||||||
|
@ -183,7 +179,6 @@ void RadioModel::AddChannels(const RadioChannelList &channels) {
|
||||||
item->display_text = channel.name;
|
item->display_text = channel.name;
|
||||||
item->sort_text = SortText(Song::TextForSource(channel.source) + QStringLiteral(" - ") + channel.name);
|
item->sort_text = SortText(Song::TextForSource(channel.source) + QStringLiteral(" - ") + channel.name);
|
||||||
item->channel = channel;
|
item->channel = channel;
|
||||||
item->lazy_loaded = true;
|
|
||||||
items_ << item;
|
items_ << item;
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,11 +51,7 @@ const int SmartPlaylistsModel::kSmartPlaylistsVersion = 1;
|
||||||
SmartPlaylistsModel::SmartPlaylistsModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent)
|
SmartPlaylistsModel::SmartPlaylistsModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent)
|
||||||
: SimpleTreeModel<SmartPlaylistsItem>(new SmartPlaylistsItem(this), parent),
|
: SimpleTreeModel<SmartPlaylistsItem>(new SmartPlaylistsItem(this), parent),
|
||||||
collection_backend_(collection_backend),
|
collection_backend_(collection_backend),
|
||||||
icon_(IconLoader::Load(QStringLiteral("view-media-playlist"))) {
|
icon_(IconLoader::Load(QStringLiteral("view-media-playlist"))) {}
|
||||||
|
|
||||||
root_->lazy_loaded = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SmartPlaylistsModel::~SmartPlaylistsModel() { delete root_; }
|
SmartPlaylistsModel::~SmartPlaylistsModel() { delete root_; }
|
||||||
|
|
||||||
|
@ -164,7 +160,6 @@ void SmartPlaylistsModel::ItemFromSmartPlaylist(const Settings &s, const bool no
|
||||||
item->sort_text = item->display_text;
|
item->sort_text = item->display_text;
|
||||||
item->smart_playlist_type = PlaylistGenerator::Type(s.value("type").toInt());
|
item->smart_playlist_type = PlaylistGenerator::Type(s.value("type").toInt());
|
||||||
item->smart_playlist_data = s.value("data").toByteArray();
|
item->smart_playlist_data = s.value("data").toByteArray();
|
||||||
item->lazy_loaded = true;
|
|
||||||
|
|
||||||
if (notify) item->InsertNotify(root_);
|
if (notify) item->InsertNotify(root_);
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
|
@ -52,6 +51,7 @@
|
||||||
#include "utilities/randutils.h"
|
#include "utilities/randutils.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "subsonicservice.h"
|
#include "subsonicservice.h"
|
||||||
#include "subsonicurlhandler.h"
|
#include "subsonicurlhandler.h"
|
||||||
#include "subsonicrequest.h"
|
#include "subsonicrequest.h"
|
||||||
|
@ -68,7 +68,6 @@ const char *SubsonicService::kApiVersion = "1.11.0";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kSongsTable[] = "subsonic_songs";
|
constexpr char kSongsTable[] = "subsonic_songs";
|
||||||
constexpr char kSongsFtsTable[] = "subsonic_songs_fts";
|
|
||||||
constexpr int kMaxRedirects = 3;
|
constexpr int kMaxRedirects = 3;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -78,7 +77,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
|
||||||
url_handler_(new SubsonicUrlHandler(app, this)),
|
url_handler_(new SubsonicUrlHandler(app, this)),
|
||||||
collection_backend_(nullptr),
|
collection_backend_(nullptr),
|
||||||
collection_model_(nullptr),
|
collection_model_(nullptr),
|
||||||
collection_sort_model_(new QSortFilterProxyModel(this)),
|
collection_filter_model_(new CollectionFilter(this)),
|
||||||
http2_(false),
|
http2_(false),
|
||||||
verify_certificate_(false),
|
verify_certificate_(false),
|
||||||
download_album_covers_(true),
|
download_album_covers_(true),
|
||||||
|
@ -92,16 +91,16 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
|
||||||
|
|
||||||
collection_backend_ = make_shared<CollectionBackend>();
|
collection_backend_ = make_shared<CollectionBackend>();
|
||||||
collection_backend_->moveToThread(app_->database()->thread());
|
collection_backend_->moveToThread(app_->database()->thread());
|
||||||
collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Subsonic, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable));
|
collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Subsonic, QLatin1String(kSongsTable));
|
||||||
|
|
||||||
// Model
|
// Model
|
||||||
|
|
||||||
collection_model_ = new CollectionModel(collection_backend_, app_, this);
|
collection_model_ = new CollectionModel(collection_backend_, app_, this);
|
||||||
collection_sort_model_->setSourceModel(collection_model_);
|
collection_filter_model_->setSourceModel(collection_model_);
|
||||||
collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
collection_sort_model_->setDynamicSortFilter(true);
|
collection_filter_model_->setDynamicSortFilter(true);
|
||||||
collection_sort_model_->setSortLocaleAware(true);
|
collection_filter_model_->setSortLocaleAware(true);
|
||||||
collection_sort_model_->sort(0);
|
collection_filter_model_->sort(0);
|
||||||
|
|
||||||
SubsonicService::ReloadSettings();
|
SubsonicService::ReloadSettings();
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
#include "internet/internetservice.h"
|
#include "internet/internetservice.h"
|
||||||
#include "settings/subsonicsettingspage.h"
|
#include "settings/subsonicsettingspage.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
@ -51,6 +50,7 @@ class SubsonicRequest;
|
||||||
class SubsonicScrobbleRequest;
|
class SubsonicScrobbleRequest;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
|
class CollectionFilter;
|
||||||
|
|
||||||
class SubsonicService : public InternetService {
|
class SubsonicService : public InternetService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -78,11 +78,11 @@ class SubsonicService : public InternetService {
|
||||||
|
|
||||||
SharedPtr<CollectionBackend> collection_backend() const { return collection_backend_; }
|
SharedPtr<CollectionBackend> collection_backend() const { return collection_backend_; }
|
||||||
CollectionModel *collection_model() const { return collection_model_; }
|
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_; }
|
SharedPtr<CollectionBackend> songs_collection_backend() override { return collection_backend_; }
|
||||||
CollectionModel *songs_collection_model() override { return collection_model_; }
|
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 CheckConfiguration();
|
||||||
void Scrobble(const QString &song_id, const bool submission, const QDateTime &time);
|
void Scrobble(const QString &song_id, const bool submission, const QDateTime &time);
|
||||||
|
@ -109,7 +109,7 @@ class SubsonicService : public InternetService {
|
||||||
|
|
||||||
SharedPtr<CollectionBackend> collection_backend_;
|
SharedPtr<CollectionBackend> collection_backend_;
|
||||||
CollectionModel *collection_model_;
|
CollectionModel *collection_model_;
|
||||||
QSortFilterProxyModel *collection_sort_model_;
|
CollectionFilter *collection_filter_model_;
|
||||||
|
|
||||||
SharedPtr<SubsonicRequest> songs_request_;
|
SharedPtr<SubsonicRequest> songs_request_;
|
||||||
SharedPtr<SubsonicScrobbleRequest> scrobble_request_;
|
SharedPtr<SubsonicScrobbleRequest> scrobble_request_;
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "collection/collectionfilter.h"
|
||||||
#include "tidalservice.h"
|
#include "tidalservice.h"
|
||||||
#include "tidalurlhandler.h"
|
#include "tidalurlhandler.h"
|
||||||
#include "tidalbaserequest.h"
|
#include "tidalbaserequest.h"
|
||||||
|
@ -85,10 +86,6 @@ constexpr char kArtistsSongsTable[] = "tidal_artists_songs";
|
||||||
constexpr char kAlbumsSongsTable[] = "tidal_albums_songs";
|
constexpr char kAlbumsSongsTable[] = "tidal_albums_songs";
|
||||||
constexpr char kSongsTable[] = "tidal_songs";
|
constexpr char kSongsTable[] = "tidal_songs";
|
||||||
|
|
||||||
constexpr char kArtistsSongsFtsTable[] = "tidal_artists_songs_fts";
|
|
||||||
constexpr char kAlbumsSongsFtsTable[] = "tidal_albums_songs_fts";
|
|
||||||
constexpr char kSongsFtsTable[] = "tidal_songs_fts";
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TidalService::TidalService(Application *app, QObject *parent)
|
TidalService::TidalService(Application *app, QObject *parent)
|
||||||
|
@ -102,9 +99,9 @@ TidalService::TidalService(Application *app, QObject *parent)
|
||||||
artists_collection_model_(nullptr),
|
artists_collection_model_(nullptr),
|
||||||
albums_collection_model_(nullptr),
|
albums_collection_model_(nullptr),
|
||||||
songs_collection_model_(nullptr),
|
songs_collection_model_(nullptr),
|
||||||
artists_collection_sort_model_(new QSortFilterProxyModel(this)),
|
artists_collection_filter_model_(new CollectionFilter(this)),
|
||||||
albums_collection_sort_model_(new QSortFilterProxyModel(this)),
|
albums_collection_filter_model_(new CollectionFilter(this)),
|
||||||
songs_collection_sort_model_(new QSortFilterProxyModel(this)),
|
songs_collection_filter_model_(new CollectionFilter(this)),
|
||||||
timer_search_delay_(new QTimer(this)),
|
timer_search_delay_(new QTimer(this)),
|
||||||
timer_login_attempt_(new QTimer(this)),
|
timer_login_attempt_(new QTimer(this)),
|
||||||
timer_refresh_login_(new QTimer(this)),
|
timer_refresh_login_(new QTimer(this)),
|
||||||
|
@ -135,37 +132,37 @@ TidalService::TidalService(Application *app, QObject *parent)
|
||||||
|
|
||||||
artists_collection_backend_ = make_shared<CollectionBackend>();
|
artists_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
artists_collection_backend_->moveToThread(app_->database()->thread());
|
artists_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kArtistsSongsTable), QLatin1String(kArtistsSongsFtsTable));
|
artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kArtistsSongsTable));
|
||||||
|
|
||||||
albums_collection_backend_ = make_shared<CollectionBackend>();
|
albums_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
albums_collection_backend_->moveToThread(app_->database()->thread());
|
albums_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kAlbumsSongsTable), QLatin1String(kAlbumsSongsFtsTable));
|
albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kAlbumsSongsTable));
|
||||||
|
|
||||||
songs_collection_backend_ = make_shared<CollectionBackend>();
|
songs_collection_backend_ = make_shared<CollectionBackend>();
|
||||||
songs_collection_backend_->moveToThread(app_->database()->thread());
|
songs_collection_backend_->moveToThread(app_->database()->thread());
|
||||||
songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kSongsTable), QLatin1String(kSongsFtsTable));
|
songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source::Tidal, QLatin1String(kSongsTable));
|
||||||
|
|
||||||
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
|
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
|
||||||
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
|
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
|
||||||
songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this);
|
songs_collection_model_ = new CollectionModel(songs_collection_backend_, app_, this);
|
||||||
|
|
||||||
artists_collection_sort_model_->setSourceModel(artists_collection_model_);
|
artists_collection_filter_model_->setSourceModel(artists_collection_model_);
|
||||||
artists_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
artists_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
artists_collection_sort_model_->setDynamicSortFilter(true);
|
artists_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
artists_collection_sort_model_->setSortLocaleAware(true);
|
artists_collection_filter_model_->setSortLocaleAware(true);
|
||||||
artists_collection_sort_model_->sort(0);
|
artists_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
albums_collection_sort_model_->setSourceModel(albums_collection_model_);
|
albums_collection_filter_model_->setSourceModel(albums_collection_model_);
|
||||||
albums_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
albums_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
albums_collection_sort_model_->setDynamicSortFilter(true);
|
albums_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
albums_collection_sort_model_->setSortLocaleAware(true);
|
albums_collection_filter_model_->setSortLocaleAware(true);
|
||||||
albums_collection_sort_model_->sort(0);
|
albums_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
songs_collection_sort_model_->setSourceModel(songs_collection_model_);
|
songs_collection_filter_model_->setSourceModel(songs_collection_model_);
|
||||||
songs_collection_sort_model_->setSortRole(CollectionModel::Role_SortText);
|
songs_collection_filter_model_->setSortRole(CollectionModel::Role_SortText);
|
||||||
songs_collection_sort_model_->setDynamicSortFilter(true);
|
songs_collection_filter_model_->setDynamicSortFilter(true);
|
||||||
songs_collection_sort_model_->setSortLocaleAware(true);
|
songs_collection_filter_model_->setSortLocaleAware(true);
|
||||||
songs_collection_sort_model_->sort(0);
|
songs_collection_filter_model_->sort(0);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
#include "settings/tidalsettingspage.h"
|
#include "settings/tidalsettingspage.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
|
||||||
|
@ -54,6 +53,7 @@ class TidalFavoriteRequest;
|
||||||
class TidalStreamURLRequest;
|
class TidalStreamURLRequest;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
|
class CollectionFilter;
|
||||||
|
|
||||||
class TidalService : public InternetService {
|
class TidalService : public InternetService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -112,9 +112,9 @@ class TidalService : public InternetService {
|
||||||
CollectionModel *albums_collection_model() override { return albums_collection_model_; }
|
CollectionModel *albums_collection_model() override { return albums_collection_model_; }
|
||||||
CollectionModel *songs_collection_model() override { return songs_collection_model_; }
|
CollectionModel *songs_collection_model() override { return songs_collection_model_; }
|
||||||
|
|
||||||
QSortFilterProxyModel *artists_collection_sort_model() override { return artists_collection_sort_model_; }
|
CollectionFilter *artists_collection_filter_model() override { return artists_collection_filter_model_; }
|
||||||
QSortFilterProxyModel *albums_collection_sort_model() override { return albums_collection_sort_model_; }
|
CollectionFilter *albums_collection_filter_model() override { return albums_collection_filter_model_; }
|
||||||
QSortFilterProxyModel *songs_collection_sort_model() override { return songs_collection_sort_model_; }
|
CollectionFilter *songs_collection_filter_model() override { return songs_collection_filter_model_; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void ShowConfig() override;
|
void ShowConfig() override;
|
||||||
|
@ -172,9 +172,9 @@ class TidalService : public InternetService {
|
||||||
CollectionModel *albums_collection_model_;
|
CollectionModel *albums_collection_model_;
|
||||||
CollectionModel *songs_collection_model_;
|
CollectionModel *songs_collection_model_;
|
||||||
|
|
||||||
QSortFilterProxyModel *artists_collection_sort_model_;
|
CollectionFilter *artists_collection_filter_model_;
|
||||||
QSortFilterProxyModel *albums_collection_sort_model_;
|
CollectionFilter *albums_collection_filter_model_;
|
||||||
QSortFilterProxyModel *songs_collection_sort_model_;
|
CollectionFilter *songs_collection_filter_model_;
|
||||||
|
|
||||||
QTimer *timer_search_delay_;
|
QTimer *timer_search_delay_;
|
||||||
QTimer *timer_login_attempt_;
|
QTimer *timer_login_attempt_;
|
||||||
|
|
|
@ -47,7 +47,7 @@ class CollectionBackendTest : public ::testing::Test {
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
database_.reset(new MemoryDatabase(nullptr));
|
database_.reset(new MemoryDatabase(nullptr));
|
||||||
backend_ = make_unique<CollectionBackend>();
|
backend_ = make_unique<CollectionBackend>();
|
||||||
backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable));
|
backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Song MakeDummySong(int directory_id) {
|
static Song MakeDummySong(int directory_id) {
|
||||||
|
@ -132,7 +132,7 @@ class SingleSong : public CollectionBackendTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddDummySong() {
|
void AddDummySong() {
|
||||||
QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted);
|
QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted);
|
||||||
|
|
||||||
// Add the song
|
// Add the song
|
||||||
|
@ -266,7 +266,7 @@ TEST_F(SingleSong, UpdateSong) {
|
||||||
new_song.set_title(QStringLiteral("A different title"));
|
new_song.set_title(QStringLiteral("A different title"));
|
||||||
|
|
||||||
QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted);
|
QSignalSpy deleted_spy(&*backend_, &CollectionBackend::SongsDeleted);
|
||||||
QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy added_spy(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
|
|
||||||
backend_->AddOrUpdateSongs(SongList() << new_song);
|
backend_->AddOrUpdateSongs(SongList() << new_song);
|
||||||
|
|
||||||
|
@ -389,7 +389,7 @@ TEST_F(TestUrls, TestUrls) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSignalSpy spy(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy spy(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
|
|
||||||
backend_->AddOrUpdateSongs(songs);
|
backend_->AddOrUpdateSongs(songs);
|
||||||
if (HasFatalFailure()) return;
|
if (HasFatalFailure()) return;
|
||||||
|
@ -474,7 +474,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSignalSpy spy(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy spy(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
|
|
||||||
backend_->UpdateSongsBySongID(songs);
|
backend_->UpdateSongsBySongID(songs);
|
||||||
|
|
||||||
|
@ -495,7 +495,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) {
|
||||||
SongMap songs;
|
SongMap songs;
|
||||||
{
|
{
|
||||||
QSqlDatabase db(database_->Connect());
|
QSqlDatabase db(database_->Connect());
|
||||||
CollectionQuery query(db, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable));
|
CollectionQuery query(db, QLatin1String(SCollection::kSongsTable));
|
||||||
EXPECT_TRUE(backend_->ExecCollectionQuery(&query, songs));
|
EXPECT_TRUE(backend_->ExecCollectionQuery(&query, songs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +512,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Remove some songs
|
{ // Remove some songs
|
||||||
QSignalSpy spy1(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy spy1(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
QSignalSpy spy2(&*backend_, &CollectionBackend::SongsDeleted);
|
QSignalSpy spy2(&*backend_, &CollectionBackend::SongsDeleted);
|
||||||
|
|
||||||
SongMap songs;
|
SongMap songs;
|
||||||
|
@ -558,7 +558,7 @@ TEST_F(UpdateSongsBySongID, UpdateSongsBySongID) {
|
||||||
|
|
||||||
{ // Update some songs
|
{ // Update some songs
|
||||||
QSignalSpy spy1(&*backend_, &CollectionBackend::SongsDeleted);
|
QSignalSpy spy1(&*backend_, &CollectionBackend::SongsDeleted);
|
||||||
QSignalSpy spy2(&*backend_, &CollectionBackend::SongsDiscovered);
|
QSignalSpy spy2(&*backend_, &CollectionBackend::SongsAdded);
|
||||||
|
|
||||||
SongMap songs;
|
SongMap songs;
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class CollectionModelTest : public ::testing::Test {
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
database_ = make_shared<MemoryDatabase>(nullptr);
|
database_ = make_shared<MemoryDatabase>(nullptr);
|
||||||
backend_ = make_shared<CollectionBackend>();
|
backend_ = make_shared<CollectionBackend>();
|
||||||
backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kFtsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable));
|
backend_->Init(database_, nullptr, Song::Source::Collection, QLatin1String(SCollection::kSongsTable), QLatin1String(SCollection::kDirsTable), QLatin1String(SCollection::kSubdirsTable));
|
||||||
model_ = make_unique<CollectionModel>(backend_, nullptr);
|
model_ = make_unique<CollectionModel>(backend_, nullptr);
|
||||||
|
|
||||||
added_dir_ = false;
|
added_dir_ = false;
|
||||||
|
@ -108,7 +108,7 @@ TEST_F(CollectionModelTest, WithInitialArtists) {
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Artist 1"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Artist 1"), QStringLiteral("Album"), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Artist 2"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Artist 2"), QStringLiteral("Album"), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Foo"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Foo"), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
ASSERT_EQ(5, model_sorted_->rowCount(QModelIndex()));
|
ASSERT_EQ(5, model_sorted_->rowCount(QModelIndex()));
|
||||||
EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
||||||
|
@ -128,7 +128,7 @@ TEST_F(CollectionModelTest, CompilationAlbums) {
|
||||||
song.set_ctime(0);
|
song.set_ctime(0);
|
||||||
|
|
||||||
AddSong(song);
|
AddSong(song);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
model_->fetchMore(model_->index(0, 0));
|
model_->fetchMore(model_->index(0, 0));
|
||||||
|
|
||||||
ASSERT_EQ(1, model_->rowCount(QModelIndex()));
|
ASSERT_EQ(1, model_->rowCount(QModelIndex()));
|
||||||
|
@ -150,7 +150,7 @@ TEST_F(CollectionModelTest, NumericHeaders) {
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("2artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("2artist"), QStringLiteral("Album"), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("0artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("0artist"), QStringLiteral("Album"), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("zartist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("zartist"), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
ASSERT_EQ(6, model_sorted_->rowCount(QModelIndex()));
|
ASSERT_EQ(6, model_sorted_->rowCount(QModelIndex()));
|
||||||
EXPECT_EQ(QStringLiteral("0-9"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
EXPECT_EQ(QStringLiteral("0-9"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
||||||
|
@ -166,7 +166,7 @@ TEST_F(CollectionModelTest, MixedCaseHeaders) {
|
||||||
|
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("artist"), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
ASSERT_EQ(3, model_sorted_->rowCount(QModelIndex()));
|
ASSERT_EQ(3, model_sorted_->rowCount(QModelIndex()));
|
||||||
EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
EXPECT_EQ(QStringLiteral("A"), model_sorted_->index(0, 0, QModelIndex()).data().toString());
|
||||||
|
@ -178,7 +178,7 @@ TEST_F(CollectionModelTest, MixedCaseHeaders) {
|
||||||
TEST_F(CollectionModelTest, UnknownArtists) {
|
TEST_F(CollectionModelTest, UnknownArtists) {
|
||||||
|
|
||||||
AddSong(QStringLiteral("Title"), QLatin1String(""), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QLatin1String(""), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
model_->fetchMore(model_->index(0, 0));
|
model_->fetchMore(model_->index(0, 0));
|
||||||
|
|
||||||
ASSERT_EQ(1, model_->rowCount(QModelIndex()));
|
ASSERT_EQ(1, model_->rowCount(QModelIndex()));
|
||||||
|
@ -194,7 +194,7 @@ TEST_F(CollectionModelTest, UnknownAlbums) {
|
||||||
|
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QLatin1String(""), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QLatin1String(""), 123);
|
||||||
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
model_->fetchMore(model_->index(0, 0));
|
model_->fetchMore(model_->index(0, 0));
|
||||||
|
|
||||||
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
||||||
|
@ -228,7 +228,7 @@ TEST_F(CollectionModelTest, VariousArtistSongs) {
|
||||||
|
|
||||||
for (int i=0 ; i < 4 ; ++i)
|
for (int i=0 ; i < 4 ; ++i)
|
||||||
AddSong(songs[i]);
|
AddSong(songs[i]);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
||||||
model_->fetchMore(artist_index);
|
model_->fetchMore(artist_index);
|
||||||
|
@ -250,7 +250,7 @@ TEST_F(CollectionModelTest, RemoveSongsLazyLoaded) {
|
||||||
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
||||||
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2);
|
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2);
|
||||||
AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album"), 123);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
// Lazy load the items
|
// Lazy load the items
|
||||||
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
||||||
|
@ -283,7 +283,7 @@ TEST_F(CollectionModelTest, RemoveSongsNotLazyLoaded) {
|
||||||
|
|
||||||
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
||||||
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2);
|
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); two.set_id(2);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
// Remove the first two songs
|
// Remove the first two songs
|
||||||
QSignalSpy spy_preremove(&*model_, &CollectionModel::rowsAboutToBeRemoved);
|
QSignalSpy spy_preremove(&*model_, &CollectionModel::rowsAboutToBeRemoved);
|
||||||
|
@ -303,7 +303,7 @@ TEST_F(CollectionModelTest, RemoveEmptyAlbums) {
|
||||||
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album 1"), 123); one.set_id(1);
|
Song one = AddSong(QStringLiteral("Title 1"), QStringLiteral("Artist"), QStringLiteral("Album 1"), 123); one.set_id(1);
|
||||||
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); two.set_id(2);
|
Song two = AddSong(QStringLiteral("Title 2"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); two.set_id(2);
|
||||||
Song three = AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); three.set_id(3);
|
Song three = AddSong(QStringLiteral("Title 3"), QStringLiteral("Artist"), QStringLiteral("Album 2"), 123); three.set_id(3);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
||||||
model_->fetchMore(artist_index);
|
model_->fetchMore(artist_index);
|
||||||
|
@ -328,7 +328,7 @@ TEST_F(CollectionModelTest, RemoveEmptyAlbums) {
|
||||||
TEST_F(CollectionModelTest, RemoveEmptyArtists) {
|
TEST_F(CollectionModelTest, RemoveEmptyArtists) {
|
||||||
|
|
||||||
Song one = AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
Song one = AddSong(QStringLiteral("Title"), QStringLiteral("Artist"), QStringLiteral("Album"), 123); one.set_id(1);
|
||||||
model_->Init(false);
|
model_->Init();
|
||||||
|
|
||||||
// Lazy load the items
|
// Lazy load the items
|
||||||
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
QModelIndex artist_index = model_->index(0, 0, QModelIndex());
|
||||||
|
@ -351,8 +351,8 @@ TEST_F(CollectionModelTest, RemoveEmptyArtists) {
|
||||||
|
|
||||||
// Test to check that the container nodes are created identical and unique all through the model with all possible collection groupings.
|
// Test to check that the container nodes are created identical and unique all through the model with all possible collection groupings.
|
||||||
// model1 - Nodes are created from a complete reset done through lazy-loading.
|
// model1 - Nodes are created from a complete reset done through lazy-loading.
|
||||||
// model2 - Initial container nodes are created in SongsDiscovered.
|
// model2 - Initial container nodes are created in SongsAdded.
|
||||||
// model3 - All container nodes are created in SongsDiscovered.
|
// model3 - All container nodes are created in SongsAdded.
|
||||||
|
|
||||||
// WARNING: This test can take up to 30 minutes to complete.
|
// WARNING: This test can take up to 30 minutes to complete.
|
||||||
#if 0
|
#if 0
|
||||||
|
@ -567,9 +567,9 @@ TEST_F(CollectionModelTest, TestContainerNodes) {
|
||||||
backend1 = make_unique<CollectionBackend>();
|
backend1 = make_unique<CollectionBackend>();
|
||||||
backend2= make_unique<CollectionBackend>();
|
backend2= make_unique<CollectionBackend>();
|
||||||
backend3 = make_unique<CollectionBackend>();
|
backend3 = make_unique<CollectionBackend>();
|
||||||
backend1->Init(database1.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
backend1->Init(database1.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
||||||
backend2->Init(database2.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
backend2->Init(database2.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
||||||
backend3->Init(database3.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
backend3->Init(database3.get(), Song::Source::Collection, SCollection::kSongsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
|
||||||
model1 = make_unique<CollectionModel>(backend1.get(), nullptr);
|
model1 = make_unique<CollectionModel>(backend1.get(), nullptr);
|
||||||
model2 = make_unique<CollectionModel>(backend2.get(), nullptr);
|
model2 = make_unique<CollectionModel>(backend2.get(), nullptr);
|
||||||
model3 = make_unique<CollectionModel>(backend3.get(), nullptr);
|
model3 = make_unique<CollectionModel>(backend3.get(), nullptr);
|
||||||
|
@ -596,7 +596,7 @@ TEST_F(CollectionModelTest, TestContainerNodes) {
|
||||||
ASSERT_EQ(model2->song_nodes().count(), 0);
|
ASSERT_EQ(model2->song_nodes().count(), 0);
|
||||||
ASSERT_EQ(model3->song_nodes().count(), songs.count());
|
ASSERT_EQ(model3->song_nodes().count(), songs.count());
|
||||||
|
|
||||||
model1->Init(false);
|
model1->Init();
|
||||||
|
|
||||||
model1->ExpandAll();
|
model1->ExpandAll();
|
||||||
model2->ExpandAll();
|
model2->ExpandAll();
|
||||||
|
|
Loading…
Reference in New Issue