mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-11-25 02:15:17 +00:00
Initial step towards the QObject rewrite
This commit is contained in:
parent
7c7d2ba195
commit
4e3395c4e5
|
@ -14,7 +14,8 @@ TARGET = harbour-sailfin
|
|||
|
||||
QT += multimedia websockets
|
||||
|
||||
CONFIG += sailfishapp c++11
|
||||
CONFIG += sailfishapp # c++17
|
||||
QMAKE_CXXFLAGS += -std=c++17
|
||||
|
||||
# Help, something keeps eating my quotes and backslashes
|
||||
|
||||
|
@ -30,6 +31,7 @@ SOURCES += \
|
|||
src/jellyfinapiclient.cpp \
|
||||
src/jellyfinapimodel.cpp \
|
||||
src/jellyfindeviceprofile.cpp \
|
||||
src/jellyfinitem.cpp \
|
||||
src/jellyfinmediasource.cpp \
|
||||
src/jellyfinwebsocket.cpp \
|
||||
src/serverdiscoverymodel.cpp
|
||||
|
@ -90,6 +92,7 @@ HEADERS += \
|
|||
src/jellyfinapiclient.h \
|
||||
src/jellyfinapimodel.h \
|
||||
src/jellyfindeviceprofile.h \
|
||||
src/jellyfinitem.h \
|
||||
src/jellyfinmediasource.h \
|
||||
src/jellyfinwebsocket.h \
|
||||
src/serverdiscoverymodel.h
|
||||
|
|
|
@ -46,8 +46,8 @@ function ticksToText(ticks) {
|
|||
}
|
||||
|
||||
function itemImageUrl(baseUrl, item, type, options) {
|
||||
if (!item.ImageTags[type]) { return "" }
|
||||
return itemModelImageUrl(baseUrl, item.Id, item.ImageTags[type], type, options)
|
||||
if (!item.imageTags[type]) { return "" }
|
||||
return itemModelImageUrl(baseUrl, item.jellyfinId, item.imageTags[type], type, options)
|
||||
}
|
||||
|
||||
function itemModelImageUrl(baseUrl, itemId, tag, type, options) {
|
||||
|
|
|
@ -31,9 +31,11 @@ import "../../components"
|
|||
*/
|
||||
Page {
|
||||
id: pageRoot
|
||||
property string itemId: ""
|
||||
property var itemData: ({})
|
||||
property bool _loading: true
|
||||
property alias itemId: jItem.jellyfinId
|
||||
property alias itemData: jItem
|
||||
//property string itemId: ""
|
||||
//property var itemData: ({})
|
||||
property bool _loading: jItem.status === "Loading"
|
||||
readonly property bool hasLogo: (typeof itemData.ImageTags !== "undefined") && (typeof itemData.ImageTags["Logo"] !== "undefined")
|
||||
readonly property var _backdropImages: itemData.BackdropImageTags
|
||||
readonly property var _parentBackdropImages: itemData.ParentBackdropImageTags
|
||||
|
@ -66,11 +68,11 @@ Page {
|
|||
running: pageRoot._loading
|
||||
}
|
||||
|
||||
onItemIdChanged: {
|
||||
itemData = {}
|
||||
if (itemId.length && PageStatus.Active) {
|
||||
pageRoot._loading = true
|
||||
ApiClient.fetchItem(itemId)
|
||||
JellyfinItem {
|
||||
id: jItem
|
||||
apiClient: ApiClient
|
||||
onStatusChanged: {
|
||||
console.log("Status changed: " + newStatus, JSON.stringify(jItem))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +83,7 @@ Page {
|
|||
}
|
||||
if (status == PageStatus.Active) {
|
||||
if (itemId) {
|
||||
ApiClient.fetchItem(itemId)
|
||||
//ApiClient.fetchItem(itemId)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -92,7 +94,7 @@ Page {
|
|||
onItemFetched: {
|
||||
if (itemId === pageRoot.itemId) {
|
||||
//console.log(JSON.stringify(result))
|
||||
pageRoot.itemData = result
|
||||
//pageRoot.itemData = result
|
||||
pageRoot._loading = false
|
||||
if (status == PageStatus.Active) {
|
||||
if (itemData.Type === "CollectionFolder") {
|
||||
|
|
|
@ -26,12 +26,12 @@ import "../../"
|
|||
|
||||
VideoPage {
|
||||
subtitle: {
|
||||
if (typeof itemData.IndexNumberEnd !== "undefined") {
|
||||
qsTr("Episode %1–%2 | %3").arg(itemData.IndexNumber)
|
||||
.arg(itemData.IndexNumberEnd)
|
||||
.arg(itemData.SeasonName)
|
||||
if (typeof itemData.indexNumberEnd !== "undefined") {
|
||||
qsTr("Episode %1–%2 | %3").arg(itemData.indexNumber)
|
||||
.arg(itemData.indexNumberEnd)
|
||||
.arg(itemData.seasonName)
|
||||
} else {
|
||||
qsTr("Episode %1 | %2").arg(itemData.IndexNumber).arg(itemData.SeasonName)
|
||||
qsTr("Episode %1 | %2").arg(itemData.indexNumber).arg(itemData.seasonName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ VideoPage {
|
|||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview || qsTr("No overview available")
|
||||
text: itemData.overview || qsTr("No overview available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import "../../components"
|
|||
import "../.."
|
||||
|
||||
VideoPage {
|
||||
subtitle: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks))
|
||||
subtitle: qsTr("Released: %1 — Run time: %2").arg(itemData.productionYear).arg(Utils.ticksToText(itemData.runTimeTicks))
|
||||
|
||||
SectionHeader {
|
||||
text: qsTr("Overview")
|
||||
|
@ -34,7 +34,7 @@ VideoPage {
|
|||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview
|
||||
text: itemData.overview
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ BaseDetailPage {
|
|||
|
||||
PageHeader {
|
||||
id: header
|
||||
title: itemData.Name
|
||||
title: itemData.name
|
||||
visible: !hasLogo
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ BaseDetailPage {
|
|||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview
|
||||
text: itemData.overview
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ BaseDetailPage {
|
|||
ShowSeasonsModel {
|
||||
id: showSeasonsModel
|
||||
apiClient: ApiClient
|
||||
show: itemData.Id
|
||||
show: itemData.jellyfinId
|
||||
}
|
||||
|
||||
SilicaListView {
|
||||
|
@ -87,7 +87,7 @@ BaseDetailPage {
|
|||
}
|
||||
}
|
||||
onItemDataChanged: {
|
||||
showSeasonsModel.show = itemData.Id
|
||||
showSeasonsModel.show = itemData.jellyfinId
|
||||
showSeasonsModel.reload()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ BaseDetailPage {
|
|||
SilicaFlickable {
|
||||
anchors.fill: parent
|
||||
PageHeader {
|
||||
title: itemData.Name
|
||||
title: itemData.name
|
||||
}
|
||||
ViewPlaceholder {
|
||||
|
||||
enabled: true
|
||||
text: qsTr("Item type (%1) unsupported").arg(itemData.Type)
|
||||
text: qsTr("Item type (%1) unsupported").arg(itemData.type)
|
||||
hintText: qsTr("This is still an alpha version :)")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ BaseDetailPage {
|
|||
|
||||
PageHeader {
|
||||
id: pageHeader
|
||||
title: itemData.Name
|
||||
title: itemData.name
|
||||
description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks))
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ BaseDetailPage {
|
|||
VideoTrackSelector {
|
||||
id: trackSelector
|
||||
width: parent.width
|
||||
tracks: itemData.MediaStreams
|
||||
tracks: itemData.mediaStreams
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,3 +23,4 @@ git-change-log
|
|||
# Use the subjects (first lines) of tag annotations when no entry would be
|
||||
# included for a revision otherwise
|
||||
#git-change-log --auto-add-annotations
|
||||
exit 0
|
||||
|
|
|
@ -30,6 +30,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
#include "jellyfinapiclient.h"
|
||||
#include "jellyfinapimodel.h"
|
||||
#include "jellyfinitem.h"
|
||||
#include "jellyfinmediasource.h"
|
||||
#include "serverdiscoverymodel.h"
|
||||
|
||||
|
@ -47,6 +48,7 @@ void registerQml() {
|
|||
|
||||
// API models
|
||||
Jellyfin::registerModels(QML_NAMESPACE);
|
||||
Jellyfin::registerSerializableJsonTypes(QML_NAMESPACE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
|
201
src/jellyfinitem.cpp
Normal file
201
src/jellyfinitem.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#include "jellyfinitem.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
JsonSerializable::JsonSerializable(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void JsonSerializable::deserialize(const QJsonObject &jObj) {
|
||||
const QMetaObject *obj = this->metaObject();
|
||||
|
||||
// Loop over each property,
|
||||
for (int i = 0; i < obj->propertyCount(); i++) {
|
||||
QMetaProperty prop = obj->property(i);
|
||||
// Skip properties which are not stored (usually derrived of other properties)
|
||||
if (!prop.isStored()) continue;
|
||||
if (!prop.isWritable()) continue;
|
||||
|
||||
qDebug() << toPascalCase(prop.name());
|
||||
// Hardcoded exception for the property id, since its special inside QML
|
||||
if (QString(prop.name()) == "jellyfinId" && jObj.contains("Id")) {
|
||||
QJsonValue val = jObj["Id"];
|
||||
prop.write(this, jsonToVariant(prop, val, jObj));
|
||||
} else if (jObj.contains(toPascalCase(prop.name()))) {
|
||||
QJsonValue val = jObj[toPascalCase(prop.name())];
|
||||
prop.write(this, jsonToVariant(prop, val, jObj));
|
||||
} else {
|
||||
qDebug() << "Ignored " << prop.name() << " while deserializing";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant JsonSerializable::jsonToVariant(QMetaProperty prop, const QJsonValue &val, const QJsonObject &root) const {
|
||||
switch(val.type()) {
|
||||
case QJsonValue::Null:
|
||||
case QJsonValue::Undefined:
|
||||
return QVariant();
|
||||
case QJsonValue::Bool:
|
||||
case QJsonValue::Double:
|
||||
case QJsonValue::String:
|
||||
return val.toVariant();
|
||||
case QJsonValue::Array:
|
||||
if (prop.type() == QVariant::List) {
|
||||
QJsonArray arr = val.toArray();
|
||||
QVariantList varArr;
|
||||
for (auto it = arr.begin(); it < arr.end(); it++) {
|
||||
varArr << jsonToVariant(prop, *it, root);
|
||||
}
|
||||
return QVariant(varArr);
|
||||
} else {
|
||||
qDebug() << prop.name() << " is not a " << prop.typeName();
|
||||
return QVariant();
|
||||
}
|
||||
case QJsonValue::Object:
|
||||
QJsonObject innerObj = val.toObject();
|
||||
QObject *deserializedInnerObj = QMetaType::metaObjectForType(prop.userType())->newInstance();
|
||||
if (JsonSerializable *ser = dynamic_cast<JsonSerializable *>(deserializedInnerObj)) {
|
||||
qDebug() << "Deserializing user type " << deserializedInnerObj->metaObject()->className();
|
||||
ser->deserialize(innerObj);
|
||||
return QVariant::fromValue(ser);
|
||||
} else {
|
||||
qDebug() << "Object is not a serializable one!";
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QJsonObject JsonSerializable::serialize() const {
|
||||
QJsonObject result;
|
||||
const QMetaObject *obj = this->metaObject();
|
||||
for (int i = 0; i < obj->propertyCount(); i++) {
|
||||
QMetaProperty prop = obj->property(i);
|
||||
if (QString(prop.name()) == "jellyfinId") {
|
||||
result["Id"] = variantToJson(prop.read(this));
|
||||
} else {
|
||||
result[toPascalCase(prop.name())] = variantToJson(prop.read(this));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonValue JsonSerializable::variantToJson(const QVariant var) const {
|
||||
switch(var.type()) {
|
||||
case QVariant::Invalid:
|
||||
return QJsonValue();
|
||||
case QVariant::UserType:
|
||||
if (var.canConvert<JsonSerializable *>()) {
|
||||
JsonSerializable * obj = var.value<JsonSerializable *>();
|
||||
return obj->serialize();
|
||||
} else {
|
||||
qWarning() << "Not serializable: " << var.typeName();
|
||||
return QJsonValue();
|
||||
}
|
||||
case QVariant::Bool:
|
||||
return var.toBool();
|
||||
case QVariant::List:
|
||||
{
|
||||
QVariantList list = var.toList();
|
||||
QJsonArray arr;
|
||||
for (auto it = list.begin(); it < list.end(); it++) {
|
||||
arr << variantToJson(*it);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
default:
|
||||
if (var.canConvert(QVariant::Double)) {
|
||||
return var.toDouble();
|
||||
} if (var.canConvert(QVariant::String)) {
|
||||
return var.toString();
|
||||
} else {
|
||||
return QJsonValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString JsonSerializable::toPascalCase(QString str) {
|
||||
str[0] = str[0].toUpper();
|
||||
return str;
|
||||
}
|
||||
|
||||
QString JsonSerializable::fromPascalCase(QString str) {
|
||||
str[0] = str[0].toLower();
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
// RemoteData
|
||||
RemoteData::RemoteData(QObject *parent) : JsonSerializable (parent) {}
|
||||
|
||||
void RemoteData::setStatus(Status newStatus) {
|
||||
m_status = newStatus;
|
||||
emit statusChanged(newStatus);
|
||||
}
|
||||
|
||||
void RemoteData::setError(QNetworkReply::NetworkError error) {
|
||||
m_error = error;
|
||||
emit errorChanged(error);
|
||||
}
|
||||
|
||||
void RemoteData::setErrorString(const QString &newErrorString) {
|
||||
m_errorString = newErrorString;
|
||||
emit errorStringChanged(newErrorString);
|
||||
}
|
||||
|
||||
void RemoteData::setApiClient(ApiClient *newApiClient) {
|
||||
m_apiClient = newApiClient;
|
||||
emit apiClientChanged(newApiClient);
|
||||
reload();
|
||||
}
|
||||
|
||||
// Item
|
||||
|
||||
Item::Item(QObject *parent) : RemoteData(parent) {
|
||||
}
|
||||
|
||||
|
||||
void Item::setJellyfinId(QString newId) {
|
||||
m_id = newId.trimmed();
|
||||
if (m_id != newId) {
|
||||
emit jellyfinIdChanged(m_id);
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
void Item::reload() {
|
||||
if (m_id.isEmpty() || m_apiClient == nullptr) {
|
||||
setStatus(Uninitialised);
|
||||
return;
|
||||
} else {
|
||||
setStatus(Loading);
|
||||
}
|
||||
QNetworkReply *rep = m_apiClient->get("/Users/" + m_apiClient->userId() + "/Items/" + m_id);
|
||||
connect(rep, &QNetworkReply::finished, this, [this, rep]() {
|
||||
rep->deleteLater();
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(rep->readAll(), &error);
|
||||
if (doc.isNull()) {
|
||||
this->setError(QNetworkReply::ProtocolFailure);
|
||||
this->setErrorString(error.errorString());
|
||||
return;
|
||||
}
|
||||
if (!doc.isObject()) {
|
||||
this->setError(QNetworkReply::ProtocolFailure);
|
||||
this->setErrorString(tr("Invalid response from the server: root element is not an object."));
|
||||
return;
|
||||
}
|
||||
this->deserialize(doc.object());
|
||||
this->setStatus(Ready);
|
||||
});
|
||||
connect(rep, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
|
||||
this, [this, rep](QNetworkReply::NetworkError error) {
|
||||
rep->deleteLater();
|
||||
this->setError(error);
|
||||
this->setErrorString(rep->errorString());
|
||||
this->setStatus(Error);
|
||||
});
|
||||
}
|
||||
|
||||
void registerSerializableJsonTypes(const char* URI) {
|
||||
qmlRegisterType<Item>(URI, 1, 0, "JellyfinItem");
|
||||
}
|
||||
}
|
249
src/jellyfinitem.h
Normal file
249
src/jellyfinitem.h
Normal file
|
@ -0,0 +1,249 @@
|
|||
#ifndef JELLYFINITEM_H
|
||||
#define JELLYFINITEM_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QMetaProperty>
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <QtQml>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "jellyfinapiclient.h"
|
||||
|
||||
namespace Jellyfin {
|
||||
|
||||
/**
|
||||
* @brief Base class for a serializable object.
|
||||
*
|
||||
* This class will be (de)serialized based on its properties.
|
||||
* Note: it must have a constructor without arguments marked with Q_INVOKABLE
|
||||
*/
|
||||
class JsonSerializable : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE JsonSerializable(QObject *parent);
|
||||
/**
|
||||
* @brief Sets this objects properties based on obj.
|
||||
* @param obj The data to load into this object.
|
||||
*/
|
||||
void deserialize(const QJsonObject &obj);
|
||||
QJsonObject serialize() const;
|
||||
private:
|
||||
QVariant jsonToVariant(QMetaProperty prop, const QJsonValue &val, const QJsonObject &root) const;
|
||||
QJsonValue variantToJson(const QVariant var) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the first letter of the string to lower case (to make it camelCase).
|
||||
* @param str The string to modify
|
||||
* @return THe modified string
|
||||
*/
|
||||
static QString fromPascalCase(QString str);
|
||||
/**
|
||||
* @brief Sets the first letter of the string to uper case (to make it PascalCase).
|
||||
* @param str The string to modify
|
||||
* @return THe modified string
|
||||
*/
|
||||
static QString toPascalCase(QString str);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An "interface" for a remote data source
|
||||
*
|
||||
* This class is basically a base class for JSON data that can be fetched from over the network.
|
||||
* Subclasses should reimplement reload and call setStatus to update the QML part of the code
|
||||
* appropiatly.
|
||||
*/
|
||||
class RemoteData : public JsonSerializable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Status {
|
||||
/// The data is unitialized and not loading either.
|
||||
Uninitialised,
|
||||
/// The data is being loaded over the network
|
||||
Loading,
|
||||
/// The data is ready, the properties in this object are up to date.
|
||||
Ready,
|
||||
/// An error has occurred while loading the data. See error() for more details.
|
||||
Error
|
||||
};
|
||||
Q_ENUM(Status)
|
||||
|
||||
explicit RemoteData(QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient WRITE setApiClient NOTIFY apiClientChanged STORED false)
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged STORED false)
|
||||
Q_PROPERTY(QNetworkReply::NetworkError error READ error NOTIFY errorChanged STORED false)
|
||||
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged STORED false)
|
||||
|
||||
Status status() const { return m_status; }
|
||||
QNetworkReply::NetworkError error() const { return m_error; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
|
||||
void setApiClient(ApiClient *newApiClient);
|
||||
signals:
|
||||
void statusChanged(Status newStatus);
|
||||
void apiClientChanged(ApiClient *newApiClient);
|
||||
void errorChanged(QNetworkReply::NetworkError newError);
|
||||
void errorStringChanged(QString newErrorString);
|
||||
public slots:
|
||||
virtual void reload() = 0;
|
||||
protected:
|
||||
void setStatus(Status newStatus);
|
||||
void setError(QNetworkReply::NetworkError error);
|
||||
void setErrorString(const QString &newErrorString);
|
||||
ApiClient *m_apiClient = nullptr;
|
||||
private:
|
||||
Status m_status = Uninitialised;
|
||||
QNetworkReply::NetworkError m_error = QNetworkReply::NoError;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
class Item : public RemoteData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE explicit Item(QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString jellyfinId READ jellyfinId WRITE setJellyfinId NOTIFY jellyfinIdChanged)
|
||||
|
||||
// Based on https://github.com/jellyfin/jellyfin/blob/907695dec7fda152d0e17c1197637bc0e17c9928/MediaBrowser.Model/Dto/BaseItemDto.cs
|
||||
// I copy, pasted and replaced. I feel like a Go programmer implementing generic containers.
|
||||
// If this were D, I would've writed a compile-time C# parser to parse that source code at compile time, extract
|
||||
// the properties and generate a class based on that.
|
||||
// Doing that in C++ would be more difficult and I dislike qmake. Does it even support running programs at compile time?
|
||||
// But here I am, using ctrl-C++
|
||||
Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString originalTitle MEMBER m_originalTitle NOTIFY originalTitleChanged)
|
||||
Q_PROPERTY(QString serverId MEMBER m_serverId NOTIFY serverIdChanged)
|
||||
Q_PROPERTY(QString etag MEMBER m_etag NOTIFY etagChanged)
|
||||
Q_PROPERTY(QString sourceType MEMBER m_sourceType NOTIFY sourceTypeChanged)
|
||||
Q_PROPERTY(QString playlistItemId MEMBER m_playlistItemId NOTIFY playlistItemIdChanged)
|
||||
Q_PROPERTY(QDateTime dateCreated MEMBER m_dateCreated NOTIFY dateCreatedChanged)
|
||||
Q_PROPERTY(QDateTime dateLastMediaAdded MEMBER m_dateLastMediaAdded NOTIFY dateLastMediaAddedChanged)
|
||||
Q_PROPERTY(QString extraType MEMBER m_extraType NOTIFY extraTypeChanged)
|
||||
Q_PROPERTY(int airsBeforeSeasonNumber READ airsBeforeSeasonNumber WRITE setAirsBeforeSeasonNumber NOTIFY airsBeforeSeasonNumberChanged)
|
||||
Q_PROPERTY(int airsAfterSeasonNumber READ airsAfterSeasonNumber WRITE setAirsAfterSeasonNumber NOTIFY airsAfterSeasonNumberChanged)
|
||||
Q_PROPERTY(int airsBeforeEpisodeNumber READ airsBeforeEpisodeNumber WRITE setAirsBeforeEpisodeNumber NOTIFY airsBeforeEpisodeNumberChanged)
|
||||
Q_PROPERTY(bool canDelete READ canDelete WRITE setCanDelete NOTIFY canDeleteChanged)
|
||||
Q_PROPERTY(bool canDownload READ canDownload WRITE setCanDownload NOTIFY canDownloadChanged)
|
||||
Q_PROPERTY(bool hasSubtitles READ hasSubtitles WRITE setHasSubtitles NOTIFY hasSubtitlesChanged)
|
||||
Q_PROPERTY(QString preferredMetadataLanguage MEMBER m_preferredMetadataLanguage NOTIFY preferredMetadataLanguageChanged)
|
||||
Q_PROPERTY(QString preferredMetadataCountryCode MEMBER m_preferredMetadataCountryCode NOTIFY preferredMetadataCountryCodeChanged)
|
||||
Q_PROPERTY(bool supportsSync READ supportsSync WRITE setSupportsSync NOTIFY supportsSyncChanged)
|
||||
Q_PROPERTY(QString container MEMBER m_container NOTIFY containerChanged)
|
||||
Q_PROPERTY(QString sortName MEMBER m_sortName NOTIFY sortNameChanged)
|
||||
Q_PROPERTY(QString forcedSortName MEMBER m_forcedSortName NOTIFY forcedSortNameChanged)
|
||||
//SKIP: Video3DFormat
|
||||
Q_PROPERTY(QDateTime premiereData MEMBER m_premiereDate NOTIFY premiereDateChanged)
|
||||
//SKIP: ExternalUrls
|
||||
//SKIP: MediaSources
|
||||
|
||||
// Handpicked, important ones
|
||||
Q_PROPERTY(QString overview MEMBER m_overview NOTIFY overviewChanged)
|
||||
Q_PROPERTY(int productionYear READ productionYear WRITE setProductionYear NOTIFY productionYearChanged)
|
||||
Q_PROPERTY(int indexNumber READ indexNumber WRITE setProductionYear NOTIFY indexNumberChanged)
|
||||
|
||||
QString jellyfinId() const { return m_id; }
|
||||
void setJellyfinId(QString newId);
|
||||
|
||||
int airsBeforeSeasonNumber() const { return m_airsBeforeSeasonNumber.value_or(-1); }
|
||||
void setAirsBeforeSeasonNumber(int newAirsBeforeSeasonNumber) { m_airsBeforeSeasonNumber = newAirsBeforeSeasonNumber; emit airsBeforeSeasonNumberChanged(newAirsBeforeSeasonNumber); }
|
||||
int airsAfterSeasonNumber() const { return m_airsAfterSeasonNumber.value_or(-1); }
|
||||
void setAirsAfterSeasonNumber(int newAirsAfterSeasonNumber) { m_airsAfterSeasonNumber = newAirsAfterSeasonNumber; emit airsAfterSeasonNumberChanged(newAirsAfterSeasonNumber); }
|
||||
int airsBeforeEpisodeNumber() const { return m_airsBeforeEpisodeNumber.value_or(-1); }
|
||||
void setAirsBeforeEpisodeNumber(int newAirsBeforeEpisodeNumber) { m_airsBeforeEpisodeNumber = newAirsBeforeEpisodeNumber; emit airsBeforeEpisodeNumberChanged(newAirsBeforeEpisodeNumber); }
|
||||
|
||||
bool canDelete() const { return m_canDelete.value_or(false); }
|
||||
void setCanDelete(bool newCanDelete) { m_canDelete = newCanDelete; emit canDeleteChanged(newCanDelete); }
|
||||
bool canDownload() const { return m_canDownload.value_or(false); }
|
||||
void setCanDownload(bool newCanDownload) { m_canDownload = newCanDownload; emit canDownloadChanged(newCanDownload); }
|
||||
bool hasSubtitles() const { return m_hasSubtitles.value_or(false); }
|
||||
void setHasSubtitles(bool newHasSubtitles) { m_hasSubtitles = newHasSubtitles; emit hasSubtitlesChanged(newHasSubtitles); }
|
||||
bool supportsSync() const { return m_supportsSync.value_or(false); }
|
||||
void setSupportsSync(bool newSupportsSync) { m_supportsSync = newSupportsSync; emit supportsSyncChanged(newSupportsSync); }
|
||||
|
||||
// Handpicked, important ones
|
||||
int productionYear() const { return m_productionYear.value_or(-1); }
|
||||
void setProductionYear(int newProductionYear) { m_productionYear = newProductionYear; emit productionYearChanged(newProductionYear); }
|
||||
int indexNumber() const { return m_indexNumber.value_or(-1); }
|
||||
void setIndexNumber(int newIndexNumber) { m_indexNumber = newIndexNumber; emit indexNumberChanged(newIndexNumber); }
|
||||
|
||||
signals:
|
||||
void jellyfinIdChanged(const QString &newId);
|
||||
void nameChanged(const QString &newName);
|
||||
void originalTitleChanged(const QString &newOriginalTitle);
|
||||
void serverIdChanged(const QString &newServerId);
|
||||
void etagChanged(const QString &newEtag);
|
||||
void sourceTypeChanged(const QString &sourceType);
|
||||
void playlistItemIdChanged(const QString &playlistItemIdChanged);
|
||||
void dateCreatedChanged(QDateTime newDateCreatedChanged);
|
||||
void dateLastMediaAddedChanged(QDateTime newDateLastMediaAdded);
|
||||
void extraTypeChanged(const QString &newExtraType);
|
||||
void airsBeforeSeasonNumberChanged(int newAirsBeforeSeasonNumber);
|
||||
void airsAfterSeasonNumberChanged(int newAirsAfterSeasonNumber);
|
||||
void airsBeforeEpisodeNumberChanged(int newAirsAfterEpisodeNumber);
|
||||
bool canDeleteChanged(bool newCanDelete);
|
||||
void canDownloadChanged(bool newCanDownload);
|
||||
void hasSubtitlesChanged(bool newHasSubtitles);
|
||||
void preferredMetadataLanguageChanged(const QString &newPreferredMetadataLanguage);
|
||||
void preferredMetadataCountryCodeChanged(const QString &newPreferredMetadataCountryCode);
|
||||
void supportsSyncChanged(bool newSupportsSync);
|
||||
void containerChanged(const QString &newContainer);
|
||||
void sortNameChanged(const QString &newSortName);
|
||||
void forcedSortNameChanged(const QString &newForcedSortName);
|
||||
void premiereDateChanged(QDateTime newPremiereDate);
|
||||
|
||||
// Handpicked, important ones
|
||||
void overviewChanged(const QString &newOverview);
|
||||
void productionYearChanged(int newProductionYear);
|
||||
void indexNumberChanged(int newIndexNumber);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief (Re)loads the item from the Jellyfin server.
|
||||
*/
|
||||
void reload() override;
|
||||
protected:
|
||||
QString m_id;
|
||||
QString m_name;
|
||||
QString m_originalTitle;
|
||||
QString m_serverId;
|
||||
QString m_etag;
|
||||
QString m_sourceType;
|
||||
QString m_playlistItemId;
|
||||
QDateTime m_dateCreated;
|
||||
QDateTime m_dateLastMediaAdded;
|
||||
QString m_extraType;
|
||||
std::optional<int> m_airsBeforeSeasonNumber = std::nullopt;
|
||||
std::optional<int> m_airsAfterSeasonNumber = std::nullopt;
|
||||
std::optional<int> m_airsBeforeEpisodeNumber = std::nullopt;
|
||||
std::optional<bool> m_canDelete = std::nullopt;
|
||||
std::optional<bool> m_canDownload = std::nullopt;
|
||||
std::optional<bool> m_hasSubtitles = std::nullopt;
|
||||
QString m_preferredMetadataLanguage;
|
||||
QString m_preferredMetadataCountryCode;
|
||||
std::optional<bool> m_supportsSync = std::nullopt;
|
||||
QString m_container;
|
||||
QString m_sortName;
|
||||
QString m_forcedSortName;
|
||||
QDateTime m_premiereDate;
|
||||
|
||||
// Handpicked, important ones
|
||||
QString m_overview;
|
||||
std::optional<int> m_productionYear = std::nullopt;
|
||||
std::optional<int> m_indexNumber = std::nullopt;
|
||||
};
|
||||
|
||||
void registerSerializableJsonTypes(const char* URI);
|
||||
}
|
||||
|
||||
#endif // JELLYFINITEM_H
|
|
@ -141,6 +141,13 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Jellyfin::Item</name>
|
||||
<message>
|
||||
<source>Invalid response from the server: root element is not an object.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>LegalPage</name>
|
||||
<message>
|
||||
|
@ -218,6 +225,10 @@
|
|||
<source>Retry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SeasonPage</name>
|
||||
|
|
Loading…
Reference in a new issue