Add artist search in internet view, and use album artist
This commit is contained in:
parent
e1abd28a88
commit
35f448c34f
|
@ -144,7 +144,7 @@ const QString Song::kEmbeddedCover = "(embedded)";
|
||||||
|
|
||||||
const QRegExp Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$");
|
const QRegExp Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$");
|
||||||
const QRegExp Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered) ?((\\)|\\])?)$");
|
const QRegExp Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered) ?((\\)|\\])?)$");
|
||||||
const QRegExp Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Live) ?((\\)|\\])?)$");
|
const QRegExp Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Live|Remastered Version) ?((\\)|\\])?)$");
|
||||||
|
|
||||||
struct Song::Private : public QSharedData {
|
struct Song::Private : public QSharedData {
|
||||||
|
|
||||||
|
|
|
@ -350,8 +350,6 @@ QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) {
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
|
||||||
//qLog(Debug) << json_doc;
|
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
Error("Reply from server missing Json data.", data);
|
Error("Reply from server missing Json data.", data);
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
|
@ -373,8 +371,6 @@ QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) {
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
|
|
||||||
return json_obj;
|
return json_obj;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -412,11 +408,11 @@ QJsonValue DeezerService::ExtractData(QByteArray &data) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int DeezerService::Search(const QString &text, InternetSearch::SearchBy searchby) {
|
int DeezerService::Search(const QString &text, InternetSearch::SearchType searchby) {
|
||||||
|
|
||||||
pending_search_id_ = next_pending_search_id_;
|
pending_search_id_ = next_pending_search_id_;
|
||||||
pending_search_text_ = text;
|
pending_search_text_ = text;
|
||||||
pending_searchby_ = searchby;
|
pending_search_type_ = searchby;
|
||||||
|
|
||||||
next_pending_search_id_++;
|
next_pending_search_id_++;
|
||||||
|
|
||||||
|
@ -469,12 +465,13 @@ void DeezerService::SendSearch() {
|
||||||
QList<Param> parameters;
|
QList<Param> parameters;
|
||||||
parameters << Param("q", search_text_);
|
parameters << Param("q", search_text_);
|
||||||
QString searchparam;
|
QString searchparam;
|
||||||
switch (pending_searchby_) {
|
switch (pending_search_type_) {
|
||||||
case InternetSearch::SearchBy_Songs:
|
case InternetSearch::SearchType_Songs:
|
||||||
searchparam = "search/track";
|
searchparam = "search/track";
|
||||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||||
break;
|
break;
|
||||||
case InternetSearch::SearchBy_Albums:
|
case InternetSearch::SearchType_Albums:
|
||||||
|
case InternetSearch::SearchType_Artists:
|
||||||
default:
|
default:
|
||||||
searchparam = "search/album";
|
searchparam = "search/album";
|
||||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||||
|
@ -511,23 +508,19 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//qLog(Debug) << json_data;
|
|
||||||
|
|
||||||
for (const QJsonValue &value : json_data) {
|
for (const QJsonValue &value : json_data) {
|
||||||
//qLog(Debug) << value;
|
|
||||||
if (!value.isObject()) {
|
if (!value.isObject()) {
|
||||||
Error("Invalid Json reply, data is not an object.", value);
|
Error("Invalid Json reply, data is not an object.", value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QJsonObject json_obj = value.toObject();
|
QJsonObject json_obj = value.toObject();
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
|
|
||||||
if (!json_obj.contains("id") || !json_obj.contains("type")) {
|
if (!json_obj.contains("id") || !json_obj.contains("type")) {
|
||||||
Error("Invalid Json reply, item is missing ID or type.", json_obj);
|
Error("Invalid Json reply, item is missing ID or type.", json_obj);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//int id = json_obj["id"].toInt();
|
|
||||||
QString type = json_obj["type"].toString();
|
QString type = json_obj["type"].toString();
|
||||||
|
|
||||||
if (!json_obj.contains("artist")) {
|
if (!json_obj.contains("artist")) {
|
||||||
|
@ -575,7 +568,7 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
album = json_album["title"].toString();
|
album = json_album["title"].toString();
|
||||||
cover = json_album[coversize_].toString();
|
cover = json_album[coversize_].toString();
|
||||||
if (!fetchalbums_) {
|
if (!fetchalbums_) {
|
||||||
Song song = ParseSong(album_id, album, cover, value);
|
Song song = ParseSong(album_id, album, artist, cover, value);
|
||||||
songs_ << song;
|
songs_ << song;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -668,14 +661,12 @@ void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int al
|
||||||
|
|
||||||
bool compilation = false;
|
bool compilation = false;
|
||||||
bool multidisc = false;
|
bool multidisc = false;
|
||||||
Song first_song;
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
for (const QJsonValue &value : json_data) {
|
for (const QJsonValue &value : json_data) {
|
||||||
Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->cover, value);
|
Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->artist, album_ctx->cover, value);
|
||||||
if (!song.is_valid()) continue;
|
if (!song.is_valid()) continue;
|
||||||
if (song.disc() >= 2) multidisc = true;
|
if (song.disc() >= 2) multidisc = true;
|
||||||
if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true;
|
if (song.is_compilation()) compilation = true;
|
||||||
if (!first_song.is_valid()) first_song = song;
|
|
||||||
songs << song;
|
songs << song;
|
||||||
}
|
}
|
||||||
for (Song &song : songs) {
|
for (Song &song : songs) {
|
||||||
|
@ -692,7 +683,7 @@ void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int al
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value) {
|
Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value) {
|
||||||
|
|
||||||
if (!value.isObject()) {
|
if (!value.isObject()) {
|
||||||
Error("Invalid Json reply, track is not an object.", value);
|
Error("Invalid Json reply, track is not an object.", value);
|
||||||
|
@ -700,8 +691,6 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS
|
||||||
}
|
}
|
||||||
QJsonObject json_obj = value.toObject();
|
QJsonObject json_obj = value.toObject();
|
||||||
|
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!json_obj.contains("id") ||
|
!json_obj.contains("id") ||
|
||||||
!json_obj.contains("title") ||
|
!json_obj.contains("title") ||
|
||||||
|
@ -738,6 +727,7 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS
|
||||||
song.set_source(Song::Source_Deezer);
|
song.set_source(Song::Source_Deezer);
|
||||||
song.set_id(song_id);
|
song.set_id(song_id);
|
||||||
song.set_album_id(album_id);
|
song.set_album_id(album_id);
|
||||||
|
if (artist != album_artist) song.set_albumartist(album_artist);
|
||||||
song.set_artist(artist);
|
song.set_artist(artist);
|
||||||
song.set_album(album);
|
song.set_album(album);
|
||||||
song.set_title(title);
|
song.set_title(title);
|
||||||
|
@ -760,7 +750,6 @@ Song DeezerService::ParseSong(const int album_id, const QString &album, const QS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
song.set_url(url);
|
song.set_url(url);
|
||||||
|
|
||||||
song.set_valid(true);
|
song.set_valid(true);
|
||||||
|
|
||||||
return song;
|
return song;
|
||||||
|
|
|
@ -70,7 +70,7 @@ class DeezerService : public InternetService {
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void Logout();
|
void Logout();
|
||||||
int Search(const QString &query, InternetSearch::SearchBy searchby);
|
int Search(const QString &query, InternetSearch::SearchType searchby);
|
||||||
void CancelSearch();
|
void CancelSearch();
|
||||||
|
|
||||||
const bool app_id() { return kAppID; }
|
const bool app_id() { return kAppID; }
|
||||||
|
@ -116,7 +116,7 @@ class DeezerService : public InternetService {
|
||||||
void SendSearch();
|
void SendSearch();
|
||||||
DeezerAlbumContext *CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover);
|
DeezerAlbumContext *CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover);
|
||||||
void GetAlbum(const DeezerAlbumContext *album_ctx);
|
void GetAlbum(const DeezerAlbumContext *album_ctx);
|
||||||
Song ParseSong(const int album_id, const QString &album, const QString &album_cover, const QJsonValue &value);
|
Song ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value);
|
||||||
void CheckFinish();
|
void CheckFinish();
|
||||||
void Error(QString error, QVariant debug = QString());
|
void Error(QString error, QVariant debug = QString());
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class DeezerService : public InternetService {
|
||||||
int pending_search_id_;
|
int pending_search_id_;
|
||||||
int next_pending_search_id_;
|
int next_pending_search_id_;
|
||||||
QString pending_search_text_;
|
QString pending_search_text_;
|
||||||
InternetSearch::SearchBy pending_searchby_;
|
InternetSearch::SearchType pending_search_type_;
|
||||||
|
|
||||||
int search_id_;
|
int search_id_;
|
||||||
QString search_text_;
|
QString search_text_;
|
||||||
|
|
|
@ -69,7 +69,7 @@ InternetSearch::InternetSearch(Application *app, Song::Source source, QObject *p
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
|
||||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(AlbumArtLoaded(quint64, QImage)));
|
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(SearchAsyncSig(int, QString, SearchType)), this, SLOT(DoSearchAsync(int, QString, SearchType)));
|
||||||
connect(this, SIGNAL(ResultsAvailable(int, InternetSearch::ResultList)), SLOT(ResultsAvailableSlot(int, InternetSearch::ResultList)));
|
connect(this, SIGNAL(ResultsAvailable(int, InternetSearch::ResultList)), SLOT(ResultsAvailableSlot(int, InternetSearch::ResultList)));
|
||||||
connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage)));
|
connect(this, SIGNAL(ArtLoaded(int, QImage)), SLOT(ArtLoadedSlot(int, QImage)));
|
||||||
connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString)));
|
connect(service_, SIGNAL(UpdateStatus(QString)), SLOT(UpdateStatusSlot(QString)));
|
||||||
|
@ -113,29 +113,29 @@ bool InternetSearch::Matches(const QStringList &tokens, const QString &string) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int InternetSearch::SearchAsync(const QString &query, SearchBy searchby) {
|
int InternetSearch::SearchAsync(const QString &query, SearchType type) {
|
||||||
|
|
||||||
const int id = searches_next_id_++;
|
const int id = searches_next_id_++;
|
||||||
|
|
||||||
emit SearchAsyncSig(id, query, searchby);
|
emit SearchAsyncSig(id, query, type);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearch::SearchAsync(int id, const QString &query, SearchBy searchby) {
|
void InternetSearch::SearchAsync(int id, const QString &query, SearchType type) {
|
||||||
|
|
||||||
const int service_id = service_->Search(query, searchby);
|
const int service_id = service_->Search(query, type);
|
||||||
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearch::DoSearchAsync(int id, const QString &query, SearchBy searchby) {
|
void InternetSearch::DoSearchAsync(int id, const QString &query, SearchType type) {
|
||||||
|
|
||||||
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
int timer_id = startTimer(kDelayedSearchTimeoutMs);
|
||||||
delayed_searches_[timer_id].id_ = id;
|
delayed_searches_[timer_id].id_ = id;
|
||||||
delayed_searches_[timer_id].query_ = query;
|
delayed_searches_[timer_id].query_ = query;
|
||||||
delayed_searches_[timer_id].searchby_ = searchby;
|
delayed_searches_[timer_id].type_ = type;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ void InternetSearch::CancelSearch(int id) {
|
||||||
void InternetSearch::timerEvent(QTimerEvent *e) {
|
void InternetSearch::timerEvent(QTimerEvent *e) {
|
||||||
QMap<int, DelayedSearch>::iterator it = delayed_searches_.find(e->timerId());
|
QMap<int, DelayedSearch>::iterator it = delayed_searches_.find(e->timerId());
|
||||||
if (it != delayed_searches_.end()) {
|
if (it != delayed_searches_.end()) {
|
||||||
SearchAsync(it.value().id_, it.value().query_, it.value().searchby_);
|
SearchAsync(it.value().id_, it.value().query_, it.value().type_);
|
||||||
delayed_searches_.erase(it);
|
delayed_searches_.erase(it);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,10 @@ class InternetSearch : public QObject {
|
||||||
InternetSearch(Application *app, Song::Source source, QObject *parent = nullptr);
|
InternetSearch(Application *app, Song::Source source, QObject *parent = nullptr);
|
||||||
~InternetSearch();
|
~InternetSearch();
|
||||||
|
|
||||||
enum SearchBy {
|
enum SearchType {
|
||||||
SearchBy_Songs = 1,
|
SearchType_Artists = 1,
|
||||||
SearchBy_Albums = 2,
|
SearchType_Albums = 2,
|
||||||
|
SearchType_Songs = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Result {
|
struct Result {
|
||||||
|
@ -63,7 +64,7 @@ class InternetSearch : public QObject {
|
||||||
Song::Source source() const { return source_; }
|
Song::Source source() const { return source_; }
|
||||||
InternetService *service() const { return service_; }
|
InternetService *service() const { return service_; }
|
||||||
|
|
||||||
int SearchAsync(const QString &query, SearchBy searchby);
|
int SearchAsync(const QString &query, SearchType type);
|
||||||
int LoadArtAsync(const InternetSearch::Result &result);
|
int LoadArtAsync(const InternetSearch::Result &result);
|
||||||
|
|
||||||
void CancelSearch(int id);
|
void CancelSearch(int id);
|
||||||
|
@ -74,7 +75,7 @@ class InternetSearch : public QObject {
|
||||||
MimeData *LoadTracks(const ResultList &results);
|
MimeData *LoadTracks(const ResultList &results);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void SearchAsyncSig(int id, const QString &query, SearchBy searchby);
|
void SearchAsyncSig(int id, const QString &query, SearchType type);
|
||||||
void ResultsAvailable(int id, const InternetSearch::ResultList &results);
|
void ResultsAvailable(int id, const InternetSearch::ResultList &results);
|
||||||
void AddResults(int id, const InternetSearch::ResultList &results);
|
void AddResults(int id, const InternetSearch::ResultList &results);
|
||||||
void SearchError(const int id, const QString error);
|
void SearchError(const int id, const QString error);
|
||||||
|
@ -112,7 +113,7 @@ class InternetSearch : public QObject {
|
||||||
static bool Matches(const QStringList &tokens, const QString &string);
|
static bool Matches(const QStringList &tokens, const QString &string);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void DoSearchAsync(int id, const QString &query, SearchBy searchby);
|
void DoSearchAsync(int id, const QString &query, SearchType type);
|
||||||
void SearchDone(int id, const SongList &songs);
|
void SearchDone(int id, const SongList &songs);
|
||||||
void HandleError(const int id, const QString error);
|
void HandleError(const int id, const QString error);
|
||||||
void ResultsAvailableSlot(int id, InternetSearch::ResultList results);
|
void ResultsAvailableSlot(int id, InternetSearch::ResultList results);
|
||||||
|
@ -125,7 +126,7 @@ class InternetSearch : public QObject {
|
||||||
void UpdateProgressSlot(int max);
|
void UpdateProgressSlot(int max);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SearchAsync(int id, const QString &query, SearchBy searchby);
|
void SearchAsync(int id, const QString &query, SearchType type);
|
||||||
void HandleLoadedArt(int id, const QImage &image);
|
void HandleLoadedArt(int id, const QImage &image);
|
||||||
bool FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const;
|
bool FindCachedPixmap(const InternetSearch::Result &result, QPixmap *pixmap) const;
|
||||||
QString PixmapCacheKey(const InternetSearch::Result &result) const;
|
QString PixmapCacheKey(const InternetSearch::Result &result) const;
|
||||||
|
@ -137,7 +138,7 @@ class InternetSearch : public QObject {
|
||||||
struct DelayedSearch {
|
struct DelayedSearch {
|
||||||
int id_;
|
int id_;
|
||||||
QString query_;
|
QString query_;
|
||||||
SearchBy searchby_;
|
SearchType type_;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int kArtHeight;
|
static const int kArtHeight;
|
||||||
|
|
|
@ -41,17 +41,15 @@ InternetSearchModel::InternetSearchModel(InternetSearch *engine, QObject *parent
|
||||||
engine_(engine),
|
engine_(engine),
|
||||||
proxy_(nullptr),
|
proxy_(nullptr),
|
||||||
use_pretty_covers_(true),
|
use_pretty_covers_(true),
|
||||||
artist_icon_(IconLoader::Load("folder-sound")) {
|
artist_icon_(IconLoader::Load("folder-sound")),
|
||||||
|
album_icon_(IconLoader::Load("cdcase"))
|
||||||
|
{
|
||||||
|
|
||||||
group_by_[0] = CollectionModel::GroupBy_Artist;
|
group_by_[0] = CollectionModel::GroupBy_Artist;
|
||||||
group_by_[1] = CollectionModel::GroupBy_Album;
|
group_by_[1] = CollectionModel::GroupBy_Album;
|
||||||
group_by_[2] = CollectionModel::GroupBy_None;
|
group_by_[2] = CollectionModel::GroupBy_None;
|
||||||
|
|
||||||
QIcon nocover = IconLoader::Load("cdcase");
|
no_cover_icon_ = album_icon_.pixmap(album_icon_.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
|
|
||||||
//no_cover_icon_ = QPixmap(":/pictures/noalbumart.png").scaled(CollectionModel::kPrettyCoverSize, CollectionModel::kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
album_icon_ = no_cover_icon_;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +91,19 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
|
||||||
int year = 0;
|
int year = 0;
|
||||||
|
|
||||||
switch (group_by_[level]) {
|
switch (group_by_[level]) {
|
||||||
|
|
||||||
|
case CollectionModel::GroupBy_AlbumArtist:
|
||||||
|
if (s.is_compilation()) {
|
||||||
|
display_text = tr("Various artists");
|
||||||
|
sort_text = "aaaaaa";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
display_text = CollectionModel::TextOrUnknown(s.effective_albumartist());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.effective_albumartist());
|
||||||
|
}
|
||||||
|
has_artist_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case CollectionModel::GroupBy_Artist:
|
case CollectionModel::GroupBy_Artist:
|
||||||
if (s.is_compilation()) {
|
if (s.is_compilation()) {
|
||||||
display_text = tr("Various artists");
|
display_text = tr("Various artists");
|
||||||
|
@ -134,28 +145,42 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CollectionModel::GroupBy_Composer:
|
case CollectionModel::GroupBy_Composer:
|
||||||
display_text = s.composer();
|
display_text = CollectionModel::TextOrUnknown(s.composer());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.composer());
|
||||||
|
has_album_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case CollectionModel::GroupBy_Performer:
|
case CollectionModel::GroupBy_Performer:
|
||||||
display_text = s.performer();
|
display_text = CollectionModel::TextOrUnknown(s.performer());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.performer());
|
||||||
|
has_album_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case CollectionModel::GroupBy_Disc:
|
case CollectionModel::GroupBy_Disc:
|
||||||
display_text = s.disc();
|
display_text = s.disc();
|
||||||
case CollectionModel::GroupBy_Grouping:
|
|
||||||
display_text = s.grouping();
|
|
||||||
case CollectionModel::GroupBy_Genre:
|
|
||||||
if (display_text.isNull()) display_text = s.genre();
|
|
||||||
case CollectionModel::GroupBy_Album:
|
|
||||||
unique_tag = s.album_id();
|
|
||||||
if (display_text.isNull()) {
|
|
||||||
display_text = s.album();
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
case CollectionModel::GroupBy_AlbumArtist:
|
|
||||||
if (display_text.isNull()) display_text = s.effective_albumartist();
|
|
||||||
display_text = CollectionModel::TextOrUnknown(display_text);
|
|
||||||
sort_text = CollectionModel::SortTextForArtist(display_text);
|
sort_text = CollectionModel::SortTextForArtist(display_text);
|
||||||
has_album_icon = true;
|
has_album_icon = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CollectionModel::GroupBy_Grouping:
|
||||||
|
display_text = CollectionModel::TextOrUnknown(s.grouping());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.grouping());
|
||||||
|
has_album_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionModel::GroupBy_Genre:
|
||||||
|
display_text = CollectionModel::TextOrUnknown(s.genre());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.genre());
|
||||||
|
has_album_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CollectionModel::GroupBy_Album:
|
||||||
|
display_text = CollectionModel::TextOrUnknown(s.album());
|
||||||
|
sort_text = CollectionModel::SortTextForArtist(s.album());
|
||||||
|
unique_tag = s.album_id();
|
||||||
|
has_album_icon = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case CollectionModel::GroupBy_FileType:
|
case CollectionModel::GroupBy_FileType:
|
||||||
display_text = s.TextForFiletype();
|
display_text = s.TextForFiletype();
|
||||||
sort_text = display_text;
|
sort_text = display_text;
|
||||||
|
@ -180,6 +205,9 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (display_text.isEmpty()) display_text = "Unknown";
|
||||||
|
if (sort_text.isEmpty()) sort_text = "Unknown";
|
||||||
|
|
||||||
// Find a container for this level
|
// Find a container for this level
|
||||||
key->group_[level] = display_text + QString::number(unique_tag);
|
key->group_[level] = display_text + QString::number(unique_tag);
|
||||||
QStandardItem *container = containers_[*key];
|
QStandardItem *container = containers_[*key];
|
||||||
|
|
|
@ -81,8 +81,8 @@ class InternetSearchModel : public QStandardItemModel {
|
||||||
QSortFilterProxyModel *proxy_;
|
QSortFilterProxyModel *proxy_;
|
||||||
bool use_pretty_covers_;
|
bool use_pretty_covers_;
|
||||||
QIcon artist_icon_;
|
QIcon artist_icon_;
|
||||||
QPixmap no_cover_icon_;
|
|
||||||
QIcon album_icon_;
|
QIcon album_icon_;
|
||||||
|
QPixmap no_cover_icon_;
|
||||||
CollectionModel::Grouping group_by_;
|
CollectionModel::Grouping group_by_;
|
||||||
QMap<ContainerKey, QStandardItem*> containers_;
|
QMap<ContainerKey, QStandardItem*> containers_;
|
||||||
|
|
||||||
|
|
|
@ -144,8 +144,9 @@ InternetSearchView::InternetSearchView(Application *app, InternetSearch *engine,
|
||||||
settings_menu->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog()));
|
settings_menu->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog()));
|
||||||
ui_->settings->setMenu(settings_menu);
|
ui_->settings->setMenu(settings_menu);
|
||||||
|
|
||||||
connect(ui_->radiobutton_searchbyalbums, SIGNAL(clicked(bool)), SLOT(SearchByAlbumsClicked(bool)));
|
connect(ui_->radiobutton_search_artists, SIGNAL(clicked(bool)), SLOT(SearchArtistsClicked(bool)));
|
||||||
connect(ui_->radiobutton_searchbysongs, SIGNAL(clicked(bool)), SLOT(SearchBySongsClicked(bool)));
|
connect(ui_->radiobutton_search_albums, SIGNAL(clicked(bool)), SLOT(SearchAlbumsClicked(bool)));
|
||||||
|
connect(ui_->radiobutton_search_songs, SIGNAL(clicked(bool)), SLOT(SearchSongsClicked(bool)));
|
||||||
|
|
||||||
connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
|
connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
|
||||||
|
|
||||||
|
@ -180,18 +181,21 @@ void InternetSearchView::ReloadSettings() {
|
||||||
// Internet search settings
|
// Internet search settings
|
||||||
|
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
searchby_ = InternetSearch::SearchBy(s.value("searchby", int(InternetSearch::SearchBy_Songs)).toInt());
|
search_type_ = InternetSearch::SearchType(s.value("type", int(InternetSearch::SearchType_Artists)).toInt());
|
||||||
switch (searchby_) {
|
switch (search_type_) {
|
||||||
case InternetSearch::SearchBy_Songs:
|
case InternetSearch::SearchType_Artists:
|
||||||
ui_->radiobutton_searchbysongs->setChecked(true);
|
ui_->radiobutton_search_artists->setChecked(true);
|
||||||
break;
|
break;
|
||||||
case InternetSearch::SearchBy_Albums:
|
case InternetSearch::SearchType_Albums:
|
||||||
ui_->radiobutton_searchbyalbums->setChecked(true);
|
ui_->radiobutton_search_albums->setChecked(true);
|
||||||
|
break;
|
||||||
|
case InternetSearch::SearchType_Songs:
|
||||||
|
ui_->radiobutton_search_songs->setChecked(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetGroupBy(CollectionModel::Grouping(
|
SetGroupBy(CollectionModel::Grouping(
|
||||||
CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_Artist)).toInt()),
|
CollectionModel::GroupBy(s.value("group_by1", int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()),
|
CollectionModel::GroupBy(s.value("group_by2", int(CollectionModel::GroupBy_Album)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt())));
|
CollectionModel::GroupBy(s.value("group_by3", int(CollectionModel::GroupBy_None)).toInt())));
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
@ -233,7 +237,7 @@ void InternetSearchView::TextEdited(const QString &text) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ui_->progressbar->reset();
|
ui_->progressbar->reset();
|
||||||
last_search_id_ = engine_->SearchAsync(trimmed, searchby_);
|
last_search_id_ = engine_->SearchAsync(trimmed, search_type_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -293,7 +297,6 @@ void InternetSearchView::LazyLoadArt(const QModelIndex &proxy_index) {
|
||||||
// Is this an album?
|
// Is this an album?
|
||||||
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
||||||
if (container_type != CollectionModel::GroupBy_Album &&
|
if (container_type != CollectionModel::GroupBy_Album &&
|
||||||
container_type != CollectionModel::GroupBy_AlbumArtist &&
|
|
||||||
container_type != CollectionModel::GroupBy_YearAlbum &&
|
container_type != CollectionModel::GroupBy_YearAlbum &&
|
||||||
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
||||||
return;
|
return;
|
||||||
|
@ -545,19 +548,23 @@ void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearchView::SearchBySongsClicked(bool checked) {
|
void InternetSearchView::SearchArtistsClicked(bool checked) {
|
||||||
SetSearchBy(InternetSearch::SearchBy_Songs);
|
SetSearchType(InternetSearch::SearchType_Artists);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearchView::SearchByAlbumsClicked(bool checked) {
|
void InternetSearchView::SearchAlbumsClicked(bool checked) {
|
||||||
SetSearchBy(InternetSearch::SearchBy_Albums);
|
SetSearchType(InternetSearch::SearchType_Albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearchView::SetSearchBy(InternetSearch::SearchBy searchby) {
|
void InternetSearchView::SearchSongsClicked(bool checked) {
|
||||||
searchby_ = searchby;
|
SetSearchType(InternetSearch::SearchType_Songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternetSearchView::SetSearchType(InternetSearch::SearchType type) {
|
||||||
|
search_type_ = type;
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
s.setValue("searchby", int(searchby));
|
s.setValue("type", int(search_type_));
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
TextEdited(ui_->search->text());
|
TextEdited(ui_->search->text());
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "internetsearch.h"
|
#include "internetsearch.h"
|
||||||
//#include "settings/internetsettingspage.h"
|
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class GroupByDialog;
|
class GroupByDialog;
|
||||||
|
@ -93,10 +92,11 @@ signals:
|
||||||
|
|
||||||
void SearchForThis();
|
void SearchForThis();
|
||||||
|
|
||||||
void SearchBySongsClicked(bool);
|
void SearchArtistsClicked(bool);
|
||||||
void SearchByAlbumsClicked(bool);
|
void SearchAlbumsClicked(bool);
|
||||||
|
void SearchSongsClicked(bool);
|
||||||
void GroupByClicked(QAction *action);
|
void GroupByClicked(QAction *action);
|
||||||
void SetSearchBy(InternetSearch::SearchBy searchby);
|
void SetSearchType(InternetSearch::SearchType type);
|
||||||
void SetGroupBy(const CollectionModel::Grouping &g);
|
void SetGroupBy(const CollectionModel::Grouping &g);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -133,7 +133,7 @@ signals:
|
||||||
|
|
||||||
QTimer *swap_models_timer_;
|
QTimer *swap_models_timer_;
|
||||||
|
|
||||||
InternetSearch::SearchBy searchby_;
|
InternetSearch::SearchType search_type_;
|
||||||
bool error_;
|
bool error_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Search by</string>
|
<string>Search for</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="margin">
|
<property name="margin">
|
||||||
<number>10</number>
|
<number>10</number>
|
||||||
|
@ -80,14 +80,21 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_searchbyalbums">
|
<widget class="QRadioButton" name="radiobutton_search_artists">
|
||||||
|
<property name="text">
|
||||||
|
<string>ar&tists</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radiobutton_search_albums">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>a&lbums</string>
|
<string>a&lbums</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_searchbysongs">
|
<widget class="QRadioButton" name="radiobutton_search_songs">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>son&gs</string>
|
<string>son&gs</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -208,7 +215,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>398</width>
|
<width>398</width>
|
||||||
<height>502</height>
|
<height>521</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
|
|
@ -52,7 +52,7 @@ class InternetService : public QObject {
|
||||||
virtual void InitialLoadSettings() {}
|
virtual void InitialLoadSettings() {}
|
||||||
virtual void ReloadSettings() {}
|
virtual void ReloadSettings() {}
|
||||||
virtual QIcon Icon() { return Song::IconForSource(source_); }
|
virtual QIcon Icon() { return Song::IconForSource(source_); }
|
||||||
virtual int Search(const QString &query, InternetSearch::SearchBy searchby) = 0;
|
virtual int Search(const QString &query, InternetSearch::SearchType type) = 0;
|
||||||
virtual void CancelSearch() = 0;
|
virtual void CancelSearch() = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
|
@ -81,6 +81,7 @@ void TidalSettingsPage::Load() {
|
||||||
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
else ui_->password->setText(QString::fromUtf8(QByteArray::fromBase64(password)));
|
||||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH");
|
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH");
|
||||||
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||||
|
ui_->spinbox_artistssearchlimit->setValue(s.value("artistssearchlimit", 5).toInt());
|
||||||
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
||||||
ui_->spinbox_songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt());
|
ui_->spinbox_songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt());
|
||||||
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
||||||
|
@ -101,6 +102,7 @@ void TidalSettingsPage::Save() {
|
||||||
s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
|
s.setValue("password", QString::fromUtf8(ui_->password->text().toUtf8().toBase64()));
|
||||||
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
||||||
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
||||||
|
s.setValue("artistssearchlimit", ui_->spinbox_artistssearchlimit->value());
|
||||||
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
||||||
s.setValue("songssearchlimit", ui_->spinbox_songssearchlimit->value());
|
s.setValue("songssearchlimit", ui_->spinbox_songssearchlimit->value());
|
||||||
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
||||||
|
|
|
@ -162,6 +162,49 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_artistssearchlimit">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Artists search limit</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinbox_artistssearchlimit">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_albumssearchlimit">
|
<layout class="QHBoxLayout" name="horizontalLayout_albumssearchlimit">
|
||||||
<item>
|
<item>
|
||||||
|
|
|
@ -63,15 +63,14 @@ const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
||||||
const int TidalService::kLoginAttempts = 1;
|
const int TidalService::kLoginAttempts = 1;
|
||||||
const int TidalService::kTimeResetLoginAttempts = 60000;
|
const int TidalService::kTimeResetLoginAttempts = 60000;
|
||||||
|
|
||||||
typedef QPair<QString, QString> Param;
|
|
||||||
|
|
||||||
TidalService::TidalService(Application *app, QObject *parent)
|
TidalService::TidalService(Application *app, QObject *parent)
|
||||||
: InternetService(Song::Source_Tidal, "Tidal", "tidal", app, parent),
|
: InternetService(Song::Source_Tidal, "Tidal", "tidal", app, parent),
|
||||||
network_(new NetworkAccessManager(this)),
|
network_(new NetworkAccessManager(this)),
|
||||||
url_handler_(new TidalUrlHandler(app, this)),
|
url_handler_(new TidalUrlHandler(app, this)),
|
||||||
timer_searchdelay_(new QTimer(this)),
|
timer_search_delay_(new QTimer(this)),
|
||||||
timer_login_attempt_(new QTimer(this)),
|
timer_login_attempt_(new QTimer(this)),
|
||||||
searchdelay_(1500),
|
search_delay_(1500),
|
||||||
|
artistssearchlimit_(1),
|
||||||
albumssearchlimit_(1),
|
albumssearchlimit_(1),
|
||||||
songssearchlimit_(1),
|
songssearchlimit_(1),
|
||||||
fetchalbums_(false),
|
fetchalbums_(false),
|
||||||
|
@ -79,14 +78,17 @@ TidalService::TidalService(Application *app, QObject *parent)
|
||||||
pending_search_id_(0),
|
pending_search_id_(0),
|
||||||
next_pending_search_id_(1),
|
next_pending_search_id_(1),
|
||||||
search_id_(0),
|
search_id_(0),
|
||||||
albums_requested_(0),
|
artist_search_(false),
|
||||||
albums_received_(0),
|
artist_albums_requested_(0),
|
||||||
|
artist_albums_received_(0),
|
||||||
|
album_songs_requested_(0),
|
||||||
|
album_songs_received_(0),
|
||||||
login_sent_(false),
|
login_sent_(false),
|
||||||
login_attempts_(0)
|
login_attempts_(0)
|
||||||
{
|
{
|
||||||
|
|
||||||
timer_searchdelay_->setSingleShot(true);
|
timer_search_delay_->setSingleShot(true);
|
||||||
connect(timer_searchdelay_, SIGNAL(timeout()), SLOT(StartSearch()));
|
connect(timer_search_delay_, SIGNAL(timeout()), SLOT(StartSearch()));
|
||||||
|
|
||||||
timer_login_attempt_->setSingleShot(true);
|
timer_login_attempt_->setSingleShot(true);
|
||||||
connect(timer_login_attempt_, SIGNAL(timeout()), SLOT(ResetLoginAttempts()));
|
connect(timer_login_attempt_, SIGNAL(timeout()), SLOT(ResetLoginAttempts()));
|
||||||
|
@ -116,7 +118,8 @@ void TidalService::ReloadSettings() {
|
||||||
if (password.isEmpty()) password_.clear();
|
if (password.isEmpty()) password_.clear();
|
||||||
else password_ = QString::fromUtf8(QByteArray::fromBase64(password));
|
else password_ = QString::fromUtf8(QByteArray::fromBase64(password));
|
||||||
quality_ = s.value("quality").toString();
|
quality_ = s.value("quality").toString();
|
||||||
searchdelay_ = s.value("searchdelay", 1500).toInt();
|
search_delay_ = s.value("searchdelay", 1500).toInt();
|
||||||
|
artistssearchlimit_ = s.value("artistssearchlimit", 5).toInt();
|
||||||
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
||||||
songssearchlimit_ = s.value("songssearchlimit", 100).toInt();
|
songssearchlimit_ = s.value("songssearchlimit", 100).toInt();
|
||||||
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
||||||
|
@ -333,7 +336,7 @@ QNetworkReply *TidalService::CreateRequest(const QString &ressource_name, const
|
||||||
req.setRawHeader("X-Tidal-SessionId", session_id_.toUtf8());
|
req.setRawHeader("X-Tidal-SessionId", session_id_.toUtf8());
|
||||||
QNetworkReply *reply = network_->get(req);
|
QNetworkReply *reply = network_->get(req);
|
||||||
|
|
||||||
//qLog(Debug) << "Tidal: Sending request" << url;
|
qLog(Debug) << "Tidal: Sending request" << url;
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
|
|
||||||
|
@ -402,8 +405,6 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) {
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
|
||||||
//qLog(Debug) << json_doc;
|
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
Error("Reply from server missing Json data.", data);
|
Error("Reply from server missing Json data.", data);
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
|
@ -425,8 +426,6 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) {
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
|
|
||||||
return json_obj;
|
return json_obj;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -434,32 +433,36 @@ QJsonObject TidalService::ExtractJsonObj(QByteArray &data) {
|
||||||
QJsonValue TidalService::ExtractItems(QByteArray &data) {
|
QJsonValue TidalService::ExtractItems(QByteArray &data) {
|
||||||
|
|
||||||
QJsonObject json_obj = ExtractJsonObj(data);
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
if (json_obj.isEmpty()) return QJsonArray();
|
if (json_obj.isEmpty()) return QJsonValue();
|
||||||
|
return ExtractItems(json_obj);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonValue TidalService::ExtractItems(QJsonObject &json_obj) {
|
||||||
|
|
||||||
if (!json_obj.contains("items")) {
|
if (!json_obj.contains("items")) {
|
||||||
Error("Json reply is missing items.", json_obj);
|
Error("Json reply is missing items.", json_obj);
|
||||||
return QJsonArray();
|
return QJsonArray();
|
||||||
}
|
}
|
||||||
QJsonValue json_items = json_obj["items"];
|
QJsonValue json_items = json_obj["items"];
|
||||||
|
|
||||||
return json_items;
|
return json_items;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TidalService::Search(const QString &text, InternetSearch::SearchBy searchby) {
|
int TidalService::Search(const QString &text, InternetSearch::SearchType type) {
|
||||||
|
|
||||||
pending_search_id_ = next_pending_search_id_;
|
pending_search_id_ = next_pending_search_id_;
|
||||||
pending_search_text_ = text;
|
pending_search_text_ = text;
|
||||||
pending_searchby_ = searchby;
|
pending_search_type_ = type;
|
||||||
|
|
||||||
next_pending_search_id_++;
|
next_pending_search_id_++;
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
timer_searchdelay_->stop();
|
timer_search_delay_->stop();
|
||||||
return pending_search_id_;
|
return pending_search_id_;
|
||||||
}
|
}
|
||||||
timer_searchdelay_->setInterval(searchdelay_);
|
timer_search_delay_->setInterval(search_delay_);
|
||||||
timer_searchdelay_->start();
|
timer_search_delay_->start();
|
||||||
|
|
||||||
return pending_search_id_;
|
return pending_search_id_;
|
||||||
|
|
||||||
|
@ -487,79 +490,215 @@ void TidalService::CancelSearch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::ClearSearch() {
|
void TidalService::ClearSearch() {
|
||||||
|
|
||||||
search_id_ = 0;
|
search_id_ = 0;
|
||||||
search_text_.clear();
|
search_text_.clear();
|
||||||
search_error_.clear();
|
search_error_.clear();
|
||||||
albums_requested_ = 0;
|
artist_search_ = false;
|
||||||
albums_received_ = 0;
|
artist_albums_requested_ = 0;
|
||||||
requests_album_.clear();
|
artist_albums_received_ = 0;
|
||||||
|
album_songs_requested_ = 0;
|
||||||
|
album_songs_received_ = 0;
|
||||||
|
requests_artist_albums_.clear();
|
||||||
|
requests_album_songs_.clear();
|
||||||
requests_song_.clear();
|
requests_song_.clear();
|
||||||
|
requests_artist_album_.clear();
|
||||||
songs_.clear();
|
songs_.clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::SendSearch() {
|
void TidalService::SendSearch() {
|
||||||
|
|
||||||
emit UpdateStatus("Searching...");
|
emit UpdateStatus("Searching...");
|
||||||
|
|
||||||
QList<Param> parameters;
|
switch (pending_search_type_) {
|
||||||
parameters << Param("query", search_text_);
|
case InternetSearch::SearchType_Artists:
|
||||||
|
SendArtistsSearch();
|
||||||
QString searchparam;
|
break;
|
||||||
switch (pending_searchby_) {
|
case InternetSearch::SearchType_Albums:
|
||||||
case InternetSearch::SearchBy_Songs:
|
SendAlbumsSearch();
|
||||||
searchparam = "search/tracks";
|
break;
|
||||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
case InternetSearch::SearchType_Songs:
|
||||||
|
SendSongsSearch();
|
||||||
break;
|
break;
|
||||||
case InternetSearch::SearchBy_Albums:
|
|
||||||
default:
|
default:
|
||||||
searchparam = "search/albums";
|
Error("Invalid search type.");
|
||||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *reply = CreateRequest(searchparam, parameters);
|
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(SearchFinished(QNetworkReply*, int)), reply, search_id_);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
void TidalService::SendArtistsSearch() {
|
||||||
|
|
||||||
|
artist_search_ = true;
|
||||||
|
|
||||||
|
QList<Param> parameters;
|
||||||
|
parameters << Param("query", search_text_);
|
||||||
|
parameters << Param("limit", QString::number(artistssearchlimit_));
|
||||||
|
QNetworkReply *reply = CreateRequest("search/artists", parameters);
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistsReceived(QNetworkReply*, int)), reply, search_id_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::SendAlbumsSearch() {
|
||||||
|
|
||||||
|
QList<Param> parameters;
|
||||||
|
parameters << Param("query", search_text_);
|
||||||
|
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||||
|
QNetworkReply *reply = CreateRequest("search/albums", parameters);
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, search_id_, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::SendSongsSearch() {
|
||||||
|
|
||||||
|
QList<Param> parameters;
|
||||||
|
parameters << Param("query", search_text_);
|
||||||
|
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||||
|
QNetworkReply *reply = CreateRequest("search/tracks", parameters);
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int)), reply, search_id_, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::ArtistsReceived(QNetworkReply *reply, int search_id) {
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (id != search_id_) return;
|
if (search_id != search_id_) return;
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply, true);
|
QByteArray data = GetReplyData(reply, true);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
|
artist_search_ = false;
|
||||||
CheckFinish();
|
CheckFinish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue json_value = ExtractItems(data);
|
QJsonValue json_value = ExtractItems(data);
|
||||||
if (!json_value.isArray()) {
|
if (!json_value.isArray()) {
|
||||||
|
artist_search_ = false;
|
||||||
CheckFinish();
|
CheckFinish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonArray json_items = json_value.toArray();
|
QJsonArray json_items = json_value.toArray();
|
||||||
if (json_items.isEmpty()) {
|
if (json_items.isEmpty()) {
|
||||||
|
artist_search_ = false;
|
||||||
Error("No match.");
|
Error("No match.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//qLog(Debug) << json_items;
|
|
||||||
|
|
||||||
QVector<QString> albums;
|
|
||||||
for (const QJsonValue &value : json_items) {
|
for (const QJsonValue &value : json_items) {
|
||||||
//qLog(Debug) << value;
|
|
||||||
if (!value.isObject()) {
|
if (!value.isObject()) {
|
||||||
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||||
qLog(Debug) << value;
|
qLog(Debug) << value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QJsonObject json_obj = value.toObject();
|
QJsonObject json_obj = value.toObject();
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
int album_id(0);
|
if (!json_obj.contains("id") || !json_obj.contains("name")) {
|
||||||
QString album("");
|
qLog(Error) << "Tidal: Invalid Json reply, item missing type or album.";
|
||||||
if (json_obj.contains("type")) {
|
qLog(Debug) << json_obj;
|
||||||
// This was a albums search
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int artist_id = json_obj["id"].toInt();
|
||||||
|
if (requests_artist_albums_.contains(artist_id)) continue;
|
||||||
|
requests_artist_albums_.append(artist_id);
|
||||||
|
GetAlbums(artist_id);
|
||||||
|
artist_albums_requested_++;
|
||||||
|
if (artist_albums_requested_ >= artistssearchlimit_) break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artist_albums_requested_ > 0) {
|
||||||
|
emit UpdateStatus(QString("Retrieving albums for %1 artist%2...").arg(artist_albums_requested_).arg(artist_albums_requested_ == 1 ? "" : "s"));
|
||||||
|
emit ProgressSetMaximum(artist_albums_requested_);
|
||||||
|
emit UpdateProgress(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckFinish();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::GetAlbums(const int artist_id, const int offset) {
|
||||||
|
|
||||||
|
QList<Param> parameters;
|
||||||
|
if (offset > 0) parameters << Param("offset", QString::number(offset));
|
||||||
|
QNetworkReply *reply = CreateRequest(QString("artists/%1/albums").arg(artist_id), parameters);
|
||||||
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(AlbumsReceived(QNetworkReply*, int, int, int)), reply, search_id_, artist_id, offset);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TidalService::AlbumsReceived(QNetworkReply *reply, int search_id, int artist_id, int offset_requested) {
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (search_id != search_id_) return;
|
||||||
|
|
||||||
|
if (artist_search_) {
|
||||||
|
if (!requests_artist_albums_.contains(artist_id)) return;
|
||||||
|
artist_albums_received_++;
|
||||||
|
emit UpdateProgress(artist_albums_received_);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data = GetReplyData(reply, true);
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
AlbumsFinished(artist_id, offset_requested);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
|
if (json_obj.isEmpty()) {
|
||||||
|
AlbumsFinished(artist_id, offset_requested);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = 0;
|
||||||
|
int offset = 0;
|
||||||
|
int total_albums = 0;
|
||||||
|
if (artist_search_) { // This was a list of albums by artist
|
||||||
|
if (!json_obj.contains("limit") ||
|
||||||
|
!json_obj.contains("offset") ||
|
||||||
|
!json_obj.contains("totalNumberOfItems") ||
|
||||||
|
!json_obj.contains("items")) {
|
||||||
|
AlbumsFinished(artist_id, offset_requested);
|
||||||
|
Error("Json object missing values.", json_obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
limit = json_obj["limit"].toInt();
|
||||||
|
offset = json_obj["offset"].toInt();
|
||||||
|
total_albums = json_obj["totalNumberOfItems"].toInt();
|
||||||
|
if (offset != offset_requested) {
|
||||||
|
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||||
|
Error(QString("Offset returned does not match offset requested! %1 != %2").arg(offset).arg(offset_requested));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonValue json_value = ExtractItems(json_obj);
|
||||||
|
if (!json_value.isArray()) {
|
||||||
|
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonArray json_items = json_value.toArray();
|
||||||
|
if (json_items.isEmpty()) {
|
||||||
|
if (!artist_search_) Error("No match.");
|
||||||
|
AlbumsFinished(artist_id, offset_requested, total_albums, limit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int albums = 0;
|
||||||
|
for (const QJsonValue &value : json_items) {
|
||||||
|
albums++;
|
||||||
|
if (!value.isObject()) {
|
||||||
|
qLog(Error) << "Tidal: Invalid Json reply, item not a object.";
|
||||||
|
qLog(Debug) << value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QJsonObject json_obj = value.toObject();
|
||||||
|
|
||||||
|
int album_id = 0;
|
||||||
|
QString album;
|
||||||
|
if (json_obj.contains("type")) { // This was a albums search
|
||||||
if (!json_obj.contains("id") || !json_obj.contains("title")) {
|
if (!json_obj.contains("id") || !json_obj.contains("title")) {
|
||||||
qLog(Error) << "Tidal: Invalid Json reply, item is missing ID or title.";
|
qLog(Error) << "Tidal: Invalid Json reply, item is missing ID or title.";
|
||||||
qLog(Debug) << json_obj;
|
qLog(Debug) << json_obj;
|
||||||
|
@ -568,8 +707,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
album_id = json_obj["id"].toInt();
|
album_id = json_obj["id"].toInt();
|
||||||
album = json_obj["title"].toString();
|
album = json_obj["title"].toString();
|
||||||
}
|
}
|
||||||
else if (json_obj.contains("album")) {
|
else if (json_obj.contains("album")) { // This was a tracks search
|
||||||
// This was a tracks search
|
|
||||||
if (!fetchalbums_) {
|
if (!fetchalbums_) {
|
||||||
Song song = ParseSong(0, value);
|
Song song = ParseSong(0, value);
|
||||||
songs_ << song;
|
songs_ << song;
|
||||||
|
@ -589,6 +727,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
}
|
}
|
||||||
album_id = json_album["id"].toInt();
|
album_id = json_album["id"].toInt();
|
||||||
album = json_album["title"].toString();
|
album = json_album["title"].toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Error) << "Tidal: Invalid Json reply, item missing type or album.";
|
qLog(Error) << "Tidal: Invalid Json reply, item missing type or album.";
|
||||||
|
@ -596,7 +735,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requests_album_.contains(album_id)) continue;
|
if (requests_album_songs_.contains(album_id)) continue;
|
||||||
|
|
||||||
if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("audioQuality")) {
|
if (!json_obj.contains("artist") || !json_obj.contains("title") || !json_obj.contains("audioQuality")) {
|
||||||
qLog(Error) << "Tidal: Invalid Json reply, item missing artist, title or audioQuality.";
|
qLog(Error) << "Tidal: Invalid Json reply, item missing artist, title or audioQuality.";
|
||||||
|
@ -622,45 +761,77 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
||||||
|
|
||||||
//qLog(Debug) << "Tidal:" << artist << album << quality << copyright;
|
//qLog(Debug) << "Tidal:" << artist << album << quality << copyright;
|
||||||
|
|
||||||
QString artist_album(QString("%1-%2").arg(artist).arg(album));
|
QPair<QString,QString> artist_album(artist.toLower(), album.toLower());
|
||||||
if (albums.contains(artist_album)) {
|
if (requests_artist_album_.contains(artist_album)) {
|
||||||
qLog(Debug) << "Tidal: Skipping duplicate album" << artist << album << quality << copyright;
|
qLog(Debug) << "Tidal: Skipping duplicate album" << artist << album << quality << copyright;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
albums.insert(0, artist_album);
|
requests_artist_album_.append(artist_album);
|
||||||
|
|
||||||
requests_album_.insert(album_id, album_id);
|
requests_album_songs_.insert(album_id, artist);
|
||||||
GetAlbum(album_id);
|
album_songs_requested_++;
|
||||||
albums_requested_++;
|
if (album_songs_requested_ >= albumssearchlimit_) break;
|
||||||
if (albums_requested_ >= albumssearchlimit_) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albums_requested_ > 0) {
|
AlbumsFinished(artist_id, offset_requested, total_albums, limit, albums);
|
||||||
emit UpdateStatus(QString("Retrieving %1 album%2...").arg(albums_requested_).arg(albums_requested_ == 1 ? "" : "s"));
|
|
||||||
emit ProgressSetMaximum(albums_requested_);
|
}
|
||||||
emit UpdateProgress(0);
|
|
||||||
|
void TidalService::AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums, const int limit, const int albums) {
|
||||||
|
|
||||||
|
if (artist_search_) { // This is a artist search.
|
||||||
|
if (albums > limit) {
|
||||||
|
Error("Albums returned does not match limit returned!");
|
||||||
|
}
|
||||||
|
int offset_next = offset_requested + albums;
|
||||||
|
if (album_songs_requested_ < albumssearchlimit_ && offset_next < total_albums) {
|
||||||
|
GetAlbums(artist_id, offset_next);
|
||||||
|
artist_albums_requested_++;
|
||||||
|
}
|
||||||
|
else if (artist_albums_received_ >= artist_albums_requested_) { // Artist search is finished.
|
||||||
|
artist_search_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!artist_search_) {
|
||||||
|
// Get songs for the albums.
|
||||||
|
QHashIterator<int, QString> i(requests_album_songs_);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
GetSongs(i.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (album_songs_requested_ > 0) {
|
||||||
|
emit UpdateStatus(QString("Retrieving songs for %1 album%2...").arg(album_songs_requested_).arg(album_songs_requested_ == 1 ? "" : "s"));
|
||||||
|
emit ProgressSetMaximum(album_songs_requested_);
|
||||||
|
emit UpdateProgress(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckFinish();
|
CheckFinish();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::GetAlbum(const int album_id) {
|
void TidalService::GetSongs(const int album_id) {
|
||||||
|
|
||||||
QList<Param> parameters;
|
QList<Param> parameters;
|
||||||
QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters);
|
QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters);
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_id);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(SongsReceived(QNetworkReply*, int, int)), reply, search_id_, album_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id) {
|
void TidalService::SongsReceived(QNetworkReply *reply, int search_id, int album_id) {
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (search_id != search_id_) return;
|
if (search_id != search_id_) return;
|
||||||
if (!requests_album_.contains(album_id)) return;
|
if (!requests_album_songs_.contains(album_id)) return;
|
||||||
albums_received_++;
|
QString album_artist = requests_album_songs_[album_id];
|
||||||
emit UpdateProgress(albums_received_);
|
|
||||||
|
album_songs_received_++;
|
||||||
|
if (!artist_search_) {
|
||||||
|
emit UpdateProgress(album_songs_received_);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
|
@ -682,14 +853,12 @@ void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int alb
|
||||||
|
|
||||||
bool compilation = false;
|
bool compilation = false;
|
||||||
bool multidisc = false;
|
bool multidisc = false;
|
||||||
Song first_song;
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
for (const QJsonValue &value : json_items) {
|
for (const QJsonValue &value : json_items) {
|
||||||
Song song = ParseSong(album_id, value);
|
Song song = ParseSong(album_id, value, album_artist);
|
||||||
if (!song.is_valid()) continue;
|
if (!song.is_valid()) continue;
|
||||||
if (song.disc() >= 2) multidisc = true;
|
if (song.disc() >= 2) multidisc = true;
|
||||||
if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true;
|
if (song.is_compilation()) compilation = true;
|
||||||
if (!first_song.is_valid()) first_song = song;
|
|
||||||
songs << song;
|
songs << song;
|
||||||
}
|
}
|
||||||
for (Song &song : songs) {
|
for (Song &song : songs) {
|
||||||
|
@ -705,7 +874,7 @@ void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int alb
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &value) {
|
Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &value, QString album_artist) {
|
||||||
|
|
||||||
Song song;
|
Song song;
|
||||||
|
|
||||||
|
@ -716,8 +885,6 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||||
}
|
}
|
||||||
QJsonObject json_obj = value.toObject();
|
QJsonObject json_obj = value.toObject();
|
||||||
|
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!json_obj.contains("album") ||
|
!json_obj.contains("album") ||
|
||||||
!json_obj.contains("allowStreaming") ||
|
!json_obj.contains("allowStreaming") ||
|
||||||
|
@ -786,7 +953,6 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||||
|
|
||||||
if (!allow_streaming || !stream_ready) {
|
if (!allow_streaming || !stream_ready) {
|
||||||
qLog(Error) << "Tidal: Skipping song" << artist << album << title << "because allowStreaming is false OR streamReady is false.";
|
qLog(Error) << "Tidal: Skipping song" << artist << album << title << "because allowStreaming is false OR streamReady is false.";
|
||||||
//qLog(Debug) << json_obj;
|
|
||||||
return song;
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,8 +964,9 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||||
song.set_source(Song::Source_Tidal);
|
song.set_source(Song::Source_Tidal);
|
||||||
song.set_id(song_id);
|
song.set_id(song_id);
|
||||||
song.set_album_id(album_id);
|
song.set_album_id(album_id);
|
||||||
song.set_artist(artist);
|
if (album_artist != artist) song.set_albumartist(album_artist);
|
||||||
song.set_album(album);
|
song.set_album(album);
|
||||||
|
song.set_artist(artist);
|
||||||
song.set_title(title);
|
song.set_title(title);
|
||||||
song.set_track(track);
|
song.set_track(track);
|
||||||
song.set_disc(disc);
|
song.set_disc(disc);
|
||||||
|
@ -836,11 +1003,11 @@ void TidalService::GetStreamURL(const QUrl &url) {
|
||||||
|
|
||||||
QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters);
|
QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters);
|
||||||
|
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetStreamURLFinished(QNetworkReply*, int, QUrl)), reply, song_id, url);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(StreamURLReceived(QNetworkReply*, int, QUrl)), reply, song_id, url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TidalService::GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url) {
|
void TidalService::StreamURLReceived(QNetworkReply *reply, const int song_id, const QUrl original_url) {
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
if (requests_song_.contains(song_id)) requests_song_.remove(song_id);
|
if (requests_song_.contains(song_id)) requests_song_.remove(song_id);
|
||||||
|
@ -892,7 +1059,7 @@ void TidalService::CheckFinish() {
|
||||||
|
|
||||||
if (search_id_ == 0) return;
|
if (search_id_ == 0) return;
|
||||||
|
|
||||||
if (!login_sent_ && albums_requested_ <= albums_received_) {
|
if (!login_sent_ && !artist_search_ && artist_albums_requested_ <= artist_albums_received_ && album_songs_requested_ <= album_songs_received_) {
|
||||||
if (songs_.isEmpty()) {
|
if (songs_.isEmpty()) {
|
||||||
if (search_error_.isEmpty()) emit SearchError(search_id_, "Unknown error");
|
if (search_error_.isEmpty()) emit SearchError(search_id_, "Unknown error");
|
||||||
else emit SearchError(search_id_, search_error_);
|
else emit SearchError(search_id_, search_error_);
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
@ -54,7 +55,7 @@ class TidalService : public InternetService {
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void Logout();
|
void Logout();
|
||||||
int Search(const QString &query, InternetSearch::SearchBy searchby);
|
int Search(const QString &query, InternetSearch::SearchType type);
|
||||||
void CancelSearch();
|
void CancelSearch();
|
||||||
|
|
||||||
const bool login_sent() { return login_sent_; }
|
const bool login_sent() { return login_sent_; }
|
||||||
|
@ -84,20 +85,29 @@ class TidalService : public InternetService {
|
||||||
void HandleAuthReply(QNetworkReply *reply);
|
void HandleAuthReply(QNetworkReply *reply);
|
||||||
void ResetLoginAttempts();
|
void ResetLoginAttempts();
|
||||||
void StartSearch();
|
void StartSearch();
|
||||||
void SearchFinished(QNetworkReply *reply, int search_id);
|
void ArtistsReceived(QNetworkReply *reply, int search_id);
|
||||||
void GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id);
|
void AlbumsReceived(QNetworkReply *reply, int search_id, int artist_id, int offset_requested = 0);
|
||||||
void GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url);
|
void AlbumsFinished(const int artist_id, const int offset_requested, const int total_albums = 0, const int limit = 0, const int albums = 0);
|
||||||
|
void SongsReceived(QNetworkReply *reply, int search_id, int album_id);
|
||||||
|
void StreamURLReceived(QNetworkReply *reply, const int song_id, const QUrl original_url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
typedef QPair<QString, QString> Param;
|
||||||
|
|
||||||
void ClearSearch();
|
void ClearSearch();
|
||||||
void LoadSessionID();
|
void LoadSessionID();
|
||||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<QPair<QString, QString>> ¶ms);
|
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<QPair<QString, QString>> ¶ms);
|
||||||
QByteArray GetReplyData(QNetworkReply *reply, const bool sendlogin = false);
|
QByteArray GetReplyData(QNetworkReply *reply, const bool sendlogin = false);
|
||||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||||
QJsonValue ExtractItems(QByteArray &data);
|
QJsonValue ExtractItems(QByteArray &data);
|
||||||
|
QJsonValue ExtractItems(QJsonObject &json_obj);
|
||||||
void SendSearch();
|
void SendSearch();
|
||||||
void GetAlbum(const int album_id);
|
void SendArtistsSearch();
|
||||||
Song ParseSong(const int album_id_requested, const QJsonValue &value);
|
void SendAlbumsSearch();
|
||||||
|
void SendSongsSearch();
|
||||||
|
void GetAlbums(const int artist_id, const int offset = 0);
|
||||||
|
void GetSongs(const int album_id);
|
||||||
|
Song ParseSong(const int album_id_requested, const QJsonValue &value, QString album_artist = QString());
|
||||||
void CheckFinish();
|
void CheckFinish();
|
||||||
void Error(QString error, QVariant debug = QVariant());
|
void Error(QString error, QVariant debug = QVariant());
|
||||||
|
|
||||||
|
@ -110,13 +120,14 @@ class TidalService : public InternetService {
|
||||||
|
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
TidalUrlHandler *url_handler_;
|
TidalUrlHandler *url_handler_;
|
||||||
QTimer *timer_searchdelay_;
|
QTimer *timer_search_delay_;
|
||||||
QTimer *timer_login_attempt_;
|
QTimer *timer_login_attempt_;
|
||||||
|
|
||||||
QString username_;
|
QString username_;
|
||||||
QString password_;
|
QString password_;
|
||||||
QString quality_;
|
QString quality_;
|
||||||
int searchdelay_;
|
int search_delay_;
|
||||||
|
int artistssearchlimit_;
|
||||||
int albumssearchlimit_;
|
int albumssearchlimit_;
|
||||||
int songssearchlimit_;
|
int songssearchlimit_;
|
||||||
bool fetchalbums_;
|
bool fetchalbums_;
|
||||||
|
@ -130,14 +141,19 @@ class TidalService : public InternetService {
|
||||||
int pending_search_id_;
|
int pending_search_id_;
|
||||||
int next_pending_search_id_;
|
int next_pending_search_id_;
|
||||||
QString pending_search_text_;
|
QString pending_search_text_;
|
||||||
InternetSearch::SearchBy pending_searchby_;
|
InternetSearch::SearchType pending_search_type_;
|
||||||
|
|
||||||
int search_id_;
|
int search_id_;
|
||||||
QString search_text_;
|
QString search_text_;
|
||||||
QHash<int, int> requests_album_;
|
bool artist_search_;
|
||||||
|
QList<int> requests_artist_albums_;
|
||||||
|
QHash<int, QString> requests_album_songs_;
|
||||||
QHash<int, QUrl> requests_song_;
|
QHash<int, QUrl> requests_song_;
|
||||||
int albums_requested_;
|
QList<QPair<QString, QString>> requests_artist_album_;
|
||||||
int albums_received_;
|
int artist_albums_requested_;
|
||||||
|
int artist_albums_received_;
|
||||||
|
int album_songs_requested_;
|
||||||
|
int album_songs_received_;
|
||||||
SongList songs_;
|
SongList songs_;
|
||||||
QString search_error_;
|
QString search_error_;
|
||||||
bool login_sent_;
|
bool login_sent_;
|
||||||
|
|
Loading…
Reference in New Issue