From af234763f270842e1530ea357105e60c2e054af8 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sat, 27 Nov 2010 20:09:00 +0000 Subject: [PATCH] Do library searching and grouping in a background thread --- src/library/librarymodel.cpp | 114 +++++++++++++++++++++++++++++------ src/library/librarymodel.h | 21 +++++-- src/library/sqlrow.h | 4 +- 3 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/library/librarymodel.cpp b/src/library/librarymodel.cpp index 87939626e..9555e7837 100644 --- a/src/library/librarymodel.cpp +++ b/src/library/librarymodel.cpp @@ -27,10 +27,13 @@ #include "smartplaylists/querygenerator.h" #include "ui/iconloader.h" +#include +#include +#include #include #include #include -#include +#include #include @@ -43,6 +46,9 @@ const char* LibraryModel::kSmartPlaylistsMimeType = "application/x-clementine-sm const char* LibraryModel::kSmartPlaylistsSettingsGroup = "SerialisedSmartPlaylists"; const int LibraryModel::kSmartPlaylistsVersion = 3; +typedef QFuture RootQueryFuture; +typedef QFutureWatcher RootQueryWatcher; + LibraryModel::LibraryModel(LibraryBackend* backend, QObject* parent) : SimpleTreeModel(new LibraryItem(this), parent), backend_(backend), @@ -386,11 +392,42 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const { return QVariant(); } +SqlRowList LibraryModel::RunRootQuery(const QueryOptions& query_options, + const Grouping& group_by) { + // Warning: Some copy-paste with LazyPopulate here + + // Information about what we want the children to be + GroupBy child_type = group_by[0]; + + // Initialise the query. child_type says what type of thing we want (artists, + // songs, etc.) + LibraryQuery q(query_options); + InitQuery(child_type, &q); + + // Top-level artists is special - we don't want compilation albums appearing + if (child_type == GroupBy_Artist) { + q.AddCompilationRequirement(false); + } + + // Execute the query + QMutexLocker l(backend_->db()->Mutex()); + if (!backend_->ExecQuery(&q)) + return SqlRowList(); + + SqlRowList rows; + while (q.Next()) { + rows << SqlRow(q); + } + return rows; +} + void LibraryModel::LazyPopulate(LibraryItem* parent, bool signal) { if (parent->lazy_loaded) return; parent->lazy_loaded = true; + // Warning: Some copy-paste with RunRootQuery here + // Information about what we want the children to be int child_level = parent->container_level + 1; GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level]; @@ -419,9 +456,12 @@ void LibraryModel::LazyPopulate(LibraryItem* parent, bool signal) { // Step through the results while (q.Next()) { + // Warning: Some copy-paste with ResetAsyncQueryFinished here + // Create the item - it will get inserted into the model here LibraryItem* item = - ItemFromQuery(child_type, signal, child_level == 0, parent, q, child_level); + ItemFromQuery(child_type, signal, child_level == 0, parent, SqlRow(q), + child_level); // Save a pointer to it for later if (child_type == GroupBy_None) @@ -431,7 +471,43 @@ void LibraryModel::LazyPopulate(LibraryItem* parent, bool signal) { } } -void LibraryModel::Reset() { +void LibraryModel::ResetAsync() { + RootQueryFuture future = QtConcurrent::run( + this, &LibraryModel::RunRootQuery, query_options_, group_by_); + RootQueryWatcher* watcher = new RootQueryWatcher(this); + watcher->setFuture(future); + + connect(watcher, SIGNAL(finished()), SLOT(ResetAsyncQueryFinished())); +} + +void LibraryModel::ResetAsyncQueryFinished() { + RootQueryWatcher* watcher = static_cast(sender()); + const SqlRowList rows = watcher->result(); + watcher->deleteLater(); + + BeginReset(); + root_->lazy_loaded = true; + + foreach (const SqlRow& row, rows) { + // Warning: Some copy-paste with LazyPopulate here + + const GroupBy child_type = group_by_[0]; + + // Create the item - it will get inserted into the model here + LibraryItem* item = + ItemFromQuery(child_type, false, true, root_, row, 0); + + // Save a pointer to it for later + if (child_type == GroupBy_None) + song_nodes_[item->metadata.id()] = item; + else + container_nodes_[0][item->key] = item; + } + + reset(); +} + +void LibraryModel::BeginReset() { delete root_; song_nodes_.clear(); container_nodes_[0].clear(); @@ -454,6 +530,10 @@ void LibraryModel::Reset() { // Smart playlists? if (show_smart_playlists_ && query_options_.filter.isEmpty()) CreateSmartPlaylists(); +} + +void LibraryModel::Reset() { + BeginReset(); // Populate top level LazyPopulate(root_, false); @@ -553,29 +633,29 @@ LibraryItem* LibraryModel::InitItem(GroupBy type, bool signal, LibraryItem *pare } LibraryItem* LibraryModel::ItemFromQuery(GroupBy type, - bool signal, bool create_divider, - LibraryItem* parent, const LibraryQuery& q, - int container_level) { + bool signal, bool create_divider, + LibraryItem* parent, const SqlRow& row, + int container_level) { LibraryItem* item = InitItem(type, signal, parent, container_level); int year = 0; switch (type) { case GroupBy_Artist: - item->key = q.Value(0).toString(); + item->key = row.value(0).toString(); item->display_text = TextOrUnknown(item->key); item->sort_text = SortTextForArtist(item->key); break; case GroupBy_YearAlbum: - year = qMax(0, q.Value(0).toInt()); - item->metadata.set_year(q.Value(0).toInt()); - item->metadata.set_album(q.Value(1).toString()); + year = qMax(0, row.value(0).toInt()); + item->metadata.set_year(row.value(0).toInt()); + item->metadata.set_album(row.value(1).toString()); item->key = PrettyYearAlbum(year, item->metadata.album()); item->sort_text = SortTextForYear(year) + item->metadata.album(); break; case GroupBy_Year: - year = qMax(0, q.Value(0).toInt()); + year = qMax(0, row.value(0).toInt()); item->key = QString::number(year); item->sort_text = SortTextForYear(year) + " "; break; @@ -584,18 +664,18 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type, case GroupBy_Genre: case GroupBy_Album: case GroupBy_AlbumArtist: - item->key = q.Value(0).toString(); + item->key = row.value(0).toString(); item->display_text = TextOrUnknown(item->key); item->sort_text = SortTextForArtist(item->key); break; case GroupBy_FileType: - item->metadata.set_filetype(Song::FileType(q.Value(0).toInt())); + item->metadata.set_filetype(Song::FileType(row.value(0).toInt())); item->key = item->metadata.TextForFiletype(); break; case GroupBy_None: - item->metadata.InitFromQuery(q); + item->metadata.InitFromQuery(row); item->key = item->metadata.title(); item->display_text = item->metadata.TitleWithCompilationArtist(); item->sort_text = SortTextForSong(item->metadata); @@ -841,12 +921,12 @@ SongList LibraryModel::GetChildSongs(const QModelIndex &index) const { void LibraryModel::SetFilterAge(int age) { query_options_.max_age = age; - Reset(); + ResetAsync(); } void LibraryModel::SetFilterText(const QString& text) { query_options_.filter = text; - Reset(); + ResetAsync(); } bool LibraryModel::canFetchMore(const QModelIndex &parent) const { @@ -860,7 +940,7 @@ bool LibraryModel::canFetchMore(const QModelIndex &parent) const { void LibraryModel::SetGroupBy(const Grouping& g) { group_by_ = g; - Reset(); + ResetAsync(); emit GroupingChanged(g); } diff --git a/src/library/librarymodel.h b/src/library/librarymodel.h index 312384c1d..c65e8da8e 100644 --- a/src/library/librarymodel.h +++ b/src/library/librarymodel.h @@ -21,9 +21,10 @@ #include #include -#include "librarywatcher.h" -#include "libraryquery.h" #include "libraryitem.h" +#include "libraryquery.h" +#include "librarywatcher.h" +#include "sqlrow.h" #include "core/backgroundthread.h" #include "core/simpletreemodel.h" #include "core/song.h" @@ -134,6 +135,7 @@ class LibraryModel : public SimpleTreeModel { void SetGroupBy(const LibraryModel::Grouping& g); void Init(); void Reset(); + void ResetAsync(); protected: void LazyPopulate(LibraryItem* item) { LazyPopulate(item, true); } @@ -145,21 +147,32 @@ class LibraryModel : public SimpleTreeModel { void SongsDeleted(const SongList& songs); void SongsStatisticsChanged(const SongList& songs); + // Called after ResetAsync + void ResetAsyncQueryFinished(); + private: void Initialise(); + // Provides some optimisations for loading the list of items in the root. + // This gets called a lot when filtering the playlist, so it's nice to be + // able to do it in a background thread. + SqlRowList RunRootQuery(const QueryOptions& query_options, + const Grouping& group_by); + + void BeginReset(); + // Functions for working with queries and creating items. // When the model is reset or when a node is lazy-loaded the Library // 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. - void InitQuery(GroupBy type, LibraryQuery* q); + static void InitQuery(GroupBy type, LibraryQuery* q); void FilterQuery(GroupBy type, LibraryItem* item, LibraryQuery* q); // Items can be created either from a query that's been run to populate a // node, or by a spontaneous SongsDiscovered emission from the backend. LibraryItem* ItemFromQuery(GroupBy type, bool signal, bool create_divider, - LibraryItem* parent, const LibraryQuery& q, + LibraryItem* parent, const SqlRow& row, int container_level); LibraryItem* ItemFromSong(GroupBy type, bool signal, bool create_divider, LibraryItem* parent, const Song& s, diff --git a/src/library/sqlrow.h b/src/library/sqlrow.h index ed3cc3ff8..c6807d9b9 100644 --- a/src/library/sqlrow.h +++ b/src/library/sqlrow.h @@ -31,7 +31,7 @@ class SqlRow { SqlRow(const QSqlQuery& query); SqlRow(const LibraryQuery& query); - QVariant value(int i) const { return columns_[i]; } + const QVariant& value(int i) const { return columns_[i]; } private: SqlRow(); @@ -41,4 +41,6 @@ class SqlRow { QList columns_; }; +typedef QList SqlRowList; + #endif