spiegel-keyman/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift
Shawn Schantz 5cd8112532 feat(mac): add loading of kmp files
currently reads from the old data directory
UI is for testing only
2026-03-04 16:55:34 -05:00

168 lines
3.6 KiB
Swift

/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by Shawn Schantz on 2025-12-19
*
* Value object that describes a package as loaded from a .KMP file
*
*/
import Foundation
public struct PackageSource: Identifiable, Decodable, Hashable, Equatable {
public var id = UUID()
var directoryUrl: URL?
var jsonFileUrl: URL?
let system: SystemInfo?
let options: Options?
let info: Info
let files: [PackageFile]?
let keyboards: [KeyboardSource]?
// computed properties for convenience
var packageName: String {
return info.name.description
}
var packageVersion: String {
return info.version.description
}
var copyright: String? {
if let copy = info.copyright?.description {
return copy
} else {
return nil
}
}
var readmeFilename: String? {
if let filename = options?.readmeFile {
return filename
} else {
return nil
}
}
var graphicFilename: String? {
if let filename = options?.graphicFile {
return filename
} else {
return nil
}
}
enum CodingKeys: String, CodingKey {
case system
case options
case info
case files
case keyboards
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
directoryUrl = nil
jsonFileUrl = nil
self.info = try container.decode(Info.self, forKey: .info)
self.keyboards = try container.decodeIfPresent([KeyboardSource].self, forKey: .keyboards)
self.system = try container.decodeIfPresent(SystemInfo.self, forKey: .system)
self.options = try container.decodeIfPresent(Options.self, forKey: .options)
self.files = try container.decodeIfPresent([PackageFile].self, forKey: .files)
// Invalidation: must contain files or keyboards
if files == nil && keyboards == nil {
throw DecodingError.dataCorrupted(
.init(
codingPath: decoder.codingPath,
debugDescription: "KeymanPackage must contain either 'files' or 'keyboards'."
)
)
}
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id) // only combine the unique ID
}
// Custom Equatable conformance (required by Hashable)
public static func == (lhs: PackageSource, rhs: PackageSource) -> Bool {
return lhs.id == rhs.id // only compare unique IDs
}
}
struct Info: Decodable {
let name: DescribedValue
let version: DescribedValue
let copyright: DescribedValue?
let author: Author?
let website: Website?
enum CodingKeys: String, CodingKey {
case name
case copyright
case author
case version
case website
}
}
struct DescribedValue: Decodable {
let description: String
enum CodingKeys: String, CodingKey {
case description
}
}
struct Author: Decodable {
let description: String?
let url: String?
enum CodingKeys: String, CodingKey {
case description
case url
}
}
struct Website: Decodable {
let description: String?
let url: String?
enum CodingKeys: String, CodingKey {
case description
case url
}
}
struct SystemInfo: Decodable {
let keymanDeveloperVersion: String?
let fileVersion: String?
enum CodingKeys: String, CodingKey {
case keymanDeveloperVersion
case fileVersion
}
}
struct Options: Decodable {
let readmeFile: String?
let graphicFile: String?
enum CodingKeys: String, CodingKey {
case readmeFile
case graphicFile
}
}
struct PackageFile: Decodable {
let name: String?
let description: String?
enum CodingKeys: String, CodingKey {
case name
case description
}
}