Files
Adobe-Downloader/Adobe Downloader/Utils/NewJSONParser.swift
X1a0He 83abcc7316 refactor: AboutView, AppCardView, VersionPickerView
1. AboutView: Subdivided some Views
2. AboutView: Added the display of system version number and specific chip model
3. AppCardView: Added the display of the minimum system version and module display
4. AppCardView: Optimized the display of the number of available versions and the number of dependent components
5. VersionPickerView: Added the product icon display in the Header part
6. VersionPickerView: Optimized the download architecture prompt
7. VersionPickerView: Optimized the display of product version, provided the display of productVersion and buildGuid
8. VersionPickerView: Optimized the display of dependent components, added the display of total number of dependencies and buildGuid
9. VersionPickerView: In DEBUG mode, you can check whether the dependent component hits the correct version, the selected version, and the reason for not hitting, and added the selected version icon
10. VersionPickerView: Adjusted the version display of dependent components, and no longer displayed baseVersion by default
11. VersionPickerView: Added optional module display and information
2025-03-06 01:40:01 +08:00

403 lines
20 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// NewJSONParser.swift
// Adobe Downloader
//
// Created by X1a0He on 2/26/25.
//
import Foundation
/**
v6: https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v6/products/all?channel=ccm&channel=sti&platform=macarm64,macuniversal,osx10-64,osx10&_type=json&productType=Desktop
v5: https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v5/products/all?channel=ccm&channel=sti&platform=macarm64,macuniversal,osx10-64,osx10&_type=json&productType=Desktop
v4: https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v4/products/all?channel=ccm&channel=sti&platform=macarm64,macuniversal,osx10-64,osx10&_type=json&productType=Desktop
*/
class NewJSONParser {
static func parse(jsonString: String) throws {
guard let jsonData = jsonString.data(using: .utf8),
let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
throw ParserError.invalidJSON
}
let apiVersion = Int(StorageData.shared.apiVersion) ?? 6
globalStiResult = try parseSti(jsonObject: jsonObject, apiVersion: apiVersion)
globalCcmResult = try parseCcm(jsonObject: jsonObject, apiVersion: apiVersion)
// CDN
if !globalCcmResult.cdn.isEmpty {
globalCdn = globalCcmResult.cdn
} else if !globalStiResult.cdn.isEmpty {
globalCdn = globalStiResult.cdn
}
}
static func parseStiProducts(jsonString: String) throws {
guard let jsonData = jsonString.data(using: .utf8),
let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
throw ParserError.invalidJSON
}
let apiVersion = Int(StorageData.shared.apiVersion) ?? 6
let result = try parseSti(jsonObject: jsonObject, apiVersion: apiVersion)
globalStiResult = result
// CDN
globalCdn = result.cdn
}
static func parseCcmProducts(jsonString: String) throws {
guard let jsonData = jsonString.data(using: .utf8),
let jsonObject = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
throw ParserError.invalidJSON
}
let apiVersion = Int(StorageData.shared.apiVersion) ?? 6
let result = try parseCcm(jsonObject: jsonObject, apiVersion: apiVersion)
globalCcmResult = result
// CDN
globalCdn = result.cdn
}
private static func parseSti(jsonObject: [String: Any], apiVersion: Int) throws -> NewParseResult {
let cdnPath: [String]
if apiVersion == 6 {
cdnPath = ["channels", "channel"]
} else {
cdnPath = ["channel"]
}
func getValue(from dict: [String: Any], path: [String]) -> Any? {
var current: Any = dict
for key in path {
guard let dict = current as? [String: Any],
let value = dict[key] else {
return nil
}
current = value
}
return current
}
var channelArray: [[String: Any]] = []
if let channels = getValue(from: jsonObject, path: cdnPath) {
if let array = channels as? [[String: Any]] {
channelArray = array
} else if let dict = channels as? [String: Any],
let array = dict["channel"] as? [[String: Any]] {
channelArray = array
}
}
guard let firstChannel = channelArray.first,
let cdn = (firstChannel["cdn"] as? [String: Any])?["secure"] as? String else {
throw ParserError.missingCDN
}
var products = [Product]()
for channel in channelArray {
let channelName = channel["name"] as? String ?? ""
if(channelName != "sti") { continue }
guard let productsContainer = channel["products"] as? [String: Any],
let productArray = productsContainer["product"] as? [[String: Any]] else {
continue
}
for product in productArray {
guard let productId = product["id"] as? String,
let productDisplayName = product["displayName"] as? String,
let productVersion = product["version"] as? String else {
continue
}
/**
sti referencedProducts
icon
*/
var productObject = Product(
type: product["type"] as? String ?? "",
displayName: productDisplayName,
family: product["family"] as? String ?? "",
appLineage: product["appLineage"] as? String ?? "",
familyName: product["familyName"] as? String ?? "",
productIcons: [],
platforms: [],
referencedProducts: [],
version: productVersion,
id: productId,
hidden: true
)
if let platforms = product["platforms"] as? [String: Any],
let platformArray = platforms["platform"] as? [[String: Any]] {
for platform in platformArray {
guard let platformId = platform["id"] as? String,
let languageSets = platform["languageSet"] as? [[String: Any]],
let languageSet = languageSets.first else {
continue
}
// sti dependencies
let newLanguageSet = Product.Platform.LanguageSet(
manifestURL: (languageSet["urls"] as? [String: Any])?["manifestURL"] as? String ?? "",
dependencies: [],
productCode: languageSet["productCode"] as? String ?? "",
name: languageSet["name"] as? String ?? "",
installSize: languageSet["installSize"] as? Int ?? 0,
buildGuid: languageSet["buildGuid"] as? String ?? "", // buildGuid LanguageSet
baseVersion: languageSet["baseVersion"] as? String ?? "",
productVersion: languageSet["productVersion"] as? String ?? ""
)
// sti module
var newPlatform = Product.Platform(
languageSet: [newLanguageSet],
modules: [],
range: [],
id: platformId
)
if let range = platform["systemCompatibility"] as? [String: Any],
let operatingSystem = range["operatingSystem"] as? [String: Any],
let rangeArray = operatingSystem["range"] as? [String] {
let min = rangeArray.first ?? ""
let max = rangeArray.count > 1 ? rangeArray[1] : ""
let newRange = Product.Platform.Range(min: min, max: max)
newPlatform.range = [newRange]
}
productObject.platforms.append(newPlatform)
}
}
products.append(productObject)
}
}
return NewParseResult(products: products, cdn: cdn)
}
private static func parseCcm(jsonObject: [String: Any], apiVersion: Int) throws -> NewParseResult {
let cdnPath: [String]
if apiVersion == 6 {
cdnPath = ["channels", "channel"]
} else {
cdnPath = ["channel"]
}
func getValue(from dict: [String: Any], path: [String]) -> Any? {
var current: Any = dict
for key in path {
guard let dict = current as? [String: Any],
let value = dict[key] else {
return nil
}
current = value
}
return current
}
var channelArray: [[String: Any]] = []
if let channels = getValue(from: jsonObject, path: cdnPath) {
if let array = channels as? [[String: Any]] {
channelArray = array
} else if let dict = channels as? [String: Any],
let array = dict["channel"] as? [[String: Any]] {
channelArray = array
}
}
guard let firstChannel = channelArray.first,
let cdn = (firstChannel["cdn"] as? [String: Any])?["secure"] as? String else {
throw ParserError.missingCDN
}
var products = [Product]()
for channel in channelArray {
let channelName = channel["name"] as? String ?? ""
if(channelName != "ccm") { continue }
guard let productsContainer = channel["products"] as? [String: Any],
let productArray = productsContainer["product"] as? [[String: Any]] else {
continue
}
for product in productArray {
guard let productId = product["id"] as? String,
let productDisplayName = product["displayName"] as? String,
let productVersion = product["version"] as? String else {
continue
}
let icons = (product["productIcons"] as? [String: Any])?["icon"] as? [[String: Any]] ?? []
let productIcons = icons.compactMap { icon -> Product.ProductIcon? in
guard let size = icon["size"] as? String,
let value = icon["value"] as? String else {
return nil
}
return Product.ProductIcon(value: value, size: size)
}
var productObject = Product(
type: product["type"] as? String ?? "",
displayName: productDisplayName,
family: product["family"] as? String ?? "",
appLineage: product["appLineage"] as? String ?? "",
familyName: product["familyName"] as? String ?? "",
productIcons: productIcons,
platforms: [],
referencedProducts: [],
version: productVersion,
id: productId,
hidden: false
)
if let platforms = product["platforms"] as? [String: Any],
let platformArray = platforms["platform"] as? [[String: Any]] {
for platform in platformArray {
guard let platformId = platform["id"] as? String,
let languageSets = platform["languageSet"] as? [[String: Any]],
let languageSet = languageSets.first else {
continue
}
var newLanguageSet = Product.Platform.LanguageSet(
manifestURL: (languageSet["urls"] as? [String: Any])?["manifestURL"] as? String ?? "",
dependencies: [],
productCode: languageSet["productCode"] as? String ?? "",
name: languageSet["name"] as? String ?? "",
installSize: languageSet["installSize"] as? Int ?? 0,
buildGuid: languageSet["buildGuid"] as? String ?? "",
baseVersion: languageSet["baseVersion"] as? String ?? "",
productVersion: languageSet["productVersion"] as? String ?? ""
)
var dependencies: [Product.Platform.LanguageSet.Dependency] = []
if let deps = languageSet["dependencies"] as? [String: Any],
let depArray = deps["dependency"] as? [[String: Any]] {
dependencies = depArray.compactMap { dep in
guard let sapCode = dep["sapCode"] as? String,
let baseVersion = dep["baseVersion"] as? String else {
return Product.Platform.LanguageSet.Dependency(sapCode: "",baseVersion: "",productVersion: "",buildGuid: "")
}
let targetPlatform = AppStatics.isAppleSilicon ? "macarm64" : "osx10-64"
let cacheKey = DependencyCacheKey(sapCode: sapCode, targetPlatform: targetPlatform)
if let cachedDependency = globalDependencyCache[cacheKey] {
return cachedDependency
}
var productVersion = ""
var buildGuid = ""
var isMatchPlatform = false
var selectedPlatform = ""
var selectedReason = ""
if !globalStiResult.products.isEmpty {
let matchingProducts = globalStiResult.products.filter { $0.id == sapCode }
if let latestProduct = matchingProducts.sorted(by: {
return AppStatics.compareVersions($0.version, $1.version) > 0
}).first {
if let matchingPlatform = latestProduct.platforms.first(where: { platform in
platform.id == targetPlatform || platform.id == "macuniversal"
}),
let firstLanguageSet = matchingPlatform.languageSet.first {
productVersion = firstLanguageSet.productVersion
buildGuid = firstLanguageSet.buildGuid
isMatchPlatform = true
selectedPlatform = matchingPlatform.id
selectedReason = matchingPlatform.id == "macuniversal" ?
"成功匹配通用平台 macuniversal支持所有 Mac 平台)" :
"成功匹配目标平台"
} else {
if let firstAvailablePlatform = latestProduct.platforms.first,
let firstLanguageSet = firstAvailablePlatform.languageSet.first {
productVersion = firstLanguageSet.productVersion
buildGuid = firstLanguageSet.buildGuid
isMatchPlatform = false
selectedPlatform = firstAvailablePlatform.id
selectedReason = "当前依赖所有版本中无匹配平台,使用可用平台: \(firstAvailablePlatform.id)"
} else {
selectedReason = "未找到任何可用平台"
}
}
} else {
selectedReason = "未找到最新版本产品"
}
} else {
selectedReason = "globalStiResult.products 为空"
}
let dependency = Product.Platform.LanguageSet.Dependency(
sapCode: sapCode,
baseVersion: baseVersion,
productVersion: productVersion,
buildGuid: buildGuid,
isMatchPlatform: isMatchPlatform,
targetPlatform: targetPlatform,
selectedPlatform: selectedPlatform,
selectedReason: selectedReason
)
globalDependencyCache[cacheKey] = dependency
return dependency
}
newLanguageSet.dependencies.append(contentsOf: dependencies)
}
var newPlatform = Product.Platform(
languageSet: [newLanguageSet],
modules: [],
range: [],
id: platformId
)
if let modules = platform["modules"] as? [String: Any],
let moduleArray = modules["module"] as? [[String: Any]] {
let newModules: [Product.Platform.Module] = moduleArray.compactMap { (module: [String: Any]) -> Product.Platform.Module? in
guard let displayName = module["displayName"] as? String,
let deploymentType = module["deploymentType"] as? String,
let id = module["id"] as? String else {
return nil
}
return Product.Platform.Module(displayName: displayName, deploymentType: deploymentType, id: id)
}
newPlatform.modules = newModules
}
if let range = platform["systemCompatibility"] as? [String: Any],
let operatingSystem = range["operatingSystem"] as? [String: Any],
let rangeArray = operatingSystem["range"] as? [String] {
let min = rangeArray.first ?? ""
let max = rangeArray.count > 1 ? rangeArray[1] : ""
let newRange = Product.Platform.Range(min: min, max: max)
newPlatform.range = [newRange]
}
productObject.platforms.append(newPlatform)
}
}
if let referencedProductsArray = product["referencedProducts"] as? [[String: Any]] {
let referencedProducts: [Product.ReferencedProduct] = referencedProductsArray.compactMap { (refProduct: [String: Any]) -> Product.ReferencedProduct? in
guard let sapCode = refProduct["sapCode"] as? String,
let version = refProduct["version"] as? String else {
return nil
}
return Product.ReferencedProduct(sapCode: sapCode, version: version)
}
productObject.referencedProducts = referencedProducts
}
products.append(productObject)
}
}
return NewParseResult(products: products, cdn: cdn)
}
}