diff --git a/Adobe Downloader.xcodeproj/project.pbxproj b/Adobe Downloader.xcodeproj/project.pbxproj index 9982169..599b65f 100644 --- a/Adobe Downloader.xcodeproj/project.pbxproj +++ b/Adobe Downloader.xcodeproj/project.pbxproj @@ -416,7 +416,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 150; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\""; DEVELOPMENT_TEAM = TG862GVKHK; @@ -432,7 +432,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.1; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -450,7 +450,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 141; + CURRENT_PROJECT_VERSION = 150; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\""; DEVELOPMENT_TEAM = TG862GVKHK; @@ -466,7 +466,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.1; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Adobe Downloader/HelperManager/PrivilegedHelperManager.swift b/Adobe Downloader/HelperManager/PrivilegedHelperManager.swift index 4db538f..c75ce4c 100644 --- a/Adobe Downloader/HelperManager/PrivilegedHelperManager.swift +++ b/Adobe Downloader/HelperManager/PrivilegedHelperManager.swift @@ -396,20 +396,21 @@ class PrivilegedHelperManager: NSObject { } private func parseCommand(_ command: String) -> (CommandType, String, String, Int) { - let components = command.components(separatedBy: " ") + let components = command.split(separator: " ", omittingEmptySubsequences: true).map(String.init) if command.hasPrefix("installer -pkg") { - return (.install, components[2].replacingOccurrences(of: "\"", with: ""), "", 0) + return (.install, components[2], "", 0) } else if command.hasPrefix("rm -rf") { - return (.uninstall, components[2].replacingOccurrences(of: "\"", with: ""), "", 0) + let path = components.dropFirst(2).joined(separator: " ") + return (.uninstall, path, "", 0) } else if command.hasPrefix("mv") || command.hasPrefix("cp") { - return (.moveFile, - components[1].replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: ""), - components[2].replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: ""), - 0) + let paths = components.dropFirst(1) + let sourcePath = String(paths.first ?? "") + let destPath = paths.dropFirst().joined(separator: " ") + return (.moveFile, sourcePath, destPath, 0) } else if command.hasPrefix("chmod") { return (.setPermissions, - components[2].replacingOccurrences(of: "\"", with: ""), + components.dropFirst(2).joined(separator: " "), "", Int(components[1]) ?? 0) } @@ -637,13 +638,13 @@ enum HelperError: LocalizedError { var errorDescription: String? { switch self { case .connectionFailed: - return "无法连接到 Helper" + return String(localized: "无法连接到 Helper") case .proxyError: - return "无法获取 Helper 代理" + return String(localized: "无法获取 Helper 代理") case .authorizationFailed: - return "获取授权失败" + return String(localized: "获取授权失败") case .installationFailed(let reason): - return "安装失败: \(reason)" + return String(localized: "安装失败: \(reason)") } } } diff --git a/Adobe Downloader/Utils/DownloadUtils.swift b/Adobe Downloader/Utils/DownloadUtils.swift index 8780c9e..e248de0 100644 --- a/Adobe Downloader/Utils/DownloadUtils.swift +++ b/Adobe Downloader/Utils/DownloadUtils.swift @@ -1072,7 +1072,8 @@ class DownloadUtils { func downloadX1a0HeCCPackages( progressHandler: @escaping (Double, String) -> Void, - cancellationHandler: @escaping () -> Bool + cancellationHandler: @escaping () -> Bool, + shouldProcess: Bool = true ) async throws { let baseUrl = "https://cdn-ffc.oobesaas.adobe.com/core/v1/applications?name=CreativeCloud&platform=\(AppStatics.isAppleSilicon ? "macarm64" : "osx10")" @@ -1159,6 +1160,7 @@ class DownloadUtils { let destinationURL = tempDirectory.appendingPathComponent("\(package.name).zip") var downloadRequest = URLRequest(url: package.url) + print(downloadRequest) NetworkConstants.downloadHeaders.forEach { downloadRequest.setValue($0.value, forHTTPHeaderField: $0.key) } let (downloadURL, downloadResponse) = try await session.download(for: downloadRequest) @@ -1172,7 +1174,7 @@ class DownloadUtils { } await MainActor.run { - progressHandler(0.9, "正在安装组件...") + progressHandler(0.9, shouldProcess ? "正在安装组件..." : "正在完成下载...") } let targetDirectory = "/Library/Application\\ Support/Adobe/Adobe\\ Desktop\\ Common" @@ -1227,21 +1229,23 @@ class DownloadUtils { try await Task.sleep(nanoseconds: 1_000_000_000) - try await withCheckedThrowingContinuation { continuation in - ModifySetup.backupAndModifySetupFile { success, message in - if success { - continuation.resume() - } else { - continuation.resume(throwing: NetworkError.installError(message)) + if shouldProcess { + 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() try? FileManager.default.removeItem(at: tempDirectory) await MainActor.run { - progressHandler(1.0, "安装完成") + progressHandler(1.0, shouldProcess ? "安装完成" : "下载完成") } } catch { print("发生错误: \(error.localizedDescription)") diff --git a/Adobe Downloader/Utils/InstallManager.swift b/Adobe Downloader/Utils/InstallManager.swift index 171390b..fa07881 100644 --- a/Adobe Downloader/Utils/InstallManager.swift +++ b/Adobe Downloader/Utils/InstallManager.swift @@ -115,7 +115,7 @@ actor InstallManager { let errorMessage: String switch exitCode { case 107: - errorMessage = String(localized: "安装失败: 架构或本不一致 (退出代码: \(exitCode))") + errorMessage = String(localized: "安装失败: 架构或版本不一致 (退出代码: \(exitCode))") case 103: errorMessage = String(localized: "安装失败: 权限问题 (退出代码: \(exitCode))") case 182: diff --git a/Adobe Downloader/Utils/ModifySetup.swift b/Adobe Downloader/Utils/ModifySetup.swift index 87641ab..9063365 100644 --- a/Adobe Downloader/Utils/ModifySetup.swift +++ b/Adobe Downloader/Utils/ModifySetup.swift @@ -158,7 +158,7 @@ class ModifySetup { DispatchQueue.global(qos: .userInitiated).async { if !isSetupExists() { DispatchQueue.main.async { - completion(false, "未找到 Setup 组件") + completion(false, String(localized: "未找到 Setup 组件")) } return } @@ -166,7 +166,7 @@ class ModifySetup { backupSetupFile { backupSuccess in if !backupSuccess { DispatchQueue.main.async { - completion(false, "备份 Setup 组件失败") + completion(false, String(localized: "备份 Setup 组件失败")) } return } @@ -174,9 +174,9 @@ class ModifySetup { modifySetupFile { modifySuccess in DispatchQueue.main.async { if modifySuccess { - completion(true, "所有操作已成功完成") + completion(true, String(localized: "所有操作已成功完成")) } else { - completion(false, "修改 Setup 组件失败") + completion(false, String(localized: "修改 Setup 组件失败")) } } } diff --git a/Adobe Downloader/Views/AboutView.swift b/Adobe Downloader/Views/AboutView.swift index c562ce8..f62c68f 100644 --- a/Adobe Downloader/Views/AboutView.swift +++ b/Adobe Downloader/Views/AboutView.swift @@ -20,7 +20,7 @@ private enum AboutViewConstants { static let formPadding: CGFloat = 8 static let links: [(title: String, url: String)] = [ - ("@X1a0He", "https://t.me/X1a0He"), + ("@X1a0He", "https://t.me/X1a0He_bot"), ("Github: Adobe Downloader", "https://github.com/X1a0He/Adobe-Downloader"), ("Drovosek01: adobe-packager", "https://github.com/Drovosek01/adobe-packager"), ("QiuChenly: InjectLib", "https://github.com/QiuChenly/InjectLib") @@ -40,27 +40,43 @@ struct ExternalLinkView: View { struct AboutView: View { private let updater: SPUUpdater + @State private var selectedTab = "general_settings" init(updater: SPUUpdater) { self.updater = updater } var body: some View { - TabView { + TabView(selection: $selectedTab) { GeneralSettingsView(updater: updater) .tabItem { Label("通用", systemImage: "gear") } - .id("general_settings") + .tag("general_settings") + + CleanupView() + .tabItem { + Label("清理工具", systemImage: "trash") + } + .tag("cleanup_view") + + QAView() + .tabItem { + Label("常见问题", systemImage: "questionmark.circle") + } + .tag("qa_view") AboutAppView() .tabItem { Label("关于", systemImage: "info.circle") } - .id("about_app") + .tag("about_app") } .background(Color(NSColor.windowBackgroundColor)) .frame(width: 600) + .onAppear { + selectedTab = "general_settings" + } } } @@ -309,7 +325,7 @@ struct GeneralSettingsView: View { } message: { Text("检测到系统中不存在 Setup 组件,需要先下载组件才能继续操作。") } - .alert("确认下载", isPresented: $viewModel.showDownloadConfirmAlert) { + .alert("确认下载并处理", isPresented: $viewModel.showDownloadConfirmAlert) { Button("取消", role: .cancel) { } Button("确定") { Task { @@ -321,11 +337,12 @@ struct GeneralSettingsView: View { viewModel.setupDownloadProgress = progress viewModel.setupDownloadStatus = status }, - cancellationHandler: { viewModel.isCancelled } + cancellationHandler: { viewModel.isCancelled }, + shouldProcess: true ) viewModel.setupVersion = ModifySetup.checkComponentVersion() viewModel.isSuccess = true - viewModel.alertMessage = String(localized: "Setup 组件安装成功") + viewModel.alertMessage = String(localized: "X1a0He CC 下载并处理成功") } catch NetworkError.cancelled { viewModel.isSuccess = false viewModel.alertMessage = String(localized: "下载已取消") @@ -338,21 +355,39 @@ struct GeneralSettingsView: View { } } } message: { - Text("确定要下载并安装 X1a0He CC 吗?") + Text("确定要下载并处理 X1a0He CC 吗?这将完成下载并自动对 Setup 组件进行处理") } - .alert("确认重新处理", isPresented: $viewModel.showReprocessConfirmAlert) { + .alert("确认下载", isPresented: $viewModel.showReprocessConfirmAlert) { Button("取消", role: .cancel) { } Button("确定") { - viewModel.isProcessing = true - ModifySetup.backupAndModifySetupFile { success, message in - viewModel.isProcessing = false - viewModel.isSuccess = success - viewModel.alertMessage = message + Task { + viewModel.isDownloadingSetup = true + viewModel.isCancelled = false + do { + try await networkManager.downloadUtils.downloadX1a0HeCCPackages( + progressHandler: { progress, status in + viewModel.setupDownloadProgress = progress + viewModel.setupDownloadStatus = status + }, + cancellationHandler: { viewModel.isCancelled }, + shouldProcess: false + ) + viewModel.setupVersion = ModifySetup.checkComponentVersion() + viewModel.isSuccess = true + viewModel.alertMessage = String(localized: "X1a0He CC 下载成功") + } catch NetworkError.cancelled { + viewModel.isSuccess = false + viewModel.alertMessage = String(localized: "下载已取消") + } catch { + viewModel.isSuccess = false + viewModel.alertMessage = error.localizedDescription + } viewModel.showAlert = true + viewModel.isDownloadingSetup = false } } } message: { - Text("确定要重新处理 Setup 组件吗?") + Text("确定要下载 X1a0He CC 吗?下载完成后需要手动处理。") } .alert(viewModel.isSuccess ? "操作成功" : "操作失败", isPresented: $viewModel.showAlert) { Button("确定") { } @@ -420,10 +455,27 @@ struct CCSettingsView: View { struct UpdateSettingsView: View { @ObservedObject var viewModel: GeneralSettingsViewModel + private var appVersion: String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" + } + private var buildVersion: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" + } var body: some View { GroupBox(label: Text("更新设置").padding(.bottom, 8)) { VStack(alignment: .leading, spacing: 12) { + HStack { + Text("当前版本:") + .foregroundColor(.secondary) + Text(appVersion) + Text("(\(buildVersion))") + .foregroundColor(.secondary) + } + .font(.system(size: 12)) + + Divider() + AutoUpdateRow(viewModel: viewModel) Divider() AutoDownloadRow(viewModel: viewModel) @@ -821,11 +873,27 @@ struct SetupComponentRow: View { viewModel.cancelDownload() } } else { - Button(action: { - viewModel.showDownloadConfirmAlert = true - }) { - Text("下载 X1a0He CC") + Menu { + Button(action: { + viewModel.showDownloadConfirmAlert = true + }) { + Label("下载并处理", systemImage: "arrow.down.circle.fill") + .frame(maxWidth: .infinity, alignment: .leading) + } + + Button(action: { + viewModel.showReprocessConfirmAlert = true + }) { + Label("仅下载", systemImage: "arrow.down") + .frame(maxWidth: .infinity, alignment: .leading) + } + } label: { + Text("X1a0He CC") + .frame(width: 100) } + .menuStyle(.borderlessButton) + .fixedSize() + .help("选择下载 X1a0He CC 的方式") } } } @@ -859,6 +927,863 @@ struct AutoDownloadRow: View { } } +struct QAView: View { + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Group { + QAItem( + question: String(localized: "为什么需要安装 Helper?"), + answer: String(localized: "Helper 是一个具有管理员权限的辅助工具,用于执行需要管理员权限的操作,如修改系统文件等。没有 Helper 将无法正常使用软件的某些功能。") + ) + + QAItem( + question: String(localized: "为什么需要下载 Setup 组件?"), + answer: String(localized: "Setup 组件是 Adobe 官方的安装程序组件,我们需要对其进行修改以实现绕过验证的功能。如果没有下载并处理 Setup 组件,将无法使用安装功能。") + ) + + QAItem( + question: String(localized: "为什么有时候下载会失败?"), + answer: String(localized: "下载失败可能有多种原因:\n1. 网络连接不稳定\n2. Adobe 服务器响应超时\n3. 本地磁盘空间不足\n建议您检查网络连接并重试,如果问题持续存在,可以尝试使用代理或 VPN。") + ) + + QAItem( + question: String(localized: "如何修复安装失败的问题?"), + answer: String(localized: "如果安装失败,您可以尝试以下步骤:\n1. 确保已正确安装并连接 Helper\n2. 确保已下载并处理 Setup 组件\n3. 检查磁盘剩余空间是否充足\n4. 尝试重新下载并安装\n如果问题仍然存在,可以尝试重新安装 Helper 和重新处理 Setup 组件。") + ) + + QAItem( + question: String(localized: "为什么我安装的时候会遇到错误代码,错误代码表示什么意思?"), + answer: String(localized: "• 错误 2700:不太可能会出现,除非 Setup 组件处理失败了\n• 错误 107:所下载的文件架构与系统架构不一致或者安装文件被损坏\n• 错误 103:出现权限问题,请确保 Helper 状态正常\n• 错误 182:文件不齐全或文件被损坏,或者你的Setup组件不一致,请重新下载 X1a0He CC\n• 错误 133:系统磁盘空间不足\n• 错误 -1:Setup 组件未处理或处理失败,请联系开发者\n• 错误 195:所下载的产品不支持你当前的系统\n• 错误 146:请在 Mac 系统设置中给予 Adobe Downloader 全磁盘权限\n• 错误 255:Setup 组件需要更新,请联系开发者解决") + ) + } + } + .padding() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +struct QAItem: View { + let question: String + let answer: String + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(question) + .font(.headline) + .foregroundColor(.primary) + + Text(answer) + .font(.body) + .foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) + + Divider() + } + } +} + +struct CleanupLog: Identifiable { + let id = UUID() + let timestamp: Date + let command: String + let status: LogStatus + let message: String + + enum LogStatus { + case running + case success + case error + case cancelled + } + + static func getCleanupDescription(for command: String) -> String { + if command.contains("Library/Logs") || command.contains("DiagnosticReports") { + if command.contains("Adobe Creative Cloud") { + return String(localized: "正在清理 Creative Cloud 日志文件...") + } else if command.contains("CrashReporter") { + return String(localized: "正在清理崩溃报告日志...") + } else { + return String(localized: "正在清理应用程序日志文件...") + } + } else if command.contains("Library/Caches") { + return String(localized: "正在清理缓存文件...") + } else if command.contains("Library/Preferences") { + return String(localized: "正在清理偏好设置文件...") + } else if command.contains("Applications") { + if command.contains("Creative Cloud") { + return String(localized: "正在清理 Creative Cloud 应用...") + } else { + return String(localized: "正在清理 Adobe 应用程序...") + } + } else if command.contains("LaunchAgents") || command.contains("LaunchDaemons") { + return String(localized: "正在清理启动项服务...") + } else if command.contains("security") { + return String(localized: "正在清理钥匙串数据...") + } else if command.contains("AdobeGenuineClient") || command.contains("AdobeGCClient") { + return String(localized: "正在清理正版验证服务...") + } else if command.contains("hosts") { + return String(localized: "正在清理 hosts 文件...") + } else if command.contains("kill") { + return String(localized: "正在停止 Adobe 相关进程...") + } else if command.contains("receipts") { + return String(localized: "正在清理安装记录...") + } else { + return String(localized: "正在清理其他文件...") + } + } +} + +struct CleanupView: View { + @State private var showConfirmation = false + @State private var showAlert = false + @State private var alertMessage = "" + @State private var selectedOptions = Set() + @State private var isProcessing = false + @State private var cleanupLogs: [CleanupLog] = [] + @State private var currentCommandIndex = 0 + @State private var totalCommands = 0 + @State private var expandedOptions = Set() + @State private var isCancelled = false + @State private var isLogExpanded = false + + enum CleanupOption: String, CaseIterable, Identifiable { + case adobeApps = "Adobe 应用程序" + case adobeCreativeCloud = "Adobe Creative Cloud" + case adobePreferences = "Adobe 偏好设置" + case adobeCaches = "Adobe 缓存文件" + case adobeLicenses = "Adobe 许可文件" + case adobeLogs = "Adobe 日志文件" + case adobeServices = "Adobe 服务" + case adobeKeychain = "Adobe 钥匙串" + case adobeGenuineService = "Adobe 正版验证服务" + case adobeHosts = "Adobe Hosts" + + var id: String { self.rawValue } + + var localizedName: String { + switch self { + case .adobeApps: + return String(localized: "Adobe 应用程序") + case .adobeCreativeCloud: + return String(localized: "Adobe Creative Cloud") + case .adobePreferences: + return String(localized: "Adobe 偏好设置") + case .adobeCaches: + return String(localized: "Adobe 缓存文件") + case .adobeLicenses: + return String(localized: "Adobe 许可文件") + case .adobeLogs: + return String(localized: "Adobe 日志文件") + case .adobeServices: + return String(localized: "Adobe 服务") + case .adobeKeychain: + return String(localized: "Adobe 钥匙串") + case .adobeGenuineService: + return String(localized: "Adobe 正版验证服务") + case .adobeHosts: + return String(localized: "Adobe Hosts") + } + } + + var commands: [String] { + switch self { + case .adobeApps: + return [ + "sudo find /Applications -name 'Adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo find /Applications/Utilities -name 'Adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo rm -rf /Applications/Adobe Creative Cloud", + "sudo rm -rf /Applications/Utilities/Adobe Creative Cloud", + "sudo rm -rf /Applications/Utilities/Adobe Creative Cloud Experience", + "sudo rm -rf /Applications/Utilities/Adobe Installers/Uninstall Adobe Creative Cloud", + "sudo rm -rf /Applications/Utilities/Adobe Sync", + "sudo rm -rf /Applications/Utilities/Adobe Genuine Service" + ] + case .adobeCreativeCloud: + return [ + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/ADBox", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/ADS", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/AppsPanel", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/CEF", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/Core", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/CoreExt", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/DEBox", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/ElevationManager", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/FilesPanel", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/FontsPanel", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/HEX", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/LCC", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/NHEX", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/Notifications", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/pim.db", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/RemoteComponents", + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/TCC", + "sudo rm -rf /Library/Application Support/Adobe/ARMNext", + "sudo rm -rf /Library/Application Support/Adobe/ARMDC/Application", + "sudo rm -rf /Library/Application Support/Adobe/PII/com.adobe.pii.prefs", + "sudo rm -rf /Library/Application Support/Adobe/ACPLocal*", + "sudo rm -rf /Library/Application Support/regid.1986-12.com.adobe", + "sudo rm -rf /Library/Internet Plug-Ins/AdobeAAMDetect.plugin", + "sudo rm -rf /Library/Internet Plug-Ins/AdobePDF*", + "sudo rm -rf /Library/PDF Services/Save as Adobe PDF*", + "sudo rm -rf /Library/ScriptingAdditions/Adobe Unit Types.osax", + "sudo rm -rf /Library/Automator/Save as Adobe PDF.action", + "sudo rm -rf ~/.adobe", + "sudo rm -rf ~/Creative Cloud Files*", + "sudo find ~/Library/Application\\ Scripts -name '*com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo find ~/Library/Group\\ Containers -name '*com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo rm -rf ~/Library/Application\\ Scripts/Adobe-Hub-App || true", + "sudo rm -rf ~/Library/Group\\ Containers/Adobe-Hub-App || true", + "sudo rm -rf ~/Library/Application\\ Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.adobe* || true", + "sudo find ~/Library/Application\\ Support -name 'Acrobat*' ! -path '*/Adobe Downloader/*' -print0 | xargs -0 sudo rm -rf || true", + "sudo find ~/Library/Application\\ Support -name 'Adobe*' ! -name '*Adobe Downloader*' ! -path '*/Adobe Downloader/*' -print0 | xargs -0 sudo rm -rf || true", + "sudo find ~/Library/Application\\ Support -name 'com.adobe*' ! -name '*Adobe Downloader*' ! -path '*/Adobe Downloader/*' -print0 | xargs -0 sudo rm -rf || true", + "sudo rm -rf ~/Library/Application Support/io.branch", + "sudo rm -rf ~/Library/PhotoshopCrashes", + "sudo rm -rf ~/Library/WebKit/com.adobe*" + ] + case .adobePreferences: + return [ + "sudo find /Library/Preferences -name 'com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo find ~/Library/Preferences -name 'com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo find ~/Library/Preferences -name 'Adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo find ~/Library/Preferences/ByHost -name 'com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo rm -rf ~/Library/Preferences/adobe.com*", + "sudo rm -rf ~/Library/Preferences/AIRobin*", + "sudo rm -rf ~/Library/Preferences/Macromedia*", + "sudo rm -rf ~/Library/Saved Application State/com.adobe*" + ] + case .adobeCaches: + return [ + "sudo find ~/Library/Caches -name 'Adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo find ~/Library/Caches -name 'com.adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo rm -rf ~/Library/Caches/Acrobat* || true", + "sudo rm -rf ~/Library/Caches/CSXS || true", + "sudo rm -rf ~/Library/Caches/com.crashlytics.data/com.adobe* || true", + "sudo rm -rf ~/Library/Containers/com.adobe* || true", + "sudo rm -rf ~/Library/Cookies/com.adobe* || true", + "sudo find ~/Library/HTTPStorages -name '*Adobe*' ! -name '*Adobe Downloader*' ! -name '*com.x1a0he.macOS.Adobe-Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo find ~/Library/HTTPStorages -name 'com.adobe*' ! -name '*Adobe Downloader*' ! -name '*com.x1a0he.macOS.Adobe-Downloader*' -print0 | xargs -0 sudo rm -rf || true", + "sudo rm -rf ~/Library/HTTPStorages/Creative\\ Cloud\\ Content\\ Manager.node || true" + ] + case .adobeLicenses: + return [ + "sudo rm -rf /Library/Application Support/Adobe/Adobe PCD", + "sudo rm -rf /Library/Application Support/Adobe/AdobeGCClient", + "sudo rm -rf /Library/Application Support/regid.1986-12.com.adobe", + "sudo rm -rf /private/var/db/receipts/com.adobe*", + "sudo rm -rf /private/var/db/receipts/*Photoshop*", + "sudo rm -rf /private/var/db/receipts/*CreativeCloud*", + "sudo rm -rf /private/var/db/receipts/*CCXP*", + "sudo rm -rf /private/var/db/receipts/*mygreatcompany*", + "sudo rm -rf /private/var/db/receipts/*AntiCC*", + "sudo rm -rf /private/var/db/receipts/*.RiD.*", + "sudo rm -rf /private/var/db/receipts/*.CCRuntime.*" + ] + case .adobeLogs: + return [ + "sudo find ~/Library/Logs -name 'Adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo find ~/Library/Logs -name 'adobe*' ! -name '*Adobe Downloader*' -print0 | xargs -0 sudo rm -rf", + "sudo rm -rf ~/Library/Logs/Adobe Creative Cloud Cleaner Tool.log", + "sudo rm -rf ~/Library/Logs/CreativeCloud", + "sudo rm -rf /Library/Logs/CreativeCloud", + "sudo rm -rf ~/Library/Logs/CSXS", + "sudo rm -rf ~/Library/Logs/amt3.log", + "sudo rm -rf ~/Library/Logs/CoreSyncInstall.log", + "sudo rm -rf ~/Library/Logs/CrashReporter/*Adobe*", + "sudo rm -rf ~/Library/Logs/acroLicLog.log", + "sudo rm -rf ~/Library/Logs/acroNGLLog.txt", + "sudo rm -rf ~/Library/Logs/DiagnosticReports/*Adobe*", + "sudo rm -rf ~/Library/Logs/distNGLLog.txt", + "sudo rm -rf ~/Library/Logs/NGL*", + "sudo rm -rf ~/Library/Logs/oobelib.log", + "sudo rm -rf ~/Library/Logs/PDApp*", + "sudo rm -rf /Library/Logs/adobe*", + "sudo rm -rf /Library/Logs/Adobe*", + "sudo rm -rf ~/Library/Logs/Adobe*", + "sudo rm -rf ~/Library/Logs/adobe*", + "sudo rm -rf /Library/Logs/DiagnosticReports/*Adobe*", + "sudo rm -rf /Library/Application Support/CrashReporter/*Adobe*", + "sudo rm -rf ~/Library/Application Support/CrashReporter/*Adobe*" + ] + case .adobeServices: + return [ + "sudo launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.adobe.* || true", + "sudo launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.adobe.* || true", + "sudo launchctl unload /Library/LaunchDaemons/com.adobe.* || true", + "sudo launchctl remove com.adobe.AdobeCreativeCloud || true", + "sudo launchctl remove com.adobe.AdobeGenuineService.plist || true", + "sudo ps aux | grep -i 'Adobe' | grep -v 'Adobe Downloader' | grep -v 'Adobe-Downloader.helper' | grep -v grep | awk '{print $2}' | { pids=$(cat); [ ! -z \"$pids\" ] && echo \"$pids\" | xargs sudo kill -9; } || true", + "sudo rm -rf /Library/LaunchAgents/com.adobe.*", + "sudo rm -rf /Library/LaunchDaemons/com.adobe.*", + "sudo rm -rf /Library/LaunchAgents/com.adobe.ARMDCHelper*", + "sudo rm -rf /Library/LaunchAgents/com.adobe.AdobeCreativeCloud.plist", + "sudo rm -rf /Library/LaunchAgents/com.adobe.ccxprocess.plist" + ] + case .adobeKeychain: + return [ + "sudo security dump-keychain /Library/Keychains/System.keychain | grep -i 'acrobat.com' | grep -i 'srvr' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do sudo security delete-internet-password -s \"$line\" /Library/Keychains/System.keychain; done || true", + "sudo security dump-keychain ~/Library/Keychains/login.keychain-db | grep -i 'acrobat.com' | grep -i 'srvr' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do security delete-internet-password -s \"$line\" ~/Library/Keychains/login.keychain-db; done || true", + "sudo security dump-keychain /Library/Keychains/System.keychain | grep -i 'Adobe.APS' | grep -v 'Adobe Downloader' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do sudo security delete-generic-password -l \"$line\" /Library/Keychains/System.keychain; done || true", + "sudo security dump-keychain ~/Library/Keychains/login.keychain-db | grep -i 'Adobe.APS' | grep -v 'Adobe Downloader' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do security delete-generic-password -l \"$line\" ~/Library/Keychains/login.keychain-db; done || true", + "sudo security dump-keychain /Library/Keychains/System.keychain | grep -i 'Adobe App Info\\|Adobe App Prefetched Info\\|Adobe User\\|com.adobe\\|Adobe Lightroom' | grep -v 'Adobe Downloader' | grep -i 'svce' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do sudo security delete-generic-password -s \"$line\" /Library/Keychains/System.keychain; done || true", + "sudo security dump-keychain ~/Library/Keychains/login.keychain-db | grep -i 'Adobe App Info\\|Adobe App Prefetched Info\\|Adobe User\\|com.adobe\\|Adobe Lightroom' | grep -v 'Adobe Downloader' | grep -i 'svce' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do security delete-generic-password -s \"$line\" ~/Library/Keychains/login.keychain-db; done || true", + "sudo security dump-keychain /Library/Keychains/System.keychain | grep -i 'Adobe Content \\|Adobe Intermediate' | grep -v 'Adobe Downloader' | grep -i 'alis' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do sudo security delete-certificate -c \"$line\" /Library/Keychains/System.keychain; done || true", + "sudo security dump-keychain ~/Library/Keychains/login.keychain-db | grep -i 'Adobe Content \\|Adobe Intermediate' | grep -v 'Adobe Downloader' | grep -i 'alis' | awk -F '=' '{print $2}' | cut -d '\"' -f2 | while read -r line; do security delete-certificate -c \"$line\" ~/Library/Keychains/login.keychain-db; done || true" + ] + case .adobeGenuineService: + return [ + "sudo rm -rf /Library/Application Support/Adobe/Adobe Desktop Common/AdobeGenuineClient", + "sudo rm -rf /Library/Application Support/Adobe/AdobeGCClient", + "sudo rm -rf /Library/Preferences/com.adobe.AdobeGenuineService.plist", + "sudo rm -rf /Applications/Utilities/Adobe Creative Cloud/Utils/AdobeGenuineValidator", + "sudo rm -rf /Applications/Utilities/Adobe Genuine Service", + "sudo rm -rf /Library/PrivilegedHelperTools/com.adobe.acc*", + "sudo find /private/tmp -type d -iname '*adobe*' ! -iname '*Adobe Downloader*' -o -type f -iname '*adobe*' ! -iname '*Adobe Downloader*' | xargs rm -rf {} \\+", + "sudo find /private/tmp -type d -iname '*CCLBS*' ! -iname '*Adobe Downloader*' -o -type f -iname '*adobe*' ! -iname '*Adobe Downloader*' | xargs rm -rf {} \\+", + "sudo find /private/var/folders/ -type d -iname '*adobe*' ! -iname '*Adobe Downloader*' -o -type f -iname '*adobe*' ! -iname '*Adobe Downloader*' | xargs rm -rf {} \\+", + "sudo rm -rf /private/tmp/com.adobe*", + "sudo rm -rf /private/tmp/Adobe*", + "sudo rm -rf /private/tmp/.adobe*" + ] + case .adobeHosts: + return [ + "sudo sh -c 'grep -v \"adobe\" /etc/hosts > /etc/hosts.temp && mv /etc/hosts.temp /etc/hosts'" + ] + } + } + + var description: String { + switch self { + case .adobeApps: + return String(localized: "删除所有已安装的 Adobe 应用程序(不包括 Adobe Downloader)") + case .adobeCreativeCloud: + return String(localized: "删除 Adobe Creative Cloud 应用程序及其组件") + case .adobePreferences: + return String(localized: "删除 Adobe 应用程序的偏好设置文件(不包括 Adobe Downloader)") + case .adobeCaches: + return String(localized: "删除 Adobe 应用程序的缓存文件(不包括 Adobe Downloader)") + case .adobeLicenses: + return String(localized: "删除 Adobe 许可和激活相关文件") + case .adobeLogs: + return String(localized: "删除 Adobe 应用程序的日志文件(不包括 Adobe Downloader)") + case .adobeServices: + return String(localized: "停止并删除 Adobe 相关服务") + case .adobeKeychain: + return String(localized: "删除钥匙串中的 Adobe 相关条目") + case .adobeGenuineService: + return String(localized: "删除 Adobe 正版验证服务及其组件") + case .adobeHosts: + return String(localized: "清理 hosts 文件中的 Adobe 相关条目") + } + } + } + + var body: some View { + VStack(alignment: .leading) { + Text("选择要清理的内容") + .font(.headline) + .padding(.bottom, 4) + + Text("注意:清理过程不会影响 Adobe Downloader 的文件和下载数据") + .font(.subheadline) + .foregroundColor(.secondary) + + ScrollView(showsIndicators: false) { + VStack(alignment: .leading) { + ForEach(CleanupOption.allCases) { option in + VStack() { + #if DEBUG + Button(action: { + withAnimation { + if expandedOptions.contains(option) { + expandedOptions.remove(option) + } else { + expandedOptions.insert(option) + } + } + }) { + HStack { + Toggle(isOn: Binding( + get: { selectedOptions.contains(option) }, + set: { isSelected in + if isSelected { + selectedOptions.insert(option) + } else { + selectedOptions.remove(option) + } + } + )) { + EmptyView() + } + .disabled(isProcessing) + .labelsHidden() + + VStack(alignment: .leading, spacing: 4) { + Text(option.localizedName) + .font(.system(size: 14, weight: .medium)) + Text(option.description) + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: expandedOptions.contains(option) ? "chevron.down" : "chevron.right") + .foregroundColor(.secondary) + .rotationEffect(.degrees(expandedOptions.contains(option) ? 0 : -90)) + } + .padding(.vertical, 8) + .padding(.horizontal, 6) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .disabled(isProcessing) + + if expandedOptions.contains(option) { + VStack(alignment: .leading, spacing: 4) { + Text("将执行的命令:") + .font(.system(size: 11, weight: .medium)) + .foregroundColor(.secondary) + .padding(.top, 4) + .padding(.horizontal, 6) + + ForEach(option.commands, id: \.self) { command in + Text(command) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(.secondary) + .padding(8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(NSColor.textBackgroundColor)) + .cornerRadius(4) + .padding(.horizontal, 6) + } + } + .padding(.bottom, 8) + } + #else + HStack { + Toggle(isOn: Binding( + get: { selectedOptions.contains(option) }, + set: { isSelected in + if isSelected { + selectedOptions.insert(option) + } else { + selectedOptions.remove(option) + } + } + )) { + EmptyView() + } + .disabled(isProcessing) + .labelsHidden() + + VStack(alignment: .leading, spacing: 4) { + Text(option.localizedName) + .font(.system(size: 14, weight: .medium)) + Text(option.description) + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 6) + #endif + } + .background(Color(NSColor.controlBackgroundColor)) + .cornerRadius(8) + } + } + } + + Divider() + .padding(.vertical, 8) + + VStack(alignment: .leading, spacing: 8) { + if isProcessing { + HStack { + ProgressView(value: Double(currentCommandIndex), total: Double(totalCommands)) { + Text("清理进度:\(currentCommandIndex)/\(totalCommands)") + .font(.system(size: 12)) + } + .progressViewStyle(LinearProgressViewStyle()) + + Button(action: { + isCancelled = true + }) { + Text("取消") + } + .disabled(isCancelled) + } + + if let lastLog = cleanupLogs.last { + #if DEBUG + Text("当前执行:\(lastLog.command)") + .font(.system(size: 12)) + .foregroundColor(.secondary) + .lineLimit(1) + .truncationMode(.middle) + #else + Text("当前执行:\(CleanupLog.getCleanupDescription(for: lastLog.command))") + .font(.system(size: 12)) + .foregroundColor(.secondary) + .lineLimit(1) + .truncationMode(.middle) + #endif + } + } + + VStack(alignment: .leading, spacing: 4) { + Button(action: { + withAnimation { + isLogExpanded.toggle() + } + }) { + HStack { + Text("最近日志:") + .font(.system(size: 12, weight: .medium)) + + if isProcessing { + Text("正在执行...") + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: isLogExpanded ? "chevron.down" : "chevron.right") + .foregroundColor(.secondary) + } + .padding(.vertical, 4) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + + ScrollView { + VStack(alignment: .leading, spacing: 4) { + if cleanupLogs.isEmpty { + HStack { + Spacer() + Text("暂无清理记录") + .font(.system(size: 11)) + .foregroundColor(.secondary) + Spacer() + } + .padding(.vertical, 8) + } else { + if isLogExpanded { + ForEach(cleanupLogs.reversed()) { log in + LogEntryView(log: log) + } + } else if let lastLog = cleanupLogs.last { + LogEntryView(log: lastLog) + } + } + } + .padding(.vertical, 4) + } + .frame(height: cleanupLogs.isEmpty ? 40 : (isLogExpanded ? 200 : 40)) + .animation(.easeInOut, value: isLogExpanded) + } + .padding(8) + .background(Color(NSColor.textBackgroundColor)) + .cornerRadius(6) + } + + HStack { + Button(action: { + selectedOptions = Set(CleanupOption.allCases) + }) { + Text("全选") + } + .disabled(isProcessing) + + Button(action: { + selectedOptions.removeAll() + }) { + Text("取消全选") + } + .disabled(isProcessing) + + #if DEBUG + Button(action: { + if expandedOptions.count == CleanupOption.allCases.count { + expandedOptions.removeAll() + } else { + expandedOptions = Set(CleanupOption.allCases) + } + }) { + Text(expandedOptions.count == CleanupOption.allCases.count ? "折叠全部" : "展开全部") + } + .disabled(isProcessing) + #endif + + Spacer() + + Button(action: { + if !selectedOptions.isEmpty { + showConfirmation = true + } + }) { + Text("开始清理") + } + .disabled(selectedOptions.isEmpty || isProcessing) + } + .padding(.top, 8) + } + .padding() + .alert("确认清理", isPresented: $showConfirmation) { + Button("取消", role: .cancel) { } + Button("确定", role: .destructive) { + cleanupSelectedItems() + } + } message: { + Text("这将删除所选的 Adobe 相关文件,该操作不可撤销。清理过程不会影响 Adobe Downloader 的文件和下载数据。是否继续?") + } + .alert(isPresented: $showAlert) { + Alert( + title: Text("清理结果"), + message: Text(alertMessage), + dismissButton: .default(Text("确定")) + ) + } + } + + private func cleanupSelectedItems() { + isProcessing = true + cleanupLogs.removeAll() + currentCommandIndex = 0 + isCancelled = false + + let userHome = NSHomeDirectory() + + var commands: [String] = [] + for option in selectedOptions { + let userCommands = option.commands.map { command in + command.replacingOccurrences(of: "~/", with: "\(userHome)/") + } + commands.append(contentsOf: userCommands) + } + + totalCommands = commands.count + + executeNextCommand(commands: commands) + } + + private func executeNextCommand(commands: [String]) { + guard currentCommandIndex < commands.count else { + DispatchQueue.main.async { + isProcessing = false + alertMessage = isCancelled ? String(localized: "清理已取消") : String(localized: "清理完成") + showAlert = true + selectedOptions.removeAll() + } + return + } + + if isCancelled { + DispatchQueue.main.async { + isProcessing = false + alertMessage = String(localized: "清理已取消") + showAlert = true + selectedOptions.removeAll() + } + return + } + + let command = commands[currentCommandIndex] + cleanupLogs.append(CleanupLog( + timestamp: Date(), + command: command, + status: .running, + message: String(localized: "正在执行...") + )) + + let timeoutTimer = DispatchSource.makeTimerSource(queue: .global()) + timeoutTimer.schedule(deadline: .now() + 30) + timeoutTimer.setEventHandler { [self] in + if let index = cleanupLogs.lastIndex(where: { $0.command == command }) { + DispatchQueue.main.async { + cleanupLogs[index] = CleanupLog( + timestamp: Date(), + command: command, + status: .error, + message: String(localized: "执行结果:执行超时\n执行命令:\(command)") + ) + currentCommandIndex += 1 + executeNextCommand(commands: commands) + } + } + } + timeoutTimer.resume() + + PrivilegedHelperManager.shared.executeCommand(command) { [self] output in + timeoutTimer.cancel() + DispatchQueue.main.async { + if let index = cleanupLogs.lastIndex(where: { $0.command == command }) { + if isCancelled { + cleanupLogs[index] = CleanupLog( + timestamp: Date(), + command: command, + status: .cancelled, + message: String(localized: "已取消") + ) + } else { + let isSuccess = output.isEmpty || output.lowercased() == "success" + let message = if isSuccess { + String(localized: "执行成功") + } else { + String(localized: "执行结果:\(output)\n执行命令:\(command)") + } + cleanupLogs[index] = CleanupLog( + timestamp: Date(), + command: command, + status: isSuccess ? .success : .error, + message: message + ) + } + } + currentCommandIndex += 1 + executeNextCommand(commands: commands) + } + } + } + + private func statusIcon(for status: CleanupLog.LogStatus) -> String { + switch status { + case .running: + return "arrow.triangle.2.circlepath" + case .success: + return "checkmark.circle.fill" + case .error: + return "exclamationmark.circle.fill" + case .cancelled: + return "xmark.circle.fill" + } + } + + private func statusColor(for status: CleanupLog.LogStatus) -> Color { + switch status { + case .running: + return .blue + case .success: + return .green + case .error: + return .red + case .cancelled: + return .orange + } + } + + private func timeString(from date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter.string(from: date) + } +} + +struct LogEntryView: View { + let log: CleanupLog + @State private var showCopyButton = false + + var body: some View { + HStack { + Image(systemName: statusIcon(for: log.status)) + .foregroundColor(statusColor(for: log.status)) + + Text(timeString(from: log.timestamp)) + .font(.system(size: 11)) + .foregroundColor(.secondary) + + #if DEBUG + Text(log.command) + .font(.system(size: 11, design: .monospaced)) + .lineLimit(1) + .truncationMode(.middle) + #else + Text(CleanupLog.getCleanupDescription(for: log.command)) + .font(.system(size: 11)) + .lineLimit(1) + .truncationMode(.middle) + #endif + + Spacer() + + if log.status == .error && !log.message.isEmpty { + HStack(spacing: 4) { + Text(truncatedErrorMessage(log.message)) + .font(.system(size: 11)) + .foregroundColor(.secondary) + + Button(action: { + copyToClipboard(log.message) + }) { + Image(systemName: "doc.on.doc") + .font(.system(size: 11)) + } + .buttonStyle(.plain) + .help("复制完整错误信息") + } + } else { + Text(log.message) + .font(.system(size: 11)) + .foregroundColor(.secondary) + } + } + .padding(.vertical, 4) + .padding(.horizontal, 8) + } + + private func truncatedErrorMessage(_ message: String) -> String { + if message.hasPrefix("执行失败:") { + let errorMessage = String(message.dropFirst(5)) + if errorMessage.count > 30 { + return "执行失败:" + errorMessage.prefix(30) + "..." + } + } + return message + } + + private func copyToClipboard(_ message: String) { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(message, forType: .string) + } + + private func statusIcon(for status: CleanupLog.LogStatus) -> String { + switch status { + case .running: + return "arrow.triangle.2.circlepath" + case .success: + return "checkmark.circle.fill" + case .error: + return "exclamationmark.circle.fill" + case .cancelled: + return "xmark.circle.fill" + } + } + + private func statusColor(for status: CleanupLog.LogStatus) -> Color { + switch status { + case .running: + return .blue + case .success: + return .green + case .error: + return .red + case .cancelled: + return .orange + } + } + + private func timeString(from date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter.string(from: date) + } +} #Preview("About Tab") { AboutAppView() @@ -872,3 +1797,18 @@ struct AutoDownloadRow: View { } .fixedSize() } + +#Preview("Q&A View") { + QAView() + .frame(width: 600) +} + +#Preview("Cleanup View") { + CleanupView() + .frame(width: 600) +} + +#Preview("Complete About View") { + AboutView(updater: PreviewUpdater()) + .environmentObject(NetworkManager()) +} diff --git a/Adobe Downloader/Views/DownloadProgressView.swift b/Adobe Downloader/Views/DownloadProgressView.swift index fdfe2db..b62e63c 100644 --- a/Adobe Downloader/Views/DownloadProgressView.swift +++ b/Adobe Downloader/Views/DownloadProgressView.swift @@ -20,6 +20,8 @@ struct DownloadProgressView: View { @State private var expandedProducts: Set = [] @State private var iconImage: NSImage? = nil @State private var showSetupProcessAlert = false + @State private var showCommandLineInstall = false + @State private var showCopiedAlert = false private var statusLabel: some View { Text(task.status.description) @@ -78,12 +80,14 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.orange) + .controlSize(.regular) Button(action: onCancel) { Label("取消", systemImage: "xmark") } .buttonStyle(.borderedProminent) .tint(.red) + .controlSize(.regular) case .paused: Button(action: onResume) { @@ -91,12 +95,14 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.blue) + .controlSize(.regular) Button(action: onCancel) { Label("取消", systemImage: "xmark") } .buttonStyle(.borderedProminent) .tint(.red) + .controlSize(.regular) case .failed(let info): if info.recoverable { @@ -105,6 +111,7 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.blue) + .controlSize(.regular) } Button(action: onRemove) { @@ -112,11 +119,24 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.red) + .controlSize(.regular) case .completed: HStack(spacing: 8) { if task.displayInstallButton { Button(action: { + #if DEBUG + do { + _ = try PrivilegedHelperManager.shared.getHelperProxy() + showInstallPrompt = false + isInstalling = true + Task { + await networkManager.installProduct(at: task.directory) + } + } catch { + showSetupProcessAlert = true + } + #else if !ModifySetup.isSetupModified() { showSetupProcessAlert = true } else { @@ -131,11 +151,13 @@ struct DownloadProgressView: View { showSetupProcessAlert = true } } + #endif }) { Label("安装", systemImage: "square.and.arrow.down.on.square") } .buttonStyle(.borderedProminent) .tint(.green) + .controlSize(.regular) .alert("Setup 组件未处理", isPresented: $showSetupProcessAlert) { Button("确定") { } } message: { @@ -154,6 +176,7 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.red) + .controlSize(.regular) } case .retrying: @@ -162,6 +185,7 @@ struct DownloadProgressView: View { } .buttonStyle(.borderedProminent) .tint(.red) + .controlSize(.regular) } } .controlSize(.small) @@ -410,21 +434,74 @@ struct DownloadProgressView: View { Divider() VStack(alignment: .leading, spacing: 6) { - Button(action: { - withAnimation { - isPackageListExpanded.toggle() + HStack { + Button(action: { + withAnimation { + isPackageListExpanded.toggle() + } + }) { + HStack { + Image(systemName: isPackageListExpanded ? "chevron.down" : "chevron.right") + .foregroundColor(.secondary) + Text("产品和包列表") + .font(.caption) + .foregroundColor(.secondary) + } + .contentShape(Rectangle()) } - }) { - HStack { - Image(systemName: isPackageListExpanded ? "chevron.down" : "chevron.right") - .foregroundColor(.secondary) - Text("产品和包列表") - .font(.caption) - .foregroundColor(.secondary) + .buttonStyle(.plain) + + Spacer() + + if case .completed = task.status { + Button(action: { + showCommandLineInstall.toggle() + }) { + Label("命令行安装", systemImage: "terminal") + } + .buttonStyle(.borderedProminent) + .tint(.purple) + .controlSize(.regular) + .popover(isPresented: $showCommandLineInstall, arrowEdge: .bottom) { + VStack(alignment: .leading, spacing: 8) { + Button("复制命令") { + let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup" + let driverPath = "\(task.directory.path)/driver.xml" + let command = "sudo \"\(setupPath)\" --install=1 --driverXML=\"\(driverPath)\"" + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(command, forType: .string) + showCopiedAlert = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + showCopiedAlert = false + } + } + + if showCopiedAlert { + Text("已复制") + .font(.caption) + .foregroundColor(.green) + } + + let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup" + let driverPath = "\(task.directory.path)/driver.xml" + let command = "sudo \"\(setupPath)\" --install=1 --driverXML=\"\(driverPath)\"" + Text(command) + .font(.system(.caption, design: .monospaced)) + .foregroundColor(.secondary) + .textSelection(.enabled) + .padding(8) + .background(Color.secondary.opacity(0.1)) + .cornerRadius(6) + } + .padding() + .frame(width: 400) + } } - .contentShape(Rectangle()) + + actionButtons } - .buttonStyle(.plain) if isPackageListExpanded { ScrollView(showsIndicators: false) { @@ -442,11 +519,6 @@ struct DownloadProgressView: View { } } } - - HStack { - Spacer() - actionButtons - } } .padding() .background(Color(NSColor.windowBackgroundColor)) @@ -603,20 +675,29 @@ struct PackageRow: View { } #Preview("下载中") { - DownloadProgressView( + let product = ProductsToDownload( + sapCode: "AUDT", + version: "25.0", + buildGuid: "123" + ) + product.packages = [ + Package( + type: "Application", + fullPackageName: "AdobeAudition25All", + downloadSize: 878454797, + downloadURL: "https://example.com/download", + packageVersion: "25.0.0.1" + ) + ] + + return DownloadProgressView( task: NewDownloadTask( sapCode: "AUDT", version: "25.0", language: "zh_CN", displayName: "Adobe Audition", - directory: URL(fileURLWithPath: "Adobe Downloader Audition_25.0-zh_CN-macuniversal"), - productsToDownload: [ - ProductsToDownload( - sapCode: "AUDT", - version: "25.0", - buildGuid: "123" - ) - ], + directory: URL(fileURLWithPath: "/Users/test/Downloads/Adobe Audition_25.0-zh_CN-macuniversal"), + productsToDownload: [product], createAt: Date(), totalStatus: .downloading(DownloadStatus.DownloadInfo( fileName: "AdobeAudition25All_stripped.zip", @@ -629,7 +710,7 @@ struct PackageRow: View { totalDownloadedSize: 457424883, totalSize: 878454797, totalSpeed: 1024 * 1024 * 2, - platform: "" + platform: "macuniversal" ), onCancel: {}, onPause: {}, @@ -641,20 +722,32 @@ struct PackageRow: View { } #Preview("已完成") { - DownloadProgressView( + let product = ProductsToDownload( + sapCode: "AUDT", + version: "25.0", + buildGuid: "123" + ) + let package = Package( + type: "Application", + fullPackageName: "AdobeAudition25All", + downloadSize: 878454797, + downloadURL: "https://example.com/download", + packageVersion: "25.0.0.1" + ) + package.status = .completed + package.progress = 1.0 + package.downloadedSize = 878454797 + package.downloaded = true + product.packages = [package] + + return DownloadProgressView( task: NewDownloadTask( sapCode: "AUDT", version: "25.0", language: "zh_CN", displayName: "Adobe Audition", - directory: URL(fileURLWithPath: "Adobe Downloader Audition_25.0-zh_CN-macuniversal"), - productsToDownload: [ - ProductsToDownload( - sapCode: "AUDT", - version: "25.0", - buildGuid: "123" - ) - ], + directory: URL(fileURLWithPath: "/Users/test/Downloads/Adobe Audition_25.0-zh_CN-macuniversal"), + productsToDownload: [product], createAt: Date(), totalStatus: .completed(DownloadStatus.CompletionInfo( timestamp: Date(), @@ -665,7 +758,7 @@ struct PackageRow: View { totalDownloadedSize: 878454797, totalSize: 878454797, totalSpeed: 0, - platform: "" + platform: "macuniversal" ), onCancel: {}, onPause: {}, @@ -677,20 +770,31 @@ struct PackageRow: View { } #Preview("暂停") { - DownloadProgressView( + let product = ProductsToDownload( + sapCode: "AUDT", + version: "25.0", + buildGuid: "123" + ) + let package = Package( + type: "Application", + fullPackageName: "AdobeAudition25All", + downloadSize: 878454797, + downloadURL: "https://example.com/download", + packageVersion: "25.0.0.1" + ) + package.status = .paused + package.progress = 0.52 + package.downloadedSize = 457424883 + product.packages = [package] + + return DownloadProgressView( task: NewDownloadTask( sapCode: "AUDT", version: "25.0", language: "zh_CN", displayName: "Adobe Audition", - directory: URL(fileURLWithPath: "Adobe Downloader Audition_25.0-zh_CN-macuniversal"), - productsToDownload: [ - ProductsToDownload( - sapCode: "AUDT", - version: "25.0", - buildGuid: "123" - ) - ], + directory: URL(fileURLWithPath: "/Users/test/Downloads/Adobe Audition_25.0-zh_CN-macuniversal"), + productsToDownload: [product], createAt: Date(), totalStatus: .paused(DownloadStatus.PauseInfo( reason: .userRequested, @@ -701,7 +805,7 @@ struct PackageRow: View { totalDownloadedSize: 457424883, totalSize: 878454797, totalSpeed: 0, - platform: "" + platform: "macuniversal" ), onCancel: {}, onPause: {}, diff --git a/AdobeDownloaderHelperTool/main.swift b/AdobeDownloaderHelperTool/main.swift index f5c69c9..5f3bd7e 100644 --- a/AdobeDownloaderHelperTool/main.swift +++ b/AdobeDownloaderHelperTool/main.swift @@ -25,9 +25,17 @@ class SecureCommandHandler { case .install: return "installer -pkg \"\(path1)\" -target /" case .uninstall: + if path1.contains("*") { + return "rm -rf \(path1)" + } + if path1.hasPrefix("\"") && path1.hasSuffix("\"") { + return "rm -rf \(path1)" + } return "rm -rf \"\(path1)\"" case .moveFile: - return "cp \"\(path1)\" \"\(path2)\"" + let source = path1.hasPrefix("\"") ? path1 : "\"\(path1)\"" + let dest = path2.hasPrefix("\"") ? path2 : "\"\(path2)\"" + return "cp \(source) \(dest)" case .setPermissions: return "chmod \(permissions) \"\(path1)\"" case .shellCommand: @@ -36,13 +44,14 @@ class SecureCommandHandler { } static func validatePath(_ path: String) -> Bool { + let cleanPath = path.trimmingCharacters(in: .init(charactersIn: "\"'")) let allowedPaths = ["/Library/Application Support/Adobe"] - if allowedPaths.contains(where: { path.hasPrefix($0) }) { + if allowedPaths.contains(where: { cleanPath.hasPrefix($0) }) { return true } let forbiddenPaths = ["/System", "/usr", "/bin", "/sbin", "/var"] - return !forbiddenPaths.contains { path.hasPrefix($0) } + return !forbiddenPaths.contains { cleanPath.hasPrefix($0) } } } @@ -81,14 +90,18 @@ class HelperTool: NSObject, HelperToolProtocol { func executeCommand(type: CommandType, path1: String, path2: String, permissions: Int, withReply reply: @escaping (String) -> Void) { operationQueue.async { - self.logger.notice("收到安全命令执行请求") - guard let shellCommand = SecureCommandHandler.createCommand(type: type, path1: path1, path2: path2, permissions: permissions) else { self.logger.error("不安全的路径访问被拒绝") reply("Error: Invalid path access") return } + #if DEBUG + self.logger.notice("收到安全命令执行请求: \(shellCommand, privacy: .public)") + #else + self.logger.notice("收到安全命令执行请求") + #endif + let isSetupCommand = shellCommand.contains("Setup") && shellCommand.contains("--install") let task = Process() @@ -210,18 +223,33 @@ extension HelperTool: NSXPCListenerDelegate { return false } - var requirement: SecRequirement? - let requirementString = "anchor apple generic and identifier \"com.x1a0he.macOS.Adobe-Downloader\"" - guard SecRequirementCreateWithString(requirementString as CFString, - [], &requirement) == errSecSuccess, - let req = requirement else { - logger.error("签名要求创建失败") + var staticCode: SecStaticCode? + let staticCodeResult = SecCodeCopyStaticCode(code, [], &staticCode) + guard staticCodeResult == errSecSuccess, + let staticCodeRef = staticCode else { + logger.error("获取静态代码失败: \(staticCodeResult)") return false } - let validityResult = SecCodeCheckValidity(code, [], req) + var requirement: SecRequirement? + let requirementString = "identifier \"com.x1a0he.macOS.Adobe-Downloader\" and anchor apple generic and certificate leaf[subject.CN] = \"Apple Development: x1a0he@outlook.com (LFN2762T4F)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */" + guard SecRequirementCreateWithString(requirementString as CFString, + [], &requirement) == errSecSuccess, + let req = requirement else { + self.logger.error("签名要求创建失败") + return false + } + + let validityResult = SecStaticCodeCheckValidity(staticCodeRef, [], req) if validityResult != errSecSuccess { - logger.error("代码签名验证不匹配: \(validityResult)") + self.logger.error("代码签名验证不匹配: \(validityResult), 要求字符串: \(requirementString)") + + var signingInfo: CFDictionary? + if SecCodeCopySigningInformation(staticCodeRef, [], &signingInfo) == errSecSuccess, + let info = signingInfo as? [String: Any] { + self.logger.notice("实际签名信息: \(String(describing: info))") + } + return false } diff --git a/Localizables/Localizable.xcstrings b/Localizables/Localizable.xcstrings index a6c6180..2d7101e 100644 --- a/Localizables/Localizable.xcstrings +++ b/Localizables/Localizable.xcstrings @@ -6,6 +6,9 @@ }, " [%@]" : { + }, + "(%@)" : { + }, "(可能导致处理 Setup 组件失败)" : { "localizations" : { @@ -124,6 +127,16 @@ } } }, + "• 错误 2700:不太可能会出现,除非 Setup 组件处理失败了\n• 错误 107:所下载的文件架构与系统架构不一致或者安装文件被损坏\n• 错误 103:出现权限问题,请确保 Helper 状态正常\n• 错误 182:文件不齐全或文件被损坏,或者你的Setup组件不一致,请重新下载 X1a0He CC\n• 错误 133:系统磁盘空间不足\n• 错误 -1:Setup 组件未处理或处理失败,请联系开发者\n• 错误 195:所下载的产品不支持你当前的系统\n• 错误 146:请在 Mac 系统设置中给予 Adobe Downloader 全磁盘权限\n• 错误 255:Setup 组件需要更新,请联系开发者解决" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "• Error 2700: This is unlikely to occur, unless the Setup component processing fails\n• Error 107: The downloaded file architecture does not match the system architecture or the installation file is damaged\n• Error 103: Permission issues, please ensure the Helper status is normal\n• Error 182: The file is incomplete or damaged, or your Setup component is inconsistent, please re-download X1a0He CC\n• Error 133: System disk space is insufficient\n• Error -1: Setup component is not processed or processed failed, please contact the developer\n• Error 195: The downloaded product does not support your current system\n• Error 146: Please grant Adobe Downloader full disk permission in the Mac system settings\n• Error 255: The Setup component needs to be updated, please contact the developer to solve" + } + } + } + }, "|" : { }, @@ -136,6 +149,9 @@ } } } + }, + "Adobe Creative Cloud" : { + }, "Adobe Downloader %@" : { @@ -170,6 +186,96 @@ } } }, + "Adobe Hosts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Hosts" + } + } + } + }, + "Adobe 偏好设置" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Preferences" + } + } + } + }, + "Adobe 应用程序" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Applications" + } + } + } + }, + "Adobe 日志文件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Log Files" + } + } + } + }, + "Adobe 服务" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Services" + } + } + } + }, + "Adobe 正版验证服务" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Genuine Service" + } + } + } + }, + "Adobe 缓存文件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Cache Files" + } + } + } + }, + "Adobe 许可文件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe License Files" + } + } + } + }, + "Adobe 钥匙串" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adobe Keychain" + } + } + } + }, "API:" : { }, @@ -220,6 +326,16 @@ } } }, + "Helper 是一个具有管理员权限的辅助工具,用于执行需要管理员权限的操作,如修改系统文件等。没有 Helper 将无法正常使用软件的某些功能。" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Helper is a tool with administrator privileges, used to perform operations that require administrator privileges, such as modifying system files. Without Helper, some features of the software may not work properly." + } + } + } + }, "Helper 未安装将导致无法执行需要管理员权限的操作" : { "localizations" : { "en" : { @@ -314,6 +430,16 @@ } } }, + "Setup 组件是 Adobe 官方的安装程序组件,我们需要对其进行修改以实现绕过验证的功能。如果没有下载并处理 Setup 组件,将无法使用安装功能。" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Setup component is the official installation program component of Adobe. We need to modify it to bypass the verification function. If the Setup component is not downloaded and processed, the installation function cannot be used." + } + } + } + }, "Setup 组件未处理" : { "localizations" : { "en" : { @@ -342,6 +468,29 @@ }, "v6" : { + }, + "X1a0He CC" : { + + }, + "X1a0He CC 下载并处理成功" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "X1a0He CC downloaded and processed successfully" + } + } + } + }, + "X1a0He CC 下载成功" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "X1a0He CC downloaded successfully" + } + } + } }, "X1a0He CC 处理状态: " : { "localizations" : { @@ -453,6 +602,16 @@ } } }, + "下载失败可能有多种原因:\n1. 网络连接不稳定\n2. Adobe 服务器响应超时\n3. 本地磁盘空间不足\n建议您检查网络连接并重试,如果问题持续存在,可以尝试使用代理或 VPN。" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download failed for multiple reasons:\n1. Unstable network connection\n2. Adobe server response timeout\n3. Insufficient local disk space\nPlease check your network connection and try again. If the problem persists, you can try using a proxy or VPN." + } + } + } + }, "下载已取消" : { "comment" : "Download cancelled", "localizations" : { @@ -464,6 +623,16 @@ } } }, + "下载并处理" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Download and process" + } + } + } + }, "下载此版本" : { "comment" : "版本选择页面", "localizations" : { @@ -549,6 +718,46 @@ } } }, + "为什么我安装的时候会遇到错误代码,错误代码表示什么意思?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Why do I encounter error codes when installing? What do the error codes mean?" + } + } + } + }, + "为什么有时候下载会失败?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Why does the download sometimes fail?" + } + } + } + }, + "为什么需要下载 Setup 组件?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Why do I need to download the Setup component?" + } + } + } + }, + "为什么需要安装 Helper?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Why do I need to install Helper?" + } + } + } + }, "产品和包列表" : { "localizations" : { "en" : { @@ -559,6 +768,16 @@ } } }, + "仅下载" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Only download" + } + } + } + }, "你可以在设置中随时更改以上选项" : { "localizations" : { "en" : { @@ -629,6 +848,36 @@ } } }, + "修改 Setup 组件失败" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modify Setup component failed" + } + } + } + }, + "停止并删除 Adobe 相关服务" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stop and delete Adobe related services" + } + } + } + }, + "全选" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select all" + } + } + } + }, "全部暂停" : { "localizations" : { "en" : { @@ -669,6 +918,16 @@ } } }, + "最近日志:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recent logs:" + } + } + } + }, "准备中: %@" : { "comment" : "Download status preparing", "localizations" : { @@ -700,6 +959,86 @@ } } }, + "删除 Adobe Creative Cloud 应用程序及其组件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe Creative Cloud applications and components" + } + } + } + }, + "删除 Adobe 应用程序的偏好设置文件(不包括 Adobe Downloader)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe application preferences (excluding Adobe Downloader)" + } + } + } + }, + "删除 Adobe 应用程序的日志文件(不包括 Adobe Downloader)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe application log files (excluding Adobe Downloader)" + } + } + } + }, + "删除 Adobe 应用程序的缓存文件(不包括 Adobe Downloader)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe application cache files (excluding Adobe Downloader)" + } + } + } + }, + "删除 Adobe 正版验证服务及其组件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe Genuine Service and its components" + } + } + } + }, + "删除 Adobe 许可和激活相关文件" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe license and activation related files" + } + } + } + }, + "删除所有已安装的 Adobe 应用程序(不包括 Adobe Downloader)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete all installed Adobe applications (excluding Adobe Downloader)" + } + } + } + }, + "删除钥匙串中的 Adobe 相关条目" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Adobe related entries in the keychain" + } + } + } + }, "前往 Adobe Creative Cloud" : { "localizations" : { "en" : { @@ -730,6 +1069,16 @@ } } }, + "取消全选" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deselect all" + } + } + } + }, "可用版本: %lld" : { "localizations" : { "en" : { @@ -760,6 +1109,26 @@ } } }, + "命令行安装" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Command line" + } + } + } + }, + "备份 Setup 组件失败" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backup Setup component failed" + } + } + } + }, "备份失败" : { "localizations" : { "en" : { @@ -790,6 +1159,16 @@ } } }, + "复制完整错误信息" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copy full error message" + } + } + } + }, "多次尝试连接失败" : { "localizations" : { "en" : { @@ -811,6 +1190,16 @@ } } }, + "如何修复安装失败的问题?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "How to fix the installation failure problem?" + } + } + } + }, "如果在设置里没找到当前App,可以尝试重置守护程序" : { "localizations" : { "en" : { @@ -821,6 +1210,16 @@ } } }, + "如果安装失败,您可以尝试以下步骤:\n1. 确保已正确安装并连接 Helper\n2. 确保已下载并处理 Setup 组件\n3. 检查磁盘剩余空间是否充足\n4. 尝试重新下载并安装\n如果问题仍然存在,可以尝试重新安装 Helper 和重新处理 Setup 组件。" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If the installation fails, you can try the following steps:\n1. Ensure that the Helper is correctly installed and connected\n2. Ensure that the Setup component has been downloaded and processed\n3. Check if there is enough disk space\n4. Try to re-download and install\nIf the problem persists, you can try to reinstall the Helper and re-process the Setup component." + } + } + } + }, "存储空间不足" : { "localizations" : { "en" : { @@ -912,12 +1311,12 @@ } } }, - "安装失败: 架构或本不一致 (退出代码: %lld)" : { + "安装失败: 架构或版本不一致 (退出代码: %lld)" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Install failed: architecture inconsistency (exit code: %lld)" + "value" : "Install failed: Architecture or version mismatch (Exit Code: %lld)" } } } @@ -984,6 +1383,17 @@ } } }, + "将执行的命令:" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The command to be executed:" + } + } + } + }, "尝试使用不同的搜索关键词" : { "localizations" : { "en" : { @@ -1014,6 +1424,27 @@ } } }, + "展开全部" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expand all" + } + } + } + }, + "已取消" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancelled" + } + } + } + }, "已处理" : { "localizations" : { "en" : { @@ -1107,6 +1538,16 @@ } } }, + "常见问题" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Q & A" + } + } + } + }, "应用信息错误: %@" : { "comment" : "Application info error", "localizations" : { @@ -1129,6 +1570,26 @@ } } }, + "开始清理" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start cleaning" + } + } + } + }, + "当前执行:%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Current execution: %@" + } + } + } + }, "当前架构: %@" : { "localizations" : { "en" : { @@ -1139,6 +1600,56 @@ } } }, + "当前版本:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Current Version: " + } + } + } + }, + "所有操作已成功完成" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "All operations completed successfully" + } + } + } + }, + "执行成功" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Execution successful" + } + } + } + }, + "执行结果:%@\n执行命令:%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Execution result: %1$@\nExecution command: %2$@" + } + } + } + }, + "执行结果:执行超时\n执行命令:%@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Execution result: execution timeout\nExecution command: %@" + } + } + } + }, "找不到安装程序" : { "localizations" : { "en" : { @@ -1149,6 +1660,17 @@ } } }, + "折叠全部" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Collapse all" + } + } + } + }, "按名称" : { "localizations" : { "en" : { @@ -1409,6 +1931,16 @@ } } }, + "暂无清理记录" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No cleanup history" + } + } + } + }, "更新设置" : { "localizations" : { "en" : { @@ -1583,7 +2115,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "It is detected that the Setup file has not been backed up. If you need to install the program, the Setup must be processed. After clicking OK, you need to enter the password. Adobe Downloader will automatically process and back up the Setup.original." + "value" : "It is detected that the Setup file has not been backed up. If you need to install the program, the Setup must be processed. After clicking Confirm, you need to enter the password. Adobe Downloader will automatically process and back up the Setup.original." } } } @@ -1609,6 +2141,16 @@ } } }, + "正在停止 Adobe 相关进程..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stopping Adobe related processes..." + } + } + } + }, "正在准备安装..." : { "localizations" : { "en" : { @@ -1659,6 +2201,146 @@ } } }, + "正在执行..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executing..." + } + } + } + }, + "正在清理 Adobe 应用程序..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up Adobe applications..." + } + } + } + }, + "正在清理 Creative Cloud 应用..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up Creative Cloud applications..." + } + } + } + }, + "正在清理 Creative Cloud 日志文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up Creative Cloud log files..." + } + } + } + }, + "正在清理 hosts 文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up hosts file..." + } + } + } + }, + "正在清理偏好设置文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up preferences file..." + } + } + } + }, + "正在清理其他文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up other files..." + } + } + } + }, + "正在清理启动项服务..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up startup items..." + } + } + } + }, + "正在清理安装记录..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up installation records..." + } + } + } + }, + "正在清理崩溃报告日志..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up crash report logs..." + } + } + } + }, + "正在清理应用程序日志文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up application log files..." + } + } + } + }, + "正在清理正版验证服务..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up Adobe Genuine Service..." + } + } + } + }, + "正在清理缓存文件..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up cache files..." + } + } + } + }, + "正在清理钥匙串数据..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleaning up keychain data..." + } + } + } + }, "正在连接" : { "localizations" : { "en" : { @@ -1689,6 +2371,56 @@ } } }, + "注意:清理过程不会影响 Adobe Downloader 的文件和下载数据" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Note: The cleanup process will not affect any Adobe Downloader files or downloaded data" + } + } + } + }, + "清理 hosts 文件中的 Adobe 相关条目" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleanup Adobe related entries in the hosts file" + } + } + } + }, + "清理完成" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleanup completed" + } + } + } + }, + "清理工具" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleanup Utility" + } + } + } + }, + "清理已取消" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleanup cancelled" + } + } + } + }, "清理已完成" : { "localizations" : { "en" : { @@ -1699,6 +2431,26 @@ } } }, + "清理结果" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cleanup Results" + } + } + } + }, + "清理进度:%lld/%lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Progress: %1$lld/%2$lld" + } + } + } + }, "版本不兼容: 当前版本 %@, 需要版本 %@" : { "comment" : "Incompatible version", "localizations" : { @@ -1720,22 +2472,22 @@ } } }, - "确定要下载并安装 X1a0He CC 吗?" : { + "确定要下载 X1a0He CC 吗?下载完成后需要手动处理。" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Are you sure you want to download and install X1a0He CC?" + "value" : "Are you sure you want to download X1a0He CC? After downloading, it needs to be manually processed." } } } }, - "确定要重新处理 Setup 组件吗?" : { + "确定要下载并处理 X1a0He CC 吗?这将完成下载并自动对 Setup 组件进行处理" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Are you sure you want to reprocess the Setup component?" + "value" : "Are you sure you want to download and process X1a0He CC? This will complete the download and automatically process the Setup component." } } } @@ -1760,6 +2512,26 @@ } } }, + "确认下载并处理" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm download and process" + } + } + } + }, + "确认清理" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm Cleanup" + } + } + } + }, "确认退出" : { "localizations" : { "en" : { @@ -1780,16 +2552,6 @@ } } }, - "确认重新处理" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Confirm reprocess" - } - } - } - }, "确认重置程序" : { "localizations" : { "en" : { @@ -2028,6 +2790,16 @@ } } }, + "这将删除所选的 Adobe 相关文件,该操作不可撤销。清理过程不会影响 Adobe Downloader 的文件和下载数据。是否继续?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This will delete the selected Adobe-related files. This operation cannot be undone. The cleanup process will not affect Adobe Downloader files and download data. Do you want to continue?" + } + } + } + }, "这将清空所有配置并结束应用程序,确定要继续吗?" : { "localizations" : { "en" : { @@ -2088,6 +2860,16 @@ } } }, + "选择下载 X1a0He CC 的方式" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select the way to download X1a0He CC" + } + } + } + }, "选择安装语言" : { "localizations" : { "en" : { @@ -2109,6 +2891,16 @@ } } }, + "选择要清理的内容" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select options to clean up" + } + } + } + }, "通用" : { "localizations" : { "en" : {