/* * Strawberry Music Player * This code was part of Clementine (GlobalSearch) * Copyright 2010, David Sansome * Copyright 2018, Jonas Kvinge * * Strawberry 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. * * Strawberry 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 Strawberry. If not, see . * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/application.h" #include "core/logging.h" #include "core/closure.h" #include "core/iconloader.h" #include "core/song.h" #include "covermanager/albumcoverloader.h" #include "internet/internetsongmimedata.h" #include "playlist/songmimedata.h" #include "internetsearch.h" #include "internetservice.h" #include "internetservices.h" using std::advance; const int InternetSearch::kDelayedSearchTimeoutMs = 200; const int InternetSearch::kMaxResultsPerEmission = 2000; const int InternetSearch::kArtHeight = 32; InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *parent) : QObject(parent), app_(app), source_(source), service_(app->internet_services()->ServiceBySource(source)), searches_next_id_(1), art_searches_next_id_(1) { cover_loader_options_.desired_height_ = kArtHeight; cover_loader_options_.pad_output_image_ = true; cover_loader_options_.scale_output_image_ = true; connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage))); connect(this, SIGNAL(SearchAsyncSig(int, QString, SearchBy)), this, SLOT(DoSearchAsync(int, QString, SearchBy))); connect(this, SIGNAL(ResultsAvailable(int, InternetSearch::ResultList)), SLOT(ResultsAvailableSlot(int, InternetSearch::ResultList))); connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage))); connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString))); connect(service_, SIGNAL(ProgressSetMaximum(int)), SLOT(ProgressSetMaximumSlot(int))); connect(service_, SIGNAL(UpdateProgress(int)), SLOT(UpdateProgressSlot(int))); connect(service_, SIGNAL(SearchResults(int, SongList)), SLOT(SearchDone(int, SongList))); connect(service_, SIGNAL(SearchError(int, QString)), SLOT(HandleError(int, QString))); } InternetSearch::~InternetSearch() {} QStringList InternetSearch::TokenizeQuery(const QString &query) { QStringList tokens(query.split(QRegExp("\\s+"))); for (QStringList::iterator it = tokens.begin(); it != tokens.end(); ++it) { (*it).remove('('); (*it).remove(')'); (*it).remove('"'); const int colon = (*it).indexOf(":"); if (colon != -1) { (*it).remove(0, colon + 1); } } return tokens; } bool InternetSearch::Matches(const QStringList &tokens, const QString &string) { for (const QString &token : tokens) { if (!string.contains(token, Qt::CaseInsensitive)) { return false; } } return true; } int InternetSearch::SearchAsync(const QString &query, SearchBy searchby) { const int id = searches_next_id_++; emit SearchAsyncSig(id, query, searchby); return id; } void InternetSearch::SearchAsync(int id, const QString &query, SearchBy searchby) { const int service_id = service_->Search(query, searchby); pending_searches_[service_id] = PendingState(id, TokenizeQuery(query)); } void InternetSearch::DoSearchAsync(int id, const QString &query, SearchBy searchby) { int timer_id = startTimer(kDelayedSearchTimeoutMs); delayed_searches_[timer_id].id_ = id; delayed_searches_[timer_id].query_ = query; delayed_searches_[timer_id].searchby_ = searchby; } void InternetSearch::SearchDone(int service_id, const SongList &songs) { // Map back to the original id. const PendingState state = pending_searches_.take(service_id); const int search_id = state.orig_id_; ResultList ret; for (const Song &song : songs) { Result result; result.metadata_ = song; ret << result; } emit ResultsAvailable(search_id, ret); MaybeSearchFinished(search_id); } void InternetSearch::HandleError(const int id, const QString error) { emit SearchError(id, error); } void InternetSearch::MaybeSearchFinished(int id) { if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) { emit SearchFinished(id); } } void InternetSearch::CancelSearch(int id) { QMap::iterator it; for (it = delayed_searches_.begin(); it != delayed_searches_.end(); ++it) { if (it.value().id_ == id) { killTimer(it.key()); delayed_searches_.erase(it); return; } } service_->CancelSearch(); } void InternetSearch::timerEvent(QTimerEvent *e) { QMap::iterator it = delayed_searches_.find(e->timerId()); if (it != delayed_searches_.end()) { SearchAsync(it.value().id_, it.value().query_, it.value().searchby_); delayed_searches_.erase(it); return; } QObject::timerEvent(e); } void InternetSearch::ResultsAvailableSlot(int id, InternetSearch::ResultList results) { if (results.isEmpty()) return; // Limit the number of results that are used from each emission. if (results.count() > kMaxResultsPerEmission) { InternetSearch::ResultList::iterator begin = results.begin(); std::advance(begin, kMaxResultsPerEmission); results.erase(begin, results.end()); } // Load cached pixmaps into the results for (InternetSearch::ResultList::iterator it = results.begin(); it != results.end(); ++it) { it->pixmap_cache_key_ = PixmapCacheKey(*it); } emit AddResults(id, results); } QString InternetSearch::PixmapCacheKey(const InternetSearch::Result &result) const { return "internet:" % result.metadata_.url().toString(); } bool InternetSearch::FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const { return pixmap_cache_.find(result.pixmap_cache_key_, pixmap); } int InternetSearch::LoadArtAsync(const InternetSearch::Result &result) { const int id = art_searches_next_id_++; pending_art_searches_[id] = result.pixmap_cache_key_; quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_); cover_loader_tasks_[loader_id] = id; return id; } void InternetSearch::ArtLoadedSlot(int id, const QImage &image) { HandleLoadedArt(id, image); } void InternetSearch::AlbumArtLoaded(quint64 id, const QImage &image) { if (!cover_loader_tasks_.contains(id)) return; int orig_id = cover_loader_tasks_.take(id); HandleLoadedArt(orig_id, image); } void InternetSearch::HandleLoadedArt(int id, const QImage &image) { const QString key = pending_art_searches_.take(id); QPixmap pixmap = QPixmap::fromImage(image); pixmap_cache_.insert(key, pixmap); emit ArtLoaded(id, pixmap); } QImage InternetSearch::ScaleAndPad(const QImage &image) { if (image.isNull()) return QImage(); const QSize target_size = QSize(kArtHeight, kArtHeight); if (image.size() == target_size) return image; // Scale the image down QImage copy; copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); // Pad the image to kHeight x kHeight if (copy.size() == target_size) return copy; QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32); padded_image.fill(0); QPainter p(&padded_image); p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, copy); p.end(); return padded_image; } MimeData *InternetSearch::LoadTracks(const ResultList &results) { if (results.isEmpty()) { return nullptr; } ResultList results_copy; for (const Result &result : results) { results_copy << result; } SongList songs; for (const Result &result : results) { songs << result.metadata_; } InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_); internet_song_mime_data->songs = songs; MimeData *mime_data = internet_song_mime_data; QList urls; for (const Result &result : results) { urls << result.metadata_.url(); } mime_data->setUrls(urls); return mime_data; } void InternetSearch::UpdateStatusSlot(QString text) { emit UpdateStatus(text); } void InternetSearch::ProgressSetMaximumSlot(int max) { emit ProgressSetMaximum(max); } void InternetSearch::UpdateProgressSlot(int progress) { emit UpdateProgress(progress); }