1
0
Fork 0
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:
Chris Josten 2021-03-28 04:00:00 +02:00
parent e421adf733
commit 729e343661
1412 changed files with 13967 additions and 33794 deletions

View file

@ -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.
*/

View file

@ -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 &parameters) 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 &parameters) { 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 &parameters) 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 &parameters) 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 &parameters) const = 0;
virtual QUrlQuery query(const P &parameters) 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