mirror of
https://github.com/X1a0He/Adobe-Downloader.git
synced 2025-11-25 03:14:57 +08:00
feat: Brings a new transparent glass material background
- fix: Fixed the issue where the 182/107 error code appeared during installation on some models and scenarios
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "81"
|
||||
endingLineNumber = "81"
|
||||
landmarkName = "executeInstallation(at:progressHandler:)"
|
||||
landmarkName = "getAdobeInstallLogDetails()"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
|
||||
@@ -57,43 +57,48 @@ struct Adobe_DownloaderApp: App {
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environmentObject(globalNetworkManager)
|
||||
.frame(minWidth: 792, minHeight: 600)
|
||||
.tint(.blue)
|
||||
.task {
|
||||
await setupApplication()
|
||||
}
|
||||
.sheet(isPresented: $showCreativeCloudAlert) {
|
||||
ShouldExistsSetUpView()
|
||||
.environmentObject(globalNetworkManager)
|
||||
}
|
||||
.alert("Setup未备份提示", isPresented: $showBackupAlert) {
|
||||
Button("确定") {
|
||||
handleBackup()
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
} message: {
|
||||
Text("检测到Setup文件尚未备份,如果你需要安装程序,则Setup必须被处理,点击确定后你需要输入密码,Adobe Downloader将自动处理并备份为Setup.original")
|
||||
}
|
||||
.alert(backupSuccess ? "备份成功" : "备份失败", isPresented: $showBackupResultAlert) {
|
||||
Button("确定") { }
|
||||
} message: {
|
||||
Text(backupResultMessage)
|
||||
}
|
||||
.sheet(isPresented: $showTipsSheet) {
|
||||
TipsSheetView(
|
||||
showTipsSheet: $showTipsSheet,
|
||||
showLanguagePicker: $showLanguagePicker
|
||||
)
|
||||
ZStack {
|
||||
BlurView()
|
||||
.ignoresSafeArea()
|
||||
|
||||
ContentView()
|
||||
.environmentObject(globalNetworkManager)
|
||||
.sheet(isPresented: $showLanguagePicker) {
|
||||
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
|
||||
storage.defaultLanguage = language
|
||||
showLanguagePicker = false
|
||||
.frame(minWidth: 792, minHeight: 600)
|
||||
.tint(.blue)
|
||||
.task {
|
||||
await setupApplication()
|
||||
}
|
||||
.sheet(isPresented: $showCreativeCloudAlert) {
|
||||
ShouldExistsSetUpView()
|
||||
.environmentObject(globalNetworkManager)
|
||||
}
|
||||
.alert("Setup未备份提示", isPresented: $showBackupAlert) {
|
||||
Button("确定") {
|
||||
handleBackup()
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
} message: {
|
||||
Text("检测到Setup文件尚未备份,如果你需要安装程序,则Setup必须被处理,点击确定后你需要输入密码,Adobe Downloader将自动处理并备份为Setup.original")
|
||||
}
|
||||
.alert(backupSuccess ? "备份成功" : "备份失败", isPresented: $showBackupResultAlert) {
|
||||
Button("确定") { }
|
||||
} message: {
|
||||
Text(backupResultMessage)
|
||||
}
|
||||
.sheet(isPresented: $showTipsSheet) {
|
||||
TipsSheetView(
|
||||
showTipsSheet: $showTipsSheet,
|
||||
showLanguagePicker: $showLanguagePicker
|
||||
)
|
||||
.environmentObject(globalNetworkManager)
|
||||
.sheet(isPresented: $showLanguagePicker) {
|
||||
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
|
||||
storage.defaultLanguage = language
|
||||
showLanguagePicker = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizabilityContentSize()
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
struct BlurView: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> NSVisualEffectView {
|
||||
let effectView = NSVisualEffectView()
|
||||
effectView.blendingMode = .behindWindow
|
||||
effectView.material = .hudWindow
|
||||
effectView.state = .active
|
||||
effectView.isEmphasized = true
|
||||
return effectView
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private var eventMonitor: Any?
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.mainMenu = nil
|
||||
|
||||
|
||||
for window in NSApplication.shared.windows {
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.backgroundColor = NSColor(white: 1, alpha: 0)
|
||||
|
||||
if let titlebarView = window.standardWindowButton(.closeButton)?.superview {
|
||||
let blurView = NSVisualEffectView(frame: titlebarView.bounds)
|
||||
blurView.blendingMode = .behindWindow
|
||||
blurView.material = .hudWindow
|
||||
blurView.state = .active
|
||||
blurView.autoresizingMask = [.width, .height]
|
||||
titlebarView.addSubview(blurView, positioned: .below, relativeTo: nil)
|
||||
}
|
||||
}
|
||||
|
||||
if let window = NSApp.windows.first {
|
||||
window.minSize = NSSize(width: 792, height: 600)
|
||||
}
|
||||
|
||||
@@ -418,6 +418,20 @@ enum DownloadStatus: Equatable, Codable {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isCompleted: Bool {
|
||||
if case .completed = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isFailed: Bool {
|
||||
if case .failed = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension DownloadStatus.PrepareInfo: Equatable {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
//
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
import Foundation
|
||||
import AppKit
|
||||
@@ -1,6 +0,0 @@
|
||||
//
|
||||
// NewExtensions.swift
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2/26/25.
|
||||
//
|
||||
@@ -1,141 +0,0 @@
|
||||
////
|
||||
//// Adobe Downloader
|
||||
////
|
||||
//// Created by X1a0He on 2024/10/30.
|
||||
////
|
||||
//import Foundation
|
||||
//
|
||||
//
|
||||
//class ProductsToDownload: ObservableObject, Codable {
|
||||
// var sapCode: String
|
||||
// var version: String
|
||||
// var buildGuid: String
|
||||
// var applicationJson: String?
|
||||
// @Published var packages: [Package] = []
|
||||
// @Published var completedPackages: Int = 0
|
||||
//
|
||||
// var totalPackages: Int {
|
||||
// packages.count
|
||||
// }
|
||||
//
|
||||
// init(sapCode: String, version: String, buildGuid: String, applicationJson: String = "") {
|
||||
// self.sapCode = sapCode
|
||||
// self.version = version
|
||||
// self.buildGuid = buildGuid
|
||||
// self.applicationJson = applicationJson
|
||||
// }
|
||||
//
|
||||
// func updateCompletedPackages() {
|
||||
// Task { @MainActor in
|
||||
// completedPackages = packages.filter { $0.downloaded }.count
|
||||
// objectWillChange.send()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case sapCode, version, buildGuid, applicationJson, packages
|
||||
// }
|
||||
//
|
||||
// func encode(to encoder: Encoder) throws {
|
||||
// var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
// try container.encode(sapCode, forKey: .sapCode)
|
||||
// try container.encode(version, forKey: .version)
|
||||
// try container.encode(buildGuid, forKey: .buildGuid)
|
||||
// try container.encodeIfPresent(applicationJson, forKey: .applicationJson)
|
||||
// try container.encode(packages, forKey: .packages)
|
||||
// }
|
||||
//
|
||||
// required init(from decoder: Decoder) throws {
|
||||
// let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// sapCode = try container.decode(String.self, forKey: .sapCode)
|
||||
// version = try container.decode(String.self, forKey: .version)
|
||||
// buildGuid = try container.decode(String.self, forKey: .buildGuid)
|
||||
// applicationJson = try container.decodeIfPresent(String.self, forKey: .applicationJson)
|
||||
// packages = try container.decode([Package].self, forKey: .packages)
|
||||
// completedPackages = 0
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct SapCodes: Identifiable {
|
||||
// var id: String { sapCode }
|
||||
// var sapCode: String
|
||||
// var displayName: String
|
||||
//}
|
||||
//
|
||||
//struct Sap: Codable, Equatable {
|
||||
// var id: String { sapCode }
|
||||
// var hidden: Bool
|
||||
// var displayName: String
|
||||
// var sapCode: String
|
||||
// var versions: [String: Versions]
|
||||
// var icons: [ProductIcon]
|
||||
// var productsToDownload: [ProductsToDownload]? = nil
|
||||
//
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case hidden, displayName, sapCode, versions, icons
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: Sap, rhs: Sap) -> Bool {
|
||||
// return lhs.sapCode == rhs.sapCode &&
|
||||
// lhs.hidden == rhs.hidden &&
|
||||
// lhs.displayName == rhs.displayName &&
|
||||
// lhs.versions == rhs.versions &&
|
||||
// lhs.icons == rhs.icons
|
||||
// }
|
||||
//
|
||||
// struct Versions: Codable, Equatable {
|
||||
// var sapCode: String
|
||||
// var baseVersion: String
|
||||
// var productVersion: String
|
||||
// var apPlatform: String
|
||||
// var dependencies: [Dependencies]
|
||||
// var buildGuid: String
|
||||
//
|
||||
// struct Dependencies: Codable, Equatable {
|
||||
// var sapCode: String
|
||||
// var version: String
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// struct ProductIcon: Codable, Equatable {
|
||||
// let size: String
|
||||
// let url: String
|
||||
//
|
||||
// var dimension: Int {
|
||||
// let components = size.split(separator: "x")
|
||||
// if components.count == 2,
|
||||
// let dimension = Int(components[0]) {
|
||||
// return dimension
|
||||
// }
|
||||
// return 0
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var isValid: Bool { !hidden }
|
||||
//
|
||||
// func getBestIcon() -> ProductIcon? {
|
||||
// if let icon = icons.first(where: { $0.size == "192x192" }) {
|
||||
// return icon
|
||||
// }
|
||||
// return icons.max(by: { $0.dimension < $1.dimension })
|
||||
// }
|
||||
//
|
||||
// func hasValidVersions(allowedPlatform: [String]) -> Bool {
|
||||
// if hidden { return false }
|
||||
//
|
||||
// for version in Array(versions.values).reversed() {
|
||||
// if !version.buildGuid.isEmpty &&
|
||||
// (!version.buildGuid.contains("/") || sapCode == "APRO") &&
|
||||
// allowedPlatform.contains(version.apPlatform) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//struct ProductsResponse: Codable {
|
||||
// let products: [String: Sap]
|
||||
// let cdn: String
|
||||
//}
|
||||
@@ -57,10 +57,12 @@ struct ContentView: View {
|
||||
filteredProducts: filteredProducts,
|
||||
onRetry: { networkManager.retryFetchData() }
|
||||
)
|
||||
.background(Color(.clear))
|
||||
.animation(.easeInOut, value: networkManager.loadingState)
|
||||
.animation(.easeInOut, value: filteredProducts)
|
||||
}
|
||||
.sheet(isPresented: $showDownloadManager) {
|
||||
.background(Color(.clear))
|
||||
.sheet(isPresented: $showDownloadManager) {
|
||||
DownloadManagerView()
|
||||
}
|
||||
.onChange(of: currentApiVersion) { newValue in
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
//
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
extension DownloadStatus {
|
||||
var isCompleted: Bool {
|
||||
if case .completed = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isFailed: Bool {
|
||||
if case .failed = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//class NewDownloadTask: Identifiable, ObservableObject, Equatable {
|
||||
// let id = UUID()
|
||||
// var sapCode: String
|
||||
// let version: String
|
||||
// let language: String
|
||||
// let displayName: String
|
||||
// let directory: URL
|
||||
// var productsToDownload: [ProductsToDownload]
|
||||
// var retryCount: Int
|
||||
// let createAt: Date
|
||||
// var displayInstallButton: Bool
|
||||
// @Published var totalStatus: DownloadStatus?
|
||||
// @Published var totalProgress: Double
|
||||
// @Published var totalDownloadedSize: Int64
|
||||
// @Published var totalSize: Int64
|
||||
// @Published var totalSpeed: Double
|
||||
// @Published var currentPackage: Package? {
|
||||
// didSet {
|
||||
// objectWillChange.send()
|
||||
// }
|
||||
// }
|
||||
// let platform: String
|
||||
//
|
||||
// var status: DownloadStatus {
|
||||
// totalStatus ?? .waiting
|
||||
// }
|
||||
//
|
||||
// var destinationURL: URL { directory }
|
||||
//
|
||||
// var downloadedSize: Int64 {
|
||||
// get { totalDownloadedSize }
|
||||
// set { totalDownloadedSize = newValue }
|
||||
// }
|
||||
//
|
||||
// var progress: Double {
|
||||
// get { totalProgress }
|
||||
// set { totalProgress = newValue }
|
||||
// }
|
||||
//
|
||||
// var speed: Double {
|
||||
// get { totalSpeed }
|
||||
// set { totalSpeed = newValue }
|
||||
// }
|
||||
//
|
||||
// var formattedTotalSize: String {
|
||||
// ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file)
|
||||
// }
|
||||
//
|
||||
// var formattedDownloadedSize: String {
|
||||
// ByteCountFormatter.string(fromByteCount: totalDownloadedSize, countStyle: .file)
|
||||
// }
|
||||
//
|
||||
// @Published var completedPackages: Int = 0
|
||||
// @Published var totalPackages: Int = 0
|
||||
//
|
||||
// func setStatus(_ newStatus: DownloadStatus) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.totalStatus = newStatus
|
||||
// self.objectWillChange.send()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func updateProgress(downloaded: Int64, total: Int64, speed: Double) {
|
||||
// DispatchQueue.main.async {
|
||||
// self.totalDownloadedSize = downloaded
|
||||
// self.totalSize = total
|
||||
// self.totalSpeed = speed
|
||||
// self.totalProgress = total > 0 ? Double(downloaded) / Double(total) : 0
|
||||
// self.objectWillChange.send()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// init(sapCode: String, version: String, language: String, displayName: String, directory: URL, productsToDownload: [ProductsToDownload] = [], retryCount: Int = 0, createAt: Date, totalStatus: DownloadStatus? = nil, totalProgress: Double, totalDownloadedSize: Int64 = 0, totalSize: Int64 = 0, totalSpeed: Double = 0, currentPackage: Package? = nil, platform: String) {
|
||||
// self.sapCode = sapCode
|
||||
// self.version = version
|
||||
// self.language = language
|
||||
// self.displayName = displayName
|
||||
// self.directory = directory
|
||||
// self.productsToDownload = productsToDownload
|
||||
// self.retryCount = retryCount
|
||||
// self.createAt = createAt
|
||||
// self.totalStatus = totalStatus
|
||||
// self.totalProgress = totalProgress
|
||||
// self.totalDownloadedSize = totalDownloadedSize
|
||||
// self.totalSize = totalSize
|
||||
// self.totalSpeed = totalSpeed
|
||||
// self.currentPackage = currentPackage
|
||||
// self.displayInstallButton = sapCode != "APRO"
|
||||
// self.platform = platform
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: NewDownloadTask, rhs: NewDownloadTask) -> Bool {
|
||||
// return lhs.id == rhs.id
|
||||
// }
|
||||
//}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ actor InstallManager {
|
||||
case installationFailed(String)
|
||||
case cancelled
|
||||
case permissionDenied
|
||||
case installationFailedWithDetails(String, String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
@@ -25,6 +26,7 @@ actor InstallManager {
|
||||
case .installationFailed(let message): return message
|
||||
case .cancelled: return String(localized: "安装已取消")
|
||||
case .permissionDenied: return String(localized: "权限被拒绝")
|
||||
case .installationFailedWithDetails(let message, _): return message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +73,45 @@ actor InstallManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func getAdobeInstallLogDetails() async -> String? {
|
||||
let logPath = "/Library/Logs/Adobe/Installers/Install.log"
|
||||
guard FileManager.default.fileExists(atPath: logPath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let logContent = try String(contentsOfFile: logPath, encoding: .utf8)
|
||||
let lines = logContent.components(separatedBy: .newlines)
|
||||
|
||||
let fatalLines = lines.filter {
|
||||
line in line.contains("FATAL:")
|
||||
}
|
||||
|
||||
var uniqueLines: [String] = []
|
||||
var seen = Set<String>()
|
||||
|
||||
for line in fatalLines {
|
||||
if !seen.contains(line) {
|
||||
seen.insert(line)
|
||||
uniqueLines.append(line)
|
||||
}
|
||||
}
|
||||
|
||||
if uniqueLines.isEmpty, lines.count > 10 {
|
||||
uniqueLines = Array(lines.suffix(10))
|
||||
}
|
||||
|
||||
if !uniqueLines.isEmpty {
|
||||
return uniqueLines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
} catch {
|
||||
print("读取安装日志失败: \(error.localizedDescription)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func executeInstallation(
|
||||
at appPath: URL,
|
||||
progressHandler: @escaping (Double, String) -> Void
|
||||
@@ -90,6 +131,27 @@ actor InstallManager {
|
||||
throw InstallError.installationFailed("driver.xml 文件没有读取权限")
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
progressHandler(0.0, String(localized: "正在清理安装环境..."))
|
||||
}
|
||||
|
||||
let logFiles = [
|
||||
"/Library/Logs/Adobe/Installers/Install.log",
|
||||
]
|
||||
|
||||
for logFile in logFiles {
|
||||
let removeCommand = "rm -f '\(logFile)'"
|
||||
let result = await withCheckedContinuation { continuation in
|
||||
PrivilegedHelperManager.shared.executeCommand(removeCommand) { result in
|
||||
continuation.resume(returning: result)
|
||||
}
|
||||
}
|
||||
|
||||
if result.contains("Error") {
|
||||
print("清理安装日志失败: \(logFile) - \(result)")
|
||||
}
|
||||
}
|
||||
|
||||
let installCommand = "sudo \"\(setupPath)\" --install=1 --driverXML=\"\(driverPath)\""
|
||||
|
||||
@@ -113,20 +175,12 @@ actor InstallManager {
|
||||
return
|
||||
} else {
|
||||
let errorMessage: String
|
||||
switch exitCode {
|
||||
case 107:
|
||||
errorMessage = String(localized: "架构或版本不一致 (退出代码: \(exitCode))")
|
||||
case 103:
|
||||
errorMessage = String(localized: "权限问题 (退出代码: \(exitCode))")
|
||||
case 182:
|
||||
errorMessage = String(localized: "安装文件不完整或损坏 (退出代码: \(exitCode))")
|
||||
case -1:
|
||||
errorMessage = String(localized: "Setup 组件未被处理 (退出代码: \(exitCode))")
|
||||
default:
|
||||
errorMessage = String(localized: "(退出代码: \(exitCode))")
|
||||
errorMessage = String(localized: "错误代码(\(exitCode)),请查看日志详情并向开发者汇报")
|
||||
if let logDetails = await self.getAdobeInstallLogDetails() {
|
||||
continuation.resume(throwing: InstallError.installationFailedWithDetails(errorMessage, logDetails))
|
||||
} else {
|
||||
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
|
||||
}
|
||||
progressHandler(0.0, errorMessage)
|
||||
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
////
|
||||
//// JSONParser.swift
|
||||
//// Adobe Downloader
|
||||
////
|
||||
//// Created by X1a0He on 11/18/24.
|
||||
////
|
||||
//
|
||||
//import Foundation
|
||||
//
|
||||
// struct ParseResult {
|
||||
// var products: [String: Sap]
|
||||
// var cdn: String
|
||||
// }
|
||||
//
|
||||
//class JSONParser {
|
||||
// static func parse(jsonString: String) throws -> ParseResult {
|
||||
// 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
|
||||
//
|
||||
// return try parseProductsJSON(jsonObject: jsonObject, apiVersion: apiVersion)
|
||||
// }
|
||||
//
|
||||
// private static func parseProductsJSON(jsonObject: [String: Any], apiVersion: Int) throws -> ParseResult {
|
||||
// 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 = [String: Sap](minimumCapacity: 200)
|
||||
//
|
||||
// for channel in channelArray {
|
||||
// let channelName = channel["name"] as? String
|
||||
// let hidden = channelName != "ccm"
|
||||
//
|
||||
// guard let productsContainer = channel["products"] as? [String: Any],
|
||||
// let productArray = productsContainer["product"] as? [[String: Any]] else {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// for product in productArray {
|
||||
// guard let sap = product["id"] as? String,
|
||||
// let displayName = product["displayName"] as? String,
|
||||
// let productVersion = product["version"] as? String else {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if products[sap] == nil {
|
||||
// let icons = (product["productIcons"] as? [String: Any])?["icon"] as? [[String: Any]] ?? []
|
||||
// let productIcons = icons.compactMap { icon -> Sap.ProductIcon? in
|
||||
// guard let size = icon["size"] as? String,
|
||||
// let value = icon["value"] as? String else {
|
||||
// return nil
|
||||
// }
|
||||
// return Sap.ProductIcon(size: size, url: value)
|
||||
// }
|
||||
//
|
||||
// products[sap] = Sap(
|
||||
// hidden: hidden,
|
||||
// displayName: displayName,
|
||||
// sapCode: sap,
|
||||
// versions: [:],
|
||||
// icons: productIcons
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// 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
|
||||
// }
|
||||
//
|
||||
// if let existingVersion = products[sap]?.versions[productVersion],
|
||||
// StorageData.shared.allowedPlatform.contains(existingVersion.apPlatform) {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// var baseVersion = languageSet["baseVersion"] as? String ?? ""
|
||||
// var buildGuid = languageSet["buildGuid"] as? String ?? ""
|
||||
// var finalProductVersion = productVersion
|
||||
//
|
||||
// if sap == "APRO" {
|
||||
// baseVersion = productVersion
|
||||
// if apiVersion == 4 || apiVersion == 5 {
|
||||
// if let appVersion = (languageSet["nglLicensingInfo"] as? [String: Any])?["appVersion"] as? String {
|
||||
// finalProductVersion = appVersion
|
||||
// }
|
||||
// } else if apiVersion == 6 {
|
||||
// if let builds = jsonObject["builds"] as? [String: Any],
|
||||
// let buildArray = builds["build"] as? [[String: Any]] {
|
||||
// for build in buildArray {
|
||||
// if build["id"] as? String == sap && build["version"] as? String == baseVersion,
|
||||
// let appVersion = (build["nglLicensingInfo"] as? [String: Any])?["appVersion"] as? String {
|
||||
// finalProductVersion = appVersion
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let urls = languageSet["urls"] as? [String: Any],
|
||||
// let manifestURL = urls["manifestURL"] as? String {
|
||||
// buildGuid = manifestURL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var dependencies: [Sap.Versions.Dependencies] = []
|
||||
// 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 version = dep["baseVersion"] as? String else {
|
||||
// return nil
|
||||
// }
|
||||
// return Sap.Versions.Dependencies(sapCode: sapCode, version: version)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !buildGuid.isEmpty {
|
||||
// let version = Sap.Versions(
|
||||
// sapCode: sap,
|
||||
// baseVersion: baseVersion,
|
||||
// productVersion: finalProductVersion,
|
||||
// apPlatform: platformId,
|
||||
// dependencies: dependencies,
|
||||
// buildGuid: buildGuid
|
||||
// )
|
||||
// products[sap]?.versions[finalProductVersion] = version
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return ParseResult(products: products, cdn: cdn)
|
||||
// }
|
||||
//}
|
||||
@@ -38,7 +38,7 @@ class NetworkManager: ObservableObject {
|
||||
case idle
|
||||
case installing(progress: Double, status: String)
|
||||
case completed
|
||||
case failed(Error)
|
||||
case failed(Error, String? = nil)
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -62,7 +62,6 @@ class NetworkManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
func startDownload(productId: String, selectedVersion: String, language: String, destinationURL: URL) async throws {
|
||||
print(destinationURL)
|
||||
// 从 globalCcmResult 中获取 productId 对应的 ProductInfo
|
||||
guard let productInfo = globalCcmResult.products.first(where: { $0.id == productId }) else {
|
||||
throw NetworkError.productNotFound
|
||||
@@ -215,26 +214,20 @@ class NetworkManager: ObservableObject {
|
||||
await MainActor.run {
|
||||
self.installCommand = command
|
||||
|
||||
var errorDetails: String? = nil
|
||||
var mainError = error
|
||||
|
||||
if let installError = error as? InstallManager.InstallError {
|
||||
switch installError {
|
||||
case .installationFailed(let message):
|
||||
if message.contains("需要重新输入密码") {
|
||||
Task {
|
||||
await installProduct(at: path)
|
||||
}
|
||||
} else {
|
||||
installationState = .failed(InstallManager.InstallError.installationFailed(message))
|
||||
}
|
||||
case .cancelled:
|
||||
installationState = .failed(InstallManager.InstallError.cancelled)
|
||||
case .setupNotFound:
|
||||
installationState = .failed(InstallManager.InstallError.setupNotFound)
|
||||
case .permissionDenied:
|
||||
installationState = .failed(InstallManager.InstallError.permissionDenied)
|
||||
case .installationFailedWithDetails(let message, let details):
|
||||
errorDetails = details
|
||||
mainError = InstallManager.InstallError.installationFailed(message)
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
installationState = .failed(InstallManager.InstallError.installationFailed(error.localizedDescription))
|
||||
}
|
||||
|
||||
installationState = .failed(mainError, errorDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,17 +261,18 @@ class NetworkManager: ObservableObject {
|
||||
installationState = .completed
|
||||
}
|
||||
} catch {
|
||||
if case InstallManager.InstallError.installationFailed(let message) = error,
|
||||
message.contains("需要重新输入密码") {
|
||||
await installProduct(at: path)
|
||||
} else {
|
||||
await MainActor.run {
|
||||
if let installError = error as? InstallManager.InstallError {
|
||||
installationState = .failed(installError)
|
||||
} else {
|
||||
installationState = .failed(error)
|
||||
await MainActor.run {
|
||||
var errorDetails: String? = nil
|
||||
var mainError = error
|
||||
|
||||
if let installError = error as? InstallManager.InstallError {
|
||||
if case .installationFailedWithDetails(let message, let details) = installError {
|
||||
errorDetails = details
|
||||
mainError = InstallManager.InstallError.installationFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
installationState = .failed(mainError, errorDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,10 +129,6 @@ class NewDownloadUtils {
|
||||
}
|
||||
}
|
||||
|
||||
for dependencyToDownload in dependenciesToDownload {
|
||||
print("\(dependencyToDownload.sapCode), \(dependencyToDownload.version), \(dependencyToDownload.buildGuid)")
|
||||
}
|
||||
|
||||
for dependencyToDownload in dependenciesToDownload {
|
||||
await MainActor.run {
|
||||
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
|
||||
@@ -147,15 +143,72 @@ class NewDownloadUtils {
|
||||
if !FileManager.default.fileExists(atPath: productDir.path) {
|
||||
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
}
|
||||
let jsonURL = productDir.appendingPathComponent("application.json")
|
||||
try jsonString.write(to: jsonURL, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
guard let jsonData = jsonString.data(using: .utf8),
|
||||
var processedJsonString = jsonString
|
||||
if dependencyToDownload.sapCode == productInfo.id {
|
||||
if let jsonData = jsonString.data(using: .utf8),
|
||||
var appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
|
||||
|
||||
if var modules = appInfo["Modules"] as? [String: Any] {
|
||||
modules["Module"] = [] as [[String: Any]]
|
||||
appInfo["Modules"] = modules
|
||||
}
|
||||
|
||||
if var packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] {
|
||||
|
||||
var filteredPackages: [[String: Any]] = []
|
||||
|
||||
for package in packageArray {
|
||||
var shouldKeep = false
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
let isCore = packageType == "core"
|
||||
|
||||
let condition = package["Condition"] as? String ?? ""
|
||||
|
||||
if isCore {
|
||||
if condition.isEmpty {
|
||||
shouldKeep = true
|
||||
} else {
|
||||
if condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") {
|
||||
shouldKeep = true
|
||||
}
|
||||
if condition.contains("[installLanguage]==\(task.language)") || task.language == "ALL" {
|
||||
shouldKeep = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldKeep = (condition.contains("[installLanguage]==\(task.language)") || task.language == "ALL")
|
||||
}
|
||||
|
||||
if shouldKeep {
|
||||
filteredPackages.append(package)
|
||||
}
|
||||
}
|
||||
|
||||
packages["Package"] = filteredPackages
|
||||
appInfo["Packages"] = packages
|
||||
}
|
||||
|
||||
if let processedData = try? JSONSerialization.data(withJSONObject: appInfo, options: .prettyPrinted),
|
||||
let processedString = String(data: processedData, encoding: .utf8) {
|
||||
processedJsonString = processedString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let jsonURL = productDir.appendingPathComponent("application.json")
|
||||
try processedJsonString.write(to: jsonURL, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
guard let jsonData = processedJsonString.data(using: .utf8),
|
||||
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
||||
let packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] else {
|
||||
throw NetworkError.invalidData("无法解析产品信息")
|
||||
}
|
||||
// TODO: 暂时没想好module怎么处理
|
||||
// let modules = appInfo["Modules"] as? [String: Any]
|
||||
// let moduleArray = modules?["Module"] as? [[String: Any]] ?? []
|
||||
|
||||
var corePackageCount = 0
|
||||
var nonCorePackageCount = 0
|
||||
@@ -175,134 +228,93 @@ class NewDownloadUtils {
|
||||
2. 如果没有Condition,就下载
|
||||
3. 如果有Condition,目前分析到的基本上是语言之分
|
||||
i. 判断是否为目标语言
|
||||
|
||||
|
||||
PS: 下面是留给看源码的人的
|
||||
哪怕是官方的ACC下载任何一款App,都是这个逻辑,不信自己去翻,你可能会说,为什么官方能下通用的,你问这个问题之前,可以自己去拿正版的看看他是怎么下载的,他下载的包数量跟我的是不是一致的,他也只是下载了对应架构的包
|
||||
|
||||
其实要下载通用的也很简单,不是判断架构吗,那下载通用的时候,两个架构同时成立不就好了,但我并没有在官方的下载逻辑中看到,也没尝试过,如果你尝试之后发现可以,请你告诉我
|
||||
*/
|
||||
|
||||
for package in packageArray {
|
||||
var shouldDownload = false
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
let isCore = packageType == "core"
|
||||
|
||||
guard let downloadURL = package["Path"] as? String, !downloadURL.isEmpty else { continue }
|
||||
|
||||
let packageVersion: String = package["PackageVersion"] as? String ?? ""
|
||||
let fullPackageName: String
|
||||
let packageVersion: String
|
||||
|
||||
if let name = package["fullPackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = name
|
||||
packageVersion = package["PackageVersion"] as? String ?? ""
|
||||
} else if let name = package["PackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = "\(name).zip"
|
||||
packageVersion = package["PackageVersion"] as? String ?? ""
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
let downloadSize: Int64
|
||||
if let sizeNumber = package["DownloadSize"] as? NSNumber {
|
||||
downloadSize = sizeNumber.int64Value
|
||||
} else if let sizeString = package["DownloadSize"] as? String,
|
||||
let parsedSize = Int64(sizeString) {
|
||||
downloadSize = parsedSize
|
||||
} else if let sizeInt = package["DownloadSize"] as? Int {
|
||||
downloadSize = Int64(sizeInt)
|
||||
} else { continue }
|
||||
|
||||
if dependencyToDownload.sapCode != productInfo.id {
|
||||
|
||||
switch package["DownloadSize"] {
|
||||
case let sizeNumber as NSNumber:
|
||||
downloadSize = sizeNumber.int64Value
|
||||
case let sizeString as String:
|
||||
guard let parsedSize = Int64(sizeString) else { continue }
|
||||
downloadSize = parsedSize
|
||||
case let sizeInt as Int:
|
||||
downloadSize = Int64(sizeInt)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
let isCore = packageType == "core"
|
||||
let installLanguage = "[installLanguage]==\(task.language)"
|
||||
let condition = package["Condition"] as? String ?? ""
|
||||
|
||||
var shouldDownload = false
|
||||
|
||||
if dependencyToDownload.sapCode == productInfo.id {
|
||||
if isCore {
|
||||
let installLanguage = "[installLanguage]==\(task.language)"
|
||||
if let condition = package["Condition"] as? String {
|
||||
if condition.isEmpty {
|
||||
shouldDownload = true
|
||||
} else {
|
||||
if condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") {
|
||||
shouldDownload = true
|
||||
}
|
||||
// if condition.contains("[OSArchitecture]==x64") {
|
||||
// shouldDownload = true
|
||||
// }
|
||||
if condition.contains(installLanguage) || task.language == "ALL" {
|
||||
shouldDownload = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldDownload = true
|
||||
}
|
||||
shouldDownload = condition.isEmpty ||
|
||||
condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") ||
|
||||
condition.contains(installLanguage) || task.language == "ALL"
|
||||
} else {
|
||||
shouldDownload = false
|
||||
shouldDownload = condition.contains(installLanguage) || task.language == "ALL"
|
||||
}
|
||||
} else {
|
||||
let installLanguage = "[installLanguage]==\(task.language)"
|
||||
if let condition = package["Condition"] as? String {
|
||||
if condition.isEmpty {
|
||||
shouldDownload = true
|
||||
} else {
|
||||
if condition.contains("[OSVersion]") {
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let currentVersion = Double("\(osVersion.majorVersion).\(osVersion.minorVersion)") ?? 0.0
|
||||
|
||||
let versionPattern = #"\[OSVersion\](>=|<=|<|>|==)([\d.]+)"#
|
||||
let regex = try? NSRegularExpression(pattern: versionPattern)
|
||||
let range = NSRange(condition.startIndex..<condition.endIndex, in: condition)
|
||||
|
||||
if let matches = regex?.matches(in: condition, range: range) {
|
||||
var meetsAllConditions = true
|
||||
|
||||
for match in matches {
|
||||
guard let operatorRange = Range(match.range(at: 1), in: condition),
|
||||
let versionRange = Range(match.range(at: 2), in: condition),
|
||||
let requiredVersion = Double(condition[versionRange]) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let operatorSymbol = String(condition[operatorRange])
|
||||
let meets = compareVersions(current: currentVersion, required: requiredVersion, operator: operatorSymbol)
|
||||
|
||||
if !meets {
|
||||
meetsAllConditions = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if meetsAllConditions {
|
||||
shouldDownload = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if condition.contains(installLanguage) || task.language == "ALL" {
|
||||
shouldDownload = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldDownload = true
|
||||
}
|
||||
}
|
||||
|
||||
if isCore {
|
||||
corePackageCount += 1
|
||||
} else {
|
||||
nonCorePackageCount += 1
|
||||
shouldDownload = condition.isEmpty ||
|
||||
(condition.contains("[OSVersion]") && checkOSVersionCondition(condition)) ||
|
||||
condition.contains(installLanguage) || task.language == "ALL"
|
||||
}
|
||||
|
||||
isCore ? (corePackageCount += 1) : (nonCorePackageCount += 1)
|
||||
|
||||
if shouldDownload {
|
||||
let newPackage = Package(
|
||||
dependencyToDownload.packages.append(Package(
|
||||
type: packageType,
|
||||
fullPackageName: fullPackageName,
|
||||
downloadSize: downloadSize,
|
||||
downloadURL: downloadURL,
|
||||
packageVersion: packageVersion
|
||||
)
|
||||
dependencyToDownload.packages.append(newPackage)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func checkOSVersionCondition(_ condition: String) -> Bool {
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let currentVersion = Double("\(osVersion.majorVersion).\(osVersion.minorVersion)") ?? 0.0
|
||||
|
||||
let versionPattern = #"\[OSVersion\](>=|<=|<|>|==)([\d.]+)"#
|
||||
guard let regex = try? NSRegularExpression(pattern: versionPattern) else { return false }
|
||||
|
||||
let nsRange = NSRange(condition.startIndex..<condition.endIndex, in: condition)
|
||||
let matches = regex.matches(in: condition, range: nsRange)
|
||||
|
||||
for match in matches {
|
||||
guard match.numberOfRanges >= 3,
|
||||
let operatorRange = Range(match.range(at: 1), in: condition),
|
||||
let versionRange = Range(match.range(at: 2), in: condition),
|
||||
let requiredVersion = Double(condition[versionRange]) else { continue }
|
||||
|
||||
let operatorSymbol = String(condition[operatorRange])
|
||||
if !compareVersions(current: currentVersion, required: requiredVersion, operator: operatorSymbol) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return !matches.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
let finalProducts = dependenciesToDownload
|
||||
@@ -490,8 +502,7 @@ class NewDownloadUtils {
|
||||
)))
|
||||
|
||||
if let currentPackage = task.currentPackage {
|
||||
let destinationDir = task.directory
|
||||
.appendingPathComponent("\(task.productId ?? "")")
|
||||
let destinationDir = task.directory.appendingPathComponent("\(task.productId)")
|
||||
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
@@ -714,27 +725,35 @@ class NewDownloadUtils {
|
||||
}
|
||||
|
||||
// 构建依赖列表
|
||||
let dependencies = languageSet.first?.dependencies.map { dependency in
|
||||
let dependencies = (languageSet.first?.dependencies.map { dependency in
|
||||
"""
|
||||
<Dependency>
|
||||
<SAPCode>\(productInfo.id)</SAPCode>
|
||||
<BaseVersion>\(languageSet.first?.baseVersion)</BaseVersion>
|
||||
<EsdDirectory>\(productInfo.id)</EsdDirectory>
|
||||
</Dependency>
|
||||
<Dependency>
|
||||
<BuildGuid>\(dependency.buildGuid)</BuildGuid>
|
||||
<BuildVersion>\(dependency.productVersion)</BuildVersion>
|
||||
<CodexVersion>\(dependency.baseVersion)</CodexVersion>
|
||||
<Platform>\(dependency.selectedPlatform)</Platform>
|
||||
<SAPCode>\(dependency.sapCode)</SAPCode>
|
||||
<EsdDirectory>\(dependency.sapCode)</EsdDirectory>
|
||||
</Dependency>
|
||||
"""
|
||||
}.joined(separator: "\n")
|
||||
}.joined(separator: "\n")) ?? ""
|
||||
|
||||
let buildGuid = productInfo.platforms.first?.languageSet.first?.buildGuid ?? ""
|
||||
let buildVersion = languageSet.first?.productVersion ?? ""
|
||||
|
||||
return """
|
||||
<DriverInfo>
|
||||
<ProductInfo>
|
||||
<n>Adobe \(displayName)</n>
|
||||
<SAPCode>\(productInfo.id)</SAPCode>
|
||||
<CodexVersion>\(version)</CodexVersion>
|
||||
<Platform>mac</Platform>
|
||||
<EsdDirectory>\(productInfo.id)</EsdDirectory>
|
||||
<Dependencies>
|
||||
\(dependencies)
|
||||
</Dependencies>
|
||||
<Modules></Modules>
|
||||
<BuildGuid>\(buildGuid)</BuildGuid>
|
||||
<BuildVersion>\(buildVersion)</BuildVersion>
|
||||
<CodexVersion>\(productInfo.version)</CodexVersion>
|
||||
<Platform>\(platform)</Platform>
|
||||
<EsdDirectory>\(productInfo.id)</EsdDirectory>
|
||||
<SAPCode>\(productInfo.id)</SAPCode>
|
||||
</ProductInfo>
|
||||
<RequestInfo>
|
||||
<InstallDir>/Applications</InstallDir>
|
||||
@@ -746,9 +765,11 @@ class NewDownloadUtils {
|
||||
|
||||
func downloadAPRO(task: NewDownloadTask, productInfo: Product) async throws {
|
||||
let firstPlatform = productInfo.platforms.first
|
||||
let buildGuid = firstPlatform?.languageSet.first?.buildGuid ?? ""
|
||||
let productManifestURL = firstPlatform?.languageSet.first?.manifestURL ?? ""
|
||||
|
||||
let manifestURL = globalCdn + buildGuid
|
||||
let manifestURL = globalCdn + productManifestURL
|
||||
print("manifestURL")
|
||||
print(manifestURL)
|
||||
guard let url = URL(string: manifestURL) else {
|
||||
throw NetworkError.invalidURL(manifestURL)
|
||||
}
|
||||
@@ -757,7 +778,6 @@ class NewDownloadUtils {
|
||||
request.httpMethod = "GET"
|
||||
|
||||
var headers = NetworkConstants.adobeRequestHeaders
|
||||
headers["x-adobe-build-guid"] = buildGuid
|
||||
|
||||
headers.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
|
||||
|
||||
@@ -775,6 +795,8 @@ class NewDownloadUtils {
|
||||
throw NetworkError.invalidURL(downloadPath)
|
||||
}
|
||||
|
||||
print("downloadURL \(downloadURL)")
|
||||
|
||||
let aproPackage = Package(
|
||||
type: "dmg",
|
||||
fullPackageName: "Adobe Downloader \(task.productId)_\(firstPlatform?.languageSet.first?.productVersion ?? "unknown")_\(firstPlatform?.id ?? "unknown").dmg",
|
||||
@@ -783,8 +805,10 @@ class NewDownloadUtils {
|
||||
packageVersion: ""
|
||||
)
|
||||
|
||||
print(aproPackage)
|
||||
|
||||
await MainActor.run {
|
||||
let product = DependenciesToDownload(sapCode: task.productId, version: firstPlatform?.languageSet.first?.productVersion ?? "unknown", buildGuid: buildGuid)
|
||||
let product = DependenciesToDownload(sapCode: task.productId, version: firstPlatform?.languageSet.first?.productVersion ?? "unknown", buildGuid: "")
|
||||
product.packages = [aproPackage]
|
||||
task.dependenciesToDownload = [product]
|
||||
task.totalSize = assetSize
|
||||
|
||||
@@ -231,6 +231,8 @@ class NewJSONParser {
|
||||
continue
|
||||
}
|
||||
|
||||
if productDisplayName == "Creative Cloud" { 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,
|
||||
|
||||
@@ -72,7 +72,7 @@ struct AboutView: View {
|
||||
}
|
||||
.tag("about_app")
|
||||
}
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
.background(Color(.clear))
|
||||
.frame(width: 600)
|
||||
.onAppear {
|
||||
selectedTab = "general_settings"
|
||||
|
||||
@@ -294,7 +294,8 @@ struct AppCardView: View {
|
||||
@StateObject private var viewModel: AppCardViewModel
|
||||
@StorageValue(\.useDefaultLanguage) private var useDefaultLanguage
|
||||
@StorageValue(\.defaultLanguage) private var defaultLanguage
|
||||
|
||||
@State private var isHovered = false
|
||||
|
||||
init(uniqueProduct: UniqueProduct) {
|
||||
_viewModel = StateObject(wrappedValue: AppCardViewModel(uniqueProduct: uniqueProduct))
|
||||
}
|
||||
@@ -308,6 +309,26 @@ struct AppCardView: View {
|
||||
DownloadButtonView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(isHovered ? Color(.controlBackgroundColor) : Color(.windowBackgroundColor).opacity(0.5))
|
||||
.animation(.easeInOut(duration: 0.2), value: isHovered)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(isHovered ? Color.blue.opacity(0.5) : Color.gray.opacity(0.1), lineWidth: isHovered ? 2 : 1)
|
||||
.animation(.easeInOut(duration: 0.2), value: isHovered)
|
||||
)
|
||||
.shadow(color: isHovered ? Color.black.opacity(0.1) : Color.black.opacity(0.05),
|
||||
radius: isHovered ? 4 : 2,
|
||||
x: 0,
|
||||
y: isHovered ? 2 : 1)
|
||||
.animation(.easeInOut(duration: 0.2), value: isHovered)
|
||||
.onHover { hovering in
|
||||
self.isHovered = hovering
|
||||
}
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isHovered)
|
||||
.contentShape(Rectangle())
|
||||
.modifier(CardModifier())
|
||||
.modifier(SheetModifier(viewModel: viewModel))
|
||||
.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
|
||||
@@ -417,9 +438,11 @@ private struct ProductInfoView: View {
|
||||
MetricView(icon: "square.stack.3d.up", value: "\(modulesCount)")
|
||||
}
|
||||
}
|
||||
.background(Color(.clear))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.background(Color(.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,10 +489,7 @@ private struct CardModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: AppCardConstants.cornerRadius)
|
||||
.fill(Color(NSColor.windowBackgroundColor))
|
||||
)
|
||||
.background(Color(NSColor.clear))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppCardConstants.cornerRadius)
|
||||
.stroke(Color.gray.opacity(AppCardConstants.strokeOpacity),
|
||||
|
||||
@@ -5,11 +5,11 @@ struct BannerView: View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundColor(.orange)
|
||||
Text("Adobe Downloader 完全免费: https://github.com/X1a0He/Adobe-Downloader")
|
||||
Text("Adobe Downloader 完全开源免费: https://github.com/X1a0He/Adobe-Downloader")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 5)
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
.background(Color(.clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ struct DownloadManagerView: View {
|
||||
removeTask: removeTask
|
||||
)
|
||||
}
|
||||
.background(Color(.clear))
|
||||
.frame(width:800, height: 600)
|
||||
}
|
||||
}
|
||||
@@ -86,16 +87,7 @@ private struct DownloadManagerToolbar: View {
|
||||
|
||||
Divider()
|
||||
}
|
||||
.background(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color(NSColor.windowBackgroundColor).opacity(0.95),
|
||||
Color(NSColor.windowBackgroundColor)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.background(Color(NSColor.clear))
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 2, x: 0, y: 1)
|
||||
}
|
||||
}
|
||||
@@ -143,6 +135,9 @@ private struct ToolbarButtons: View {
|
||||
if case .completed = task.status {
|
||||
return true
|
||||
}
|
||||
if case .failed = task.status {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
globalNetworkManager.updateDockBadge()
|
||||
@@ -160,6 +155,7 @@ private struct ToolbarButtons: View {
|
||||
}
|
||||
.buttonStyle(BeautifulButtonStyle(baseColor: .red))
|
||||
}
|
||||
.background(Color(NSColor.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +180,7 @@ private struct DownloadTaskList: View {
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
.background(Color(NSColor.clear))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ struct DownloadProgressView: View {
|
||||
},
|
||||
onRetry: nil
|
||||
)
|
||||
} else if case .failed(let error) = globalNetworkManager.installationState {
|
||||
} else if case .failed(let error, let errorDetails) = globalNetworkManager.installationState {
|
||||
InstallProgressView(
|
||||
productName: task.displayName,
|
||||
progress: 0,
|
||||
@@ -270,7 +270,8 @@ struct DownloadProgressView: View {
|
||||
Task {
|
||||
await globalNetworkManager.retryInstallation(at: task.directory)
|
||||
}
|
||||
}
|
||||
},
|
||||
errorDetails: errorDetails
|
||||
)
|
||||
} else {
|
||||
InstallProgressView(
|
||||
@@ -285,7 +286,7 @@ struct DownloadProgressView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 400, minHeight: 200)
|
||||
.frame(minWidth: 700, minHeight: 200)
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,21 @@ struct InstallProgressView: View {
|
||||
let status: String
|
||||
let onCancel: () -> Void
|
||||
let onRetry: (() -> Void)?
|
||||
let errorDetails: String?
|
||||
|
||||
init(productName: String,
|
||||
progress: Double,
|
||||
status: String,
|
||||
onCancel: @escaping () -> Void,
|
||||
onRetry: (() -> Void)? = nil,
|
||||
errorDetails: String? = nil) {
|
||||
self.productName = productName
|
||||
self.progress = progress
|
||||
self.status = status
|
||||
self.onCancel = onCancel
|
||||
self.onRetry = onRetry
|
||||
self.errorDetails = errorDetails
|
||||
}
|
||||
|
||||
private var isCompleted: Bool {
|
||||
progress >= 1.0 || status == String(localized: "安装完成")
|
||||
@@ -79,7 +94,8 @@ struct InstallProgressView: View {
|
||||
if isFailed {
|
||||
ErrorSection(
|
||||
status: status,
|
||||
isFailed: true
|
||||
isFailed: true,
|
||||
errorDetails: errorDetails
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,11 +107,10 @@ struct InstallProgressView: View {
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
.frame(minWidth: 500)
|
||||
.frame(minWidth: 600)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color(NSColor.windowBackgroundColor))
|
||||
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
|
||||
.fill(Color(NSColor.clear))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -147,6 +162,15 @@ private struct ProgressSection: View {
|
||||
private struct ErrorSection: View {
|
||||
let status: String
|
||||
let isFailed: Bool
|
||||
let errorDetails: String?
|
||||
|
||||
init(status: String,
|
||||
isFailed: Bool,
|
||||
errorDetails: String? = nil) {
|
||||
self.status = status
|
||||
self.isFailed = isFailed
|
||||
self.errorDetails = errorDetails
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -175,6 +199,37 @@ private struct ErrorSection: View {
|
||||
.strokeBorder(Color.red.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
|
||||
if let errorDetails = errorDetails, !errorDetails.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "doc.text.magnifyingglass")
|
||||
.foregroundColor(.orange.opacity(0.7))
|
||||
.font(.system(size: 14))
|
||||
Text("日志详情")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundColor(.primary.opacity(0.8))
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Text(errorDetails)
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
.textSelection(.enabled)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.fill(Color.orange.opacity(0.05))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.strokeBorder(Color.orange.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.frame(maxHeight: 120)
|
||||
}
|
||||
}
|
||||
|
||||
if isFailed {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "terminal.fill")
|
||||
@@ -351,7 +406,8 @@ private struct CommandPopover: View {
|
||||
progress: 0.45,
|
||||
status: "正在安装核心组件...",
|
||||
onCancel: {},
|
||||
onRetry: nil
|
||||
onRetry: nil,
|
||||
errorDetails: nil
|
||||
)
|
||||
.environmentObject(networkManager)
|
||||
}
|
||||
@@ -363,7 +419,8 @@ private struct CommandPopover: View {
|
||||
progress: 0.0,
|
||||
status: "安装失败: 权限被拒绝",
|
||||
onCancel: {},
|
||||
onRetry: {}
|
||||
onRetry: {},
|
||||
errorDetails: "详细错误日志"
|
||||
)
|
||||
.environmentObject(networkManager)
|
||||
.onAppear {
|
||||
@@ -378,7 +435,8 @@ private struct CommandPopover: View {
|
||||
progress: 1.0,
|
||||
status: "安装完成",
|
||||
onCancel: {},
|
||||
onRetry: nil
|
||||
onRetry: nil,
|
||||
errorDetails: nil
|
||||
)
|
||||
.environmentObject(networkManager)
|
||||
}
|
||||
@@ -390,7 +448,8 @@ private struct CommandPopover: View {
|
||||
progress: 0.75,
|
||||
status: "正在安装...",
|
||||
onCancel: {},
|
||||
onRetry: nil
|
||||
onRetry: nil,
|
||||
errorDetails: nil
|
||||
)
|
||||
.environmentObject(networkManager)
|
||||
.preferredColorScheme(.dark)
|
||||
|
||||
@@ -7,9 +7,6 @@ struct MainContentView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(NSColor.windowBackgroundColor)
|
||||
.ignoresSafeArea()
|
||||
|
||||
switch loadingState {
|
||||
case .idle, .loading:
|
||||
ProgressView("正在加载...")
|
||||
@@ -50,6 +47,7 @@ struct MainContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color(.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,5 +95,6 @@ struct ProductGridView: View {
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.background(Color(.clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,6 @@ struct ToolbarView: View {
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
.background(Color(.clear))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ private struct ExistingPathButton: View {
|
||||
|
||||
var body: some View {
|
||||
if isVisible {
|
||||
Text("可能已存在目录")
|
||||
Text("已存在")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundColor(.blue.opacity(0.9))
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"(可能导致处理失败)" : {
|
||||
|
||||
},
|
||||
"(将导致无法使用安装功能)" : {
|
||||
"extractionState" : "stale",
|
||||
@@ -40,12 +37,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"(无法使用安装功能)" : {
|
||||
|
||||
},
|
||||
"(退出代码: %lld)" : {
|
||||
|
||||
},
|
||||
"/" : {
|
||||
|
||||
@@ -156,6 +147,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"✅" : {
|
||||
|
||||
},
|
||||
"❌" : {
|
||||
|
||||
},
|
||||
"🔔 即将下载 %@ (%@) 版本 🔔" : {
|
||||
"extractionState" : "stale",
|
||||
@@ -173,12 +170,8 @@
|
||||
},
|
||||
"Adobe Downloader %@" : {
|
||||
|
||||
},
|
||||
"Adobe Downloader 完全免费: https://github.com/X1a0He/Adobe-Downloader" : {
|
||||
|
||||
},
|
||||
"Adobe Downloader 完全开源免费: https://github.com/X1a0He/Adobe-Downloader" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -314,7 +307,6 @@
|
||||
|
||||
},
|
||||
"Debug 模式" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -452,9 +444,15 @@
|
||||
},
|
||||
"macOS %@" : {
|
||||
|
||||
},
|
||||
"Match:" : {
|
||||
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
},
|
||||
"Reason:" : {
|
||||
|
||||
},
|
||||
"Setup 组件安装成功" : {
|
||||
"extractionState" : "stale",
|
||||
@@ -486,9 +484,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Setup 组件未被处理 (退出代码: %lld)" : {
|
||||
|
||||
},
|
||||
"Setup未备份提示" : {
|
||||
"localizations" : {
|
||||
@@ -499,6 +494,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Target:" : {
|
||||
|
||||
},
|
||||
"v%@" : {
|
||||
|
||||
@@ -1152,6 +1150,7 @@
|
||||
}
|
||||
},
|
||||
"可能已存在目录" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1418,9 +1417,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"安装文件不完整或损坏 (退出代码: %lld)" : {
|
||||
|
||||
},
|
||||
"安装状态: " : {
|
||||
|
||||
@@ -1468,7 +1464,6 @@
|
||||
}
|
||||
},
|
||||
"将执行的命令:" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1512,7 +1507,6 @@
|
||||
|
||||
},
|
||||
"展开全部" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1539,6 +1533,7 @@
|
||||
|
||||
},
|
||||
"已处理" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1549,6 +1544,7 @@
|
||||
}
|
||||
},
|
||||
"已备份" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -1567,6 +1563,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"已存在" : {
|
||||
|
||||
},
|
||||
"已安装 (build %@)" : {
|
||||
"localizations" : {
|
||||
@@ -1762,7 +1761,6 @@
|
||||
}
|
||||
},
|
||||
"折叠全部" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2014,6 +2012,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"日志详情" : {
|
||||
|
||||
},
|
||||
"是否确认重新下载?这将覆盖现有的安装程序。" : {
|
||||
"localizations" : {
|
||||
@@ -2106,12 +2107,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"未处理" : {
|
||||
|
||||
},
|
||||
"未备份" : {
|
||||
|
||||
},
|
||||
"未安装" : {
|
||||
"localizations" : {
|
||||
@@ -2209,15 +2204,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"权限问题 (退出代码: %lld)" : {
|
||||
|
||||
},
|
||||
"架构或版本不一致 (退出代码: %lld)" : {
|
||||
|
||||
},
|
||||
"查看" : {
|
||||
|
||||
},
|
||||
"查看持久化文件" : {
|
||||
|
||||
},
|
||||
"检查中" : {
|
||||
"localizations" : {
|
||||
@@ -2413,6 +2405,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"正在清理安装环境..." : {
|
||||
|
||||
},
|
||||
"正在清理安装记录..." : {
|
||||
"localizations" : {
|
||||
@@ -3150,6 +3145,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"错误代码(%lld),请查看日志详情并向开发者汇报" : {
|
||||
|
||||
},
|
||||
"错误详情" : {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user