mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-01 08:52:45 +00:00
WIP: HttpLoader seems to work, Model still borked
This commit is contained in:
parent
e421adf733
commit
729e343661
1412 changed files with 13967 additions and 33794 deletions
|
@ -19,12 +19,12 @@
|
|||
#ifndef JELLYFIN_SUPPORT_JSONCONV_H
|
||||
#define JELLYFIN_SUPPORT_JSONCONV_H
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <QException>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
@ -39,47 +39,116 @@ QUuid stringToUuid(const QString &source);
|
|||
/**
|
||||
* @brief Thrown when JSON cannot be parsed.
|
||||
*/
|
||||
class ParseException : public std::runtime_error {
|
||||
class ParseException : public QException {
|
||||
public:
|
||||
explicit ParseException(const char *message)
|
||||
: std::runtime_error(message) {}
|
||||
explicit ParseException(const QString &message)
|
||||
: m_message(message.toStdString()) {}
|
||||
|
||||
/*explicit ParseException(const ParseException &other)
|
||||
: m_message(other.m_message) {}*/
|
||||
|
||||
virtual const char *what() const noexcept override;
|
||||
|
||||
virtual QException *clone() const override;
|
||||
virtual void raise() const override;
|
||||
private:
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
// https://www.fluentcpp.com/2017/08/15/function-templates-partial-specialization-cpp/
|
||||
template <typename T>
|
||||
struct convertType{};
|
||||
|
||||
/**
|
||||
* Template for converting types from JSON into their respective type.
|
||||
*/
|
||||
template <typename T>
|
||||
T fromJsonValue(const QJsonValue &source) {
|
||||
T fromJsonValue(const QJsonValue &source, convertType<T>) {
|
||||
Q_UNUSED(source)
|
||||
Q_ASSERT_X(false, "fromJsonValue<T>", "fromJsonValue called with unimplemented type");
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
QJsonValue toJsonValue(const T &source) {
|
||||
QJsonValue toJsonValue(const T &source, convertType<T>) {
|
||||
Q_UNUSED(source)
|
||||
Q_ASSERT_X(false, "toJsonValue<T>", "toJsonValue called with unimplemented type");
|
||||
std::string msg = "toJsonValue called with unimplemented type ";
|
||||
msg += typeid (T).name();
|
||||
Q_ASSERT_X(false, "toJsonValue<T>", msg.c_str());
|
||||
return QJsonValue();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T fromJsonValue(const QJsonValue &source) {
|
||||
return fromJsonValue(source, convertType<T>{});
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
QJsonValue toJsonValue(const T &source) {
|
||||
return toJsonValue(source, convertType<T>{});
|
||||
}
|
||||
|
||||
// QList
|
||||
template <typename T>
|
||||
QList<T> fromJsonValue(const QJsonArray &source) {
|
||||
QList<T> fromJsonValue(const QJsonValue &source, convertType<QList<T>>) {
|
||||
QList<T> result;
|
||||
result.reserve(source.size());
|
||||
for (auto it = source.cbegin(); it != source.cend(); it++) {
|
||||
QJsonArray arr = source.toArray();
|
||||
result.reserve(arr.size());
|
||||
for (auto it = arr.cbegin(); it != arr.cend(); it++) {
|
||||
result.append(fromJsonValue<T>(*it));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QJsonValue toJsonValue(const QList<T> &source) {
|
||||
QJsonValue toJsonValue(const QList<T> &source, convertType<QList<T>>) {
|
||||
QJsonArray result;
|
||||
for (auto it = source.cbegin(); it != source.cend(); it++) {
|
||||
result.push_back(*it);
|
||||
result.push_back(toJsonValue<T>(*it));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Optional
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> fromJsonValue(const QJsonValue &source, convertType<std::optional<T>>) {
|
||||
if (source.isNull()) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return fromJsonValue<T>(source, convertType<T>{});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QJsonValue toJsonValue(const std::optional<T> &source, convertType<std::optional<T>>) {
|
||||
if (source.has_value()) {
|
||||
return toJsonValue<T>(source.value(), convertType<T>{});
|
||||
} else {
|
||||
// Null
|
||||
return QJsonValue();
|
||||
}
|
||||
}
|
||||
|
||||
// QSharedPointer
|
||||
template <typename T>
|
||||
QSharedPointer<T> fromJsonValue(const QJsonValue &source, convertType<QSharedPointer<T>>) {
|
||||
if (source.isNull()) {
|
||||
return QSharedPointer<T>();
|
||||
}
|
||||
return QSharedPointer<T>::create(fromJsonValue<T>(source));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QJsonValue toJsonValue(const QSharedPointer<T> &source, convertType<QSharedPointer<T>>) {
|
||||
if (source.isNull()) {
|
||||
return QJsonValue();
|
||||
}
|
||||
return toJsonValue<T>(*source);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Templates for string conversion.
|
||||
*/
|
||||
|
|
|
@ -40,6 +40,10 @@ class LoadException : public QException {
|
|||
public:
|
||||
explicit LoadException(const QString &message)
|
||||
: m_message(message.toStdString()) {}
|
||||
|
||||
/*explicit LoadException(const LoadException &other)
|
||||
: m_message(other.m_message) {}*/
|
||||
|
||||
virtual const char *what() const noexcept override;
|
||||
|
||||
virtual QException *clone() const override;
|
||||
|
@ -54,6 +58,10 @@ static const int HTTP_TIMEOUT = 30000; // 30 seconds;
|
|||
* Interface describing a way to load items. Used to abstract away
|
||||
* the difference between loading from a cache or loading over the network.
|
||||
*
|
||||
* To implement this class, implement prepareLoad() and load(). These are always called
|
||||
* in the same order, but prepareLoad() must always be called on the same thread as the
|
||||
* m_apiClient, while load() may be called on another thread.
|
||||
*
|
||||
* @note: Loaders should NEVER call load() again while load() is running on another
|
||||
* thread or change the apiClient while running. This will result in undefined behaviour.
|
||||
* Please use a Mutex to enforce this.
|
||||
|
@ -67,13 +75,18 @@ class Loader {
|
|||
public:
|
||||
explicit Loader(ApiClient *apiClient)
|
||||
: m_apiClient(apiClient) {}
|
||||
|
||||
/**
|
||||
* @brief load Loads the given resource.
|
||||
* @param parameters Parameters to determine which resource should be loaded.
|
||||
* @brief Called just before load() is called. In constrast to load,
|
||||
* this runs on the same thread as the ApiClient object.
|
||||
*/
|
||||
virtual void prepareLoad() {};
|
||||
|
||||
/**
|
||||
* @brief load Loads the given resource. This usually run on a different thread.
|
||||
* @return The resource if successfull.
|
||||
*/
|
||||
virtual std::optional<R> load(const P ¶meters) const {
|
||||
Q_UNUSED(parameters)
|
||||
virtual std::optional<R> load() {
|
||||
throw LoadException(QStringLiteral("Loader not set"));
|
||||
}
|
||||
/**
|
||||
|
@ -86,38 +99,10 @@ public:
|
|||
virtual bool isAvailable() const { return false; };
|
||||
void setApiClient(ApiClient *newApiClient) { m_apiClient = newApiClient; }
|
||||
ApiClient *apiClient() const { return m_apiClient; }
|
||||
void setParameters(const P ¶meters) { m_parameters = parameters; }
|
||||
protected:
|
||||
Jellyfin::ApiClient *m_apiClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of a Loader that tries multiple loaders.
|
||||
*/
|
||||
template <typename R, typename P>
|
||||
class MultipleChoiceLoader : public Loader<R, P> {
|
||||
using L = Loader<R, P>;
|
||||
public:
|
||||
explicit MultipleChoiceLoader(ApiClient *apiClient,
|
||||
std::initializer_list<L> loaders)
|
||||
: L(apiClient), m_loaders(loaders) {}
|
||||
|
||||
virtual std::optional<R> load(const P ¶meters) const override {
|
||||
for(auto it = m_loaders.begin(); it != m_loaders.end(); it++) {
|
||||
if (it->isAvailable()) {
|
||||
try {
|
||||
std::optional<R> res = it->load(parameters);
|
||||
if (res.has_value()) return res;
|
||||
} catch (LoadException &e) {
|
||||
qDebug() << "Error while loading: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
qDebug() << "No loaders were able to fulfill the request";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
QList<L> m_loaders;
|
||||
P m_parameters;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -129,29 +114,40 @@ public:
|
|||
explicit HttpLoader(Jellyfin::ApiClient *apiClient)
|
||||
: Loader<R, P> (apiClient) {}
|
||||
|
||||
virtual std::optional<R> load(const P ¶meters) const override {
|
||||
QNetworkReply *reply = this->m_apiClient->get(path(parameters), query(parameters));
|
||||
QByteArray array;
|
||||
while (!reply->atEnd()) {
|
||||
if (!reply->waitForReadyRead(HTTP_TIMEOUT)) {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
reply->deleteLater();
|
||||
//: Loading from a resource failed due to the server taking too long to respond
|
||||
throw LoadException(QObject::tr("Network timeout"));
|
||||
}
|
||||
reply->deleteLater();
|
||||
//: An HTTP has occurred. First argument is replaced by QNetworkReply->errorString()
|
||||
throw LoadException(QObject::tr("HTTP error: %1").arg(reply->errorString()));
|
||||
}
|
||||
array.append(reply->readAll());
|
||||
virtual void prepareLoad() override {
|
||||
m_reply = this->m_apiClient->get(path(this->m_parameters), query(this->m_parameters));
|
||||
m_requestFinishedConnection = QObject::connect(m_reply, &QNetworkReply::finished, [&]() { this->requestFinished(); });
|
||||
}
|
||||
|
||||
virtual std::optional<R> load() override {
|
||||
Q_ASSERT_X(m_reply != nullptr, "HttpLoader::load", "prepareLoad() must be called before load()");
|
||||
QMutexLocker locker(&m_mutex);
|
||||
while (!m_reply->isFinished()) {
|
||||
m_waitCondition.wait(&m_mutex);
|
||||
}
|
||||
reply->deleteLater();
|
||||
QByteArray array = m_reply->readAll();
|
||||
if (m_reply->error() != QNetworkReply::NoError) {
|
||||
m_reply->deleteLater();
|
||||
//: An HTTP has occurred. First argument is replaced by QNetworkReply->errorString()
|
||||
throw LoadException(QObject::tr("HTTP error: %1").arg(m_reply->errorString()));
|
||||
}
|
||||
m_reply->deleteLater();
|
||||
m_reply = nullptr;
|
||||
QJsonParseError error;
|
||||
QJsonDocument document = QJsonDocument::fromJson(array, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning() << array;
|
||||
throw LoadException(error.errorString().toLocal8Bit().constData());
|
||||
}
|
||||
return fromJsonValue<R>(QJsonValue(document.object()));
|
||||
if (document.isNull() || document.isEmpty()) {
|
||||
return std::nullopt;
|
||||
} else if (document.isArray()) {
|
||||
return std::optional<R>(fromJsonValue<R>(document.array()));
|
||||
} else if (document.isObject()){
|
||||
return std::optional<R>(fromJsonValue<R>(document.object()));
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool isAvailable() const override {
|
||||
|
@ -169,6 +165,16 @@ protected:
|
|||
*/
|
||||
virtual QString path(const P ¶meters) const = 0;
|
||||
virtual QUrlQuery query(const P ¶meters) const = 0;
|
||||
private:
|
||||
QNetworkReply *m_reply = nullptr;
|
||||
QWaitCondition m_waitCondition;
|
||||
QMutex m_mutex;
|
||||
QMetaObject::Connection m_requestFinishedConnection;
|
||||
|
||||
void requestFinished() {
|
||||
QObject::disconnect(m_requestFinishedConnection);
|
||||
m_waitCondition.wakeAll();
|
||||
}
|
||||
};
|
||||
|
||||
} // NS Support
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue