/* This file is part of Clementine. Copyright 2010, David Sansome 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 . */ #include "musicbrainzclient.h" #include #include #include #include #include "core/closure.h" #include "core/logging.h" #include "core/network.h" #include "core/utilities.h" const char* MusicBrainzClient::kTrackUrl = "https://musicbrainz.org/ws/2/recording/"; const char* MusicBrainzClient::kDiscUrl = "https://musicbrainz.org/ws/2/discid/"; const char* MusicBrainzClient::kDateRegex = "^[12]\\d{3}"; const int MusicBrainzClient::kDefaultTimeout = 5000; // msec MusicBrainzClient::MusicBrainzClient(QObject* parent, QNetworkAccessManager* network) : QObject(parent), network_(network ? network : new NetworkAccessManager(this)), timeouts_(new NetworkTimeouts(kDefaultTimeout, this)) {} void MusicBrainzClient::Start(int id, const QString& mbid) { typedef QPair Param; QList parameters; parameters << Param("inc", "artists+releases+media"); QUrl url(kTrackUrl + mbid); url.setQueryItems(parameters); QNetworkRequest req(url); QNetworkReply* reply = network_->get(req); NewClosure(reply, SIGNAL(finished()), this, SLOT(RequestFinished(QNetworkReply*, int)), reply, id); requests_[id] = reply; timeouts_->AddReply(reply); } void MusicBrainzClient::StartDiscIdRequest(const QString& discid) { typedef QPair Param; QList parameters; parameters << Param("inc", "artists+recordings"); QUrl url(kDiscUrl + discid); url.setQueryItems(parameters); QNetworkRequest req(url); QNetworkReply* reply = network_->get(req); NewClosure(reply, SIGNAL(finished()), this, SLOT(DiscIdRequestFinished(QNetworkReply*)), reply); timeouts_->AddReply(reply); } void MusicBrainzClient::Cancel(int id) { delete requests_.take(id); } void MusicBrainzClient::CancelAll() { qDeleteAll(requests_.values()); requests_.clear(); } void MusicBrainzClient::DiscIdRequestFinished(QNetworkReply* reply) { reply->deleteLater(); ResultList ret; QString artist; QString album; if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { emit Finished(artist, album, ret); return; } // Parse xml result: // -get title // -get artist // -get all the tracks' tags QXmlStreamReader reader(reply); while (!reader.atEnd()) { QXmlStreamReader::TokenType type = reader.readNext(); if (type == QXmlStreamReader::StartElement) { QStringRef name = reader.name(); if (name == "title") { album = reader.readElementText(); } else if (name == "artist") { ParseArtist(&reader, &artist); } else if (name == "track-list") { break; } } } while (!reader.atEnd()) { QXmlStreamReader::TokenType token = reader.readNext(); if (token == QXmlStreamReader::StartElement && reader.name() == "recording") { ResultList tracks = ParseTrack(&reader); for (const Result& track : tracks) { if (!track.title_.isEmpty()) { ret << track; } } } else if (token == QXmlStreamReader::EndElement && reader.name() == "track-list") { break; } } emit Finished(artist, album, UniqueResults(ret)); } void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id) { reply->deleteLater(); requests_.remove(id); ResultList ret; if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { emit Finished(id, ret); return; } QXmlStreamReader reader(reply); while (!reader.atEnd()) { if (reader.readNext() == QXmlStreamReader::StartElement && reader.name() == "recording") { ResultList tracks = ParseTrack(&reader); for (const Result& track : tracks) { if (!track.title_.isEmpty()) { ret << track; } } } } emit Finished(id, UniqueResults(ret)); } MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack( QXmlStreamReader* reader) { Result result; QList releases; while (!reader->atEnd()) { QXmlStreamReader::TokenType type = reader->readNext(); if (type == QXmlStreamReader::StartElement) { QStringRef name = reader->name(); if (name == "title") { result.title_ = reader->readElementText(); } else if (name == "length") { result.duration_msec_ = reader->readElementText().toInt(); } else if (name == "artist") { ParseArtist(reader, &result.artist_); } else if (name == "release") { releases << ParseRelease(reader); } } if (type == QXmlStreamReader::EndElement && reader->name() == "recording") { break; } } ResultList ret; if (releases.isEmpty()) { ret << result; } else { for (const Release& release : releases) { ret << release.CopyAndMergeInto(result); } } return ret; } void MusicBrainzClient::ParseArtist(QXmlStreamReader* reader, QString* artist) { while (!reader->atEnd()) { QXmlStreamReader::TokenType type = reader->readNext(); if (type == QXmlStreamReader::StartElement && reader->name() == "name") { *artist = reader->readElementText(); } if (type == QXmlStreamReader::EndElement && reader->name() == "artist") { return; } } } MusicBrainzClient::Release MusicBrainzClient::ParseRelease( QXmlStreamReader* reader) { Release ret; while (!reader->atEnd()) { QXmlStreamReader::TokenType type = reader->readNext(); if (type == QXmlStreamReader::StartElement) { QStringRef name = reader->name(); if (name == "title") { ret.album_ = reader->readElementText(); } else if (name == "date") { QRegExp regex(kDateRegex); if (regex.indexIn(reader->readElementText()) == 0) { ret.year_ = regex.cap(0).toInt(); } } else if (name == "track-list") { ret.track_ = reader->attributes().value("offset").toString().toInt() + 1; Utilities::ConsumeCurrentElement(reader); } } if (type == QXmlStreamReader::EndElement && reader->name() == "release") { break; } } return ret; } MusicBrainzClient::ResultList MusicBrainzClient::UniqueResults( const ResultList& results) { ResultList ret = QSet::fromList(results).toList(); qSort(ret); return ret; }