feat: Added "Cleanup Tool" and "Common Issues" functions in the program settings page

1. Fixed the issue of Helper not being able to reconnect in some cases
2. Fixed the issue of not being able to reconnect after reinstalling the program and reinstalling Helper
3. Adjusted the content translation of X1a0He CC, version 1.5.0 can choose "Download and Process" and "Only Download"
4. Adjusted the translation of some Setup component content
5. Added "Cleanup Tool" and "Common Issues" functions in the program settings page
6. Added the current version display in the program settings page
- PS: The "Cleanup Tool" function in the current version is an experimental feature. If some files are not cleaned up, please feedback in time
- PS: 1.5.0 version will be the last open source version, please be aware
This commit is contained in:
X1a0He
2025-02-05 23:09:46 +08:00
parent 04b0d6f39f
commit 2cb51c7076
9 changed files with 1996 additions and 127 deletions

View File

@@ -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;

View File

@@ -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)")
}
}
}

View File

@@ -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)")

View File

@@ -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:

View File

@@ -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 组件失败"))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ struct DownloadProgressView: View {
@State private var expandedProducts: Set<String> = []
@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: {},

View File

@@ -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
}

File diff suppressed because it is too large Load Diff