From 4273d05526e7d59ce2cfd1248a496585ee72015c Mon Sep 17 00:00:00 2001 From: X1a0He Date: Wed, 30 Jul 2025 20:15:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=20Helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ModernPrivilegedHelperManager.swift | 132 +++++++++++++++--- .../PrivilegedHelperAdapter.swift | 2 + Adobe Downloader/Info.plist | 9 +- Adobe Downloader/Views/AboutView.swift | 25 ++-- ...x1a0he.macOS.Adobe-Downloader.helper.plist | 4 +- 5 files changed, 140 insertions(+), 32 deletions(-) diff --git a/Adobe Downloader/HelperManager/ModernPrivilegedHelperManager.swift b/Adobe Downloader/HelperManager/ModernPrivilegedHelperManager.swift index 44c47bf..3cb2b1e 100644 --- a/Adobe Downloader/HelperManager/ModernPrivilegedHelperManager.swift +++ b/Adobe Downloader/HelperManager/ModernPrivilegedHelperManager.swift @@ -39,6 +39,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { case connected case disconnected case connecting + case needsApproval var description: String { switch self { @@ -48,6 +49,8 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { return String(localized: "未连接") case .connecting: return String(localized: "正在连接") + case .needsApproval: + return String(localized: "需要批准") } } } @@ -111,9 +114,10 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { } private func setupAutoReconnect() { - Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in + Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in guard let self = self else { return } - if self.connectionState == .disconnected && self.shouldAutoReconnect { + if self.connectionState == .disconnected && self.shouldAutoReconnect && !self.isInitializing { + self.logger.info("尝试自动重连Helper...") Task { await self.attemptConnection() } @@ -122,6 +126,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { } func checkAndInstallHelper() async { + isInitializing = true logger.info("开始检查 Helper 状态") let status = await getHelperStatus() @@ -135,6 +140,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { registerHelper() break case .needsApproval: + self.connectionState = .needsApproval showApprovalGuidance() break case .requiresUpdate: @@ -142,8 +148,17 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { break case .installed: Task { - await attemptConnection() - connectionSuccessBlock?() + let connectionResult = await attemptConnection() + if connectionResult { + logger.info("Helper 连接成功") + connectionSuccessBlock?() + } else { + logger.warning("Helper 安装成功但连接失败,可能存在权限问题") + await MainActor.run { + self.connectionState = .disconnected + } + } + self.isInitializing = false } } } @@ -172,6 +187,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { return .installed case .requiresApproval: + logger.info("Helper需要用户批准") return .needsApproval case .notFound: @@ -179,7 +195,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { @unknown default: logger.warning("未知的 SMAppService 状态: \(status.rawValue)") - return .notInstalled + return .needsApproval } } @@ -199,15 +215,57 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { Task { - await self.attemptConnection() + await self.tryEnableHelper() } } } catch { logger.error("Helper 注册失败: \(error)") handleRegistrationError(error) + isInitializing = false } } + + private func tryEnableHelper() async { + guard let appService = appService else { return } + + let currentStatus = appService.status + logger.info("尝试启用Helper,当前状态: \(currentStatus.rawValue)") + + if currentStatus != .enabled { + do { + logger.info("尝试重新注册Helper以启用") + try appService.register() + + try await Task.sleep(nanoseconds: 2_000_000_000) + + let newStatus = appService.status + logger.info("重新注册后状态: \(newStatus.rawValue)") + + if newStatus == .enabled { + logger.info("Helper成功启用") + let connectionResult = await attemptConnection() + if connectionResult { + logger.info("Helper连接成功") + return + } + } + } catch { + logger.error("重新注册失败: \(error)") + } + } + + let connectionResult = await attemptConnection() + if !connectionResult { + logger.warning("Helper 注册成功但连接失败,需要用户批准") + await MainActor.run { + self.connectionState = .needsApproval + self.showApprovalGuidance() + } + } + + self.isInitializing = false + } private func updateHelper() { registerHelper() @@ -286,17 +344,33 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { var isConnected = false if let helper = newConnection.remoteObjectProxy as? HelperToolProtocol { - helper.executeCommand(type: .shellCommand, path1: "id -u", path2: "", permissions: 0) { [weak self] result in - if result.contains("0") || result == "0" { + helper.executeCommand(type: .shellCommand, path1: "whoami", path2: "", permissions: 0) { [weak self] result in + let trimmedResult = result.trimmingCharacters(in: .whitespacesAndNewlines) + self?.logger.info("Helper 响应: \(trimmedResult)") + + if trimmedResult == "root" { isConnected = true DispatchQueue.main.async { self?.connection = newConnection self?.connectionState = .connected + self?.logger.info("Helper 权限验证成功,当前用户: \(trimmedResult)") } + } else if !trimmedResult.starts(with: "Error:") && !trimmedResult.isEmpty { + isConnected = true + DispatchQueue.main.async { + self?.connection = newConnection + self?.connectionState = .connected + self?.logger.warning("Helper 连接成功但权限可能不足,当前用户: \(trimmedResult)") + } + } else { + self?.logger.error("Helper 连接失败或权限不足: \(trimmedResult)") } semaphore.signal() } - _ = semaphore.wait(timeout: .now() + 1.0) + let waitResult = semaphore.wait(timeout: .now() + 8.0) + if waitResult == .timedOut { + logger.warning("Helper 连接超时") + } } if !isConnected { @@ -335,7 +409,7 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { } func getHelperProxy() throws -> HelperToolProtocol { - if connectionState != .connected { + if connectionState != .connected || connection == nil { guard let newConnection = connectionQueue.sync(execute: { createConnection() }) else { throw HelperError.connectionFailed } @@ -344,7 +418,9 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { guard let helper = connection?.remoteObjectProxyWithErrorHandler({ [weak self] error in self?.logger.error("XPC 代理错误: \(error)") - self?.connectionState = .disconnected + DispatchQueue.main.async { + self?.connectionState = .disconnected + } }) as? HelperToolProtocol else { throw HelperError.proxyError } @@ -375,8 +451,11 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { } } } catch { - connectionState = .disconnected - completion("Error: \(error.localizedDescription)") + DispatchQueue.main.async { [weak self] in + self?.connectionState = .disconnected + self?.logger.error("执行命令失败: \(error.localizedDescription)") + completion("Error: \(error.localizedDescription)") + } } } @@ -435,10 +514,13 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { } private func updateConnectionState(from result: String) { - if result.starts(with: "Error:") { - connectionState = .disconnected - } else { - connectionState = .connected + DispatchQueue.main.async { [weak self] in + if result.starts(with: "Error:") { + self?.connectionState = .disconnected + self?.logger.warning("命令执行失败,连接状态设为断开: \(result)") + } else { + self?.connectionState = .connected + } } } @@ -554,8 +636,20 @@ class ModernPrivilegedHelperManager: NSObject, ObservableObject { private func showApprovalGuidance() { let alert = NSAlert() - alert.messageText = String(localized: "需要在系统设置中允许 Helper") - alert.informativeText = String(localized: "Adobe Downloader 需要通过后台服务来安装与移动文件。请在\"系统设置 → 通用 → 登录项与扩展\"中允许此应用的后台项目。") + alert.messageText = String(localized: "Helper服务需要批准") + alert.informativeText = String(localized: """ + Adobe Downloader需要后台Helper服务来执行安装和文件操作。 + + 解决方法: + 1. 点击下方「打开系统设置」按钮 + 2. 在「登录项与扩展」中找到 Adobe Downloader + 3. 确保应用已被允许,并检查是否有任何需要启用的后台项目 + 4. 如果看不到相关选项,请尝试: + - 重启 Adobe Downloader + - 或重启系统后再试 + + 注意:macOS可能需要重启才能完全激活Helper服务。 + """) alert.addButton(withTitle: String(localized: "打开系统设置")) alert.addButton(withTitle: String(localized: "稍后设置")) diff --git a/Adobe Downloader/HelperManager/PrivilegedHelperAdapter.swift b/Adobe Downloader/HelperManager/PrivilegedHelperAdapter.swift index 836d004..3761486 100644 --- a/Adobe Downloader/HelperManager/PrivilegedHelperAdapter.swift +++ b/Adobe Downloader/HelperManager/PrivilegedHelperAdapter.swift @@ -181,6 +181,8 @@ class PrivilegedHelperAdapter: NSObject, ObservableObject { return .disconnected case .connecting: return .connecting + case .needsApproval: + return .disconnected } } diff --git a/Adobe Downloader/Info.plist b/Adobe Downloader/Info.plist index b40f88e..409d01d 100644 --- a/Adobe Downloader/Info.plist +++ b/Adobe Downloader/Info.plist @@ -9,10 +9,13 @@ NSAllowsArbitraryLoads - SMPrivilegedExecutables + SMAppService - com.x1a0he.macOS.Adobe-Downloader.helper - identifier "com.x1a0he.macOS.Adobe-Downloader.helper" 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 */ + com.x1a0he.macOS.Adobe-Downloader.helper + + PlistName + com.x1a0he.macOS.Adobe-Downloader.helper.plist + SUFeedURL https://raw.githubusercontent.com/X1a0He/Adobe-Downloader/refs/heads/main/appcast.xml diff --git a/Adobe Downloader/Views/AboutView.swift b/Adobe Downloader/Views/AboutView.swift index c468e2a..dd2ee7a 100644 --- a/Adobe Downloader/Views/AboutView.swift +++ b/Adobe Downloader/Views/AboutView.swift @@ -7,6 +7,7 @@ import SwiftUI import Sparkle import Combine +import ServiceManagement private enum AboutViewConstants { @@ -267,6 +268,7 @@ final class GeneralSettingsViewModel: ObservableObject { case connecting case disconnected case checking + case needsApproval } init(updater: SPUUpdater) { @@ -287,6 +289,8 @@ final class GeneralSettingsViewModel: ObservableObject { self?.helperConnectionStatus = .disconnected case .connecting: self?.helperConnectionStatus = .connecting + case .needsApproval: + self?.helperConnectionStatus = .needsApproval } } .store(in: &cancellables) @@ -966,8 +970,11 @@ struct HelperStatusRow: View { Spacer() Button(action: { - if helperStatus == .installed && - viewModel.helperConnectionStatus != .connected { + if viewModel.helperConnectionStatus == .needsApproval { + // 打开系统设置让用户批准Helper + SMAppService.openSystemSettingsLoginItems() + } else if helperStatus == .installed && + viewModel.helperConnectionStatus != .connected { Task { do { try await ModernPrivilegedHelperManager.shared.reconnectHelper() @@ -987,9 +994,9 @@ struct HelperStatusRow: View { } }) { HStack(spacing: 4) { - Image(systemName: "network") + Image(systemName: viewModel.helperConnectionStatus == .needsApproval ? "gear" : "network") .font(.system(size: 12)) - Text("重新连接") + Text(viewModel.helperConnectionStatus == .needsApproval ? "打开设置" : "重新连接") .font(.system(size: 13)) } .frame(minWidth: 90) @@ -997,7 +1004,7 @@ struct HelperStatusRow: View { .buttonStyle(BeautifulButtonStyle(baseColor: shouldDisableReconnectButton ? Color.gray.opacity(0.6) : Color.blue.opacity(0.8))) .foregroundColor(shouldDisableReconnectButton ? Color.white.opacity(0.8) : .white) .disabled(shouldDisableReconnectButton) - .help("尝试重新连接到已安装的 Helper") + .help(viewModel.helperConnectionStatus == .needsApproval ? "打开系统设置批准Helper" : "尝试重新连接到已安装的 Helper") } } .task { @@ -1011,13 +1018,13 @@ struct HelperStatusRow: View { case .connecting: return .orange case .disconnected: return .red case .checking: return .orange + case .needsApproval: return .yellow } } private var shouldDisableReconnectButton: Bool { - return helperStatus != .installed || - viewModel.helperConnectionStatus == .connected || - isReinstallingHelper + // 只在重新安装Helper时禁用按钮,其他情况都允许用户点击 + return isReinstallingHelper } private var helperStatusBackgroundColor: Color { @@ -1026,6 +1033,7 @@ struct HelperStatusRow: View { case .connecting: return Color.orange.opacity(0.1) case .disconnected: return Color.red.opacity(0.1) case .checking: return Color.orange.opacity(0.1) + case .needsApproval: return Color.yellow.opacity(0.1) } } @@ -1035,6 +1043,7 @@ struct HelperStatusRow: View { case .connecting: return String(localized: "正在连接") case .disconnected: return String(localized: "连接断开") case .checking: return String(localized: "检查中") + case .needsApproval: return String(localized: "需要批准") } } } diff --git a/AdobeDownloaderHelperTool/com.x1a0he.macOS.Adobe-Downloader.helper.plist b/AdobeDownloaderHelperTool/com.x1a0he.macOS.Adobe-Downloader.helper.plist index 15a07b8..e7f4312 100644 --- a/AdobeDownloaderHelperTool/com.x1a0he.macOS.Adobe-Downloader.helper.plist +++ b/AdobeDownloaderHelperTool/com.x1a0he.macOS.Adobe-Downloader.helper.plist @@ -1,7 +1,7 @@ - + Label com.x1a0he.macOS.Adobe-Downloader.helper MachServices @@ -19,5 +19,5 @@ /tmp/com.x1a0he.macOS.Adobe-Downloader.helper.err StandardOutPath /tmp/com.x1a0he.macOS.Adobe-Downloader.helper.out - + \ No newline at end of file