Skip to content

Commit

Permalink
Non-Fixed Width Integers, Hashable, Inline Patching, and 10.8.9 (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
LePips authored Apr 12, 2023
1 parent 9f300ff commit 0878c23
Show file tree
Hide file tree
Showing 705 changed files with 3,617 additions and 3,370 deletions.
42 changes: 20 additions & 22 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
{
"object": {
"pins": [
{
"package": "Get",
"repositoryURL": "https://github.com/kean/Get",
"state": {
"branch": null,
"revision": "db5248ce985c703c5ea0030b7c4d3f908db304c9",
"version": "1.0.2"
}
},
{
"package": "URLQueryEncoder",
"repositoryURL": "https://github.com/CreateAPI/URLQueryEncoder",
"state": {
"branch": null,
"revision": "4cc975d4d075d0e62699a796a08284e217d4ee93",
"version": "0.2.0"
}
"pins" : [
{
"identity" : "get",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Get",
"state" : {
"revision" : "a7dd8e0233d4041330591445de21b7e5d4c953c6",
"version" : "1.0.4"
}
]
},
"version": 1
},
{
"identity" : "urlqueryencoder",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CreateAPI/URLQueryEncoder",
"state" : {
"revision" : "4cc975d4d075d0e62699a796a08284e217d4ee93",
"version" : "0.2.0"
}
}
],
"version" : 2
}
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
.library(name: "JellyfinAPI", targets: ["JellyfinAPI"]),
],
dependencies: [
.package(url: "https://github.com/kean/Get", from: "1.0.2"),
.package(url: "https://github.com/kean/Get", from: "1.0.4"),
.package(url: "https://github.com/CreateAPI/URLQueryEncoder", from: "0.2.0"),
],
targets: [
Expand Down
56 changes: 56 additions & 0 deletions Plugins/CreateAPI/AnyJSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// jellyfin-sdk-swift is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023 Jellyfin & Jellyfin Contributors
//

import Foundation

public enum AnyJSON: Hashable, Codable {
case string(String)
case number(Double)
case object([String: AnyJSON])
case array([AnyJSON])
case bool(Bool)
var value: Any {
switch self {
case let .string(string): return string
case let .number(double): return double
case let .object(dictionary): return dictionary
case let .array(array): return array
case let .bool(bool): return bool
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .array(array): try container.encode(array)
case let .object(object): try container.encode(object)
case let .string(string): try container.encode(string)
case let .number(number): try container.encode(number)
case let .bool(bool): try container.encode(bool)
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let object = try? container.decode([String: AnyJSON].self) {
self = .object(object)
} else if let array = try? container.decode([AnyJSON].self) {
self = .array(array)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let number = try? container.decode(Double.self) {
self = .number(number)
} else {
throw DecodingError.dataCorrupted(
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
)
}
}
}
189 changes: 178 additions & 11 deletions Plugins/CreateAPI/GeneratePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,196 @@
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
// Copyright (c) 2023 Jellyfin & Jellyfin Contributors
//

import Foundation
import PackagePlugin

@main
struct Plugin: CommandPlugin {

func performCommand(context: PluginContext, arguments: [String]) async throws {
let createAPICommand = try context.tool(named: "create-api")
let workingDirectory = context.package.directory.appending("Sources")

// Apply schema pre patches
try await patchBaseItemDtoSchema(context: context)

try await generate(context: context)

// Apply post patches
try await patchRemoteSearchResult(context: context)
try await patchAnyJSON(context: context)

// Move patch files
try await addSpecialFeatureType(context: context)

try await lint(context: context)

try await deletePatchedSchema(context: context)
}

private func runProcess(_ commandLine: String, context: PluginContext, workingDirectory: String? = nil) throws {

var arguments = commandLine.split(separator: " ").map { String($0) }
let commandName = arguments.removeFirst()

let command = try context.tool(named: commandName)

let actualWorkingDirectory: Path

if let workingDirectory {
actualWorkingDirectory = context.package.directory.appending(workingDirectory)
} else {
actualWorkingDirectory = context.package.directory
}

let process = Process()
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.string)
process.executableURL = URL(fileURLWithPath: createAPICommand.path.string)
process.arguments = [
"generate",
"jellyfin-openapi-stable.json",
"--config", "create-api-config.yaml",
"--output", ".",
]
process.currentDirectoryURL = URL(fileURLWithPath: actualWorkingDirectory.string)
process.executableURL = URL(fileURLWithPath: command.path.string)
process.arguments = arguments

try process.run()
process.waitUntilExit()
}

private func generate(context: PluginContext) async throws {
try runProcess(
"create-api generate jellyfin-openapi-stable-patched.json --config create-api-config.yaml --output .",
context: context,
workingDirectory: "Sources"
)
}

private func lint(context: PluginContext) async throws {
try runProcess(
"swiftformat .",
context: context
)
}

private func parseOriginalSchema(context: PluginContext) async throws -> AnyJSON {
let filePath = context
.package
.directory
.appending(["Sources", "jellyfin-openapi-stable.json"])

let decoder = JSONDecoder()
let data = Data(referencing: try NSData(contentsOfFile: filePath.string))
return try decoder.decode(AnyJSON.self, from: data)
}

private func savePatchedSchema(context: PluginContext, json: AnyJSON) async throws {
let filePath = context
.package
.directory
.appending(["Sources", "jellyfin-openapi-stable-patched.json"])

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
let data = try encoder.encode(json)

try data.write(to: URL(fileURLWithPath: filePath.string))
}

private func deletePatchedSchema(context: PluginContext) async throws {
let filePath = context
.package
.directory
.appending(["Sources", "jellyfin-openapi-stable-patched.json"])

try FileManager.default.removeItem(atPath: filePath.string)
}

// BaseItemDto: add SpecialFeatureType string format and CollectionTypeOptions
// object reference to properties prior to generation
private func patchBaseItemDtoSchema(context: PluginContext) async throws {
let contents = try await parseOriginalSchema(context: context)

guard case var .object(file) = contents else { return }
guard case var .object(components) = file["components"] else { return }
guard case var .object(schemas) = components["schemas"] else { return }
guard case var .object(baseItemDto) = schemas["BaseItemDto"] else { return }
guard case var .object(properties) = baseItemDto["properties"] else { return }

properties["ExtraType"] = AnyJSON.object(
[
"type": .string("string"),
"format": .string("SpecialFeatureType"),
"nullable": .bool(true),
]
)

// TODO: Uncomment once Swiftfin has refactored how it uses the existing CollectionType
// property for library management
// properties["CollectionType"] = AnyJSON.object(
// [
// "allOf": .array([.object(["$ref": .string("#/components/schemas/CollectionTypeOptions")])]),
// "nullable": .bool(true),
// "description": .string("Gets or sets the type of the collection."),
// ]
// )

baseItemDto["properties"] = .object(properties)
schemas["BaseItemDto"] = .object(baseItemDto)
components["schemas"] = .object(schemas)
file["components"] = .object(components)

try await savePatchedSchema(context: context, json: .object(file))
}

// Entities/RemoteSearchResult.swift: remove `Hashable`
private func patchRemoteSearchResult(context: PluginContext) async throws {
let filePath = context
.package
.directory
.appending(["Sources", "Entities", "RemoteSearchResult.swift"])

let contents = try String(contentsOfFile: filePath.string)
var lines = contents
.split(separator: "\n")
.map { String($0) }

lines[8] = "\npublic final class RemoteSearchResult: Codable {"

try lines
.joined(separator: "\n")
.data(using: .utf8)?
.write(to: URL(fileURLWithPath: filePath.string))
}

// Extensions/AnyJSON.swift: replace `Equatable` with `Hashable`
private func patchAnyJSON(context: PluginContext) async throws {
let filePath = context
.package
.directory
.appending(["Sources", "Extensions", "AnyJSON.swift"])

let contents = try String(contentsOfFile: filePath.string)
var lines = contents
.split(separator: "\n")
.map { String($0) }

lines[8] = "\npublic enum AnyJSON: Hashable, Codable {"

try lines
.joined(separator: "\n")
.data(using: .utf8)?
.write(to: URL(fileURLWithPath: filePath.string))
}

private func addSpecialFeatureType(context: PluginContext) async throws {
let sourceFilePath = context
.package
.directory
.appending(["Plugins", "CreateAPI", "PatchFiles", "SpecialFeatureType.swift"])

let sourceData = Data(referencing: try NSData(contentsOfFile: sourceFilePath.string))

let finalFilePath = context
.package
.directory
.appending(["Sources", "Entities", "SpecialFeatureType.swift"])

try sourceData.write(to: URL(fileURLWithPath: finalFilePath.string))
}
}
22 changes: 22 additions & 0 deletions Plugins/CreateAPI/PatchFiles/SpecialFeatureType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// jellyfin-sdk-swift is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023 Jellyfin & Jellyfin Contributors
//

import Foundation

public enum SpecialFeatureType: String, Codable, CaseIterable {
case unknown = "Unknown"
case clip = "Clip"
case trailer = "Trailer"
case behindTheScenes = "BehindTheScenes"
case deletedScene = "DeletedScene"
case interview = "Interview"
case scene = "Scene"
case sample = "Sample"
case themeSong = "ThemeSong"
case themeVideo = "ThemeVideo"
}
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ Swift SDK to work with Jellyfin servers.
## Usage

The provided `JellyfinClient` uses an underlying [Get APIClient](https://github.com/kean/Get) to provide basic functionality for interfacing with a Jellyfin server:
- inject required headers for every request
- encoding/decoding of all `Date` values to expected
`JellyfinClient` uses an underlying [Get APIClient](https://github.com/kean/Get) to provide basic functionality for interfacing with a Jellyfin server:
- inject required `Authorization` header for every request
- encoding/decoding of expected `Date` values
- `signIn` for generating a session access token
- `signOut` for revoking the current access token

```swift
// Create
// Create client instance
let jellyfinClient = JellyfinClient(configuration: configuration)

// Provided by JellyfinClient
// Sign in user with credentials
let response = jellyfinClient.signIn(username: "jelly", password: "fin")
```

You can forego `JellyfinClient` and use your own network stack, using the provided **Entities** and **Paths**.
Alternatively, you can use your own network stack with the generated **Entities** and **Paths**.

## Generation

Expand Down
Binary file modified Sources/.DS_Store
Binary file not shown.
Loading

0 comments on commit 0878c23

Please sign in to comment.