Do library searching and grouping in a background thread
This commit is contained in:
parent
e63c101223
commit
af234763f2
|
@ -27,10 +27,13 @@
|
||||||
#include "smartplaylists/querygenerator.h"
|
#include "smartplaylists/querygenerator.h"
|
||||||
#include "ui/iconloader.h"
|
#include "ui/iconloader.h"
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QMetaEnum>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QMetaEnum>
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
@ -43,6 +46,9 @@ const char* LibraryModel::kSmartPlaylistsMimeType = "application/x-clementine-sm
|
||||||
const char* LibraryModel::kSmartPlaylistsSettingsGroup = "SerialisedSmartPlaylists";
|
const char* LibraryModel::kSmartPlaylistsSettingsGroup = "SerialisedSmartPlaylists";
|
||||||
const int LibraryModel::kSmartPlaylistsVersion = 3;
|
const int LibraryModel::kSmartPlaylistsVersion = 3;
|
||||||
|
|
||||||
|
typedef QFuture<SqlRowList> RootQueryFuture;
|
||||||
|
typedef QFutureWatcher<SqlRowList> RootQueryWatcher;
|
||||||
|
|
||||||
LibraryModel::LibraryModel(LibraryBackend* backend, QObject* parent)
|
LibraryModel::LibraryModel(LibraryBackend* backend, QObject* parent)
|
||||||
: SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent),
|
: SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent),
|
||||||
backend_(backend),
|
backend_(backend),
|
||||||
|
@ -386,11 +392,42 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const {
|
||||||
return QVariant();
|
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) {
|
void LibraryModel::LazyPopulate(LibraryItem* parent, bool signal) {
|
||||||
if (parent->lazy_loaded)
|
if (parent->lazy_loaded)
|
||||||
return;
|
return;
|
||||||
parent->lazy_loaded = true;
|
parent->lazy_loaded = true;
|
||||||
|
|
||||||
|
// Warning: Some copy-paste with RunRootQuery here
|
||||||
|
|
||||||
// Information about what we want the children to be
|
// Information about what we want the children to be
|
||||||
int child_level = parent->container_level + 1;
|
int child_level = parent->container_level + 1;
|
||||||
GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level];
|
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
|
// Step through the results
|
||||||
while (q.Next()) {
|
while (q.Next()) {
|
||||||
|
// Warning: Some copy-paste with ResetAsyncQueryFinished here
|
||||||
|
|
||||||
// Create the item - it will get inserted into the model here
|
// Create the item - it will get inserted into the model here
|
||||||
LibraryItem* item =
|
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
|
// Save a pointer to it for later
|
||||||
if (child_type == GroupBy_None)
|
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_;
|
delete root_;
|
||||||
song_nodes_.clear();
|
song_nodes_.clear();
|
||||||
container_nodes_[0].clear();
|
container_nodes_[0].clear();
|
||||||
|
@ -454,6 +530,10 @@ void LibraryModel::Reset() {
|
||||||
// Smart playlists?
|
// Smart playlists?
|
||||||
if (show_smart_playlists_ && query_options_.filter.isEmpty())
|
if (show_smart_playlists_ && query_options_.filter.isEmpty())
|
||||||
CreateSmartPlaylists();
|
CreateSmartPlaylists();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LibraryModel::Reset() {
|
||||||
|
BeginReset();
|
||||||
|
|
||||||
// Populate top level
|
// Populate top level
|
||||||
LazyPopulate(root_, false);
|
LazyPopulate(root_, false);
|
||||||
|
@ -553,29 +633,29 @@ LibraryItem* LibraryModel::InitItem(GroupBy type, bool signal, LibraryItem *pare
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
|
LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
|
||||||
bool signal, bool create_divider,
|
bool signal, bool create_divider,
|
||||||
LibraryItem* parent, const LibraryQuery& q,
|
LibraryItem* parent, const SqlRow& row,
|
||||||
int container_level) {
|
int container_level) {
|
||||||
LibraryItem* item = InitItem(type, signal, parent, container_level);
|
LibraryItem* item = InitItem(type, signal, parent, container_level);
|
||||||
int year = 0;
|
int year = 0;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case GroupBy_Artist:
|
case GroupBy_Artist:
|
||||||
item->key = q.Value(0).toString();
|
item->key = row.value(0).toString();
|
||||||
item->display_text = TextOrUnknown(item->key);
|
item->display_text = TextOrUnknown(item->key);
|
||||||
item->sort_text = SortTextForArtist(item->key);
|
item->sort_text = SortTextForArtist(item->key);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_YearAlbum:
|
case GroupBy_YearAlbum:
|
||||||
year = qMax(0, q.Value(0).toInt());
|
year = qMax(0, row.value(0).toInt());
|
||||||
item->metadata.set_year(q.Value(0).toInt());
|
item->metadata.set_year(row.value(0).toInt());
|
||||||
item->metadata.set_album(q.Value(1).toString());
|
item->metadata.set_album(row.value(1).toString());
|
||||||
item->key = PrettyYearAlbum(year, item->metadata.album());
|
item->key = PrettyYearAlbum(year, item->metadata.album());
|
||||||
item->sort_text = SortTextForYear(year) + item->metadata.album();
|
item->sort_text = SortTextForYear(year) + item->metadata.album();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_Year:
|
case GroupBy_Year:
|
||||||
year = qMax(0, q.Value(0).toInt());
|
year = qMax(0, row.value(0).toInt());
|
||||||
item->key = QString::number(year);
|
item->key = QString::number(year);
|
||||||
item->sort_text = SortTextForYear(year) + " ";
|
item->sort_text = SortTextForYear(year) + " ";
|
||||||
break;
|
break;
|
||||||
|
@ -584,18 +664,18 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type,
|
||||||
case GroupBy_Genre:
|
case GroupBy_Genre:
|
||||||
case GroupBy_Album:
|
case GroupBy_Album:
|
||||||
case GroupBy_AlbumArtist:
|
case GroupBy_AlbumArtist:
|
||||||
item->key = q.Value(0).toString();
|
item->key = row.value(0).toString();
|
||||||
item->display_text = TextOrUnknown(item->key);
|
item->display_text = TextOrUnknown(item->key);
|
||||||
item->sort_text = SortTextForArtist(item->key);
|
item->sort_text = SortTextForArtist(item->key);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_FileType:
|
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();
|
item->key = item->metadata.TextForFiletype();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GroupBy_None:
|
case GroupBy_None:
|
||||||
item->metadata.InitFromQuery(q);
|
item->metadata.InitFromQuery(row);
|
||||||
item->key = item->metadata.title();
|
item->key = item->metadata.title();
|
||||||
item->display_text = item->metadata.TitleWithCompilationArtist();
|
item->display_text = item->metadata.TitleWithCompilationArtist();
|
||||||
item->sort_text = SortTextForSong(item->metadata);
|
item->sort_text = SortTextForSong(item->metadata);
|
||||||
|
@ -841,12 +921,12 @@ SongList LibraryModel::GetChildSongs(const QModelIndex &index) const {
|
||||||
|
|
||||||
void LibraryModel::SetFilterAge(int age) {
|
void LibraryModel::SetFilterAge(int age) {
|
||||||
query_options_.max_age = age;
|
query_options_.max_age = age;
|
||||||
Reset();
|
ResetAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibraryModel::SetFilterText(const QString& text) {
|
void LibraryModel::SetFilterText(const QString& text) {
|
||||||
query_options_.filter = text;
|
query_options_.filter = text;
|
||||||
Reset();
|
ResetAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LibraryModel::canFetchMore(const QModelIndex &parent) const {
|
bool LibraryModel::canFetchMore(const QModelIndex &parent) const {
|
||||||
|
@ -860,7 +940,7 @@ bool LibraryModel::canFetchMore(const QModelIndex &parent) const {
|
||||||
void LibraryModel::SetGroupBy(const Grouping& g) {
|
void LibraryModel::SetGroupBy(const Grouping& g) {
|
||||||
group_by_ = g;
|
group_by_ = g;
|
||||||
|
|
||||||
Reset();
|
ResetAsync();
|
||||||
emit GroupingChanged(g);
|
emit GroupingChanged(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
#include "librarywatcher.h"
|
|
||||||
#include "libraryquery.h"
|
|
||||||
#include "libraryitem.h"
|
#include "libraryitem.h"
|
||||||
|
#include "libraryquery.h"
|
||||||
|
#include "librarywatcher.h"
|
||||||
|
#include "sqlrow.h"
|
||||||
#include "core/backgroundthread.h"
|
#include "core/backgroundthread.h"
|
||||||
#include "core/simpletreemodel.h"
|
#include "core/simpletreemodel.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
@ -134,6 +135,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
|
||||||
void SetGroupBy(const LibraryModel::Grouping& g);
|
void SetGroupBy(const LibraryModel::Grouping& g);
|
||||||
void Init();
|
void Init();
|
||||||
void Reset();
|
void Reset();
|
||||||
|
void ResetAsync();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void LazyPopulate(LibraryItem* item) { LazyPopulate(item, true); }
|
void LazyPopulate(LibraryItem* item) { LazyPopulate(item, true); }
|
||||||
|
@ -145,21 +147,32 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
|
||||||
void SongsDeleted(const SongList& songs);
|
void SongsDeleted(const SongList& songs);
|
||||||
void SongsStatisticsChanged(const SongList& songs);
|
void SongsStatisticsChanged(const SongList& songs);
|
||||||
|
|
||||||
|
// Called after ResetAsync
|
||||||
|
void ResetAsyncQueryFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Initialise();
|
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.
|
// Functions for working with queries and creating items.
|
||||||
// When the model is reset or when a node is lazy-loaded the Library
|
// 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
|
// constructs a database query to populate the items. Filters are added
|
||||||
// for each parent item, restricting the songs returned to a particular
|
// for each parent item, restricting the songs returned to a particular
|
||||||
// album or artist for example.
|
// 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);
|
void FilterQuery(GroupBy type, LibraryItem* item, LibraryQuery* q);
|
||||||
|
|
||||||
// Items can be created either from a query that's been run to populate a
|
// 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.
|
// node, or by a spontaneous SongsDiscovered emission from the backend.
|
||||||
LibraryItem* ItemFromQuery(GroupBy type, bool signal, bool create_divider,
|
LibraryItem* ItemFromQuery(GroupBy type, bool signal, bool create_divider,
|
||||||
LibraryItem* parent, const LibraryQuery& q,
|
LibraryItem* parent, const SqlRow& row,
|
||||||
int container_level);
|
int container_level);
|
||||||
LibraryItem* ItemFromSong(GroupBy type, bool signal, bool create_divider,
|
LibraryItem* ItemFromSong(GroupBy type, bool signal, bool create_divider,
|
||||||
LibraryItem* parent, const Song& s,
|
LibraryItem* parent, const Song& s,
|
||||||
|
|
|
@ -31,7 +31,7 @@ class SqlRow {
|
||||||
SqlRow(const QSqlQuery& query);
|
SqlRow(const QSqlQuery& query);
|
||||||
SqlRow(const LibraryQuery& query);
|
SqlRow(const LibraryQuery& query);
|
||||||
|
|
||||||
QVariant value(int i) const { return columns_[i]; }
|
const QVariant& value(int i) const { return columns_[i]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SqlRow();
|
SqlRow();
|
||||||
|
@ -41,4 +41,6 @@ class SqlRow {
|
||||||
QList<QVariant> columns_;
|
QList<QVariant> columns_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef QList<SqlRow> SqlRowList;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue