1
/* This file is part of Clementine.
2
Copyright 2010, David Sansome <me@davidsansome.com>
4
Clementine is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
9
Clementine is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
18
#include "librarysearchprovider.h"
19
#include "core/logging.h"
20
#include "covers/albumcoverloader.h"
21
#include "library/librarybackend.h"
22
#include "library/libraryquery.h"
23
#include "library/sqlrow.h"
24
#include "playlist/songmimedata.h"
27
LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
31
bool enabled_by_default,
33
: BlockingSearchProvider(parent),
36
Hints hints = WantsSerialisedArtQueries | ArtIsInSongMetadata |
39
if (!enabled_by_default) {
40
hints |= DisabledByDefault;
43
Init(name, id, icon, hints);
46
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
48
options.set_filter(query);
50
LibraryQuery q(options);
51
q.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
53
if (!backend_->ExecQuery(&q)) {
57
const QStringList tokens = TokenizeQuery(query);
59
QMultiMap<QString, Song> albums;
60
QSet<QString> albums_with_non_track_matches;
66
song.InitFromQuery(q, true);
68
QString album_key = song.album();
69
if (song.is_compilation() && !song.albumartist().isEmpty()) {
70
album_key.prepend(song.albumartist() + " - ");
71
} else if (!song.is_compilation()) {
72
album_key.prepend(song.artist());
75
globalsearch::MatchQuality quality = MatchQuality(tokens, song.title());
77
if (quality != globalsearch::Quality_None) {
78
// If the query matched in the song title then we're interested in this
79
// as an individual song.
81
result.type_ = globalsearch::Type_Track;
82
result.metadata_ = song;
83
result.match_quality_ = quality;
86
// Otherwise we record this as being an interesting album.
87
albums_with_non_track_matches.insert(album_key);
90
albums.insertMulti(album_key, song);
93
// Add any albums that contained least one song that wasn't matched on the
95
foreach (const QString& key, albums_with_non_track_matches) {
97
result.type_ = globalsearch::Type_Album;
98
result.metadata_ = albums.value(key);
99
result.album_size_ = albums.count(key);
100
result.match_quality_ =
102
MatchQuality(tokens, result.metadata_.albumartist()),
103
qMin(MatchQuality(tokens, result.metadata_.artist()),
104
MatchQuality(tokens, result.metadata_.album())));
106
result.album_songs_ = albums.values(key);
107
SortSongs(&result.album_songs_);
115
void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
118
switch (result.type_) {
119
case globalsearch::Type_Track:
120
// This is really easy - we just emit the track again.
121
ret << result.metadata_;
124
case globalsearch::Type_Album: {
125
// Find all the songs in this album.
127
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
128
query.AddCompilationRequirement(result.metadata_.is_compilation());
129
query.AddWhere("album", result.metadata_.album());
131
if (!result.metadata_.is_compilation())
132
query.AddWhere("artist", result.metadata_.artist());
134
if (!backend_->ExecQuery(&query)) {
138
while (query.Next()) {
140
song.InitFromQuery(query, true);
151
SongMimeData* mime_data = new SongMimeData;
152
mime_data->backend = backend_;
153
mime_data->songs = ret;
155
emit TracksLoaded(id, mime_data);
158
QString LibrarySearchProvider::GetSuggestion() {
159
// We'd like to use order by random(), but that's O(n) in sqlite, so instead
160
// get the largest ROWID and pick a couple of random numbers within that
164
q.SetColumnSpec("ROWID");
165
q.SetOrderBy("ROWID DESC");
166
q.SetIncludeUnavailable(true);
169
if (!backend_->ExecQuery(&q) || !q.Next()) {
173
const int largest_rowid = q.Value(0).toInt();
175
for (int attempt=0 ; attempt<10 ; ++attempt) {
177
q.SetColumnSpec("artist, album");
178
q.SetIncludeUnavailable(true);
179
q.AddWhere("ROWID", qrand() % largest_rowid);
182
if (!backend_->ExecQuery(&q) || !q.Next()) {
186
const QString artist = q.Value(0).toString();
187
const QString album = q.Value(1).toString();
189
if (!artist.isEmpty() && !album.isEmpty())
190
return (qrand() % 2 == 0) ? artist : album;
191
else if (!artist.isEmpty())
193
else if (!album.isEmpty())