Scrobbler: Simplify error handling

This commit is contained in:
Jonas Kvinge
2023-04-21 02:11:23 +02:00
parent f0fe446f7f
commit f36ac5272b
12 changed files with 260 additions and 450 deletions

View File

@@ -149,6 +149,51 @@ void ScrobblingAPI20::Logout() {
}
ScrobblingAPI20::ReplyResult ScrobblingAPI20::GetJsonObject(QNetworkReply *reply, QJsonObject &json_obj, QString &error_description) {
ReplyResult reply_error_type = ReplyResult::ServerError;
if (reply->error() == QNetworkReply::NoError) {
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
reply_error_type = ReplyResult::Success;
}
else {
error_description = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
}
else {
error_description = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
// See if there is Json data containing "error" and "message" - then use that instead.
if (reply->error() == QNetworkReply::NoError || reply->error() >= 200) {
const QByteArray data = reply->readAll();
int error_code = 0;
if (!data.isEmpty() && ExtractJsonObj(data, json_obj, error_description) && json_obj.contains("error") && json_obj.contains("message")) {
error_code = json_obj["error"].toInt();
QString error_message = json_obj["message"].toString();
error_description = QString("%1 (%2)").arg(error_message).arg(error_code);
reply_error_type = ReplyResult::APIError;
}
const ScrobbleErrorCode lastfm_error_code = static_cast<ScrobbleErrorCode>(error_code);
if (reply->error() == QNetworkReply::ContentAccessDenied ||
reply->error() == QNetworkReply::ContentOperationNotPermittedError ||
reply->error() == QNetworkReply::AuthenticationRequiredError ||
lastfm_error_code == ScrobbleErrorCode::InvalidSessionKey ||
lastfm_error_code == ScrobbleErrorCode::UnauthorizedToken ||
lastfm_error_code == ScrobbleErrorCode::LoginRequired ||
lastfm_error_code == ScrobbleErrorCode::AuthenticationFailed ||
lastfm_error_code == ScrobbleErrorCode::APIKeySuspended
) {
// Session is probably expired
Logout();
}
}
return reply_error_type;
}
void ScrobblingAPI20::Authenticate(const bool https) {
if (!server_) {
@@ -262,57 +307,10 @@ void ScrobblingAPI20::AuthenticateReplyFinished(QNetworkReply *reply) {
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data;
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
data = reply->readAll();
}
else {
if (reply->error() < 200) {
// This is a network error, there is nothing more to do.
AuthError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
}
else {
// See if there is Json data containing "error" and "message" - then use that instead.
data = reply->readAll();
QString error;
QJsonParseError json_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
if (json_error.error == QJsonParseError::NoError && !json_doc.isEmpty() && json_doc.isObject()) {
QJsonObject json_obj = json_doc.object();
if (json_obj.contains("error") && json_obj.contains("message")) {
int code = json_obj["error"].toInt();
QString message = json_obj["message"].toString();
error = "Error: " + QString::number(code) + ": " + message;
}
else {
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
}
if (error.isEmpty()) {
if (reply->error() != QNetworkReply::NoError) {
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
else {
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
}
AuthError(error);
}
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
AuthError("Json document from server was empty.");
return;
}
if (json_obj.contains("error") && json_obj.contains("message")) {
int error = json_obj["error"].toInt();
QString message = json_obj["message"].toString();
QString failure_reason = "Error: " + QString::number(error) + ": " + message;
AuthError(failure_reason);
QJsonObject json_obj;
QString error_message;
if (GetJsonObject(reply, json_obj, error_message) != ReplyResult::Success) {
AuthError(error_message);
return;
}
@@ -392,63 +390,6 @@ QNetworkReply *ScrobblingAPI20::CreateRequest(const ParamList &request_params) {
}
QByteArray ScrobblingAPI20::GetReplyData(QNetworkReply *reply) {
QByteArray data;
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
data = reply->readAll();
}
else {
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
// This is a network error, there is nothing more to do.
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
}
else {
QString error;
// See if there is Json data containing "error" and "message" - then use that instead.
data = reply->readAll();
QJsonParseError json_error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
int error_code = -1;
if (json_error.error == QJsonParseError::NoError && !json_doc.isEmpty() && json_doc.isObject()) {
QJsonObject json_obj = json_doc.object();
if (json_obj.contains("error") && json_obj.contains("message")) {
error_code = json_obj["error"].toInt();
QString error_message = json_obj["message"].toString();
error = QString("%1 (%2)").arg(error_message).arg(error_code);
}
}
if (error.isEmpty()) {
if (reply->error() != QNetworkReply::NoError) {
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
else {
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
}
}
const ScrobbleErrorCode lastfm_error_code = static_cast<ScrobbleErrorCode>(error_code);
if (reply->error() == QNetworkReply::ContentAccessDenied ||
reply->error() == QNetworkReply::ContentOperationNotPermittedError ||
reply->error() == QNetworkReply::AuthenticationRequiredError ||
lastfm_error_code == ScrobbleErrorCode::InvalidSessionKey ||
lastfm_error_code == ScrobbleErrorCode::UnauthorizedToken ||
lastfm_error_code == ScrobbleErrorCode::LoginRequired ||
lastfm_error_code == ScrobbleErrorCode::AuthenticationFailed ||
lastfm_error_code == ScrobbleErrorCode::APIKeySuspended
){
// Session is probably expired
Logout();
}
Error(error);
}
return QByteArray();
}
return data;
}
void ScrobblingAPI20::UpdateNowPlaying(const Song &song) {
CheckScrobblePrevSong();
@@ -484,21 +425,10 @@ void ScrobblingAPI20::UpdateNowPlayingRequestFinished(QNetworkReply *reply) {
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
return;
}
if (json_obj.contains("error") && json_obj.contains("message")) {
int error_code = json_obj["error"].toInt();
QString error_message = json_obj["message"].toString();
QString error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
Error(error_reason);
QJsonObject json_obj;
QString error_message;
if (GetJsonObject(reply, json_obj, error_message) != ReplyResult::Success) {
Error(error_message);
return;
}
@@ -566,43 +496,43 @@ void ScrobblingAPI20::Submit() {
ParamList params = ParamList() << Param("method", "track.scrobble");
int i = 0;
QList<quint64> list;
ScrobblerCacheItemPtrList items = cache_->List();
for (ScrobblerCacheItemPtr item : items) {
if (item->sent) continue;
item->sent = true;
ScrobblerCacheItemPtrList all_cache_items = cache_->List();
ScrobblerCacheItemPtrList cache_items_sent;
for (ScrobblerCacheItemPtr cache_item : all_cache_items) {
if (cache_item->sent) continue;
cache_item->sent = true;
if (!batch_) {
SendSingleScrobble(item);
SendSingleScrobble(cache_item);
continue;
}
list << item->timestamp;
params << Param(QString("%1[%2]").arg("artist").arg(i), prefer_albumartist_ ? item->metadata.effective_albumartist() : item->metadata.artist);
params << Param(QString("%1[%2]").arg("track").arg(i), StripTitle(item->metadata.title));
params << Param(QString("%1[%2]").arg("timestamp").arg(i), QString::number(item->timestamp));
params << Param(QString("%1[%2]").arg("duration").arg(i), QString::number(item->metadata.length_nanosec / kNsecPerSec));
if (!item->metadata.album.isEmpty()) {
params << Param(QString("%1[%2]").arg("album").arg(i), StripAlbum(item->metadata.album));
cache_items_sent << cache_item;
params << Param(QString("%1[%2]").arg("artist").arg(i), prefer_albumartist_ ? cache_item->metadata.effective_albumartist() : cache_item->metadata.artist);
params << Param(QString("%1[%2]").arg("track").arg(i), StripTitle(cache_item->metadata.title));
params << Param(QString("%1[%2]").arg("timestamp").arg(i), QString::number(cache_item->timestamp));
params << Param(QString("%1[%2]").arg("duration").arg(i), QString::number(cache_item->metadata.length_nanosec / kNsecPerSec));
if (!cache_item->metadata.album.isEmpty()) {
params << Param(QString("%1[%2]").arg("album").arg(i), StripAlbum(cache_item->metadata.album));
}
if (!prefer_albumartist_ && !item->metadata.albumartist.isEmpty()) {
params << Param(QString("%1[%2]").arg("albumArtist").arg(i), item->metadata.albumartist);
if (!prefer_albumartist_ && !cache_item->metadata.albumartist.isEmpty()) {
params << Param(QString("%1[%2]").arg("albumArtist").arg(i), cache_item->metadata.albumartist);
}
if (item->metadata.track > 0) {
params << Param(QString("%1[%2]").arg("trackNumber").arg(i), QString::number(item->metadata.track));
if (cache_item->metadata.track > 0) {
params << Param(QString("%1[%2]").arg("trackNumber").arg(i), QString::number(cache_item->metadata.track));
}
++i;
if (i >= kScrobblesPerRequest) break;
if (cache_items_sent.count() >= kScrobblesPerRequest) break;
}
if (!batch_ || i <= 0) return;
if (!batch_ || cache_items_sent.count() <= 0) return;
submitted_ = true;
QNetworkReply *reply = CreateRequest(params);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, list]() { ScrobbleRequestFinished(reply, list); });
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, cache_items_sent]() { ScrobbleRequestFinished(reply, cache_items_sent); });
}
void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, const QList<quint64> &list) {
void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, ScrobblerCacheItemPtrList cache_items) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
@@ -611,34 +541,17 @@ void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, const QList<
submitted_ = false;
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
cache_->ClearSent(list);
QJsonObject json_obj;
QString error_message;
if (GetJsonObject(reply, json_obj, error_message) != ReplyResult::Success) {
Error(error_message);
cache_->ClearSent(cache_items);
submit_error_ = true;
StartSubmit();
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
cache_->ClearSent(list);
submit_error_ = true;
StartSubmit();
return;
}
if (json_obj.contains("error") && json_obj.contains("message")) {
int error_code = json_obj["error"].toInt();
QString error_message = json_obj["message"].toString();
QString error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
Error(error_reason);
cache_->ClearSent(list);
submit_error_ = true;
StartSubmit();
return;
}
cache_->Flush(list);
cache_->Flush(cache_items);
submit_error_ = false;
if (!json_obj.contains("scrobbles")) {
@@ -799,52 +712,32 @@ void ScrobblingAPI20::SendSingleScrobble(ScrobblerCacheItemPtr item) {
}
QNetworkReply *reply = CreateRequest(params);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, item]() { SingleScrobbleRequestFinished(reply, item->timestamp); });
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, item]() { SingleScrobbleRequestFinished(reply, item); });
}
void ScrobblingAPI20::SingleScrobbleRequestFinished(QNetworkReply *reply, const quint64 timestamp) {
void ScrobblingAPI20::SingleScrobbleRequestFinished(QNetworkReply *reply, ScrobblerCacheItemPtr cache_item) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
ScrobblerCacheItemPtr item = cache_->Get(timestamp);
if (!item) {
Error(QString("Received reply for non-existing cache entry %1.").arg(timestamp));
return;
}
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
item->sent = false;
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
item->sent = false;
return;
}
if (json_obj.contains("error") && json_obj.contains("message")) {
int error_code = json_obj["error"].toInt();
QString error_message = json_obj["message"].toString();
QString error_reason = QString("%1 (%2)").arg(error_message).arg(error_code);
Error(error_reason);
item->sent = false;
QJsonObject json_obj;
QString error_message;
if (GetJsonObject(reply, json_obj, error_message) != ReplyResult::Success) {
Error(error_message);
cache_item->sent = false;
return;
}
if (!json_obj.contains("scrobbles")) {
Error("Json reply from server is missing scrobbles.", json_obj);
item->sent = false;
cache_item->sent = false;
return;
}
cache_->Remove(timestamp);
item = nullptr;
cache_->Remove(cache_item);
QJsonValue value_scrobbles = json_obj["scrobbles"];
if (!value_scrobbles.isObject()) {
@@ -963,13 +856,10 @@ void ScrobblingAPI20::LoveRequestFinished(QNetworkReply *reply) {
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
return;
}
QJsonObject json_obj = ExtractJsonObj(data, true);
if (json_obj.isEmpty()) {
QJsonObject json_obj;
QString error_message;
if (GetJsonObject(reply, json_obj, error_message) != ReplyResult::Success) {
Error(error_message);
return;
}
@@ -1008,7 +898,10 @@ void ScrobblingAPI20::LoveRequestFinished(QNetworkReply *reply) {
}
void ScrobblingAPI20::AuthError(const QString &error) {
qLog(Error) << name_ << error;
emit AuthenticationComplete(false, error);
}
void ScrobblingAPI20::Error(const QString &error, const QVariant &debug) {