2020-09-27 18:38:33 +00:00
|
|
|
/*
|
|
|
|
Sailfin: a Jellyfin client written using Qt
|
|
|
|
Copyright (C) 2020 Chris Josten
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with this library; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
#ifndef JELLYFIN_API_MODEL
|
|
|
|
#define JELLYFIN_API_MODEL
|
|
|
|
|
|
|
|
#include <QAbstractListModel>
|
|
|
|
#include <QFlags>
|
|
|
|
#include <QMetaEnum>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonObject>
|
2020-10-08 01:00:08 +00:00
|
|
|
#include <QtQml>
|
2020-09-15 14:53:13 +00:00
|
|
|
#include <QVariant>
|
|
|
|
|
|
|
|
#include "jellyfinapiclient.h"
|
2020-10-10 12:30:49 +00:00
|
|
|
#include "jsonhelper.h"
|
2020-09-15 14:53:13 +00:00
|
|
|
|
|
|
|
namespace Jellyfin {
|
2020-10-01 09:56:02 +00:00
|
|
|
class SortOptions : public QObject{
|
|
|
|
Q_OBJECT
|
2020-09-15 14:53:13 +00:00
|
|
|
public:
|
2020-10-01 09:56:02 +00:00
|
|
|
explicit SortOptions (QObject *parent = nullptr) : QObject(parent) {}
|
2020-09-15 14:53:13 +00:00
|
|
|
enum SortBy {
|
|
|
|
Album,
|
|
|
|
AlbumArtist,
|
|
|
|
Artist,
|
|
|
|
Budget,
|
|
|
|
CommunityRating,
|
|
|
|
CriticRating,
|
|
|
|
DateCreated,
|
|
|
|
DatePlayed,
|
|
|
|
PlayCount,
|
|
|
|
PremiereDate,
|
|
|
|
ProductionYear,
|
|
|
|
SortName,
|
|
|
|
Random,
|
|
|
|
Revenue,
|
|
|
|
Runtime
|
|
|
|
};
|
|
|
|
Q_ENUM(SortBy)
|
|
|
|
};
|
|
|
|
|
2020-10-01 09:56:02 +00:00
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
/**
|
|
|
|
* @brief Abstract model for displaying a REST JSON collection. Role names will be based on the fields encountered in the
|
|
|
|
* first record.
|
|
|
|
*
|
|
|
|
* To create a new model, extend this class and create an QObject-parent constructor.
|
|
|
|
* Call the right super constructor with the right values, depending which path should be queried and
|
|
|
|
* how the result should be interpreted.
|
|
|
|
*
|
|
|
|
* Register the model in QML and create an instance. Don't forget to set the apiClient attribute or else
|
|
|
|
* the model you've created will be useless!
|
|
|
|
*
|
|
|
|
* Rolenames are based on the fields in the first object within the array of results, with the first letter
|
|
|
|
* lowercased, to accomodate for QML style guidelines. (This ain't C# here).
|
|
|
|
*
|
|
|
|
* If a call to /cats/new results in
|
|
|
|
* @code{.json}
|
|
|
|
* [
|
|
|
|
* {"Name": "meow", "Id": 432},
|
|
|
|
* {"Name": "miew", "Id": 323}
|
|
|
|
* ]
|
|
|
|
* @endcode
|
|
|
|
* The model will have roleNames for "name" and "id".
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
class ApiModel : public QAbstractListModel {
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
enum ModelStatus {
|
|
|
|
Uninitialised,
|
|
|
|
Loading,
|
|
|
|
Ready,
|
2020-09-27 01:14:05 +00:00
|
|
|
Error,
|
|
|
|
LoadingMore
|
2020-09-15 14:53:13 +00:00
|
|
|
};
|
|
|
|
Q_ENUM(ModelStatus)
|
|
|
|
|
2020-10-01 09:56:02 +00:00
|
|
|
enum SortOrder {
|
|
|
|
Unspecified,
|
|
|
|
Ascending,
|
|
|
|
Descending
|
|
|
|
};
|
|
|
|
Q_ENUM(SortOrder)
|
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
/**
|
|
|
|
* @brief Creates a new basemodel
|
|
|
|
* @param path The path (relative to the baseUrl of JellyfinApiClient) to make the call to.
|
|
|
|
* @param subfield Leave empty if the root of the result is the array with results. Otherwise, set to the key name in the
|
|
|
|
* root object which contains the data.
|
|
|
|
* @param parent Parent (Standard QObject stuff)
|
|
|
|
*
|
|
|
|
* If the response looks something like this:
|
|
|
|
* @code{.json}
|
|
|
|
* [{...}, {...}, {...}]
|
|
|
|
* @endcode
|
2020-09-27 01:14:05 +00:00
|
|
|
*
|
|
|
|
* or
|
|
|
|
* @code{.json}
|
|
|
|
* {...}
|
|
|
|
* @endcode
|
|
|
|
* responseHasRecords should be false
|
2020-09-15 14:53:13 +00:00
|
|
|
*
|
|
|
|
* If the response looks something like this:
|
|
|
|
* @code{.json}
|
|
|
|
* {
|
2020-09-27 01:14:05 +00:00
|
|
|
* "Offset": 0,
|
|
|
|
* "Count": 20,
|
|
|
|
* "Items": [{...}, {...}, {...}, ..., {...}]
|
2020-09-15 14:53:13 +00:00
|
|
|
* }
|
|
|
|
* @endcode
|
2020-09-27 01:14:05 +00:00
|
|
|
* responseHasRecords should be true
|
2020-09-15 14:53:13 +00:00
|
|
|
*/
|
2020-09-27 01:14:05 +00:00
|
|
|
explicit ApiModel(QString path, bool responseHasRecords, bool passUserId = false, QObject *parent = nullptr);
|
2020-10-09 00:33:08 +00:00
|
|
|
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient NOTIFY apiClientChanged)
|
2020-09-15 14:53:13 +00:00
|
|
|
Q_PROPERTY(ModelStatus status READ status NOTIFY statusChanged)
|
2020-09-26 00:51:37 +00:00
|
|
|
|
|
|
|
// Query properties
|
2020-09-15 14:53:13 +00:00
|
|
|
Q_PROPERTY(int limit MEMBER m_limit NOTIFY limitChanged)
|
|
|
|
Q_PROPERTY(QString parentId MEMBER m_parentId NOTIFY parentIdChanged)
|
2020-09-26 00:51:37 +00:00
|
|
|
Q_PROPERTY(QList<QString> sortBy MEMBER m_sortBy NOTIFY sortByChanged)
|
|
|
|
Q_PROPERTY(QList<QString> fields MEMBER m_fields NOTIFY fieldsChanged)
|
|
|
|
Q_PROPERTY(QString seasonId MEMBER m_seasonId NOTIFY seasonIdChanged)
|
|
|
|
Q_PROPERTY(QList<QString> imageTypes MEMBER m_imageTypes NOTIFY imageTypesChanged)
|
2020-10-26 21:29:07 +00:00
|
|
|
Q_PROPERTY(QList<QString> includeItemTypes MEMBER m_includeItemTypes NOTIFY includeItemTypesChanged)
|
2020-09-26 00:51:37 +00:00
|
|
|
Q_PROPERTY(bool recursive MEMBER m_recursive)
|
2020-10-01 09:56:02 +00:00
|
|
|
Q_PROPERTY(SortOrder sortOrder MEMBER m_sortOrder NOTIFY sortOrderChanged)
|
2021-01-17 16:08:07 +00:00
|
|
|
Q_PROPERTY(QString searchTerm MEMBER m_searchTerm WRITE setSearchTerm NOTIFY searchTermChanged)
|
2020-09-26 00:51:37 +00:00
|
|
|
|
|
|
|
// Path properties
|
|
|
|
Q_PROPERTY(QString show MEMBER m_show NOTIFY showChanged)
|
2020-09-15 14:53:13 +00:00
|
|
|
|
2020-09-27 01:14:05 +00:00
|
|
|
// Standard QAbstractItemModel overrides
|
2020-09-15 14:53:13 +00:00
|
|
|
int rowCount(const QModelIndex &index) const override {
|
|
|
|
if (!index.isValid()) return m_array.size();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
QHash<int, QByteArray> roleNames() const override { return m_roles; }
|
|
|
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
2020-09-27 01:14:05 +00:00
|
|
|
bool canFetchMore(const QModelIndex &parent) const override;
|
|
|
|
void fetchMore(const QModelIndex &parent) override;
|
2020-09-15 14:53:13 +00:00
|
|
|
|
2021-01-17 16:08:07 +00:00
|
|
|
virtual void setSearchTerm(QString newSearchTerm) {
|
|
|
|
this->m_searchTerm = newSearchTerm;
|
|
|
|
emit searchTermChanged(newSearchTerm);
|
|
|
|
reload();
|
|
|
|
}
|
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
ModelStatus status() const { return m_status; }
|
|
|
|
|
2020-09-27 01:14:05 +00:00
|
|
|
// Helper methods
|
2020-09-15 14:53:13 +00:00
|
|
|
template<typename QEnum>
|
|
|
|
QString enumToString (const QEnum anEnum) { return QVariant::fromValue(anEnum).toString(); }
|
|
|
|
|
|
|
|
template<typename QEnum>
|
|
|
|
QString enumListToString (const QList<QEnum> enumList) {
|
|
|
|
QString result;
|
|
|
|
for (QEnum e : enumList) {
|
|
|
|
result += QVariant::fromValue(e).toString() + ",";
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-09-27 01:14:05 +00:00
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
signals:
|
2020-10-09 00:33:08 +00:00
|
|
|
void apiClientChanged(ApiClient *newApiClient);
|
2020-09-15 14:53:13 +00:00
|
|
|
void statusChanged(ModelStatus newStatus);
|
|
|
|
void limitChanged(int newLimit);
|
|
|
|
void parentIdChanged(QString newParentId);
|
2020-09-26 00:51:37 +00:00
|
|
|
void sortByChanged(QList<QString> newSortOrder);
|
2020-10-01 09:56:02 +00:00
|
|
|
void sortOrderChanged(SortOrder newSortOrder);
|
2020-09-26 00:51:37 +00:00
|
|
|
void showChanged(QString newShow);
|
|
|
|
void seasonIdChanged(QString newSeasonId);
|
|
|
|
void fieldsChanged(QList<QString> newFields);
|
|
|
|
void imageTypesChanged(QList<QString> newImageTypes);
|
2020-10-26 21:29:07 +00:00
|
|
|
void includeItemTypesChanged(const QList<QString> &newIncludeItemTypes);
|
2021-01-17 16:08:07 +00:00
|
|
|
void searchTermChanged(QString newSearchTerm);
|
2020-09-15 14:53:13 +00:00
|
|
|
|
|
|
|
public slots:
|
|
|
|
/**
|
|
|
|
* @brief (Re)loads the data into this model. This might make a network request.
|
|
|
|
*/
|
|
|
|
void reload();
|
|
|
|
protected:
|
2020-09-27 01:14:05 +00:00
|
|
|
|
|
|
|
enum LoadType {
|
2021-01-17 16:08:07 +00:00
|
|
|
INITIAL_LOAD,
|
2020-09-27 01:14:05 +00:00
|
|
|
RELOAD,
|
|
|
|
LOAD_MORE
|
|
|
|
};
|
|
|
|
|
|
|
|
void load(LoadType loadType);
|
|
|
|
/**
|
|
|
|
* @brief Adds parameters to the query
|
|
|
|
* @param query The query to add parameters to
|
|
|
|
*
|
|
|
|
* This method is intended to be overrided by subclasses. It gets called
|
|
|
|
* before a request is made to the server and can be used to enable
|
|
|
|
* query types specific for a certain model to be available.
|
|
|
|
*/
|
|
|
|
virtual void addQueryParameters(QUrlQuery &query);
|
2021-01-17 16:08:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief setArray gets called when response data that replaces the current data has been received.
|
|
|
|
* The default implementation just resets the model, subclasses may do more advanced operations.
|
|
|
|
* @param newData The new data to store.
|
|
|
|
*/
|
|
|
|
virtual void setArray(const QJsonArray &newData);
|
|
|
|
|
2020-09-25 12:46:39 +00:00
|
|
|
ApiClient *m_apiClient = nullptr;
|
2020-09-15 14:53:13 +00:00
|
|
|
ModelStatus m_status = Uninitialised;
|
|
|
|
|
|
|
|
QString m_path;
|
|
|
|
QJsonArray m_array;
|
2020-09-27 01:14:05 +00:00
|
|
|
bool m_hasRecordResponse;
|
2020-09-15 14:53:13 +00:00
|
|
|
|
2020-09-26 00:51:37 +00:00
|
|
|
// Path properties
|
|
|
|
QString m_show;
|
|
|
|
|
2020-09-27 01:14:05 +00:00
|
|
|
// Query/record controlling properties
|
2020-09-15 14:53:13 +00:00
|
|
|
int m_limit = -1;
|
2020-09-27 01:14:05 +00:00
|
|
|
int m_startIndex = 0;
|
|
|
|
int m_totalRecordCount = 0;
|
|
|
|
const int DEFAULT_LIMIT = 100;
|
|
|
|
|
|
|
|
// Query properties
|
2020-09-26 00:51:37 +00:00
|
|
|
bool m_addUserId = false;
|
2021-01-17 16:08:07 +00:00
|
|
|
bool m_recursive = false;
|
2020-09-15 14:53:13 +00:00
|
|
|
QString m_parentId;
|
2020-09-26 00:51:37 +00:00
|
|
|
QString m_seasonId;
|
2020-10-26 21:29:07 +00:00
|
|
|
QList<QString> m_fields = {};
|
|
|
|
QList<QString> m_imageTypes = {};
|
2020-09-26 00:51:37 +00:00
|
|
|
QList<QString> m_sortBy = {};
|
2020-10-26 21:29:07 +00:00
|
|
|
QList<QString> m_includeItemTypes = {};
|
2020-10-01 09:56:02 +00:00
|
|
|
SortOrder m_sortOrder = Unspecified;
|
2021-01-17 16:08:07 +00:00
|
|
|
QString m_searchTerm;
|
2020-09-15 14:53:13 +00:00
|
|
|
|
|
|
|
QHash<int, QByteArray> m_roles;
|
|
|
|
|
|
|
|
void setStatus(ModelStatus newStatus) {
|
|
|
|
this->m_status = newStatus;
|
|
|
|
emit this->statusChanged(newStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/**
|
|
|
|
* @brief Generates roleNames based on the first record in m_array.
|
|
|
|
*/
|
2021-01-17 16:08:07 +00:00
|
|
|
void generateFields(const QJsonArray &newData);
|
2020-10-01 09:56:02 +00:00
|
|
|
QString sortByToString(SortOptions::SortBy sortBy);
|
2020-09-15 14:53:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief List of the public users on the server.
|
|
|
|
*/
|
|
|
|
class PublicUserModel : public ApiModel {
|
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit PublicUserModel (QObject *parent = nullptr);
|
2020-09-15 14:53:13 +00:00
|
|
|
};
|
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
/**
|
|
|
|
* @brief Base class for each model that works with items.
|
|
|
|
*
|
|
|
|
* Listens for updates in the library and updates the model accordingly.
|
|
|
|
*/
|
|
|
|
class ItemModel : public ApiModel {
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
explicit ItemModel (QString path, bool responseHasRecords, bool replaceUser, QObject *parent = nullptr);
|
|
|
|
public slots:
|
|
|
|
void onUserDataChanged(const QString &itemId, QSharedPointer<UserData> userData);
|
|
|
|
};
|
|
|
|
|
2021-01-17 16:08:07 +00:00
|
|
|
/**
|
|
|
|
* @brief Lists search items and provides a nice animation when search items change their position.
|
|
|
|
*/
|
|
|
|
class SearchModel : public ItemModel {
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
explicit SearchModel(QObject *parent = nullptr);
|
|
|
|
Q_PROPERTY(int searchTimeout MEMBER m_searchTimeout NOTIFY searchTimeoutChanged)
|
|
|
|
signals:
|
|
|
|
void searchTimeoutChanged(int newTimeout);
|
|
|
|
protected:
|
|
|
|
void setArray(const QJsonArray &newData) override;
|
|
|
|
void setSearchTerm(QString searchTerm) override;
|
|
|
|
/**
|
|
|
|
* @brief DIFF_SIZE amount of items to keep track of when the position changes.
|
|
|
|
*/
|
|
|
|
const static size_t DIFF_SIZE = 10;
|
|
|
|
// Store the ids of the
|
|
|
|
std::array<QString, DIFF_SIZE> m_diffTracker;
|
|
|
|
|
|
|
|
// Time in ms before we can search again
|
|
|
|
int m_searchTimeout = 500;
|
|
|
|
QTimer m_searchTimeoutTimer;
|
|
|
|
};
|
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
class UserViewModel : public ApiModel {
|
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit UserViewModel (QObject *parent = nullptr);
|
2020-09-15 14:53:13 +00:00
|
|
|
};
|
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
class UserItemModel : public ItemModel {
|
2020-09-15 14:53:13 +00:00
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit UserItemModel (QObject *parent = nullptr);
|
2020-09-15 14:53:13 +00:00
|
|
|
};
|
2020-09-27 14:54:45 +00:00
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
class UserItemResumeModel : public ItemModel {
|
2020-09-27 14:54:45 +00:00
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit UserItemResumeModel (QObject *parent = nullptr);
|
2020-09-27 14:54:45 +00:00
|
|
|
};
|
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
class UserItemLatestModel : public ItemModel {
|
2020-09-25 15:14:44 +00:00
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit UserItemLatestModel (QObject *parent = nullptr);
|
2020-09-26 00:51:37 +00:00
|
|
|
};
|
|
|
|
|
2020-10-10 14:26:08 +00:00
|
|
|
class ShowNextUpModel : public ItemModel {
|
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit ShowNextUpModel (QObject *parent = nullptr);
|
2020-10-10 14:26:08 +00:00
|
|
|
};
|
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
class ShowSeasonsModel : public ItemModel {
|
2020-09-26 00:51:37 +00:00
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit ShowSeasonsModel (QObject *parent = nullptr);
|
2020-09-26 00:51:37 +00:00
|
|
|
};
|
|
|
|
|
2020-10-09 00:33:08 +00:00
|
|
|
class ShowEpisodesModel : public ItemModel {
|
2020-09-26 00:51:37 +00:00
|
|
|
public:
|
2020-10-25 18:58:02 +00:00
|
|
|
explicit ShowEpisodesModel (QObject *parent = nullptr);
|
2020-09-25 15:14:44 +00:00
|
|
|
};
|
|
|
|
|
2020-09-15 14:53:13 +00:00
|
|
|
|
|
|
|
void registerModels(const char *URI);
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif //JELLYFIN_API_MODEL
|