mirror of
https://github.com/X1a0He/Adobe-Downloader.git
synced 2025-11-25 03:14:57 +08:00
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
403 lines
20 KiB
Swift
403 lines
20 KiB
Swift
//
|
||
// 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)
|
||
}
|
||
}
|