1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-22 01:05:17 +00:00

Initial step towards the QObject rewrite

This commit is contained in:
Chris Josten 2020-10-04 23:03:58 +02:00
parent 7c7d2ba195
commit 4e3395c4e5
13 changed files with 498 additions and 29 deletions

View file

@ -14,7 +14,8 @@ TARGET = harbour-sailfin
QT += multimedia websockets QT += multimedia websockets
CONFIG += sailfishapp c++11 CONFIG += sailfishapp # c++17
QMAKE_CXXFLAGS += -std=c++17
# Help, something keeps eating my quotes and backslashes # Help, something keeps eating my quotes and backslashes
@ -30,6 +31,7 @@ SOURCES += \
src/jellyfinapiclient.cpp \ src/jellyfinapiclient.cpp \
src/jellyfinapimodel.cpp \ src/jellyfinapimodel.cpp \
src/jellyfindeviceprofile.cpp \ src/jellyfindeviceprofile.cpp \
src/jellyfinitem.cpp \
src/jellyfinmediasource.cpp \ src/jellyfinmediasource.cpp \
src/jellyfinwebsocket.cpp \ src/jellyfinwebsocket.cpp \
src/serverdiscoverymodel.cpp src/serverdiscoverymodel.cpp
@ -90,6 +92,7 @@ HEADERS += \
src/jellyfinapiclient.h \ src/jellyfinapiclient.h \
src/jellyfinapimodel.h \ src/jellyfinapimodel.h \
src/jellyfindeviceprofile.h \ src/jellyfindeviceprofile.h \
src/jellyfinitem.h \
src/jellyfinmediasource.h \ src/jellyfinmediasource.h \
src/jellyfinwebsocket.h \ src/jellyfinwebsocket.h \
src/serverdiscoverymodel.h src/serverdiscoverymodel.h

View file

@ -46,8 +46,8 @@ function ticksToText(ticks) {
} }
function itemImageUrl(baseUrl, item, type, options) { function itemImageUrl(baseUrl, item, type, options) {
if (!item.ImageTags[type]) { return "" } if (!item.imageTags[type]) { return "" }
return itemModelImageUrl(baseUrl, item.Id, item.ImageTags[type], type, options) return itemModelImageUrl(baseUrl, item.jellyfinId, item.imageTags[type], type, options)
} }
function itemModelImageUrl(baseUrl, itemId, tag, type, options) { function itemModelImageUrl(baseUrl, itemId, tag, type, options) {

View file

@ -31,9 +31,11 @@ import "../../components"
*/ */
Page { Page {
id: pageRoot id: pageRoot
property string itemId: "" property alias itemId: jItem.jellyfinId
property var itemData: ({}) property alias itemData: jItem
property bool _loading: true //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 bool hasLogo: (typeof itemData.ImageTags !== "undefined") && (typeof itemData.ImageTags["Logo"] !== "undefined")
readonly property var _backdropImages: itemData.BackdropImageTags readonly property var _backdropImages: itemData.BackdropImageTags
readonly property var _parentBackdropImages: itemData.ParentBackdropImageTags readonly property var _parentBackdropImages: itemData.ParentBackdropImageTags
@ -66,11 +68,11 @@ Page {
running: pageRoot._loading running: pageRoot._loading
} }
onItemIdChanged: { JellyfinItem {
itemData = {} id: jItem
if (itemId.length && PageStatus.Active) { apiClient: ApiClient
pageRoot._loading = true onStatusChanged: {
ApiClient.fetchItem(itemId) console.log("Status changed: " + newStatus, JSON.stringify(jItem))
} }
} }
@ -81,7 +83,7 @@ Page {
} }
if (status == PageStatus.Active) { if (status == PageStatus.Active) {
if (itemId) { if (itemId) {
ApiClient.fetchItem(itemId) //ApiClient.fetchItem(itemId)
} }
} }
@ -92,7 +94,7 @@ Page {
onItemFetched: { onItemFetched: {
if (itemId === pageRoot.itemId) { if (itemId === pageRoot.itemId) {
//console.log(JSON.stringify(result)) //console.log(JSON.stringify(result))
pageRoot.itemData = result //pageRoot.itemData = result
pageRoot._loading = false pageRoot._loading = false
if (status == PageStatus.Active) { if (status == PageStatus.Active) {
if (itemData.Type === "CollectionFolder") { if (itemData.Type === "CollectionFolder") {

View file

@ -26,12 +26,12 @@ import "../../"
VideoPage { VideoPage {
subtitle: { subtitle: {
if (typeof itemData.IndexNumberEnd !== "undefined") { if (typeof itemData.indexNumberEnd !== "undefined") {
qsTr("Episode %1%2 | %3").arg(itemData.IndexNumber) qsTr("Episode %1%2 | %3").arg(itemData.indexNumber)
.arg(itemData.IndexNumberEnd) .arg(itemData.indexNumberEnd)
.arg(itemData.SeasonName) .arg(itemData.seasonName)
} else { } 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 { PlainLabel {
id: overviewText id: overviewText
text: itemData.Overview || qsTr("No overview available") text: itemData.overview || qsTr("No overview available")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryHighlightColor color: Theme.secondaryHighlightColor
} }

View file

@ -26,7 +26,7 @@ import "../../components"
import "../.." import "../.."
VideoPage { 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 { SectionHeader {
text: qsTr("Overview") text: qsTr("Overview")
@ -34,7 +34,7 @@ VideoPage {
PlainLabel { PlainLabel {
id: overviewText id: overviewText
text: itemData.Overview text: itemData.overview
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryHighlightColor color: Theme.secondaryHighlightColor
} }

View file

@ -35,7 +35,7 @@ BaseDetailPage {
PageHeader { PageHeader {
id: header id: header
title: itemData.Name title: itemData.name
visible: !hasLogo visible: !hasLogo
} }
@ -52,7 +52,7 @@ BaseDetailPage {
PlainLabel { PlainLabel {
id: overviewText id: overviewText
text: itemData.Overview text: itemData.overview
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryHighlightColor color: Theme.secondaryHighlightColor
} }
@ -65,7 +65,7 @@ BaseDetailPage {
ShowSeasonsModel { ShowSeasonsModel {
id: showSeasonsModel id: showSeasonsModel
apiClient: ApiClient apiClient: ApiClient
show: itemData.Id show: itemData.jellyfinId
} }
SilicaListView { SilicaListView {
@ -87,7 +87,7 @@ BaseDetailPage {
} }
} }
onItemDataChanged: { onItemDataChanged: {
showSeasonsModel.show = itemData.Id showSeasonsModel.show = itemData.jellyfinId
showSeasonsModel.reload() showSeasonsModel.reload()
} }
} }

View file

@ -24,12 +24,12 @@ BaseDetailPage {
SilicaFlickable { SilicaFlickable {
anchors.fill: parent anchors.fill: parent
PageHeader { PageHeader {
title: itemData.Name title: itemData.name
} }
ViewPlaceholder { ViewPlaceholder {
enabled: true 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 :)") hintText: qsTr("This is still an alpha version :)")
} }
} }

View file

@ -45,7 +45,7 @@ BaseDetailPage {
PageHeader { PageHeader {
id: pageHeader id: pageHeader
title: itemData.Name title: itemData.name
description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks)) description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks))
} }
@ -66,7 +66,7 @@ BaseDetailPage {
VideoTrackSelector { VideoTrackSelector {
id: trackSelector id: trackSelector
width: parent.width width: parent.width
tracks: itemData.MediaStreams tracks: itemData.mediaStreams
} }
} }
} }

View file

@ -23,3 +23,4 @@ git-change-log
# Use the subjects (first lines) of tag annotations when no entry would be # Use the subjects (first lines) of tag annotations when no entry would be
# included for a revision otherwise # included for a revision otherwise
#git-change-log --auto-add-annotations #git-change-log --auto-add-annotations
exit 0

View file

@ -30,6 +30,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "jellyfinapiclient.h" #include "jellyfinapiclient.h"
#include "jellyfinapimodel.h" #include "jellyfinapimodel.h"
#include "jellyfinitem.h"
#include "jellyfinmediasource.h" #include "jellyfinmediasource.h"
#include "serverdiscoverymodel.h" #include "serverdiscoverymodel.h"
@ -47,6 +48,7 @@ void registerQml() {
// API models // API models
Jellyfin::registerModels(QML_NAMESPACE); Jellyfin::registerModels(QML_NAMESPACE);
Jellyfin::registerSerializableJsonTypes(QML_NAMESPACE);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {

201
src/jellyfinitem.cpp Normal file
View 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
View 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

View file

@ -141,6 +141,13 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </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> <context>
<name>LegalPage</name> <name>LegalPage</name>
<message> <message>
@ -218,6 +225,10 @@
<source>Retry</source> <source>Retry</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Refresh</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SeasonPage</name> <name>SeasonPage</name>