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

@ -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