Files
Adobe-Downloader/Adobe Downloader/Utils/NewJSONParser.swift
2025-02-27 23:02:52 +08:00

365 lines
17 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: "")
}
var productVersion = ""
var buildGuid = ""
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
}).last {
let targetPlatformId = AppStatics.isAppleSilicon ? "macarm64" : "osx10-64"
let _ = latestProduct.platforms.map { $0.id }
if let matchingPlatform = latestProduct.platforms.first(where: { $0.id == targetPlatformId }),
let firstLanguageSet = matchingPlatform.languageSet.first {
productVersion = firstLanguageSet.productVersion
buildGuid = firstLanguageSet.buildGuid
}
}
}
return Product.Platform.LanguageSet.Dependency(
sapCode: sapCode,
baseVersion: baseVersion,
productVersion: productVersion,
buildGuid: buildGuid
)
}
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)
}
}