2011-08-29 00:59:18 +02:00
|
|
|
/* This file is part of Clementine.
|
|
|
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
|
|
|
|
|
|
|
Clementine 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.
|
|
|
|
|
|
|
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "spotifysearchprovider.h"
|
2012-07-12 16:35:09 +02:00
|
|
|
|
|
|
|
#include <ctime>
|
2014-02-06 14:48:00 +01:00
|
|
|
#include <random>
|
2012-07-12 16:35:09 +02:00
|
|
|
|
2011-08-29 01:32:45 +02:00
|
|
|
#include "core/logging.h"
|
2014-12-18 23:35:21 +01:00
|
|
|
#include "internet/core/internetmodel.h"
|
|
|
|
#include "internet/spotify/spotifyserver.h"
|
2011-08-29 04:26:59 +02:00
|
|
|
#include "playlist/songmimedata.h"
|
2011-08-29 00:59:18 +02:00
|
|
|
|
2013-02-12 12:33:03 +01:00
|
|
|
namespace {
|
|
|
|
const int kSearchSongLimit = 5;
|
|
|
|
const int kSearchAlbumLimit = 20;
|
|
|
|
}
|
|
|
|
|
2012-02-13 21:44:04 +01:00
|
|
|
SpotifySearchProvider::SpotifySearchProvider(Application* app, QObject* parent)
|
2014-02-07 16:34:20 +01:00
|
|
|
: SearchProvider(app, parent), server_(nullptr), service_(nullptr) {
|
2011-11-05 17:08:56 +01:00
|
|
|
Init("Spotify", "spotify", QIcon(":icons/32x32/spotify.png"),
|
2011-11-05 18:11:02 +01:00
|
|
|
WantsDelayedQueries | WantsSerialisedArtQueries | ArtIsProbablyRemote |
|
2014-02-07 16:34:20 +01:00
|
|
|
CanShowConfig | CanGiveSuggestions);
|
2011-08-29 00:59:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SpotifyServer* SpotifySearchProvider::server() {
|
2014-02-07 16:34:20 +01:00
|
|
|
if (server_) return server_;
|
2011-08-29 00:59:18 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
if (!service_) service_ = InternetModel::Service<SpotifyService>();
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
if (service_->login_state() != SpotifyService::LoginState_LoggedIn)
|
2014-02-06 16:49:49 +01:00
|
|
|
return nullptr;
|
2011-08-29 00:59:18 +02:00
|
|
|
|
2014-09-29 18:44:20 +02:00
|
|
|
if (!service_->IsBlobInstalled())
|
|
|
|
return nullptr;
|
|
|
|
|
2011-08-29 00:59:18 +02:00
|
|
|
server_ = service_->server();
|
2012-01-08 00:26:27 +01:00
|
|
|
connect(server_, SIGNAL(SearchResults(pb::spotify::SearchResponse)),
|
|
|
|
SLOT(SearchFinishedSlot(pb::spotify::SearchResponse)));
|
2014-02-07 16:34:20 +01:00
|
|
|
connect(server_, SIGNAL(ImageLoaded(QString, QImage)),
|
|
|
|
SLOT(ArtLoadedSlot(QString, QImage)));
|
2011-08-29 00:59:18 +02:00
|
|
|
connect(server_, SIGNAL(destroyed()), SLOT(ServerDestroyed()));
|
2012-07-12 16:35:09 +02:00
|
|
|
connect(server_, SIGNAL(StarredLoaded(pb::spotify::LoadPlaylistResponse)),
|
|
|
|
SLOT(SuggestionsLoaded(pb::spotify::LoadPlaylistResponse)));
|
2014-02-07 16:34:20 +01:00
|
|
|
connect(server_,
|
|
|
|
SIGNAL(ToplistBrowseResults(pb::spotify::BrowseToplistResponse)),
|
2012-07-12 16:35:09 +02:00
|
|
|
SLOT(SuggestionsLoaded(pb::spotify::BrowseToplistResponse)));
|
2011-08-29 00:59:18 +02:00
|
|
|
|
2011-11-12 19:54:42 +01:00
|
|
|
return server_;
|
2011-08-29 00:59:18 +02:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void SpotifySearchProvider::ServerDestroyed() { server_ = nullptr; }
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
void SpotifySearchProvider::SearchAsync(int id, const QString& query) {
|
|
|
|
SpotifyServer* s = server();
|
|
|
|
if (!s) {
|
|
|
|
emit SearchFinished(id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PendingState state;
|
|
|
|
state.orig_id_ = id;
|
|
|
|
state.tokens_ = TokenizeQuery(query);
|
|
|
|
|
|
|
|
const QString query_string = state.tokens_.join(" ");
|
2013-02-12 12:33:03 +01:00
|
|
|
s->Search(query_string, kSearchSongLimit, kSearchAlbumLimit);
|
2011-08-29 00:59:18 +02:00
|
|
|
queries_[query_string] = state;
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void SpotifySearchProvider::SearchFinishedSlot(
|
|
|
|
const pb::spotify::SearchResponse& response) {
|
2011-08-29 00:59:18 +02:00
|
|
|
QString query_string = QString::fromUtf8(response.request().query().c_str());
|
|
|
|
QMap<QString, PendingState>::iterator it = queries_.find(query_string);
|
2014-02-07 16:34:20 +01:00
|
|
|
if (it == queries_.end()) return;
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
PendingState state = it.value();
|
|
|
|
queries_.erase(it);
|
|
|
|
|
|
|
|
ResultList ret;
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < response.result_size(); ++i) {
|
2012-01-08 00:26:27 +01:00
|
|
|
const pb::spotify::Track& track = response.result(i);
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
Result result(this);
|
|
|
|
SpotifyService::SongFromProtobuf(track, &result.metadata_);
|
|
|
|
|
|
|
|
ret << result;
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < response.album_size(); ++i) {
|
2012-01-08 00:26:27 +01:00
|
|
|
const pb::spotify::Album& album = response.album(i);
|
2011-08-29 03:00:59 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int j = 0; j < album.track_size(); ++j) {
|
2012-06-04 19:18:37 +02:00
|
|
|
Result result(this);
|
|
|
|
SpotifyService::SongFromProtobuf(album.track(j), &result.metadata_);
|
2013-02-02 10:22:08 +01:00
|
|
|
|
|
|
|
// Just use the album index as an id.
|
|
|
|
result.metadata_.set_album_id(i);
|
|
|
|
|
2012-06-04 19:18:37 +02:00
|
|
|
ret << result;
|
2011-09-18 01:06:07 +02:00
|
|
|
}
|
2011-08-29 03:00:59 +02:00
|
|
|
}
|
|
|
|
|
2011-08-29 00:59:18 +02:00
|
|
|
emit ResultsAvailable(state.orig_id_, ret);
|
|
|
|
emit SearchFinished(state.orig_id_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpotifySearchProvider::LoadArtAsync(int id, const Result& result) {
|
|
|
|
SpotifyServer* s = server();
|
|
|
|
if (!s) {
|
|
|
|
emit ArtLoaded(id, QImage());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-08-29 02:24:32 +02:00
|
|
|
QString image_id = QUrl(result.metadata_.art_automatic()).path();
|
2014-02-07 16:34:20 +01:00
|
|
|
if (image_id.startsWith('/')) image_id.remove(0, 1);
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
pending_art_[image_id] = id;
|
|
|
|
s->LoadImage(image_id);
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void SpotifySearchProvider::ArtLoadedSlot(const QString& id,
|
|
|
|
const QImage& image) {
|
2011-08-29 00:59:18 +02:00
|
|
|
QMap<QString, int>::iterator it = pending_art_.find(id);
|
2014-02-07 16:34:20 +01:00
|
|
|
if (it == pending_art_.end()) return;
|
2011-08-29 00:59:18 +02:00
|
|
|
|
|
|
|
const int orig_id = it.value();
|
|
|
|
pending_art_.erase(it);
|
|
|
|
|
|
|
|
emit ArtLoaded(orig_id, ScaleAndPad(image));
|
|
|
|
}
|
|
|
|
|
2011-10-20 15:03:47 +02:00
|
|
|
bool SpotifySearchProvider::IsLoggedIn() {
|
|
|
|
if (server()) {
|
|
|
|
return service_->IsLoggedIn();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2011-08-29 00:59:18 +02:00
|
|
|
|
2011-10-20 15:03:47 +02:00
|
|
|
void SpotifySearchProvider::ShowConfig() {
|
|
|
|
if (service_) {
|
|
|
|
return service_->ShowConfig();
|
|
|
|
}
|
|
|
|
}
|
2012-07-12 16:35:09 +02:00
|
|
|
|
|
|
|
void SpotifySearchProvider::AddSuggestionFromTrack(
|
|
|
|
const pb::spotify::Track& track) {
|
|
|
|
if (!track.title().empty()) {
|
|
|
|
suggestions_.insert(QString::fromUtf8(track.title().c_str()));
|
|
|
|
}
|
|
|
|
for (int j = 0; j < track.artist_size(); ++j) {
|
|
|
|
if (!track.artist(j).empty()) {
|
|
|
|
suggestions_.insert(QString::fromUtf8(track.artist(j).c_str()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!track.album().empty()) {
|
|
|
|
suggestions_.insert(QString::fromUtf8(track.album().c_str()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpotifySearchProvider::AddSuggestionFromAlbum(
|
|
|
|
const pb::spotify::Album& album) {
|
|
|
|
AddSuggestionFromTrack(album.metadata());
|
|
|
|
for (int i = 0; i < album.track_size(); ++i) {
|
|
|
|
AddSuggestionFromTrack(album.track(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpotifySearchProvider::SuggestionsLoaded(
|
|
|
|
const pb::spotify::LoadPlaylistResponse& playlist) {
|
|
|
|
for (int i = 0; i < playlist.track_size(); ++i) {
|
|
|
|
AddSuggestionFromTrack(playlist.track(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpotifySearchProvider::SuggestionsLoaded(
|
|
|
|
const pb::spotify::BrowseToplistResponse& response) {
|
|
|
|
for (int i = 0; i < response.track_size(); ++i) {
|
|
|
|
AddSuggestionFromTrack(response.track(i));
|
|
|
|
}
|
|
|
|
for (int i = 0; i < response.album_size(); ++i) {
|
|
|
|
AddSuggestionFromAlbum(response.album(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpotifySearchProvider::LoadSuggestions() {
|
|
|
|
if (!server()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
server()->LoadStarred();
|
|
|
|
server()->LoadToplist();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList SpotifySearchProvider::GetSuggestions(int count) {
|
|
|
|
if (suggestions_.empty()) {
|
|
|
|
LoadSuggestions();
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList all_suggestions = suggestions_.toList();
|
|
|
|
|
2014-02-06 14:48:00 +01:00
|
|
|
std::mt19937 gen(std::time(0));
|
|
|
|
std::uniform_int_distribution<> random(0, all_suggestions.size() - 1);
|
2012-07-12 16:35:09 +02:00
|
|
|
|
|
|
|
QSet<QString> candidates;
|
|
|
|
|
|
|
|
const int max = qMin(count, all_suggestions.size());
|
|
|
|
while (candidates.size() < max) {
|
|
|
|
const int index = random(gen);
|
|
|
|
candidates.insert(all_suggestions[index]);
|
|
|
|
}
|
|
|
|
return candidates.toList();
|
|
|
|
}
|