diff --git a/Adobe Downloader.xcodeproj/xcuserdata/hejinhui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Adobe Downloader.xcodeproj/xcuserdata/hejinhui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 8e2767e..a0335d4 100644 --- a/Adobe Downloader.xcodeproj/xcuserdata/hejinhui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Adobe Downloader.xcodeproj/xcuserdata/hejinhui.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -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"> diff --git a/Adobe Downloader/Commons/Enums.swift b/Adobe Downloader/Commons/Enums.swift index 0ba46ca..0c5a647 100644 --- a/Adobe Downloader/Commons/Enums.swift +++ b/Adobe Downloader/Commons/Enums.swift @@ -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") } } diff --git a/Adobe Downloader/NetworkManager.swift b/Adobe Downloader/NetworkManager.swift index 38195cb..0a602c0 100644 --- a/Adobe Downloader/NetworkManager.swift +++ b/Adobe Downloader/NetworkManager.swift @@ -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 - } } diff --git a/Adobe Downloader/Utils/DownloadUtils.swift b/Adobe Downloader/Utils/DownloadUtils.swift index 57086c9..11cf360 100644 --- a/Adobe Downloader/Utils/DownloadUtils.swift +++ b/Adobe Downloader/Utils/DownloadUtils.swift @@ -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 } } } diff --git a/Adobe Downloader/Utils/InstallManager.swift b/Adobe Downloader/Utils/InstallManager.swift index f5c7eff..87d838f 100644 --- a/Adobe Downloader/Utils/InstallManager.swift +++ b/Adobe Downloader/Utils/InstallManager.swift @@ -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 ) } } diff --git a/Adobe Downloader/Utils/ModifySetup.swift b/Adobe Downloader/Utils/ModifySetup.swift index 48aa010..a2f374c 100644 --- a/Adobe Downloader/Utils/ModifySetup.swift +++ b/Adobe Downloader/Utils/ModifySetup.swift @@ -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..= 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 + } + } } diff --git a/Adobe Downloader/Views/AboutView.swift b/Adobe Downloader/Views/AboutView.swift index 8ad360b..a983da4 100644 --- a/Adobe Downloader/Views/AboutView.swift +++ b/Adobe Downloader/Views/AboutView.swift @@ -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") } } } diff --git a/Adobe Downloader/Views/InstallProgressView.swift b/Adobe Downloader/Views/InstallProgressView.swift index bfa9c8c..3c819a7 100644 --- a/Adobe Downloader/Views/InstallProgressView.swift +++ b/Adobe Downloader/Views/InstallProgressView.swift @@ -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) - } - } - } } diff --git a/Adobe Downloader/Views/ShouldExistsSetUpView.swift b/Adobe Downloader/Views/ShouldExistsSetUpView.swift index 9ff4e78..30d827e 100644 --- a/Adobe Downloader/Views/ShouldExistsSetUpView.swift +++ b/Adobe Downloader/Views/ShouldExistsSetUpView.swift @@ -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()) diff --git a/Localizables/Localizable.xcstrings b/Localizables/Localizable.xcstrings index 5a48e16..ff7cf6a 100644 --- a/Localizables/Localizable.xcstrings +++ b/Localizables/Localizable.xcstrings @@ -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 @@ } } } + }, + "重新备份" : { + }, "重新安装" : { diff --git a/update-log.md b/update-log.md index 45ab559..53bd993 100644 --- a/update-log.md +++ b/update-log.md @@ -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 更新日志