fix: Adjust the way to obtain the version number of the Setup component to fix the crash issue under macOS 13.

This commit is contained in:
X1a0He
2024-11-14 13:30:13 +08:00
parent c44b3f3c9c
commit 6411b478aa
11 changed files with 496 additions and 459 deletions

View File

@@ -30,9 +30,9 @@
filePath = "Adobe Downloader/Utils/InstallManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "132"
endingLineNumber = "132"
landmarkName = "retry(at:progressHandler:logHandler:)"
startingLineNumber = "128"
endingLineNumber = "128"
landmarkName = "retry(at:progressHandler:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>

View File

@@ -71,6 +71,7 @@ enum NetworkError: Error, LocalizedError {
case unsupportedPlatform(String)
case incompatibleVersion(String, String)
case cancelled
case installError(String)
var errorCode: Int {
switch self {
@@ -97,6 +98,7 @@ enum NetworkError: Error, LocalizedError {
case .unsupportedPlatform: return 7002
case .incompatibleVersion: return 7003
case .cancelled: return 5004
case .installError: return 8001
}
}
@@ -157,6 +159,8 @@ enum NetworkError: Error, LocalizedError {
return NSLocalizedString("版本不兼容: 当前版本 \(current), 需要版本 \(required)", comment: "Incompatible version")
case .cancelled:
return NSLocalizedString("下载已取消", comment: "Download cancelled")
case .installError(let message):
return NSLocalizedString("安装错误: \(message)", comment: "Install error")
}
}

View File

@@ -15,13 +15,6 @@ class NetworkManager: ObservableObject {
@Published var loadingState: LoadingState = .idle
@Published var downloadTasks: [NewDownloadTask] = []
@Published var installationState: InstallationState = .idle
@Published var installationLogs: [String] = [] {
didSet {
if installationLogs.count > 1000 {
installationLogs = Array(installationLogs.suffix(1000))
}
}
}
@Published var installCommand: String = ""
private let cancelTracker = CancelTracker()
internal var downloadUtils: DownloadUtils!
@@ -205,7 +198,6 @@ class NetworkManager: ObservableObject {
func installProduct(at path: URL) async {
await MainActor.run {
installationState = .installing(progress: 0, status: "准备安装...")
installationLogs.removeAll()
}
do {
@@ -219,11 +211,6 @@ class NetworkManager: ObservableObject {
self.installationState = .installing(progress: progress, status: status)
}
}
},
logHandler: { log in
Task { @MainActor in
self.installationLogs.append(log)
}
}
)
@@ -284,11 +271,6 @@ class NetworkManager: ObservableObject {
self.installationState = .installing(progress: progress, status: status)
}
}
},
logHandler: { log in
Task { @MainActor in
self.installationLogs.append(log)
}
}
)
@@ -380,45 +362,4 @@ class NetworkManager: ObservableObject {
downloadTasks.append(contentsOf: savedTasks)
updateDockBadge()
}
private func fetchProductsWithVersion(_ version: String) async throws -> [String: Sap] {
var components = URLComponents(string: NetworkConstants.productsXmlURL)
components?.queryItems = [
URLQueryItem(name: "_type", value: "xml"),
URLQueryItem(name: "channel", value: "ccm"),
URLQueryItem(name: "channel", value: "sti"),
URLQueryItem(name: "platform", value: "osx10-64,osx10,macarm64,macuniversal"),
URLQueryItem(name: "productType", value: "Desktop"),
URLQueryItem(name: "version", value: version)
]
guard let url = components?.url else {
throw NetworkError.invalidURL(NetworkConstants.productsXmlURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
NetworkConstants.adobeRequestHeaders.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpError(httpResponse.statusCode, nil)
}
guard let xmlString = String(data: data, encoding: .utf8) else {
throw NetworkError.invalidData("无法解码XML数据")
}
let result = try await Task.detached(priority: .userInitiated) {
let parseResult = try XHXMLParser.parse(xmlString: xmlString)
return parseResult.products
}.value
return result
}
}

View File

@@ -505,10 +505,15 @@ class DownloadUtils {
let (manifestData, _) = try await URLSession.shared.data(for: request)
let manifestXML = try XMLDocument(data: manifestData)
#if DEBUG
if let manifestString = String(data: manifestData, encoding: .utf8) {
print("Manifest内容: \(manifestString)")
}
#endif
let manifestDoc = try XMLDocument(data: manifestData)
guard let downloadPath = try manifestXML.nodes(forXPath: "//asset_list/asset/asset_path").first?.stringValue,
let assetSizeStr = try manifestXML.nodes(forXPath: "//asset_list/asset/asset_size").first?.stringValue,
guard let downloadPath = try manifestDoc.nodes(forXPath: "//asset_list/asset/asset_path").first?.stringValue,
let assetSizeStr = try manifestDoc.nodes(forXPath: "//asset_list/asset/asset_size").first?.stringValue,
let assetSize = Int64(assetSizeStr) else {
throw NetworkError.invalidData("无法从manifest中获取下载信息")
}
@@ -950,140 +955,181 @@ class DownloadUtils {
}
}
func downloadSetupComponents(
func downloadX1a0HeCCPackages(
progressHandler: @escaping (Double, String) -> Void,
cancellationHandler: @escaping () -> Bool
) async throws {
let architecture = AppStatics.isAppleSilicon ? "arm" : "intel"
let baseURLs = [
"https://github.com/X1a0He/Adobe-Downloader/raw/refs/heads/main/X1a0HeCC/\(architecture)/HDBox/",
"https://github.com/X1a0He/Adobe-Downloader/raw/refs/heads/develop/X1a0HeCC/\(architecture)/HDBox/"
]
let components = [
"HDHelper",
"HDIM.dylib",
"HDPIM.dylib",
"HUM.dylib",
"Setup"
]
let targetDirectory = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox"
let baseUrl = "https://cdn-ffc.oobesaas.adobe.com/core/v1/applications?name=CreativeCloud&platform=\(AppStatics.isAppleSilicon ? "macarm64" : "osx10")"
let tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: tempDirectory.path)
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 300
configuration.httpAdditionalHeaders = NetworkConstants.downloadHeaders
let session = URLSession(configuration: configuration)
do {
var request = URLRequest(url: URL(string: baseUrl)!)
NetworkConstants.downloadHeaders.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
let xmlDoc = try XMLDocument(data: data)
for (index, component) in components.enumerated() {
if cancellationHandler() {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.cancelled
let packageSets = try xmlDoc.nodes(forXPath: "//packageSet[name='ADC']")
guard let adcPackageSet = packageSets.first else {
throw NetworkError.invalidData("找不到ADC包集")
}
await MainActor.run {
progressHandler(Double(index) / Double(components.count), "正在下载 \(component)...")
let targetPackages = ["HDBox", "IPCBox"]
var packagesToDownload: [(name: String, url: URL, size: Int64)] = []
for packageName in targetPackages {
let packageNodes = try adcPackageSet.nodes(forXPath: ".//package[name='\(packageName)']")
guard let package = packageNodes.first else {
print("未找到包: \(packageName)")
continue
}
guard let manifestUrl = try package.nodes(forXPath: ".//manifestUrl").first?.stringValue,
let cdnBase = try xmlDoc.nodes(forXPath: "//cdn/secure").first?.stringValue else {
print("无法获取manifest URL或CDN基础URL")
continue
}
let manifestFullUrl = cdnBase + manifestUrl
var manifestRequest = URLRequest(url: URL(string: manifestFullUrl)!)
NetworkConstants.downloadHeaders.forEach { manifestRequest.setValue($0.value, forHTTPHeaderField: $0.key) }
let (manifestData, manifestResponse) = try await session.data(for: manifestRequest)
guard let manifestHttpResponse = manifestResponse as? HTTPURLResponse,
(200...299).contains(manifestHttpResponse.statusCode) else {
print("获取manifest失败: HTTP \(String(describing: (manifestResponse as? HTTPURLResponse)?.statusCode))")
continue
}
#if DEBUG
if let manifestString = String(data: manifestData, encoding: .utf8) {
print("Manifest内容: \(manifestString)")
}
#endif
let manifestDoc = try XMLDocument(data: manifestData)
let assetPathNodes = try manifestDoc.nodes(forXPath: "//asset_path")
let sizeNodes = try manifestDoc.nodes(forXPath: "//asset_size")
guard let assetPath = assetPathNodes.first?.stringValue,
let sizeStr = sizeNodes.first?.stringValue,
let size = Int64(sizeStr),
let downloadUrl = URL(string: assetPath) else {
continue
}
packagesToDownload.append((packageName, downloadUrl, size))
}
guard !packagesToDownload.isEmpty else {
throw NetworkError.invalidData("没有找到可下载的包")
}
var lastError: Error? = nil
var downloaded = false
for baseURL in baseURLs {
guard !downloaded else { break }
let totalCount = packagesToDownload.count
for (index, package) in packagesToDownload.enumerated() {
if cancellationHandler() {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.cancelled
}
await MainActor.run {
progressHandler(Double(index) / Double(totalCount), "正在下载 \(package.name)...")
}
let url = URL(string: baseURL + component)!
let destinationURL = tempDirectory.appendingPathComponent(component)
print(url)
do {
let (downloadURL, response) = try await session.download(from: url)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
if httpResponse.statusCode == 404 {
lastError = NetworkError.fileNotFound(component)
continue
}
if !(200...299).contains(httpResponse.statusCode) {
lastError = NetworkError.httpError(httpResponse.statusCode, "下载 \(component) 失败")
continue
}
try FileManager.default.moveItem(at: downloadURL, to: destinationURL)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: destinationURL.path)
downloaded = true
} catch URLError.timedOut {
lastError = NetworkError.timeout
continue
} catch {
lastError = error is NetworkError ? error : NetworkError.downloadError("下载 \(component) 失败: \(error.localizedDescription)", error)
let destinationURL = tempDirectory.appendingPathComponent("\(package.name).zip")
var downloadRequest = URLRequest(url: package.url)
NetworkConstants.downloadHeaders.forEach { downloadRequest.setValue($0.value, forHTTPHeaderField: $0.key) }
let (downloadURL, downloadResponse) = try await session.download(for: downloadRequest)
guard let httpResponse = downloadResponse as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("下载失败: HTTP \(String(describing: (downloadResponse as? HTTPURLResponse)?.statusCode))")
continue
}
try FileManager.default.moveItem(at: downloadURL, to: destinationURL)
}
if !downloaded {
try? FileManager.default.removeItem(at: tempDirectory)
throw lastError ?? NetworkError.downloadError("无法下载 \(component)", nil)
await MainActor.run {
progressHandler(0.9, "正在安装组件...")
}
}
let targetDirectory = "/Library/Application Support/Adobe/Adobe Desktop Common"
await MainActor.run {
progressHandler(1.0, "下载完成,正在安装...")
}
if !FileManager.default.fileExists(atPath: targetDirectory) {
let baseCommands = [
"mkdir -p '\(targetDirectory)'",
"chmod 755 '\(targetDirectory)'"
]
let commands = [
"mkdir -p '\(targetDirectory)'",
"chmod 755 '\(targetDirectory)'",
"cp -f '\(tempDirectory.path)/'* '\(targetDirectory)/'",
"chmod 755 '\(targetDirectory)/'*",
"chown -R root:wheel '\(targetDirectory)'"
]
for command in commands {
let result = await withCheckedContinuation { continuation in
PrivilegedHelperManager.shared.executeCommand(command) { result in
continuation.resume(returning: result)
for command in baseCommands {
let result = await withCheckedContinuation { continuation in
PrivilegedHelperManager.shared.executeCommand(command) { result in
continuation.resume(returning: result)
}
}
if result.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("创建目录失败: \(result)")
}
}
}
if result.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NSError(domain: "SetupComponentsError",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "安装组件失败: \(result)"])
}
}
try await withCheckedThrowingContinuation { continuation in
ModifySetup.backupAndModifySetupFile { success, message in
if success {
continuation.resume()
} else {
continuation.resume(throwing: NSError(domain: "SetupComponentsError",
code: -1,
userInfo: [NSLocalizedDescriptionKey: message]))
for package in packagesToDownload {
let packageDir = "\(targetDirectory)/\(package.name)"
let packageCommands = [
"mkdir -p '\(packageDir)'",
"unzip -o '\(tempDirectory.path)/\(package.name).zip' -d '\(packageDir)/'",
"chmod -R 755 '\(packageDir)'",
"chown -R root:wheel '\(packageDir)'"
]
for command in packageCommands {
let result = await withCheckedContinuation { continuation in
PrivilegedHelperManager.shared.executeCommand(command) { result in
continuation.resume(returning: result)
}
}
if result.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("安装 \(package.name) 失败: \(result)")
}
}
}
}
try? FileManager.default.removeItem(at: tempDirectory)
try await withCheckedThrowingContinuation { continuation in
ModifySetup.backupAndModifySetupFile { success, message in
if success {
continuation.resume()
} else {
continuation.resume(throwing: NetworkError.installError(message))
}
}
}
ModifySetup.clearVersionCache()
ModifySetup.clearVersionCache()
await MainActor.run {
progressHandler(1.0, "安装完成")
try? FileManager.default.removeItem(at: tempDirectory)
await MainActor.run {
progressHandler(1.0, "安装完成")
}
} catch {
print("发生错误: \(error.localizedDescription)")
throw error
}
}
}

View File

@@ -34,8 +34,7 @@ actor InstallManager {
private func executeInstallation(
at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
progressHandler: @escaping (Double, String) -> Void
) async throws {
guard FileManager.default.fileExists(atPath: setupPath) else {
throw InstallError.setupNotFound
@@ -53,13 +52,13 @@ actor InstallManager {
do {
try await PrivilegedHelperManager.shared.executeInstallation(installCommand) { output in
Task { @MainActor in
logHandler(output)
if let range = output.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
let codeStr = output[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let exitCode = Int(codeStr) {
if exitCode == 0 {
progressHandler(1.0, String(localized: "安装完成"))
PrivilegedHelperManager.shared.executeCommand("pkill -f Setup") { _ in }
continuation.resume()
} else {
let errorMessage: String
@@ -104,13 +103,11 @@ actor InstallManager {
func install(
at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
progressHandler: @escaping (Double, String) -> Void
) async throws {
try await executeInstallation(
at: appPath,
progressHandler: progressHandler,
logHandler: logHandler
progressHandler: progressHandler
)
}
@@ -124,13 +121,11 @@ actor InstallManager {
func retry(
at appPath: URL,
progressHandler: @escaping (Double, String) -> Void,
logHandler: @escaping (String) -> Void
progressHandler: @escaping (Double, String) -> Void
) async throws {
try await executeInstallation(
at: appPath,
progressHandler: progressHandler,
logHandler: logHandler
progressHandler: progressHandler
)
}
}

View File

@@ -16,13 +16,13 @@ class ModifySetup {
}
static func isSetupBackup() -> Bool {
return FileManager.default.fileExists(atPath: "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original")
return isSetupExists() && FileManager.default.fileExists(atPath: "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original")
}
static func checkComponentVersion() -> String {
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
guard isSetupExists() else {
guard FileManager.default.fileExists(atPath: setupPath) else {
cachedVersion = nil
return String(localized: "未找到 Setup 组件")
}
@@ -30,42 +30,42 @@ class ModifySetup {
if let cachedVersion = cachedVersion {
return cachedVersion
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: setupPath)) else {
return "Unknown"
}
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/strings")
process.arguments = [setupPath]
let versionMarkers = ["Version ", "Adobe Setup Version: "]
for marker in versionMarkers {
if let markerData = marker.data(using: .utf8),
let markerRange = data.range(of: markerData) {
let versionStart = markerRange.upperBound
let searchRange = versionStart..<min(versionStart + 30, data.count)
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
do {
try process.run()
process.waitUntilExit()
if let output = try pipe.fileHandleForReading.readToEnd(),
let outputString = String(data: output, encoding: .utf8) {
let lines = outputString.components(separatedBy: .newlines)
for (index, line) in lines.enumerated() {
if line == "Adobe Setup Version: %s" && index + 1 < lines.count {
let version = lines[index + 1].trimmingCharacters(in: .whitespacesAndNewlines)
if version.range(of: "^[0-9.]+$", options: .regularExpression) != nil {
cachedVersion = version
return version
}
}
var versionBytes: [UInt8] = []
for i in searchRange {
let byte = data[i]
if (byte >= 0x30 && byte <= 0x39) || byte == 0x2E || byte == 0x20 {
versionBytes.append(byte)
} else if byte == 0x28 {
break
} else if versionBytes.isEmpty {
continue
} else { break }
}
if let version = String(bytes: versionBytes, encoding: .utf8)?.trimmingCharacters(in: .whitespaces),
!version.isEmpty {
cachedVersion = version
return version
}
}
let message = String(localized: "未知 Setup 组件版本号")
cachedVersion = message
return message
} catch {
print("Error checking Setup version: \(error)")
let message = String(localized: "未知 Setup 组件版本号")
cachedVersion = message
return message
}
let message = String(localized: "未知 Setup 组件版本号")
cachedVersion = message
return message
}
static func clearVersionCache() {
@@ -152,5 +152,25 @@ class ModifySetup {
}
}
}
static func isSetupModified() -> Bool {
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
guard FileManager.default.fileExists(atPath: setupPath) else { return false }
let intelPattern = Data([0x55, 0x48, 0x89, 0xE5, 0x53, 0x50, 0x48, 0x89, 0xFB, 0x48, 0x8B, 0x05, 0x70, 0xC7, 0x03, 0x00, 0x48, 0x8B, 0x00, 0x48, 0x89, 0x45, 0xF0, 0xE8, 0x24, 0xD7, 0xFE, 0xFF, 0x48, 0x83, 0xC3, 0x08, 0x48, 0x39, 0xD8, 0x0F])
let armPattern = Data([0xFF, 0xC3, 0x00, 0xD1, 0xF4, 0x4F, 0x01, 0xA9, 0xFD, 0x7B, 0x02, 0xA9, 0xFD, 0x83, 0x00, 0x91, 0xF3, 0x03, 0x00, 0xAA, 0x1F, 0x20, 0x03, 0xD5, 0x68, 0xA1, 0x1D, 0x58, 0x08, 0x01, 0x40, 0xF9, 0xE8, 0x07, 0x00, 0xF9])
do {
let fileData = try Data(contentsOf: URL(fileURLWithPath: setupPath))
if fileData.range(of: intelPattern) != nil || fileData.range(of: armPattern) != nil { return false }
return true
} catch {
print("Error reading Setup file: \(error)")
return false
}
}
}

View File

@@ -160,11 +160,11 @@ struct GeneralSettingsView: View {
Form {
DownloadSettingsView(viewModel: viewModel)
OtherSettingsView(viewModel: viewModel,
HelperSettingsView(viewModel: viewModel,
showHelperAlert: $showHelperAlert,
helperAlertMessage: $helperAlertMessage,
helperAlertSuccess: $helperAlertSuccess)
CCSettingsView(viewModel: viewModel)
UpdateSettingsView(viewModel: viewModel)
}
.padding()
@@ -181,7 +181,7 @@ struct GeneralSettingsView: View {
viewModel.isDownloadingSetup = true
viewModel.isCancelled = false
do {
try await networkManager.downloadUtils.downloadSetupComponents(
try await networkManager.downloadUtils.downloadX1a0HeCCPackages(
progressHandler: { progress, status in
viewModel.setupDownloadProgress = progress
viewModel.setupDownloadStatus = status
@@ -212,7 +212,7 @@ struct GeneralSettingsView: View {
viewModel.isDownloadingSetup = true
viewModel.isCancelled = false
do {
try await networkManager.downloadUtils.downloadSetupComponents(
try await networkManager.downloadUtils.downloadX1a0HeCCPackages(
progressHandler: { progress, status in
viewModel.setupDownloadProgress = progress
viewModel.setupDownloadStatus = status
@@ -234,7 +234,7 @@ struct GeneralSettingsView: View {
}
}
} message: {
Text("确定要下载并安装 Setup 组件吗?")
Text("确定要下载并安装 X1a0He CC 吗?")
}
.alert("确认重新处理", isPresented: $viewModel.showReprocessConfirmAlert) {
Button("取消", role: .cancel) { }
@@ -281,19 +281,30 @@ struct DownloadSettingsView: View {
}
}
struct OtherSettingsView: View {
struct HelperSettingsView: View {
@ObservedObject var viewModel: GeneralSettingsViewModel
@Binding var showHelperAlert: Bool
@Binding var helperAlertMessage: String
@Binding var helperAlertSuccess: Bool
var body: some View {
GroupBox(label: Text("其他设置").padding(.bottom, 8)) {
GroupBox(label: Text("Helper 设置").padding(.bottom, 8)) {
VStack(alignment: .leading, spacing: 12) {
HelperStatusRow(viewModel: viewModel, showHelperAlert: $showHelperAlert,
helperAlertMessage: $helperAlertMessage,
helperAlertSuccess: $helperAlertSuccess)
Divider()
}
.padding(8)
}
}
}
struct CCSettingsView: View {
@ObservedObject var viewModel: GeneralSettingsViewModel
var body: some View {
GroupBox(label: Text("X1a0He CC设置").padding(.bottom, 8)) {
VStack(alignment: .leading, spacing: 12) {
SetupComponentRow(viewModel: viewModel)
}
.padding(8)
@@ -597,11 +608,35 @@ struct SetupComponentRow: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Setup 组件状态: ")
Text("X1a0He CC 备份状态: ")
if ModifySetup.isSetupBackup() {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("已备份处理")
Text("已备份")
} else {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
Text("(可能导致处理 Setup 组件失败)")
}
Spacer()
Button(action: {
if !ModifySetup.isSetupExists() {
viewModel.showDownloadAlert = true
} else {
viewModel.showReprocessConfirmAlert = true
}
}) {
Text("重新备份")
}
}
Divider()
HStack {
Text("X1a0He CC 处理状态: ")
if ModifySetup.isSetupModified() {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("已处理")
} else {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
@@ -621,7 +656,7 @@ struct SetupComponentRow: View {
}
Divider()
HStack {
Text("Setup 组件版本: \(viewModel.setupVersion)")
Text("X1a0He CC 版本信息: \(viewModel.setupVersion)")
Spacer()
if viewModel.isDownloadingSetup {
@@ -637,7 +672,7 @@ struct SetupComponentRow: View {
Button(action: {
viewModel.showDownloadConfirmAlert = true
}) {
Text("从 GitHub 下载 Setup 组件")
Text("下载 X1a0He CC")
}
}
}

View File

@@ -75,8 +75,6 @@ struct InstallProgressView: View {
ProgressSection(progress: progress, progressText: progressText)
}
LogSection(logs: networkManager.installationLogs)
if isFailed {
ErrorSection(
status: status, isFailed: isFailed
@@ -91,7 +89,7 @@ struct InstallProgressView: View {
)
}
.padding()
.frame(minWidth: 500, minHeight: 400)
.frame(minWidth: 500)
.background(Color(NSColor.windowBackgroundColor))
.cornerRadius(8)
}
@@ -116,45 +114,6 @@ private struct ProgressSection: View {
}
}
private struct LogSection: View {
let logs: [String]
var body: some View {
ScrollViewReader { proxy in
ScrollView(showsIndicators: true) {
LazyVStack(alignment: .leading, spacing: 2) {
ForEach(Array(logs.suffix(1000).enumerated()), id: \.offset) { index, log in
Text(log)
.font(.system(.caption, design: .monospaced))
.foregroundColor(.secondary)
.id(index)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.textSelection(.enabled)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 8)
}
.frame(height: 150)
.background(Color(NSColor.textBackgroundColor))
.cornerRadius(4)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
)
.padding(.horizontal, 20)
.onChange(of: logs) { newValue in
if !newValue.isEmpty {
withAnimation {
proxy.scrollTo(newValue.count - 1, anchor: .bottom)
}
}
}
}
}
}
private struct ErrorSection: View {
let status: String
let isFailed: Bool
@@ -301,7 +260,7 @@ private struct CommandPopover: View {
}
}
#Preview("安装中带日志") {
#Preview("安装中") {
let networkManager = NetworkManager()
return InstallProgressView(
productName: "Adobe Photoshop",
@@ -311,26 +270,9 @@ private struct CommandPopover: View {
onRetry: nil
)
.environmentObject(networkManager)
.onAppear {
let previewLogs = [
"正在准备安装...",
"Progress: 10%",
"Progress: 20%",
"Progress: 30%",
"Progress: 40%",
"Progress: 45%",
"正在安装核心组件...",
]
for (index, log) in previewLogs.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
networkManager.installationLogs.append(log)
}
}
}
}
#Preview("安装失败带日志") {
#Preview("安装失败") {
let networkManager = NetworkManager()
return InstallProgressView(
productName: "Adobe Photoshop",
@@ -342,25 +284,10 @@ private struct CommandPopover: View {
.environmentObject(networkManager)
.onAppear {
networkManager.installCommand = "sudo \"/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup\" --install=1 --driverXML=\"/Users/demo/Downloads/Adobe Photoshop/driver.xml\""
let previewLogs = [
"正在准备安装...",
"Progress: 10%",
"Progress: 20%",
"检查权限...",
"权限检查失败",
"安装失败: 权限被拒绝"
]
for (index, log) in previewLogs.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
networkManager.installationLogs.append(log)
}
}
}
}
#Preview("安装完成带日志") {
#Preview("安装完成") {
let networkManager = NetworkManager()
return InstallProgressView(
productName: "Adobe Photoshop",
@@ -370,26 +297,9 @@ private struct CommandPopover: View {
onRetry: nil
)
.environmentObject(networkManager)
.onAppear {
let previewLogs = [
"正在准备安装...",
"Progress: 25%",
"Progress: 50%",
"Progress: 75%",
"Progress: 100%",
"正在完成安装...",
"安装完成"
]
for (index, log) in previewLogs.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
networkManager.installationLogs.append(log)
}
}
}
}
#Preview("在深色模式下带日志") {
#Preview("在深色模式下") {
let networkManager = NetworkManager()
return InstallProgressView(
productName: "Adobe Photoshop",
@@ -400,19 +310,4 @@ private struct CommandPopover: View {
)
.environmentObject(networkManager)
.preferredColorScheme(.dark)
.onAppear {
let previewLogs = [
"正在准备安装...",
"Progress: 25%",
"Progress: 50%",
"Progress: 75%",
"正在安装..."
]
for (index, log) in previewLogs.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.5) {
networkManager.installationLogs.append(log)
}
}
}
}

View File

@@ -20,134 +20,19 @@ struct ShouldExistsSetUpView: View {
var body: some View {
VStack(spacing: 20) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 64))
.foregroundColor(.orange)
.padding(.bottom, 5)
.frame(alignment: .bottomTrailing)
Text("未检测到 Adobe Setup 组件")
.font(.system(size: 24))
.bold()
VStack(spacing: 4) {
Text("程序检测到你的系统中不存在 Adobe Setup 组件")
.multilineTextAlignment(.center)
Text("可能导致无法使用安装功能,请确保是否使用安装功能")
.multilineTextAlignment(.center)
}
VStack(spacing: 16) {
Button(action: {
showingAlert = true
}) {
Label("不使用安装功能", systemImage: "exclamationmark.triangle.fill")
.frame(minWidth: 0,maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.orange)
.alert("确认", isPresented: $showingAlert) {
Button("取消", role: .cancel) { }
Button("确定", role: .destructive) {
dismiss()
}
} message: {
Text("你确定不使用安装功能吗?")
}
.disabled(isDownloading)
Button(action: {
isDownloading = true
isCancelled = false
Task {
do {
try await networkManager.downloadUtils.downloadSetupComponents(
progressHandler: { progress, status in
Task { @MainActor in
downloadProgress = progress
downloadStatus = status
}
},
cancellationHandler: { isCancelled }
)
await MainActor.run {
dismiss()
}
} catch NetworkError.cancelled {
await MainActor.run {
isDownloading = false
}
} catch {
await MainActor.run {
isDownloading = false
errorMessage = error.localizedDescription
showErrorAlert = true
}
}
}
}) {
if isDownloading {
VStack {
ProgressView(value: downloadProgress) {
Text(downloadStatus)
.font(.system(size: 14))
}
Text("\(Int(downloadProgress * 100))%")
.font(.system(size: 12))
.foregroundColor(.secondary)
Button("取消") {
isCancelled = true
}
.buttonStyle(.borderless)
}
.frame(maxWidth: 360)
.progressViewStyle(.linear)
.tint(.green)
} else {
Label("下载 X1a0He CC 组件", systemImage: "arrow.down")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
}
.buttonStyle(.borderedProminent)
.tint(.blue)
.disabled(isDownloading)
Button(action: {
if let url = URL(string: "https://creativecloud.adobe.com/apps/download/creative-cloud") {
NSWorkspace.shared.open(url)
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApplication.shared.terminate(nil)
}
}
}) {
Label("前往 Adobe Creative Cloud", systemImage: "cloud.fill")
.frame(minWidth: 0,maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.disabled(isDownloading)
Button(action: {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApplication.shared.terminate(nil)
}
}) {
Label("退出", systemImage: "xmark")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.red)
.keyboardShortcut(.cancelAction)
.disabled(isDownloading)
}
HeaderView()
MessageView()
ButtonsView(
isDownloading: $isDownloading,
downloadProgress: $downloadProgress,
downloadStatus: $downloadStatus,
isCancelled: $isCancelled,
showingAlert: $showingAlert,
showErrorAlert: $showErrorAlert,
errorMessage: $errorMessage,
dismiss: dismiss,
networkManager: networkManager
)
}
.frame(width: 500)
.padding()
@@ -162,6 +47,179 @@ struct ShouldExistsSetUpView: View {
}
}
private struct HeaderView: View {
var body: some View {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 64))
.foregroundColor(.orange)
.padding(.bottom, 5)
.frame(alignment: .bottomTrailing)
Text("未检测到 Adobe CC 组件")
.font(.system(size: 24))
.bold()
}
}
private struct MessageView: View {
var body: some View {
VStack(spacing: 4) {
Text("程序检测到你的系统中不存在 Adobe CC 组件")
.multilineTextAlignment(.center)
Text("可能导致无法使用安装功能,请确保是否使用安装功能")
.multilineTextAlignment(.center)
}
}
}
private struct ButtonsView: View {
@Binding var isDownloading: Bool
@Binding var downloadProgress: Double
@Binding var downloadStatus: String
@Binding var isCancelled: Bool
@Binding var showingAlert: Bool
@Binding var showErrorAlert: Bool
@Binding var errorMessage: String
let dismiss: DismissAction
let networkManager: NetworkManager
var body: some View {
VStack(spacing: 16) {
notUseButton
downloadButton
creativeCloudButton
exitButton
}
}
private var notUseButton: some View {
Button(action: { showingAlert = true }) {
Label("不使用安装功能", systemImage: "exclamationmark.triangle.fill")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.orange)
.alert("确认", isPresented: $showingAlert) {
Button("取消", role: .cancel) { }
Button("确定", role: .destructive) {
dismiss()
}
} message: {
Text("你确定不使用安装功能吗?")
}
.disabled(isDownloading)
}
private var downloadButton: some View {
Button(action: startDownload) {
if isDownloading {
downloadProgressView
} else {
Label("下载 X1a0He CC 组件", systemImage: "arrow.down")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
}
.buttonStyle(.borderedProminent)
.tint(.blue)
.disabled(isDownloading)
}
private var downloadProgressView: some View {
VStack {
ProgressView(value: downloadProgress) {
Text(downloadStatus)
.font(.system(size: 14))
}
Text("\(Int(downloadProgress * 100))%")
.font(.system(size: 12))
.foregroundColor(.secondary)
Button("取消") {
isCancelled = true
}
.buttonStyle(.borderless)
}
.frame(maxWidth: 360)
.progressViewStyle(.linear)
.tint(.green)
}
private var creativeCloudButton: some View {
Button(action: openCreativeCloud) {
Label("前往 Adobe Creative Cloud", systemImage: "cloud.fill")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.disabled(isDownloading)
}
private var exitButton: some View {
Button(action: exitApp) {
Label("退出", systemImage: "xmark")
.frame(minWidth: 0, maxWidth: 360)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.red)
.keyboardShortcut(.cancelAction)
.disabled(isDownloading)
}
private func startDownload() {
isDownloading = true
isCancelled = false
Task {
do {
try await networkManager.downloadUtils.downloadX1a0HeCCPackages(
progressHandler: { progress, status in
Task { @MainActor in
downloadProgress = progress
downloadStatus = status
}
},
cancellationHandler: { isCancelled }
)
await MainActor.run {
dismiss()
}
} catch NetworkError.cancelled {
await MainActor.run {
isDownloading = false
}
} catch {
await MainActor.run {
isDownloading = false
errorMessage = error.localizedDescription
showErrorAlert = true
}
}
}
}
private func openCreativeCloud() {
if let url = URL(string: "https://creativecloud.adobe.com/apps/download/creative-cloud") {
NSWorkspace.shared.open(url)
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApplication.shared.terminate(nil)
}
}
}
private func exitApp() {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApplication.shared.terminate(nil)
}
}
}
#Preview {
ShouldExistsSetUpView()
.environmentObject(NetworkManager())

View File

@@ -3,6 +3,9 @@
"strings" : {
"" : {
},
"(可能导致处理 Setup 组件失败)" : {
},
"(将导致无法使用安装功能)" : {
"localizations" : {
@@ -153,6 +156,9 @@
},
"Helper 当前状态: " : {
},
"Helper 设置" : {
},
"Helper未安装将导致无法执行需要管理员权限的操作" : {
@@ -171,6 +177,7 @@
}
},
"Setup 组件版本: %@" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -181,6 +188,7 @@
}
},
"Setup 组件状态: " : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -208,6 +216,18 @@
},
"v6" : {
},
"X1a0He CC 处理状态: " : {
},
"X1a0He CC 备份状态: " : {
},
"X1a0He CC 版本信息: %@" : {
},
"X1a0He CC设置" : {
},
"下载" : {
"localizations" : {
@@ -228,6 +248,9 @@
}
}
}
},
"下载 X1a0He CC" : {
},
"下载 X1a0He CC 组件" : {
"localizations" : {
@@ -343,6 +366,7 @@
}
},
"从 GitHub 下载 Setup 组件" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -463,6 +487,7 @@
}
},
"其他设置" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -667,7 +692,10 @@
}
}
},
"已备份处理" : {
"已处理" : {
},
"已备份" : {
},
"已复制" : {
@@ -937,8 +965,12 @@
}
}
}
},
"未检测到 Adobe CC 组件" : {
},
"未检测到 Adobe Setup 组件" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1120,7 +1152,7 @@
}
}
},
"确定要下载并安装 Setup 组件吗?" : {
"确定要下载并安装 X1a0He CC 吗?" : {
},
"确定要重新处理 Setup 组件吗?" : {
@@ -1205,8 +1237,12 @@
}
}
}
},
"程序检测到你的系统中不存在 Adobe CC 组件" : {
},
"程序检测到你的系统中不存在 Adobe Setup 组件" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1420,6 +1456,9 @@
}
}
}
},
"重新备份" : {
},
"重新安装" : {

View File

@@ -5,6 +5,10 @@
```markdown
1. 新增可选API版本 (v4, v5, v6)
2. 引入 Privilege Helper 来处理所有需要权限的操作
3. 修改从 Github 下载 Setup 组件功能改为从官方下载简化版CC称为 X1a0He CC
4. 调整 CC 组件备份与处理状态检测,分离二者的检测机制
5. 移除了安装日志显示
6. 调整 Setup 组件版本号的获取方式
```
## 2024-11-11 21:00 更新日志