Skip to content

Commit 6039de8

Browse files
JPKribsLePips
andauthored
Add API Version to JellyfinClient (#45)
* JellyfinClient.sdkGeneratedVersion & JellyfinClient.sdkMinimumVersion * Move to generation * add JellyfinClient.Version * make Version properties public --------- Co-authored-by: Ethan Pippin <[email protected]>
1 parent 1062467 commit 6039de8

18 files changed

+289
-45
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ generate:
1616
.PHONY: update-unstable
1717
update-unstable: download-unstable generate
1818

19-
# Donload the latest Jellyfin unstable spec
19+
# Download the latest Jellyfin unstable spec
2020
.PHONY: download-unstable
2121
download-unstable:
22-
curl -fsSL https://repo.jellyfin.org/releases/openapi/jellyfin-openapi-unstable.json -o Sources/jellyfin-openapi-stable.json
22+
curl -fsSL https://repo.jellyfin.org/releases/openapi/jellyfin-openapi-unstable.json -o Sources/jellyfin-openapi-stable.json

Plugins/CreateAPI/GeneratePlugin.swift

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import Foundation
1010
import PackagePlugin
1111

12+
// Declared to build
13+
public struct JellyfinClient {}
14+
1215
@main
1316
struct Plugin: CommandPlugin {
1417

@@ -27,6 +30,9 @@ struct Plugin: CommandPlugin {
2730
// Move patch files
2831
try await addTaskTriggerType(context: context)
2932

33+
// Create the version SDK
34+
try await generateVersionFile(context: context)
35+
3036
try await lint(context: context)
3137

3238
try await deletePatchedSchema(context: context)
@@ -134,7 +140,7 @@ struct Plugin: CommandPlugin {
134140
.directory
135141
.appending(["Sources", "Entities", "RemoteSearchResult.swift"])
136142

137-
let contents = try String(contentsOfFile: filePath.string)
143+
let contents = try String(contentsOfFile: filePath.string, encoding: .utf8)
138144
var lines = contents
139145
.split(separator: "\n")
140146
.map { String($0) }
@@ -154,7 +160,7 @@ struct Plugin: CommandPlugin {
154160
.directory
155161
.appending(["Sources", "Extensions", "AnyJSON.swift"])
156162

157-
let contents = try String(contentsOfFile: filePath.string)
163+
let contents = try String(contentsOfFile: filePath.string, encoding: .utf8)
158164
var lines = contents
159165
.split(separator: "\n")
160166
.map { String($0) }
@@ -195,11 +201,39 @@ struct Plugin: CommandPlugin {
195201
.directory
196202
.appending(["Sources", "Entities", "GroupUpdate.swift"])
197203

198-
let contents = try String(contentsOfFile: filePath.string)
204+
let contents = try String(contentsOfFile: filePath.string, encoding: .utf8)
199205
.replacingOccurrences(of: "Type", with: "_Type")
200206

201207
try contents
202208
.data(using: .utf8)?
203209
.write(to: URL(fileURLWithPath: filePath.string))
204210
}
211+
212+
// TODO: Remove if/when fixed within CreateAPI
213+
// Adds JellyfinClient.sdkVersion
214+
private func generateVersionFile(context: PluginContext) async throws {
215+
let schema = try await parseOriginalSchema(context: context)
216+
217+
guard case let .object(file) = schema else { return }
218+
guard case let .object(info) = file["info"] else { return }
219+
guard let version = info["version"]?.value as? String else { return }
220+
221+
let sourceFilePath = context
222+
.package
223+
.directory
224+
.appending(["Plugins", "CreateAPI", "PatchFiles", "JellyfinClient+Version.swift"])
225+
226+
var contents = try String(contentsOfFile: sourceFilePath.string, encoding: .utf8)
227+
228+
contents.replace(#/<SDK_VERSION>/#, with: version)
229+
230+
let destinationFilePath = context
231+
.package
232+
.directory
233+
.appending(["Sources", "Extensions", "JellyfinClient+Version.swift"])
234+
235+
try contents
236+
.data(using: .utf8)?
237+
.write(to: URL(fileURLWithPath: destinationFilePath.string))
238+
}
205239
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// jellyfin-sdk-swift is subject to the terms of the Mozilla Public
3+
// License, v2.0. If a copy of the MPL was not distributed with this
4+
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
5+
//
6+
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
7+
//
8+
9+
public extension JellyfinClient {
10+
/// Version of Jellyfin used to generate the SDK
11+
static let sdkVersion: Version = "<SDK_VERSION>"
12+
}
13+
14+
public extension JellyfinClient {
15+
16+
struct Version: Comparable, CustomStringConvertible, ExpressibleByStringLiteral {
17+
18+
public let major: Int
19+
public let minor: Int
20+
public let patch: Int
21+
22+
public var description: String {
23+
"\(major).\(minor).\(patch)"
24+
}
25+
26+
public var majorMinor: Version {
27+
Version(major: major, minor: minor, patch: 0)
28+
}
29+
30+
public init(stringLiteral value: StringLiteralType) {
31+
let parsed = Version.parse(value: value)
32+
self.major = parsed.major
33+
self.minor = parsed.minor
34+
self.patch = parsed.patch
35+
}
36+
37+
public init(major: Int, minor: Int, patch: Int) {
38+
self.major = max(0, major)
39+
self.minor = max(0, minor)
40+
self.patch = max(0, patch)
41+
}
42+
43+
private static func parse(value: String) -> (major: Int, minor: Int, patch: Int) {
44+
let components = value.split(separator: ".")
45+
46+
guard components.count == 3 else {
47+
return (0, 0, 0)
48+
}
49+
50+
guard let major = Int(components[0]),
51+
let minor = Int(components[1]),
52+
let patch = Int(components[2])
53+
else {
54+
return (0, 0, 0)
55+
}
56+
57+
guard major >= 0 && minor >= 0 && patch >= 0 else {
58+
return (0, 0, 0)
59+
}
60+
61+
return (major, minor, patch)
62+
}
63+
64+
public static func == (lhs: Version, rhs: Version) -> Bool {
65+
lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch
66+
}
67+
68+
public static func < (lhs: Version, rhs: Version) -> Bool {
69+
if lhs.major != rhs.major {
70+
return lhs.major < rhs.major
71+
}
72+
if lhs.minor != rhs.minor {
73+
return lhs.minor < rhs.minor
74+
}
75+
return lhs.patch < rhs.patch
76+
}
77+
}
78+
}

Sources/Entities/BaseItemDto.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public struct BaseItemDto: Codable, Hashable, Identifiable {
154154
public var mediaSources: [MediaSourceInfo]?
155155
/// Gets or sets the media streams.
156156
public var mediaStreams: [MediaStream]?
157-
/// Media types.
157+
/// Gets or sets the type of the media.
158158
public var mediaType: MediaType?
159159
/// Gets or sets the movie count.
160160
public var movieCount: Int?
@@ -273,7 +273,7 @@ public struct BaseItemDto: Codable, Hashable, Identifiable {
273273
public var trailerCount: Int?
274274
/// Gets or sets the trickplay manifest.
275275
public var trickplay: [String: [String: TrickplayInfo]]?
276-
/// The base item kind.
276+
/// Gets or sets the type.
277277
public var type: BaseItemKind?
278278
/// Gets or sets the user data for this item based on the user it's being requested for.
279279
public var userData: UserItemDataDto?

Sources/Entities/BaseItemPerson.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct BaseItemPerson: Codable, Hashable, Identifiable {
2020
public var primaryImageTag: String?
2121
/// Gets or sets the role.
2222
public var role: String?
23-
/// The person kind.
23+
/// Gets or sets the type.
2424
public var type: PersonKind?
2525

2626
/// Gets or sets the primary image blurhash.

Sources/Entities/DisplayPreferencesDto.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ public struct DisplayPreferencesDto: Codable, Hashable, Identifiable {
2626
public var isRememberIndexing: Bool?
2727
/// Gets or sets a value indicating whether [remember sorting].
2828
public var isRememberSorting: Bool?
29-
/// An enum representing the axis that should be scrolled.
29+
/// Gets or sets the scroll direction.
3030
public var scrollDirection: ScrollDirection?
3131
/// Gets or sets a value indicating whether to show backdrops on this item.
3232
public var isShowBackdrop: Bool?
3333
/// Gets or sets a value indicating whether [show sidebar].
3434
public var isShowSidebar: Bool?
3535
/// Gets or sets the sort by.
3636
public var sortBy: String?
37-
/// An enum representing the sorting order.
37+
/// Gets or sets the sort order.
3838
public var sortOrder: SortOrder?
3939
/// Gets or sets the type of the view.
4040
public var viewType: String?

Sources/Entities/MediaSegmentDto.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public struct MediaSegmentDto: Codable, Hashable, Identifiable {
1818
public var itemID: String?
1919
/// Gets or sets the start of the segment.
2020
public var startTicks: Int?
21-
/// Defines the types of content an individual Jellyfin.Data.Entities.MediaSegment represents.
21+
/// Gets or sets the type of content this segment defines.
2222
public var type: MediaSegmentType?
2323

2424
public init(endTicks: Int? = nil, id: String? = nil, itemID: String? = nil, startTicks: Int? = nil, type: MediaSegmentType? = nil) {

Sources/Entities/MediaStream.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Foundation
1212
public struct MediaStream: Codable, Hashable {
1313
/// Gets or sets the aspect ratio.
1414
public var aspectRatio: String?
15-
/// An enum representing formats of spatial audio.
15+
/// Gets the audio spatial format.
1616
public var audioSpatialFormat: AudioSpatialFormat?
1717
/// Gets or sets the average frame rate.
1818
public var averageFrameRate: Float?
@@ -125,9 +125,9 @@ public struct MediaStream: Codable, Hashable {
125125
public var type: MediaStreamType?
126126
/// Gets the video dovi title.
127127
public var videoDoViTitle: String?
128-
/// An enum representing video ranges.
128+
/// Gets the video range.
129129
public var videoRange: VideoRange?
130-
/// An enum representing types of video ranges.
130+
/// Gets the video range type.
131131
public var videoRangeType: VideoRangeType?
132132
/// Gets or sets the width.
133133
public var width: Int?

Sources/Entities/OpenLiveStreamDto.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,20 @@ public struct OpenLiveStreamDto: Codable, Hashable {
1414
public var isAlwaysBurnInSubtitleWhenTranscoding: Bool?
1515
/// Gets or sets the audio stream index.
1616
public var audioStreamIndex: Int?
17-
/// Gets or sets the device profile.
17+
/// A MediaBrowser.Model.Dlna.DeviceProfile represents a set of metadata which determines which content a certain device is able to
18+
/// play.
19+
///
20+
/// <br />
21+
///
22+
/// Specifically, it defines the supported <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.ContainerProfiles">containers</see> and
23+
///
24+
/// <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.CodecProfiles">codecs</see> (video and/or audio, including codec profiles and
25+
/// levels)
26+
///
27+
/// the device is able to direct play (without transcoding or remuxing),
28+
///
29+
/// as well as which <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.TranscodingProfiles">containers/codecs to transcode to</see> in
30+
/// case it isn't.
1831
public var deviceProfile: DeviceProfile?
1932
/// Gets or sets the device play protocols.
2033
public var directPlayProtocols: [MediaProtocol]?

Sources/Entities/PlaybackInfoDto.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,20 @@ public struct PlaybackInfoDto: Codable, Hashable {
2020
public var audioStreamIndex: Int?
2121
/// Gets or sets a value indicating whether to auto open the live stream.
2222
public var isAutoOpenLiveStream: Bool?
23-
/// Gets or sets the device profile.
23+
/// A MediaBrowser.Model.Dlna.DeviceProfile represents a set of metadata which determines which content a certain device is able to
24+
/// play.
25+
///
26+
/// <br />
27+
///
28+
/// Specifically, it defines the supported <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.ContainerProfiles">containers</see> and
29+
///
30+
/// <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.CodecProfiles">codecs</see> (video and/or audio, including codec profiles and
31+
/// levels)
32+
///
33+
/// the device is able to direct play (without transcoding or remuxing),
34+
///
35+
/// as well as which <see cref="P:MediaBrowser.Model.Dlna.DeviceProfile.TranscodingProfiles">containers/codecs to transcode to</see> in
36+
/// case it isn't.
2437
public var deviceProfile: DeviceProfile?
2538
/// Gets or sets a value indicating whether to enable direct play.
2639
public var enableDirectPlay: Bool?

0 commit comments

Comments
 (0)