Improve album cover searching and cover manager, use HttpStatusCodeAttribute and QSslError for services
- Improve album cover manager - Change art_automatic and art_manual to QUrl - Refresh collection album covers when new album covers are fetched - Fix automatic album cover searching for local files outside of the collection - Make all Json services check HttpStatusCodeAttribute - Show detailed SSL errors for Subsonic, Tidal and Qobuz
This commit is contained in:
@@ -100,6 +100,7 @@ QNetworkReply *SubsonicBaseRequest::CreateGetRequest(const QString &ressource_na
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleSSLErrors(QList<QSslError>)));
|
||||
replies_ << reply;
|
||||
|
||||
//qLog(Debug) << "Subsonic: Sending request" << url;
|
||||
@@ -108,7 +109,15 @@ QNetworkReply *SubsonicBaseRequest::CreateGetRequest(const QString &ressource_na
|
||||
|
||||
}
|
||||
|
||||
QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &error) {
|
||||
void SubsonicBaseRequest::HandleSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
Error(ssl_error.errorString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply) {
|
||||
|
||||
if (replies_.contains(reply)) {
|
||||
replies_.removeAll(reply);
|
||||
@@ -117,26 +126,20 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
error = Error(QString("Received HTTP code %1").arg(http_code));
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QString error;
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("error")) {
|
||||
@@ -146,15 +149,20 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
||||
if (!json_obj.isEmpty() && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
error = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = 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());
|
||||
}
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,40 +170,40 @@ QByteArray SubsonicBaseRequest::GetReplyData(QNetworkReply *reply, QString &erro
|
||||
|
||||
}
|
||||
|
||||
QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data, QString &error) {
|
||||
QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
if (json_error.error != QJsonParseError::NoError) {
|
||||
error = Error("Reply from server missing Json data.", data);
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
error = Error("Received empty Json document.", data);
|
||||
Error("Received empty Json document.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
error = Error("Json document is not an object.", json_doc);
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
error = Error("Received empty Json object.", json_doc);
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("subsonic-response")) {
|
||||
error = Error("Json reply is missing subsonic-response.", json_obj);
|
||||
Error("Json reply is missing subsonic-response.", json_obj);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonValue json_response = json_obj["subsonic-response"];
|
||||
if (!json_response.isObject()) {
|
||||
error = Error("Json response is not an object.", json_response);
|
||||
Error("Json response is not an object.", json_response);
|
||||
return QJsonObject();
|
||||
}
|
||||
json_obj = json_response.toObject();
|
||||
@@ -204,11 +212,12 @@ QJsonObject SubsonicBaseRequest::ExtractJsonObj(QByteArray &data, QString &error
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicBaseRequest::Error(QString error, QVariant debug) {
|
||||
QString SubsonicBaseRequest::ErrorsToHTML(const QStringList &errors) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
QString error_html;
|
||||
for (const QString &error : errors) {
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
return error_html;
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#include "internet/internetsearch.h"
|
||||
#include "subsonicservice.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class SubsonicUrlHandler;
|
||||
class CollectionBackend;
|
||||
@@ -61,10 +60,11 @@ class SubsonicBaseRequest : public QObject {
|
||||
|
||||
QUrl CreateUrl(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QNetworkReply *CreateGetRequest(const QString &ressource_name, const QList<Param> ¶ms_provided);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QByteArray GetReplyData(QNetworkReply *reply);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
|
||||
virtual QString Error(QString error, QVariant debug = QVariant());
|
||||
virtual void Error(const QString &error, const QVariant &debug = QVariant()) = 0;
|
||||
QString ErrorsToHTML(const QStringList &errors);
|
||||
|
||||
QString client_name() { return service_->client_name(); }
|
||||
QString api_version() { return service_->api_version(); }
|
||||
@@ -74,6 +74,9 @@ class SubsonicBaseRequest : public QObject {
|
||||
bool verify_certificate() { return service_->verify_certificate(); }
|
||||
bool download_album_covers() { return service_->download_album_covers(); }
|
||||
|
||||
private slots:
|
||||
void HandleSSLErrors(QList<QSslError> ssl_errors);
|
||||
|
||||
private:
|
||||
|
||||
SubsonicService *service_;
|
||||
|
||||
@@ -28,18 +28,20 @@
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QSslConfiguration>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "subsonicservice.h"
|
||||
#include "subsonicurlhandler.h"
|
||||
#include "subsonicrequest.h"
|
||||
@@ -48,10 +50,11 @@ const int SubsonicRequest::kMaxConcurrentAlbumsRequests = 3;
|
||||
const int SubsonicRequest::kMaxConcurrentAlbumSongsRequests = 3;
|
||||
const int SubsonicRequest::kMaxConcurrentAlbumCoverRequests = 1;
|
||||
|
||||
SubsonicRequest::SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, NetworkAccessManager *network, QObject *parent)
|
||||
SubsonicRequest::SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QObject *parent)
|
||||
: SubsonicBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
url_handler_(url_handler),
|
||||
app_(app),
|
||||
network_(network),
|
||||
finished_(false),
|
||||
albums_requests_active_(0),
|
||||
@@ -141,8 +144,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
|
||||
--albums_requests_active_;
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
@@ -151,7 +153,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
return;
|
||||
@@ -180,7 +182,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
}
|
||||
|
||||
if (!json_obj.contains("albumList") && !json_obj.contains("albumList2")) {
|
||||
error = Error("Json reply is missing albumList.", json_obj);
|
||||
Error("Json reply is missing albumList.", json_obj);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +191,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
else if (json_obj.contains("albumList2")) json_albumlist = json_obj["albumList2"];
|
||||
|
||||
if (!json_albumlist.isObject()) {
|
||||
error = Error("Json album list is not an object.", json_albumlist);
|
||||
Error("Json album list is not an object.", json_albumlist);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
json_obj = json_albumlist.toObject();
|
||||
@@ -200,7 +202,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
}
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
error = Error("Json album list does not contain album array.", json_obj);
|
||||
Error("Json album list does not contain album array.", json_obj);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
QJsonValue json_album = json_obj["album"];
|
||||
@@ -210,7 +212,7 @@ void SubsonicRequest::AlbumsReplyReceived(QNetworkReply *reply, const int offset
|
||||
return;
|
||||
}
|
||||
if (!json_album.isArray()) {
|
||||
error = Error("Json album is not an array.", json_album);
|
||||
Error("Json album is not an array.", json_album);
|
||||
AlbumsFinishCheck(offset_requested);
|
||||
}
|
||||
QJsonArray json_albums = json_album.toArray();
|
||||
@@ -329,8 +331,7 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
||||
|
||||
emit UpdateProgress(album_songs_received_);
|
||||
|
||||
QString error;
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
QByteArray data = GetReplyData(reply);
|
||||
|
||||
if (finished_) return;
|
||||
|
||||
@@ -339,7 +340,7 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
@@ -367,27 +368,27 @@ void SubsonicRequest::AlbumSongsReplyReceived(QNetworkReply *reply, const qint64
|
||||
}
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
error = Error("Json reply is missing albumList.", json_obj);
|
||||
Error("Json reply is missing albumList.", json_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonValue json_album = json_obj["album"];
|
||||
|
||||
if (!json_album.isObject()) {
|
||||
error = Error("Json album is not an object.", json_album);
|
||||
Error("Json album is not an object.", json_album);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonObject json_album_obj = json_album.toObject();
|
||||
|
||||
if (!json_album_obj.contains("song")) {
|
||||
error = Error("Json album object does not contain song array.", json_obj);
|
||||
Error("Json album object does not contain song array.", json_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
QJsonValue json_song = json_album_obj["song"];
|
||||
if (!json_song.isArray()) {
|
||||
error = Error("Json song is not an array.", json_album_obj);
|
||||
Error("Json song is not an array.", json_album_obj);
|
||||
SongsFinishCheck();
|
||||
return;
|
||||
}
|
||||
@@ -531,7 +532,7 @@ int SubsonicRequest::ParseSong(Song &song, const QJsonObject &json_obj, const qi
|
||||
if (year > 0) song.set_year(year);
|
||||
song.set_url(url);
|
||||
song.set_length_nanosec(duration);
|
||||
if (cover_url.isValid()) song.set_art_automatic(cover_url.toEncoded());
|
||||
if (cover_url.isValid()) song.set_art_automatic(cover_url);
|
||||
song.set_genre(genre);
|
||||
song.set_directory_id(0);
|
||||
song.set_filetype(filetype);
|
||||
@@ -561,56 +562,27 @@ void SubsonicRequest::GetAlbumCovers() {
|
||||
|
||||
void SubsonicRequest::AddAlbumCoverRequest(Song &song) {
|
||||
|
||||
QUrl url(song.art_automatic());
|
||||
if (!url.isValid()) return;
|
||||
QUrl cover_url(song.art_automatic());
|
||||
if (!cover_url.isValid()) return;
|
||||
|
||||
if (album_covers_requests_sent_.contains(song.album_id())) {
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
return;
|
||||
}
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.album_id = song.album_id();
|
||||
request.url = cover_url;
|
||||
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), cover_url);
|
||||
if (request.filename.isEmpty()) return;
|
||||
|
||||
album_covers_requests_sent_.insertMulti(song.album_id(), &song);
|
||||
++album_covers_requested_;
|
||||
|
||||
AlbumCoverRequest request;
|
||||
request.album_id = song.album_id();
|
||||
request.url = url;
|
||||
request.filename = AlbumCoverFileName(song);
|
||||
|
||||
album_cover_requests_queue_.enqueue(request);
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicRequest::AlbumCoverFileName(const Song &song) {
|
||||
|
||||
QString artist = song.effective_albumartist();
|
||||
QString album = song.effective_album();
|
||||
QString title = song.title();
|
||||
|
||||
artist.remove('/');
|
||||
album.remove('/');
|
||||
title.remove('/');
|
||||
|
||||
QString filename = artist + "-" + album + ".jpg";
|
||||
filename = filename.toLower();
|
||||
filename.replace(' ', '-');
|
||||
filename.replace("--", "-");
|
||||
filename.replace(230, "ae");
|
||||
filename.replace(198, "AE");
|
||||
filename.replace(246, 'o');
|
||||
filename.replace(248, 'o');
|
||||
filename.replace(214, 'O');
|
||||
filename.replace(216, 'O');
|
||||
filename.replace(228, 'a');
|
||||
filename.replace(229, 'a');
|
||||
filename.replace(196, 'A');
|
||||
filename.replace(197, 'A');
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
void SubsonicRequest::FlushAlbumCoverRequests() {
|
||||
|
||||
while (!album_cover_requests_queue_.isEmpty() && album_covers_requests_active_ < kMaxConcurrentAlbumCoverRequests) {
|
||||
@@ -657,9 +629,8 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
||||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
@@ -667,7 +638,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
error = Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
Error(QString("Received empty image data for %1").arg(url.toString()));
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
return;
|
||||
@@ -675,22 +646,19 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &al
|
||||
|
||||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
|
||||
QDir dir;
|
||||
if (dir.mkpath(service_->CoverCacheDir())) {
|
||||
QString filepath(service_->CoverCacheDir() + "/" + filename);
|
||||
if (image.save(filepath, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
song->set_art_automatic(filepath);
|
||||
}
|
||||
if (image.save(filename, "JPG")) {
|
||||
while (album_covers_requests_sent_.contains(album_id)) {
|
||||
Song *song = album_covers_requests_sent_.take(album_id);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(filename);
|
||||
song->set_art_automatic(cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
album_covers_requests_sent_.remove(album_id);
|
||||
error = Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
Error(QString("Error decoding image data from %1").arg(url.toString()));
|
||||
}
|
||||
|
||||
AlbumCoverFinishCheck();
|
||||
@@ -729,29 +697,26 @@ void SubsonicRequest::FinishCheck() {
|
||||
if (songs_.isEmpty() && errors_.isEmpty())
|
||||
emit Results(songs_, tr("Unknown error"));
|
||||
else
|
||||
emit Results(songs_, errors_);
|
||||
emit Results(songs_, ErrorsToHTML(errors_));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicRequest::Error(QString error, QVariant debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
void SubsonicRequest::Error(const QString &error, const QVariant &debug) {
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
errors_ += error;
|
||||
errors_ += "<br />";
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
errors_ << error;
|
||||
}
|
||||
FinishCheck();
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
return error;
|
||||
FinishCheck();
|
||||
|
||||
}
|
||||
|
||||
void SubsonicRequest::Warn(QString error, QVariant debug) {
|
||||
void SubsonicRequest::Warn(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
@@ -27,19 +27,18 @@
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "subsonicbaserequest.h"
|
||||
|
||||
class Application;
|
||||
class NetworkAccessManager;
|
||||
class SubsonicService;
|
||||
class SubsonicUrlHandler;
|
||||
@@ -49,7 +48,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
|
||||
public:
|
||||
|
||||
SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, NetworkAccessManager *network, QObject *parent);
|
||||
SubsonicRequest(SubsonicService *service, SubsonicUrlHandler *url_handler, Application *app, NetworkAccessManager *network, QObject *parent);
|
||||
~SubsonicRequest();
|
||||
|
||||
void ReloadSettings();
|
||||
@@ -64,7 +63,6 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
void UpdateProgress(const int max);
|
||||
|
||||
private slots:
|
||||
|
||||
void AlbumsReplyReceived(QNetworkReply *reply, const int offset_requested);
|
||||
void AlbumSongsReplyReceived(QNetworkReply *reply, const qint64 artist_id, const qint64 album_id, const QString &album_artist);
|
||||
void AlbumCoverReceived(QNetworkReply *reply, const QString &album_id, const QUrl &url, const QString &filename);
|
||||
@@ -95,7 +93,6 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
void SongsFinishCheck();
|
||||
|
||||
void AddAlbumSongsRequest(const qint64 artist_id, const qint64 album_id, const QString &album_artist, const int offset = 0);
|
||||
QString AlbumCoverFileName(const Song &song);
|
||||
void FlushAlbumSongsRequests();
|
||||
|
||||
int ParseSong(Song &song, const QJsonObject &json_obj, const qint64 artist_id_requested = 0, const qint64 album_id_requested = 0, const QString &album_artist = QString());
|
||||
@@ -106,8 +103,8 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
void AlbumCoverFinishCheck();
|
||||
|
||||
void FinishCheck();
|
||||
void Warn(QString error, QVariant debug = QVariant());
|
||||
QString Error(QString error, QVariant debug = QVariant());
|
||||
void Warn(const QString &error, const QVariant &debug = QVariant());
|
||||
void Error(const QString &error, const QVariant &debug = QVariant());
|
||||
|
||||
static const int kMaxConcurrentAlbumsRequests;
|
||||
static const int kMaxConcurrentArtistAlbumsRequests;
|
||||
@@ -116,6 +113,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
|
||||
SubsonicService *service_;
|
||||
SubsonicUrlHandler *url_handler_;
|
||||
Application *app_;
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
bool finished_;
|
||||
@@ -138,7 +136,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
|
||||
int album_covers_received_;
|
||||
|
||||
SongList songs_;
|
||||
QString errors_;
|
||||
QStringList errors_;
|
||||
bool no_results_;
|
||||
QList<QNetworkReply*> album_cover_replies_;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QByteArray>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
@@ -32,6 +31,7 @@
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslError>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -118,10 +118,6 @@ void SubsonicService::ReloadSettings() {
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicService::CoverCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
|
||||
}
|
||||
|
||||
void SubsonicService::SendPing() {
|
||||
SendPing(server_url_, username_, password_);
|
||||
}
|
||||
@@ -158,19 +154,29 @@ void SubsonicService::SendPing(QUrl url, const QString &username, const QString
|
||||
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
errors_.clear();
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandlePingSSLErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandlePingReply(QNetworkReply*)), reply);
|
||||
|
||||
//qLog(Debug) << "Subsonic: Sending request" << url << query;
|
||||
|
||||
}
|
||||
|
||||
void SubsonicService::HandlePingSSLErrors(QList<QSslError> ssl_errors) {
|
||||
|
||||
for (QSslError &ssl_error : ssl_errors) {
|
||||
errors_ += ssl_error.errorString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
PingError(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
@@ -180,7 +186,6 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
||||
QByteArray data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("error")) {
|
||||
@@ -190,24 +195,25 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply) {
|
||||
if (!json_obj.isEmpty() && json_obj.contains("code") && json_obj.contains("message")) {
|
||||
int code = json_obj["code"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
errors_ << QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (errors_.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
errors_ << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
errors_ << QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
PingError(failure_reason);
|
||||
PingError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (http_code != 200) {
|
||||
PingError(QString("Received HTTP code %1").arg(http_code));
|
||||
return;
|
||||
}
|
||||
errors_.clear();
|
||||
|
||||
QByteArray data(reply->readAll());
|
||||
|
||||
@@ -330,7 +336,7 @@ void SubsonicService::GetSongs() {
|
||||
}
|
||||
|
||||
ResetSongsRequest();
|
||||
songs_request_.reset(new SubsonicRequest(this, url_handler_, network_, this));
|
||||
songs_request_.reset(new SubsonicRequest(this, url_handler_, app_, network_, this));
|
||||
connect(songs_request_.get(), SIGNAL(Results(const SongList&, const QString&)), SLOT(SongsResultsReceived(const SongList&, const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(UpdateStatus(const QString&)), SIGNAL(SongsUpdateStatus(const QString&)));
|
||||
connect(songs_request_.get(), SIGNAL(ProgressSetMaximum(const int)), SIGNAL(SongsProgressSetMaximum(const int)));
|
||||
@@ -346,14 +352,20 @@ void SubsonicService::SongsResultsReceived(const SongList &songs, const QString
|
||||
|
||||
}
|
||||
|
||||
QString SubsonicService::PingError(QString error, QVariant debug) {
|
||||
void SubsonicService::PingError(const QString &error, const QVariant &debug) {
|
||||
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
if (!error.isEmpty()) errors_ << error;
|
||||
|
||||
QString error_html;
|
||||
for (const QString &error : errors_) {
|
||||
qLog(Error) << "Subsonic:" << error;
|
||||
error_html += error + "<br />";
|
||||
}
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
|
||||
emit TestFailure(error);
|
||||
emit TestComplete(false, error);
|
||||
emit TestFailure(error_html);
|
||||
emit TestComplete(false, error_html);
|
||||
|
||||
return error;
|
||||
errors_.clear();
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
@@ -59,7 +60,8 @@ class SubsonicService : public InternetService {
|
||||
static const Song::Source kSource;
|
||||
|
||||
void ReloadSettings();
|
||||
QString CoverCacheDir();
|
||||
|
||||
Application *app() { return app_; }
|
||||
|
||||
QString client_name() { return kClientName; }
|
||||
QString api_version() { return kApiVersion; }
|
||||
@@ -89,6 +91,8 @@ class SubsonicService : public InternetService {
|
||||
void ResetSongsRequest();
|
||||
|
||||
private slots:
|
||||
//void HandlePingSSLErrors(QNetworkReply *reply, QList<QSslError> ssl_errors);
|
||||
void HandlePingSSLErrors(QList<QSslError> ssl_errors);
|
||||
void HandlePingReply(QNetworkReply *reply);
|
||||
void SongsResultsReceived(const SongList &songs, const QString &error);
|
||||
|
||||
@@ -99,7 +103,7 @@ class SubsonicService : public InternetService {
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
QString PingError(QString error, QVariant debug = QVariant());
|
||||
void PingError(const QString &error = QString(), const QVariant &debug = QVariant());
|
||||
|
||||
static const char *kClientName;
|
||||
static const char *kApiVersion;
|
||||
@@ -122,6 +126,8 @@ class SubsonicService : public InternetService {
|
||||
bool verify_certificate_;
|
||||
bool download_album_covers_;
|
||||
|
||||
QStringList errors_;
|
||||
|
||||
};
|
||||
|
||||
#endif // SUBSONICSERVICE_H
|
||||
|
||||
@@ -65,3 +65,4 @@ UrlHandler::LoadResult SubsonicUrlHandler::StartLoading(const QUrl &url) {
|
||||
return LoadResult(url, LoadResult::TrackAvailable, media_url);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ class SubsonicUrlHandler : public UrlHandler {
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
void Error(const QString &error, const QVariant &debug) {}
|
||||
|
||||
SubsonicService *service_;
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user