Do library searching and grouping in a background thread

This commit is contained in:
David Sansome 2010-11-27 20:09:00 +00:00
parent e63c101223
commit af234763f2
3 changed files with 117 additions and 22 deletions

View File

@ -27,10 +27,13 @@
#include "smartplaylists/querygenerator.h"
#include "ui/iconloader.h"
#include <QFuture>
#include <QFutureWatcher>
#include <QMetaEnum>
#include <QSettings>
#include <QStringList>
#include <QUrl>
#include <QMetaEnum>
#include <QtConcurrentRun>
#include <boost/bind.hpp>
@ -43,6 +46,9 @@ const char* LibraryModel::kSmartPlaylistsMimeType = "application/x-clementine-sm
const char* LibraryModel::kSmartPlaylistsSettingsGroup = "SerialisedSmartPlaylists";
const int LibraryModel::kSmartPlaylistsVersion = 3;
typedef QFuture<SqlRowList> RootQueryFuture;
typedef QFutureWatcher<SqlRowList> RootQueryWatcher;
LibraryModel::LibraryModel(LibraryBackend* backend, QObject* parent)
: SimpleTreeModel<LibraryItem>(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<RootQueryWatcher*>(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);
}

View File

@ -21,9 +21,10 @@
#include <QAbstractItemModel>
#include <QIcon>
#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<LibraryItem> {
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<LibraryItem> {
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,

View File

@ -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<QVariant> columns_;
};
typedef QList<SqlRow> SqlRowList;
#endif