diff --git a/Package.resolved b/Package.resolved index 9284f72..abd3cf1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "fbb2ea6138c41c6f92d8bc2ca79baabda2b27ad45e05777925979fe74ac47b0f", "pins" : [ { "identity" : "openapikit", @@ -10,15 +9,6 @@ "version" : "3.3.0" } }, - { - "identity" : "operatingsystemversion", - "kind" : "remoteSourceControl", - "location" : "https://github.com/brightdigit/OperatingSystemVersion", - "state" : { - "revision" : "bd3c70eb38da109ec6be9958e80ee495125d3232", - "version" : "1.0.0-beta.1" - } - }, { "identity" : "swift-algorithms", "kind" : "remoteSourceControl", @@ -101,5 +91,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/Package.swift b/Package.swift index 3c00a3f..0922fbd 100644 --- a/Package.swift +++ b/Package.swift @@ -9,10 +9,6 @@ let package = Package( .library(name: "IPSWDownloads", targets: ["IPSWDownloads"]) ], dependencies: [ - .package( - url: "https://github.com/brightdigit/OperatingSystemVersion", - from: "1.0.0-beta.1" - ), .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.7.0" @@ -30,7 +26,6 @@ let package = Package( .target( name: "IPSWDownloads", dependencies: [ - .product(name: "OperatingSystemVersion", package: "OperatingSystemVersion"), .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession") ], diff --git a/Package@swift-6.swift b/Package@swift-6.swift index 090c957..50b6281 100644 --- a/Package@swift-6.swift +++ b/Package@swift-6.swift @@ -34,10 +34,6 @@ let package = Package( .library(name: "IPSWDownloads", targets: ["IPSWDownloads"]) ], dependencies: [ - .package( - url: "https://github.com/brightdigit/OperatingSystemVersion", - from: "1.0.0-beta.1" - ), .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.7.0" @@ -55,7 +51,6 @@ let package = Package( .target( name: "IPSWDownloads", dependencies: [ - .product(name: "OperatingSystemVersion", package: "OperatingSystemVersion"), .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession") ], diff --git a/Sources/IPSWDownloads/Board.swift b/Sources/IPSWDownloads/Board.swift index 6ebce60..aa5641e 100644 --- a/Sources/IPSWDownloads/Board.swift +++ b/Sources/IPSWDownloads/Board.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/Data.swift b/Sources/IPSWDownloads/Data.swift index 88e7d75..f03d914 100644 --- a/Sources/IPSWDownloads/Data.swift +++ b/Sources/IPSWDownloads/Data.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/Device.swift b/Sources/IPSWDownloads/Device.swift index 9d18ef7..981883e 100644 --- a/Sources/IPSWDownloads/Device.swift +++ b/Sources/IPSWDownloads/Device.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/Firmware.swift b/Sources/IPSWDownloads/Firmware.swift index 090665b..1a6de66 100644 --- a/Sources/IPSWDownloads/Firmware.swift +++ b/Sources/IPSWDownloads/Firmware.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -28,7 +28,6 @@ // public import Foundation -public import OperatingSystemVersion /// A struct representing firmware details of a device. public struct Firmware: Sendable, Codable, Hashable, Equatable { @@ -36,7 +35,7 @@ public struct Firmware: Sendable, Codable, Hashable, Equatable { public let identifier: String /// The version of the operating system associated with the firmware. - public let version: OperatingSystemVersion + public let version: SemVer /// The build ID of the firmware. public let buildid: String @@ -77,7 +76,7 @@ public struct Firmware: Sendable, Codable, Hashable, Equatable { /// - signed: A flag indicating whether the firmware is signed. public init( identifier: String, - version: OperatingSystemVersion, + version: SemVer, buildid: String, sha1sum: Data?, md5sum: Data?, @@ -109,7 +108,7 @@ extension Firmware { internal init(component: Components.Schemas.Firmware) throws { try self.init( identifier: component.identifier, - version: OperatingSystemVersion(string: component.version), + version: SemVer(string: component.version), buildid: component.buildid, sha1sum: Data(hexString: component.sha1sum, emptyIsNil: true), md5sum: Data(hexString: component.md5sum, emptyIsNil: true), diff --git a/Sources/IPSWDownloads/FirmwareType.swift b/Sources/IPSWDownloads/FirmwareType.swift index a77ed25..3a3edf8 100644 --- a/Sources/IPSWDownloads/FirmwareType.swift +++ b/Sources/IPSWDownloads/FirmwareType.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/IPSWDownloads.swift b/Sources/IPSWDownloads/IPSWDownloads.swift index 015de2a..def00f8 100644 --- a/Sources/IPSWDownloads/IPSWDownloads.swift +++ b/Sources/IPSWDownloads/IPSWDownloads.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/RuntimeError.swift b/Sources/IPSWDownloads/RuntimeError.swift index 9ac2b72..78a89ed 100644 --- a/Sources/IPSWDownloads/RuntimeError.swift +++ b/Sources/IPSWDownloads/RuntimeError.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Sources/IPSWDownloads/SemVer+Comparable.swift b/Sources/IPSWDownloads/SemVer+Comparable.swift new file mode 100644 index 0000000..643a21b --- /dev/null +++ b/Sources/IPSWDownloads/SemVer+Comparable.swift @@ -0,0 +1,75 @@ +// +// SemVer+Comparable.swift +// IPSWDownloads +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension SemVer: Comparable { + // swiftlint:disable:next function_body_length cyclomatic_complexity + public static func < (lhs: SemVer, rhs: SemVer) -> Bool { + if lhs.major != rhs.major { + return lhs.major < rhs.major + } + if lhs.minor != rhs.minor { + return lhs.minor < rhs.minor + } + if lhs.patch != rhs.patch { + return lhs.patch < rhs.patch + } + + // If everything else is equal, compare pre-release versions + switch (lhs.prerelease, rhs.prerelease) { + case (nil, nil): + return false + + case (nil, .some): + return false // Release version is greater than pre-release + case (.some, nil): + return true // Pre-release version is less than release + case let (lhsPre?, rhsPre?): + // Compare pre-release identifiers + let lhsComponents = lhsPre.split(separator: ".") + let rhsComponents = rhsPre.split(separator: ".") + + for (lhs, rhs) in zip(lhsComponents, rhsComponents) { + if let lNum = Int(lhs), let rNum = Int(rhs) { + if lNum != rNum { + return lNum < rNum + } + } else if Int(lhs) != nil { + return true // Numeric is less than non-numeric + } else if Int(rhs) != nil { + return false // Non-numeric is greater than numeric + } else { + if lhs != rhs { + return lhs.description < rhs.description + } + } + } + return lhsComponents.count < rhsComponents.count + } + } +} diff --git a/Sources/IPSWDownloads/SemVer+CustomStringConvertible.swift b/Sources/IPSWDownloads/SemVer+CustomStringConvertible.swift new file mode 100644 index 0000000..1557129 --- /dev/null +++ b/Sources/IPSWDownloads/SemVer+CustomStringConvertible.swift @@ -0,0 +1,41 @@ +// +// SemVer+CustomStringConvertible.swift +// IPSWDownloads +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension SemVer: CustomStringConvertible { + public var description: String { + var version = "\(major).\(minor).\(patch)" + if let prerelease { + version += "-\(prerelease)" + } + if let buildMetadata { + version += "+\(buildMetadata)" + } + return version + } +} diff --git a/Sources/IPSWDownloads/SemVer.swift b/Sources/IPSWDownloads/SemVer.swift new file mode 100644 index 0000000..724eeaa --- /dev/null +++ b/Sources/IPSWDownloads/SemVer.swift @@ -0,0 +1,175 @@ +// +// SemVer.swift +// IPSWDownloads +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +public struct SemVer: Hashable, Codable, Sendable { + private enum CodingKeys: String, CodingKey { + case major + case minor + case patch + case prerelease + case buildMetadata + } + + // MARK: - String Parsing + + public enum ParsingError: Error { + case invalidFormat + case invalidNumbers + } + + // MARK: - Encoding Configuration + + public enum EncodingFormat: String, Codable { + case string + case object + } + + // swiftlint:disable:next force_unwrapping line_length + public static let encodingFormatKey = CodingUserInfoKey(rawValue: "Semver.encodingFormat")! + + public let major: Int + public let minor: Int + public let patch: Int + public let prerelease: String? + public let buildMetadata: String? + + public init( + major: Int, + minor: Int, + patch: Int = 0, + prerelease: String? = nil, + buildMetadata: String? = nil + ) { + self.major = major + self.minor = minor + self.patch = patch + self.prerelease = prerelease + self.buildMetadata = buildMetadata + } + + // swiftlint:disable:next function_body_length + public init(string: String) throws { + let components = string.split(separator: "-", maxSplits: 1) + let versionCore = components[0] + let prereleaseAndBuild = components.count > 1 ? String(components[1]) : nil + + let numbers = versionCore.split(separator: ".") + guard numbers.count >= 2 else { + throw ParsingError.invalidFormat + } + + guard + let major = Int(numbers[0]), + let minor = Int(numbers[1]) else { + throw ParsingError.invalidNumbers + } + + let patch = numbers.count > 2 ? Int(numbers[2]) ?? 0 : 0 + + if let prereleaseAndBuild { + let parts = prereleaseAndBuild.split(separator: "+", maxSplits: 1) + prerelease = String(parts[0]) + buildMetadata = parts.count > 1 ? String(parts[1]) : nil + } else { + prerelease = nil + buildMetadata = nil + } + + self.major = major + self.minor = minor + self.patch = patch + } + + // MARK: - Codable + + private init(versionString: String, decoder: any Decoder) throws { + do { + try self.init(string: versionString) + return + } catch { + throw DecodingError.dataCorrupted( + .init( + codingPath: decoder.codingPath, + debugDescription: "Invalid version string format: \(error)" + ) + ) + } + } + + // swiftlint:disable:next function_body_length + private init(jsonDecoder decoder: any Decoder) throws { + let objectContainer = try decoder.container(keyedBy: CodingKeys.self) + let major = try objectContainer.decode(Int.self, forKey: .major) + let minor = try objectContainer.decode(Int.self, forKey: .minor) + let patch = try objectContainer.decodeIfPresent(Int.self, forKey: .patch) ?? 0 + let prerelease = try objectContainer.decodeIfPresent(String.self, forKey: .prerelease) + let buildMetadata = try objectContainer.decodeIfPresent( + String.self, + forKey: .buildMetadata + ) + self.init( + major: major, + minor: minor, + patch: patch, + prerelease: prerelease, + buildMetadata: buildMetadata + ) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + // Try to decode as string first + if let versionString = try? container.decode(String.self) { + try self.init(versionString: versionString, decoder: decoder) + } else { + try self.init(jsonDecoder: decoder) + } + } + + public func encode(to encoder: any Encoder) throws { + // Check encoding format and default to object if not specified + let format = encoder.userInfo[SemVer.encodingFormatKey] as? EncodingFormat ?? .object + if format == .string { + var container = encoder.singleValueContainer() + try container.encode(description) + } else { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(major, forKey: .major) + try container.encode(minor, forKey: .minor) + try container.encode(patch, forKey: .patch) + try container.encodeIfPresent(prerelease, forKey: .prerelease) + try container.encodeIfPresent(buildMetadata, forKey: .buildMetadata) + } + } +} + +// MARK: - CustomStringConvertible + +// MARK: - Comparable diff --git a/Sources/IPSWDownloads/URL.swift b/Sources/IPSWDownloads/URL.swift index c0e8f4c..0115a88 100644 --- a/Sources/IPSWDownloads/URL.swift +++ b/Sources/IPSWDownloads/URL.swift @@ -3,7 +3,7 @@ // IPSWDownloads // // Created by Leo Dion. -// Copyright © 2024 BrightDigit. +// Copyright © 2025 BrightDigit. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation