Jellyfin Qt
QML Library for interacting with the Jellyfin multimedia server
Loading...
Searching...
No Matches
loader.h
Go to the documentation of this file.
1/*
2 * Sailfin: a Jellyfin client written using Qt
3 * Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19#ifndef JELLYFIN_SUPPORT_LOADER_H
20#define JELLYFIN_SUPPORT_LOADER_H
21
22#include <initializer_list>
23#include <optional>
24#include <string>
25#include <variant>
26
27#include <QException>
28#include <QJsonDocument>
29#include <QObject>
30#include <QUrlQuery>
31#include <QString>
32
33#include <QtConcurrent/QtConcurrent>
34
35#include "../apiclient.h"
36#include "jsonconv.h"
37
38namespace Jellyfin {
39namespace Support {
40
41
42class LoadException : public QException {
43public:
44 explicit LoadException(const QString &message)
45 : m_message(message.toStdString()) {}
46
47 /*explicit LoadException(const LoadException &other)
48 : m_message(other.m_message) {}*/
49
50 virtual const char *what() const noexcept override;
51
52 virtual QException *clone() const override;
53 virtual void raise() const override;
54private:
55 std::string m_message;
56};
57
58static const int HTTP_TIMEOUT = 30000; // 30 seconds;
59
63class LoaderBase : public QObject {
64 Q_OBJECT
65protected:
66 explicit LoaderBase(ApiClient *apiClient)
67 : m_apiClient(apiClient) {}
68
69public:
73 virtual void load() {
74 throw LoadException(QStringLiteral("Loader not set"));
75 }
76
80 virtual void cancel() {}
81
82 bool isRunning() const {
83 return m_isRunning;
84 }
85
93 virtual bool isAvailable() const { return false; };
94 void setApiClient(ApiClient *newApiClient) { m_apiClient = newApiClient; }
95 ApiClient *apiClient() const { return m_apiClient; }
96
97signals:
102 void error(QString message = QString());
106 void ready();
107protected:
109 bool m_isRunning = false;
110
111 void stopWithError(QString message = QString()) {
112 m_isRunning = false;
113 emit this->error(message);
114 }
115};
116
133template <typename R, typename P>
134class Loader : public LoaderBase {
135public:
136 using ResultType = std::optional<R>;
137 explicit Loader(ApiClient *apiClient)
138 : LoaderBase(apiClient) {}
139
145 R result() const {
146 return m_result.value();
147 }
148
149 bool hasResult() const {
150 return m_result;
151 }
159 void setParameters(const P &parameters) {
160 m_parameters = parameters;
161 }
162
163protected:
164 std::optional<P> m_parameters;
166
168 return std::nullopt;
169 }
170
172 return std::make_optional<R>(result);
173 }
174
176 return fromJsonValue<R>(QJsonValue());
177 }
178
179};
180
181template <typename P>
182class Loader<void, P> : public LoaderBase {
183public:
184 using ResultType = bool;
185 explicit Loader(ApiClient *apiClient)
186 : LoaderBase(apiClient) {}
187
188 void result() const { }
189
190 bool hasResult() const {
191 return m_result;
192 }
200 void setParameters(const P &parameters) {
201 m_parameters = parameters;
202 }
203
204protected:
205 std::optional<P> m_parameters;
207
209 return false;
210 }
211
213 return true;
214 }
215
216 static void createDummyResponse() { }
217};
218
219template<typename R, typename P>
220class HttpLoaderBase : public Loader<R, P> {
221public:
223 : Loader<R, P> (apiClient) {}
224
225 typename Loader<R, P>::ResultType parseResponse(int /*statusCode*/, QByteArray response) {
226 QJsonParseError error;
227 QJsonDocument document = QJsonDocument::fromJson(response, &error);
228 if (error.error != QJsonParseError::NoError) {
229 qWarning() << response;
230 this->stopWithError(error.errorString().toLocal8Bit().constData());
231 }
232 if (document.isNull() || document.isEmpty()) {
233 this->stopWithError(QStringLiteral("Unexpected empty JSON response"));
234 return this->createFailureResult();
235 } else if (document.isArray()) {
236 return this->createSuccessResult(fromJsonValue<R>(document.array()));
237 } else if (document.isObject()){
238 return this->createSuccessResult(fromJsonValue<R>(document.object()));
239 } else {
240 this->stopWithError(QStringLiteral("Unexpected JSON response"));
241 return this->createFailureResult();
242 }
243 }
244
245};
246
247// Specialisation for void result
248template<typename P>
249class HttpLoaderBase<void, P> : public Loader<void, P> {
250public:
252 : Loader<void, P> (apiClient) {}
253
254 typename Loader<void, P>::ResultType parseResponse(int statusCode, QByteArray response) {
255 return statusCode == 204;
256 }
257};
258
259// Specialisation for endpoints that return "true" or "false" as response.
260template<typename P>
261class HttpLoaderBase<bool, P> : public Loader<bool, P> {
262public:
264 : Loader<bool, P> (apiClient) {}
265
266 typename Loader<bool, P>::ResultType parseResponse(int statusCode, QByteArray response) {
267 QString text = QString::fromUtf8(response);
268
269 if (text == QStringLiteral("true")) {
270 return true;
271 } else if (text == QStringLiteral("false")) {
272 return false;
273 } else {
274 this->stopWithError(QStringLiteral("Could not parse boolean response: %1").arg(text));
275 return std::nullopt;
276 }
277 }
278};
279
280
284template <typename R, typename P>
285class HttpLoader : public HttpLoaderBase<R, P> {
286public:
287 explicit HttpLoader(Jellyfin::ApiClient *apiClient)
288 : HttpLoaderBase<R, P> (apiClient) {
289 this->connect(&m_parsedWatcher, &QFutureWatcher<std::optional<R>>::finished, this, &HttpLoader<R, P>::onResponseParsed);
290 }
291
292 virtual void load() override {
293 if (m_reply != nullptr) {
294 this->m_reply->deleteLater();
295 }
296 if (!this->m_parameters) {
297 this->stopWithError("No parameters set");
298 }
299 this->m_isRunning = true;
300 switch(operation()) {
301 case QNetworkAccessManager::GetOperation:
302 m_reply = this->m_apiClient->get(path(this->m_parameters.value()), query(this->m_parameters.value()));
303 break;
304 case QNetworkAccessManager::PostOperation:
305 m_reply = this->m_apiClient->post(path(this->m_parameters.value()), body(this->m_parameters.value()), query(this->m_parameters.value()));
306 break;
307 default:
308 this->stopWithError(QStringLiteral("Unsupported network okperation %1").arg(operation()));
309 return;
310
311 }
312
313 this->connect(m_reply, &QNetworkReply::finished, this, &HttpLoader<R, P>::onRequestFinished);
314 }
315
316 virtual void cancel() override {
317 if (m_reply == nullptr) return;
318 if (m_reply->isRunning()) {
319 m_reply->abort();
320 m_reply->deleteLater();
321 m_reply = nullptr;
322 }
323 }
324
325 bool isAvailable() const override {
326 if (this->m_apiClient == nullptr) {
327 return false;
328 }
329 return this->m_apiClient->online();
330 }
331protected:
338 virtual QString path(const P &parameters) const = 0;
339 virtual QUrlQuery query(const P &parameters) const = 0;
340 virtual QByteArray body(const P &parameters) const = 0;
341 virtual QNetworkAccessManager::Operation operation() const = 0;
342private:
343 QNetworkReply *m_reply = nullptr;
344 QFutureWatcher<typename Loader<R, P>::ResultType> m_parsedWatcher;
345
346 void onRequestFinished() {
347 if (m_reply->error() != QNetworkReply::NoError) {
348 m_reply->deleteLater();
349 m_parsedWatcher.cancel();
350 //: An HTTP has occurred. First argument is replaced by QNetworkReply->errorString()
351 this->stopWithError(QStringLiteral("HTTP error: %1").arg(m_reply->errorString()));
352 return;
353 }
354 QByteArray array = m_reply->readAll();
355 int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
356 m_reply->deleteLater();
357 m_reply = nullptr;
358 m_parsedWatcher.setFuture(
359 QtConcurrent::run<typename HttpLoader<R, P>::ResultType, // Result
360 HttpLoader<R, P>, // class
361 int, int, // Argument 1
362 QByteArray, QByteArray> // Argument 2
363 (this, &HttpLoader<R, P>::parseResponse, statusCode, array)
364 );
365 }
366
367 void onResponseParsed() {
368 Q_ASSERT(m_parsedWatcher.isFinished());
369 try {
370 /* In case the result is an optional, it invokes the bool cast of std::optional, checking
371 if it has a value.
372 In case the result is a boolean, it just checks the boolean */
373 if (m_parsedWatcher.result()) {
374 this->m_result = m_parsedWatcher.result();
375 this->m_isRunning = false;
376 emit this->ready();
377 } else {
378 this->m_isRunning = false;
379 }
380 } catch (QException &e) {
381 this->stopWithError(e.what());
382 }
383 }
384};
385
386} // NS Support
387} // NS Jellyfin
388
389#endif // JELLYFIN_SUPPORT_LOADER_H
An Api client for Jellyfin. Handles requests and authentication.
Definition apiclient.h:90
QNetworkReply * get(const QString &path, const QUrlQuery &params=QUrlQuery())
Definition apiclient.cpp:262
QNetworkReply * post(const QString &path, const QJsonDocument &data, const QUrlQuery &params=QUrlQuery())
Definition apiclient.cpp:268
bool online
Definition apiclient.h:116
HttpLoaderBase(Jellyfin::ApiClient *apiClient)
Definition loader.h:263
Loader< bool, P >::ResultType parseResponse(int statusCode, QByteArray response)
Definition loader.h:266
Loader< void, P >::ResultType parseResponse(int statusCode, QByteArray response)
Definition loader.h:254
HttpLoaderBase(Jellyfin::ApiClient *apiClient)
Definition loader.h:251
Definition loader.h:220
Loader< R, P >::ResultType parseResponse(int, QByteArray response)
Definition loader.h:225
HttpLoaderBase(Jellyfin::ApiClient *apiClient)
Definition loader.h:222
Definition loader.h:285
virtual QString path(const P &parameters) const =0
Subclasses should override this method to return the path to this endpoint, with all path parameters ...
HttpLoader(Jellyfin::ApiClient *apiClient)
Definition loader.h:287
virtual QByteArray body(const P &parameters) const =0
virtual void cancel() override
Definition loader.h:316
virtual QNetworkAccessManager::Operation operation() const =0
bool isAvailable() const override
Heuristic to determine if this resource can be loaded via this loaded.
Definition loader.h:325
virtual QUrlQuery query(const P &parameters) const =0
virtual void load() override
load Loads the given resource asynchronously.
Definition loader.h:292
Definition loader.h:42
virtual const char * what() const noexcept override
Definition loader.cpp:24
virtual QException * clone() const override
Definition loader.cpp:28
virtual void raise() const override
Definition loader.cpp:32
LoadException(const QString &message)
Definition loader.h:44
Base class for loaders that defines available signals.
Definition loader.h:63
LoaderBase(ApiClient *apiClient)
Definition loader.h:66
void setApiClient(ApiClient *newApiClient)
Definition loader.h:94
virtual bool isAvailable() const
Heuristic to determine if this resource can be loaded via this loaded.
Definition loader.h:93
virtual void cancel()
Definition loader.h:80
void stopWithError(QString message=QString())
Definition loader.h:111
void error(QString message=QString())
Emitted when an error has occurred during loading and no result is available.
ApiClient * apiClient() const
Definition loader.h:95
virtual void load()
load Loads the given resource asynchronously.
Definition loader.h:73
Jellyfin::ApiClient * m_apiClient
Definition loader.h:108
bool isRunning() const
Definition loader.h:82
void ready()
Emitted when data was successfully loaded.
Loader(ApiClient *apiClient)
Definition loader.h:185
ResultType createFailureResult()
Definition loader.h:208
static void createDummyResponse()
Definition loader.h:216
ResultType createSuccessResult(void)
Definition loader.h:212
std::optional< P > m_parameters
Definition loader.h:205
bool hasResult() const
Definition loader.h:190
ResultType m_result
Definition loader.h:206
bool ResultType
Definition loader.h:184
void setParameters(const P &parameters)
Sets the parameters for this loader.
Definition loader.h:200
void result() const
Definition loader.h:188
Definition loader.h:134
ResultType m_result
Definition loader.h:165
std::optional< R > ResultType
Definition loader.h:136
static R createDummyResponse()
Definition loader.h:175
ResultType createFailureResult()
Definition loader.h:167
R result() const
Retrieves the loaded resource. Only valid after the ready signal has been emitted.
Definition loader.h:145
ResultType createSuccessResult(R &&result)
Definition loader.h:171
bool hasResult() const
Definition loader.h:149
Loader(ApiClient *apiClient)
Definition loader.h:137
void setParameters(const P &parameters)
Sets the parameters for this loader.
Definition loader.h:159
std::optional< P > m_parameters
Definition loader.h:164
void writeRequestTypesFile R(File headerFile, File implementationFile, R endpoints) if(is(ElementType!R
Definition openapigenerator.d:278