Jellyfin Qt
QML Library for interacting with the Jellyfin multimedia server
Loading...
Searching...
No Matches
apimodel.h
Go to the documentation of this file.
1/*
2Sailfin: a Jellyfin client written using Qt
3Copyright (C) 2021 Chris Josten
4
5This library is free software; you can redistribute it and/or
6modify it under the terms of the GNU Lesser General Public
7License as published by the Free Software Foundation; either
8version 2.1 of the License, or (at your option) any later version.
9
10This library is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13Lesser General Public License for more details.
14
15You should have received a copy of the GNU Lesser General Public
16License along with this library; if not, write to the Free Software
17Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#ifndef JELLYFIN_API_MODEL
21#define JELLYFIN_API_MODEL
22
23#include <optional>
24#include <typeinfo>
25
26#include <QAbstractListModel>
27#include <QFlags>
28#include <QFuture>
29#include <QFutureWatcher>
30#include <QMetaEnum>
31#include <QJsonArray>
32#include <QJsonDocument>
33#include <QJsonValue>
34#include <QtConcurrent/QtConcurrent>
35#include <QtQml>
36#include <QQmlParserStatus>
37#include <QVariant>
38
39#include "apiclient.h"
40#include "jsonhelper.h"
41
42#include "dto/baseitemdto.h"
43#include "dto/userdto.h"
44#include "dto/useritemdatadto.h"
46#include "loader/requesttypes.h"
47#include "support/loader.h"
49
50Q_DECLARE_LOGGING_CATEGORY(jellyfinApiModel)
51
52namespace Jellyfin {
53
54/*
55 * A brief description of this file:
56 *
57 * The reason why all of this is this complex is because Qt MOC's lack of support for template classes
58 * with Q_OBJECT. To work around this, "base classes", such as BaseModelLoader, are created which contain
59 * all functionallity required by Q_OBJECT. This class is extended by the templated class, which in turn
60 * is extended once more, so it can be registered for the QML engine.
61 *
62 * The loading of the data has beens separated from the QAbstractModels into ViewModel::Loaders
63 * to allow for loading over the network, loading from the cache and so on, without the QAbstractModel
64 * knowing anything about how it's done, except for the parameters it can pass to the loader and the result.
65 *
66 */
67
68class BaseModelLoader : public QObject, public QQmlParserStatus {
69 Q_INTERFACES(QQmlParserStatus)
70 Q_OBJECT
71public:
72 explicit BaseModelLoader(QObject *parent = nullptr);
74 Q_PROPERTY(Jellyfin::ViewModel::ModelStatusClass::Value status READ status NOTIFY statusChanged)
75 Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged)
76 Q_PROPERTY(bool autoReload READ autoReload WRITE setAutoReload NOTIFY autoReloadChanged)
77
78 ApiClient *apiClient() const { return m_apiClient; }
79 void setApiClient(ApiClient *newApiClient);
80
81 int limit() const { return m_limit; }
82 void setLimit(int newLimit);
83 bool autoReload() const { return m_autoReload; }
84 void setAutoReload(bool newAutoReload);
85
87
91 Q_INVOKABLE virtual void reload() {};
92
93 // QQmlParserStatus interface
94 virtual void classBegin() override;
95 virtual void componentComplete() override;
96
97 void autoReloadIfNeeded();
98signals:
99 void ready();
100 void apiClientChanged(ApiClient *newApiClient);
102 void limitChanged(int newLimit);
103 void autoReloadChanged(bool newAutoReload);
104
114protected:
115 // Is this object being parsed by the QML engine
116 bool m_isBeingParsed = false;
118 bool m_autoReload = true;
120 bool m_manualLimitSet = false;
121
122 // Query/record controlling properties
123 int m_limit = -1;
126 bool m_explicitLimitSet = false;
127 const int DEFAULT_LIMIT = 100;
129 void emitItemsLoaded() { emit itemsLoaded(); }
130
131 ViewModel::ModelStatus m_status = ViewModel::ModelStatus::Uninitialised;
133 if (this->m_status != newStatus) {
134 this->m_status = newStatus;
135 emit this->statusChanged();
136 if (this->m_status == ViewModel::ModelStatus::Ready) {
137 emit ready();
138 }
139 }
140 }
141
150 virtual bool canReload() const;
151};
152
157template <class T>
159public:
160 ModelLoader(QObject *parent = nullptr)
161 : BaseModelLoader(parent) {
162 }
163
164 void reload() override {
165 if (!canReload()) {
166 return;
167 }
168 m_startIndex = 0;
171 loadMore(ViewModel::ModelStatus::Loading);
172 }
173
174 void loadMore() {
175 if (!canReload()) {
176 return;
177 }
178 loadMore(ViewModel::ModelStatus::LoadingMore);
179 }
180
181 virtual bool canLoadMore() const {
183 }
184
191 std::pair<QList<T*>, int> &&result() { return std::move(m_result); }
192protected:
201 virtual void loadMore(ViewModel::ModelStatus suggestedStatus) = 0;
202 std::pair<QList<T*>, int> m_result;
203};
204
208template <class T, class R>
209QList<T> extractRecords(const R &result) {
210 Q_UNUSED(result)
211 qDebug() << "extractRecords not implemented for type " << typeid(R).name();
212 return QList<T>();
213}
214
215template <class R>
216int extractTotalRecordCount(const R &result) {
217 Q_UNUSED(result)
218 qDebug() << "extractTotalRecourdCount not implemented for type " << typeid(R).name();
219 return -1;
220}
221template <class P>
222void setRequestLimit(P &parameters, int limit) {
223 Q_UNUSED(parameters)
224 Q_UNUSED(limit)
225 qDebug() << "setRequestLimit not implemented for type " << typeid(P).name();
226}
227
231template <class P>
232bool setRequestStartIndex(P &parameters, int startIndex) {
233 Q_UNUSED(parameters)
234 Q_UNUSED(startIndex)
235 qDebug() << "setRequestStartIndex not implemented for type " << typeid(P).name();
236 return false;
237}
238
239#ifndef JELLYFIN_APIMODEL_CPP
240extern template bool setRequestStartIndex(Loader::GetUserViewsParams &params, int startIndex);
241extern template void setRequestLimit(Loader::GetUserViewsParams &params, int limit);
242
243extern template QList<DTO::BaseItemDto> extractRecords(const DTO::BaseItemDtoQueryResult &result);
244extern template int extractTotalRecordCount(const DTO::BaseItemDtoQueryResult &result);
246extern template int extractTotalRecordCount(const QList<DTO::BaseItemDto> &result);
247
248extern template void setRequestLimit(Loader::GetLatestMediaParams &params, int limit);
249extern template bool setRequestStartIndex(Loader::GetLatestMediaParams &params, int offset);
250
251extern template void setRequestLimit(Loader::GetItemsParams &params, int limit);
252extern template bool setRequestStartIndex(Loader::GetItemsParams &params, int offset);
253
254extern template void setRequestLimit(Loader::GetResumeItemsParams &params, int limit);
255extern template bool setRequestStartIndex(Loader::GetResumeItemsParams &params, int offset);
256
257extern template void setRequestLimit(Loader::GetPublicUsersParams &params, int limit);
258extern template bool setRequestStartIndex(Loader::GetPublicUsersParams &params, int offset);
259
260extern template void setRequestLimit(Loader::GetNextUpParams &params, int limit);
261extern template bool setRequestStartIndex(Loader::GetNextUpParams &params, int offset);
262
263extern template void setRequestLimit(Loader::GetAlbumArtistsParams &params, int limit);
264extern template bool setRequestStartIndex(Loader::GetAlbumArtistsParams &params, int offset);
265
266extern template void setRequestLimit(Loader::GetLiveTvChannelsParams &params, int limit);
267extern template bool setRequestStartIndex(Loader::GetLiveTvChannelsParams &params, int offset);
268
269extern template QList<DTO::UserDto> extractRecords(const QList<DTO::UserDto> &result);
270extern template int extractTotalRecordCount(const QList<DTO::UserDto> &result);
271#endif
272
281template <class T, class D, class R, class P>
283public:
284 explicit LoaderModelLoader(Support::Loader<R, P> *loader, QObject *parent = nullptr)
285 : ModelLoader<T>(parent), m_loader(QScopedPointer<Support::Loader<R, P>>(loader)) {
288 }
289protected:
290 void loadMore(ViewModel::ModelStatus suggestedModelStatus) override {
291 // This method should only be callable on one thread.
292 // If futureWatcher's future is running, this method should not be called again.
293 if (m_loader->isRunning()) {
294 m_loader->cancel();
295 }
296 // Set an invalid result.
297 this->m_result = { QList<T*>(), -1 };
298
300 && suggestedModelStatus == ViewModel::ModelStatus::LoadingMore) {
301 // This loader's parameters does not setting a starting index,
302 // meaning loadMore is not supported.
303 return;
304 }
306
307 if (suggestedModelStatus == ViewModel::ModelStatus::LoadingMore && this->m_explicitLimitSet) {
308 // If an explicit limit is set, we should load no more
309 return;
310 }
311
312 qCDebug(jellyfinApiModel) << "Explicit limit set: " << this->m_explicitLimitSet << ", " << this->m_limit;
313 if (this->m_explicitLimitSet) {
314 setRequestLimit<P>(this->m_parameters, this->m_limit);
315 } else {
317 }
318 this->setStatus(suggestedModelStatus);
319
320 // We never want to set this while the loader is running, hence the Mutex and setting it here
321 // instead when Loader::setApiClient is called.
322 this->m_loader->setApiClient(this->m_apiClient);
323 this->m_loader->setParameters(this->m_parameters);
324 this->m_loader->load();
325 }
326
327 QScopedPointer<Support::Loader<R, P>> m_loader;
329
330 void loaderReady() {
331 try {
332 R result = m_loader->result();
334 int totalRecordCount = extractTotalRecordCount<R>(result);
335 // If totalRecordCount < 0, it is not supported for this endpoint
336 if (totalRecordCount < 0) {
337 totalRecordCount = records.size();
338 }
339 QList<T*> models;
340 models.reserve(records.size());
341
342 // Convert the DTOs into models
343 for (int i = 0; i < records.size(); i++) {
344 models.append(new T(records[i], m_loader->apiClient()));
345 }
346 this->setStatus(ViewModel::ModelStatus::Ready);
347 this->m_result = { models, this->m_startIndex};
348 this->m_startIndex += records.size();
349 this->m_totalRecordCount = totalRecordCount;
350 this->emitItemsLoaded();
351
352 } catch(QException &e) {
353 qWarning() << "Error while loading data: " << e.what();
354 this->setStatus(ViewModel::ModelStatus::Error);
355 }
356 }
357
358 void loaderError(QString error) {
359 Q_UNUSED(error)
360 this->setStatus(ViewModel::ModelStatus::Error);
361 }
362
363};
364
365class BaseApiModel : public QAbstractListModel {
366 Q_OBJECT
367public:
368 BaseApiModel(QObject *parent = nullptr)
369 : QAbstractListModel (parent) {}
370
371 Q_PROPERTY(BaseModelLoader *loader READ loader WRITE setLoader NOTIFY loaderChanged)
372
373 virtual BaseModelLoader *loader() const = 0;
374 virtual void setLoader(BaseModelLoader *newLoader) {
375 connect(newLoader, &BaseModelLoader::reloadWanted, this, &BaseApiModel::reload);
376 connect(newLoader, &BaseModelLoader::modelShouldClear, this, &BaseApiModel::clear);
378 emit loaderChanged();
379 };
381 if (oldLoader != nullptr) {
382 // Disconnect all signals
383 disconnect(oldLoader, nullptr, this, nullptr);
384 }
385 }
386public slots:
387 virtual void reload() = 0;
388 virtual void clear() = 0;
389protected slots:
390 virtual void loadingFinished() = 0;
391signals:
393};
394
403template <class T>
404class ApiModel : public BaseApiModel {
405public:
434 explicit ApiModel(QObject *parent = nullptr)
435 : BaseApiModel(parent) {
436 }
437
438 // Standard QAbstractItemModel overrides
439 int rowCount(const QModelIndex &index) const override {
440 if (!index.isValid()) return m_array.size();
441 return 0;
442 }
443
444 // QList-like API
445 QSharedPointer<T> at(int index) const { return m_array.at(index); }
449 int size() const {
450 return m_array.size();
451 }
452
453 void insert(int index, QSharedPointer<T> object) {
454 Q_ASSERT(index >= 0 && index <= size());
455 this->beginInsertRows(QModelIndex(), index, index);
456 m_array.insert(index, object);
457 this->endInsertRows();
458 }
459
460 void append(QSharedPointer<T> object) { insert(size(), object); }
461 //void append(T &object) { insert(size(), object); }
462 void append(QList<QSharedPointer<T>> &objects) {
463 int index = size();
464 this->beginInsertRows(QModelIndex(), index, index + objects.size() - 1);
465 m_array.append(objects);
466 this->endInsertRows();
467 };
468
469 QList<T*> mid(int pos, int length = -1) {
470 return m_array.mid(pos, length);
471 }
472
473 void removeAt(int index) {
474 Q_ASSERT(index < size());
475 this->beginRemoveRows(QModelIndex(), index, index);
476 m_array.removeAt(index);
477 this->endRemoveRows();
478 }
479
480 void removeUntilEnd(int from) {
481 this->beginRemoveRows(QModelIndex(), from , m_array.size());
482 while (m_array.size() != from) {
483 m_array.removeLast();
484 }
485 this->endRemoveRows();
486 }
487
488 void removeOne(QSharedPointer<T> object) {
489 int idx = m_array.indexOf(object);
490 if (idx >= 0) {
491 removeAt(idx);
492 }
493 }
494
495 void clear() override {
496 this->beginResetModel();
497 m_array.clear();
498 this->endResetModel();
499 }
500
502 return m_array;
503 }
504
505 // From AbstractListModel, gets implemented in ApiModel<T>
506 //virtual QHash<int, QByteArray> roleNames() const override = 0;
507 /*virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override = 0;*/
508 virtual bool canFetchMore(const QModelIndex &parent) const override {
509 if (parent.isValid()) return false;
510 if (m_loader == nullptr) return false;
511 return m_loader->canLoadMore();
512 }
513 virtual void fetchMore(const QModelIndex &parent) override {
514 if (parent.isValid()) return;
515 if (m_loader != nullptr) {
516 m_loader->loadMore();
517 }
518 }
519
520 BaseModelLoader *loader() const override {
521 return m_loader;
522 }
523
524 void setLoader(BaseModelLoader *newLoader) {
525 ModelLoader<T> *castedLoader = dynamic_cast<ModelLoader<T> *>(newLoader);
526 if (castedLoader != nullptr) {
527 // Hacky way to emit a signal
528 BaseApiModel::setLoader(newLoader);
530 m_loader = castedLoader;
531 reload();
532 } else {
533 qWarning() << "Somehow set a BaseModelLoader on ApiModel instead of a ModelLoader<T>";
534 }
535 }
536
537 void reload() override {
538 if (m_loader != nullptr) {
539 m_loader->reload();
540 }
541 }
542
543protected:
544 // Model-specific properties.
547
548 void loadingFinished() override {
549 Q_ASSERT(m_loader != nullptr);
550 std::pair<QList<T*>, int> result = m_loader->result();
551 if (result.second == -1) {
552 clear();
553 } else if (result.second == m_array.size()) {
554 m_array.reserve(m_array.size() + result.second);
555 for (auto it = result.first.begin(); it != result.first.end(); it++) {
556 append(QSharedPointer<T>(*it));
557 }
558 } else if (result.second < m_array.size()){
559 removeUntilEnd(result.second);
560 m_array.reserve(m_array.size() + result.second);
561 for (auto it = result.first.begin(); it != result.first.end(); it++) {
562 append(QSharedPointer<T>(*it));
563 }
564 } else {
565 // result.second > m_array.size()
566 qWarning() << "Retrieved data from loader at position bigger than size()";
567 }
568 }
569private:
570 QMetaObject::Connection m_futureWatcherConnection;
571};
572
573}
574#endif //JELLYFIN_API_MODEL
An Api client for Jellyfin. Handles requests and authentication.
Definition apiclient.h:90
void removeAt(int index)
Definition apimodel.h:473
virtual bool canFetchMore(const QModelIndex &parent) const override
Definition apimodel.h:508
void clear() override
Definition apimodel.h:495
void removeUntilEnd(int from)
Definition apimodel.h:480
QList< QSharedPointer< T > > m_array
Definition apimodel.h:545
void loadingFinished() override
Definition apimodel.h:548
void setLoader(BaseModelLoader *newLoader)
Definition apimodel.h:524
ApiModel(QObject *parent=nullptr)
Creates a new basemodel.
Definition apimodel.h:434
int rowCount(const QModelIndex &index) const override
Definition apimodel.h:439
void append(QList< QSharedPointer< T > > &objects)
Definition apimodel.h:462
void removeOne(QSharedPointer< T > object)
Definition apimodel.h:488
QList< T * > mid(int pos, int length=-1)
Definition apimodel.h:469
void append(QSharedPointer< T > object)
Definition apimodel.h:460
int size() const
Definition apimodel.h:449
void insert(int index, QSharedPointer< T > object)
Definition apimodel.h:453
const QList< QSharedPointer< T > > & toList()
Definition apimodel.h:501
void reload() override
Definition apimodel.h:537
virtual void fetchMore(const QModelIndex &parent) override
Definition apimodel.h:513
BaseModelLoader * loader() const override
Definition apimodel.h:520
QSharedPointer< T > at(int index) const
Definition apimodel.h:445
ModelLoader< T > * m_loader
Definition apimodel.h:546
virtual void clear()=0
void disconnectOldLoader(BaseModelLoader *oldLoader)
Definition apimodel.h:380
virtual void reload()=0
Definition apimodel.cpp:93
virtual void loadingFinished()=0
BaseApiModel(QObject *parent=nullptr)
Definition apimodel.h:368
BaseModelLoader * loader
Definition apimodel.h:371
virtual void setLoader(BaseModelLoader *newLoader)
Definition apimodel.h:374
Definition apimodel.h:68
void modelShouldClear()
Emitted when the model should clear itself.
int limit() const
Definition apimodel.h:81
int m_totalRecordCount
Definition apimodel.h:125
int m_limit
Definition apimodel.h:123
void setLimit(int newLimit)
Definition apimodel.cpp:65
bool m_isBeingParsed
Definition apimodel.h:116
BaseModelLoader(QObject *parent=nullptr)
Definition apimodel.cpp:37
int limit
Definition apimodel.h:75
int m_startIndex
Definition apimodel.h:124
void limitChanged(int newLimit)
ApiClient * apiClient
Definition apimodel.h:73
virtual void classBegin() override
Definition apimodel.cpp:40
bool m_needsAuthentication
Definition apimodel.h:119
void setAutoReload(bool newAutoReload)
Definition apimodel.cpp:71
bool autoReload
Definition apimodel.h:76
ViewModel::ModelStatus status() const
Definition apimodel.h:86
const int DEFAULT_LIMIT
Definition apimodel.h:127
Jellyfin::ViewModel::ModelStatusClass::Value status
Definition apimodel.h:74
bool m_explicitLimitSet
Definition apimodel.h:126
void emitItemsLoaded()
Definition apimodel.h:129
void autoReloadIfNeeded()
Definition apimodel.cpp:49
virtual Q_INVOKABLE void reload()
Clears and reloads the model.
Definition apimodel.h:91
void apiClientChanged(ApiClient *newApiClient)
void emitModelShouldClear()
Definition apimodel.h:128
void autoReloadChanged(bool newAutoReload)
bool m_autoReload
Definition apimodel.h:118
ViewModel::ModelStatus m_status
Definition apimodel.h:131
virtual void componentComplete() override
Definition apimodel.cpp:44
void setApiClient(ApiClient *newApiClient)
Definition apimodel.cpp:56
void setStatus(ViewModel::ModelStatus newStatus)
Definition apimodel.h:132
bool m_manualLimitSet
Definition apimodel.h:120
virtual bool canReload() const
Determines if this model is able to reload.
Definition apimodel.cpp:82
ApiClient * m_apiClient
Definition apimodel.h:117
bool autoReload() const
Definition apimodel.h:83
void loaderError(QString error)
Definition apimodel.h:358
QScopedPointer< Support::Loader< QList< DTO::UserDto >, Jellyfin::Loader::GetPublicUsersParams > > m_loader
Definition apimodel.h:327
void loaderReady()
Definition apimodel.h:330
void loadMore(ViewModel::ModelStatus suggestedModelStatus) override
Loads data from the given offset with a maximum count of limit. The itemsLoaded() signal is emitted w...
Definition apimodel.h:290
LoaderModelLoader(Support::Loader< R, P > *loader, QObject *parent=nullptr)
Definition apimodel.h:284
Definition apimodel.h:158
virtual bool canLoadMore() const
Definition apimodel.h:181
virtual void loadMore(ViewModel::ModelStatus suggestedStatus)=0
Loads data from the given offset with a maximum count of limit. The itemsLoaded() signal is emitted w...
void reload() override
Clears and reloads the model.
Definition apimodel.h:164
void loadMore()
Definition apimodel.h:174
std::pair< QList< T * >, int > m_result
Definition apimodel.h:202
std::pair< QList< T * >, int > && result()
Holds the result. Moves it result to the caller and therefore can be only called once when the itemsL...
Definition apimodel.h:191
ModelLoader(QObject *parent=nullptr)
Definition apimodel.h:160
void error(QString message=QString())
Emitted when an error has occurred during loading and no result is available.
void ready()
Emitted when data was successfully loaded.
Definition loader.h:134
Definition mediaplayer2.h:20
Definition activitylog.h:45
Definition accessschedule.h:128
Contains all types exposed to QML.
ModelStatusClass::Value ModelStatus
Definition modelstatus.h:44
QList< T > extractRecords(const R &result)
Definition apimodel.h:209
void setRequestLimit(P &parameters, int limit)
Definition apimodel.h:222
int extractTotalRecordCount(const R &result)
Definition apimodel.h:216
bool setRequestStartIndex(P &parameters, int startIndex)
Definition apimodel.h:232
void writeRequestTypesFile R(File headerFile, File implementationFile, R endpoints) if(is(ElementType!R
Definition openapigenerator.d:279
Q_DECLARE_LOGGING_CATEGORY(jellyfinWebSocket)