Add: Added Sparkle for checking update and fix some bugs.

This commit is contained in:
X1a0He
2024-11-07 16:14:42 +08:00
parent bc51149048
commit 6b191c3839
21 changed files with 808 additions and 163 deletions

View File

@@ -6,13 +6,30 @@
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
3CB9FF092CDBAEF200D7A58B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 3CB9FF082CDBAEF200D7A58B /* Sparkle */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
3CB9FF102CDC44DE00D7A58B /* Exceptions for "Adobe Downloader" folder in "Adobe Downloader" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
3CCC3AE22CC67B8F006E22B4 /* Adobe Downloader */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
3CB9FF102CDC44DE00D7A58B /* Exceptions for "Adobe Downloader" folder in "Adobe Downloader" target */,
);
path = "Adobe Downloader";
sourceTree = "<group>";
};
@@ -28,6 +45,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3CB9FF092CDBAEF200D7A58B /* Sparkle in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -72,6 +90,7 @@
);
name = "Adobe Downloader";
packageProductDependencies = (
3CB9FF082CDBAEF200D7A58B /* Sparkle */,
);
productName = "Adobe-Downloader";
productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */;
@@ -101,6 +120,9 @@
);
mainGroup = 3CCC3AD72CC67B8F006E22B4;
minimizedProjectReferenceProxies = 1;
packageReferences = (
3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 3CCC3AE12CC67B8F006E22B4 /* Products */;
projectDirPath = "";
@@ -185,6 +207,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "./Adobe Downloader/Info.plist";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
@@ -243,6 +266,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "./Adobe Downloader/Info.plist";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
@@ -262,7 +286,7 @@
CODE_SIGN_ENTITLEMENTS = "Adobe Downloader/Adobe Downloader.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 101;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\"";
DEVELOPMENT_TEAM = "";
@@ -275,8 +299,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -291,9 +315,10 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = "Adobe Downloader/Adobe Downloader.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 101;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Adobe Downloader/Preview Content\"";
DEVELOPMENT_TEAM = "";
@@ -306,8 +331,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.x1a0he.macOS.Adobe-Downloader";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -337,6 +362,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.6.4;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
3CB9FF082CDBAEF200D7A58B /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = 3CB9FF072CDBAEF200D7A58B /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 3CCC3AD82CC67B8F006E22B4 /* Project object */;
}

View File

@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@@ -1,4 +1,5 @@
import SwiftUI
import Sparkle
@main
struct Adobe_DownloaderApp: App {
@@ -14,8 +15,11 @@ struct Adobe_DownloaderApp: App {
@AppStorage("confirmRedownload") private var confirmRedownload: Bool = true
@AppStorage("useDefaultDirectory") private var useDefaultDirectory: Bool = true
@AppStorage("defaultDirectory") private var defaultDirectory: String = "Downloads"
private let updaterController: SPUStandardUpdaterController
init() {
updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
let isFirstRun = UserDefaults.standard.object(forKey: "downloadAppleSilicon") == nil ||
UserDefaults.standard.object(forKey: "useDefaultLanguage") == nil
@@ -165,9 +169,13 @@ struct Adobe_DownloaderApp: App {
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize)
.commands {
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updater: updaterController.updater)
}
}
Settings {
AboutView()
AboutView(updater: updaterController.updater)
.environmentObject(networkManager)
}
}

View File

@@ -43,7 +43,7 @@ enum PackageStatus: Equatable {
enum NetworkError: Error, LocalizedError {
case noConnection
case timeout(TimeInterval)
case timeout
case serverUnreachable(String)
case invalidURL(String)
@@ -104,8 +104,8 @@ enum NetworkError: Error, LocalizedError {
switch self {
case .noConnection:
return NSLocalizedString("没有网络连接", comment: "Network error")
case .timeout(let duration):
return NSLocalizedString("请求超时: \(duration)", comment: "Network timeout")
case .timeout:
return NSLocalizedString("请求超时,请检查网络连接后重试", comment: "Network timeout")
case .serverUnreachable(let server):
return NSLocalizedString("无法连接到服务器: \(server)", comment: "Server unreachable")
case .invalidURL(let url):
@@ -274,13 +274,13 @@ enum DownloadStatus: Equatable {
var isActive: Bool {
switch self {
case .downloading, .preparing, .retrying:
case .downloading, .preparing, .waiting, .retrying:
return true
default:
return false
}
}
var isFinished: Bool {
switch self {
case .completed, .failed:

View File

@@ -111,6 +111,11 @@ class ProductsToDownload: ObservableObject {
var buildGuid: String
var applicationJson: String?
@Published var packages: [Package] = []
@Published var completedPackages: Int = 0
var totalPackages: Int {
packages.count
}
init(sapCode: String, version: String, buildGuid: String, applicationJson: String = "") {
self.sapCode = sapCode
@@ -118,6 +123,16 @@ class ProductsToDownload: ObservableObject {
self.buildGuid = buildGuid
self.applicationJson = applicationJson
}
func updateCompletedPackages() {
completedPackages = packages.filter {
if case .completed = $0.status {
return true
}
return $0.downloaded
}.count
objectWillChange.send()
}
}
struct SapCodes: Identifiable {

View File

@@ -2,38 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 允许访问网络 -->
<key>com.apple.security.network.client</key>
<true/>
<!-- 允许访问本地网络 -->
<key>com.apple.security.network.server</key>
<true/>
<!-- 添加以下键值对 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<!-- 添加以下权限声明 -->
<key>NSDownloadsFolderUsageDescription</key>
<string>需要访问下载文件夹来保存Adobe安装文件</string>
<!-- 添加以下权限 -->
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>/Downloads/</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSDownloadsFolderUsageDescription</key>
<string>需要访问下载文件夹来保存Adobe安装文件</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>/Downloads/</string>
</array>
<key>SUPublicEDKey</key>
<string>gYylad3ybfiyK5ZTS3xRrw+3c/8063mpXdQnPpMB86Q=</string>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/X1a0He/Adobe-Downloader/refs/heads/main/appcast.xml</string>
</dict>
</plist>

View File

@@ -0,0 +1,34 @@
//
// CheckForUpdatesViewModel.swift
// Adobe Downloader
//
// Created by X1a0He on 11/6/24.
//
import SwiftUI
import Sparkle
final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false
init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
}
}
struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
private let updater: SPUUpdater
init(updater: SPUUpdater) {
self.updater = updater
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
}
var body: some View {
Button("检查更新...", action: updater.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}

View File

@@ -22,6 +22,7 @@ class NetworkManager: ObservableObject {
}
}
}
@Published var installCommand: String = ""
private let cancelTracker = CancelTracker()
internal var downloadUtils: DownloadUtils!
internal var progressObservers: [UUID: NSKeyValueObservation] = [:]
@@ -72,6 +73,8 @@ class NetworkManager: ObservableObject {
downloadTasks.append(task)
updateDockBadge()
NotificationCenter.default.post(name: NSNotification.Name("UpdateDownloadStatus"), object: nil)
do {
try await downloadUtils.handleDownload(task: task, productInfo: productInfo, allowedPlatform: allowedPlatform, saps: saps)
} catch {
@@ -190,7 +193,13 @@ class NetworkManager: ObservableObject {
installationState = .completed
}
} catch {
let command = await installManager.getInstallCommand(
for: path.appendingPathComponent("driver.xml").path
)
await MainActor.run {
self.installCommand = command
if let installError = error as? InstallManager.InstallError {
switch installError {
case .installationFailed(let message):

View File

@@ -365,7 +365,7 @@ class DownloadUtils {
package.progress = 1.0
package.status = .completed
package.downloaded = true
var totalDownloaded: Int64 = 0
var totalSize: Int64 = 0
@@ -395,7 +395,6 @@ class DownloadUtils {
)))
}
task.objectWillChange.send()
networkManager?.objectWillChange.send()
}
@@ -437,7 +436,6 @@ class DownloadUtils {
lastUpdateTime = now
lastBytes = totalBytesWritten
task.objectWillChange.send()
networkManager?.objectWillChange.send()
}
}
@@ -677,7 +675,7 @@ class DownloadUtils {
for product in productsToDownload {
await MainActor.run {
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
message: "正在处理 \(product.sapCode) 的包信息...",
message: String(localized: "正在处理 \(product.sapCode) 的包信息..."),
timestamp: Date(),
stage: .fetchingInfo
)))

View File

@@ -20,10 +20,10 @@ actor InstallManager {
var errorDescription: String? {
switch self {
case .setupNotFound: return "找不到安装程序"
case .installationFailed(let message): return message
case .cancelled: return "安装已取消"
case .permissionDenied: return "权限被拒绝"
case .setupNotFound: return String(localized: "找不到安装程序")
case .installationFailed(let message): return message
case .cancelled: return String(localized: "安装已取消")
case .permissionDenied: return String(localized: "权限被拒绝")
}
}
}
@@ -72,7 +72,7 @@ actor InstallManager {
installationProcess = installProcess
await MainActor.run {
progressHandler(0.0, withSudo ? "正在准备安装..." : "正在重试安装...")
progressHandler(0.0, withSudo ? String(localized: "正在准备安装...") : String(localized: "正在重试安装..."))
}
try installProcess.run()
@@ -93,10 +93,10 @@ actor InstallManager {
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let code = Int32(codeStr) {
if code != 0 {
let errorMessage = code == -1
? "安装程序调用失败请联系X1a0He"
: "(退出代码: \(code))"
let errorMessage = code == -1
? String(localized: "安装程序调用失败请联系X1a0He")
: String(localized: "(退出代码: \(code))")
installProcess.terminate()
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
return
@@ -115,9 +115,9 @@ actor InstallManager {
if installProcess.terminationStatus == 0 {
continuation.resume()
} else {
let errorMessage = withSudo
? "安装失败 (退出代码: \(installProcess.terminationStatus))"
: "重试失败,需要重新输入密码"
let errorMessage = withSudo
? String(localized: "(退出代码: \(installProcess.terminationStatus))")
: String(localized: "重试失败,需要重新输入密码")
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
}
} catch {
@@ -127,7 +127,7 @@ actor InstallManager {
}
await MainActor.run {
progressHandler(1.0, "安装完成")
progressHandler(1.0, String(localized: "安装完成"))
}
}
@@ -167,7 +167,7 @@ actor InstallManager {
authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
let authScript = """
tell application "System Events"
display dialog "" default answer "" with hidden answer ¬
display dialog "(Please enter the password to continue the installation)" default answer "" with hidden answer ¬
buttons {"", ""} default button "" ¬
with icon caution ¬
with title ""
@@ -208,22 +208,26 @@ actor InstallManager {
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let exitCode = Int(codeStr) {
return exitCode == 0 ? (1.0, "安装完成") : nil
return exitCode == 0 ? (1.0, String(localized: "安装完成")) : nil
}
if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression),
let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) {
return (progressValue / 100.0, "正在安装...")
return (progressValue / 100.0, String("正在安装..."))
}
if line.contains("Installing packages") {
return (0.0, "正在安装包...")
return (0.0, String(localized: "正在安装包..."))
} else if line.contains("Preparing") {
return (0.0, "正在准备...")
return (0.0, String(localized: "正在准备..."))
}
return nil
}
func getInstallCommand(for driverPath: String) -> String {
return "sudo \"\(setupPath)\" --install=1 --driverXML=\"\(driverPath)\""
}
}

View File

@@ -5,11 +5,18 @@
//
import SwiftUI
import Sparkle
struct AboutView: View {
private let updater: SPUUpdater
init(updater: SPUUpdater) {
self.updater = updater
}
var body: some View {
TabView {
GeneralSettingsView()
GeneralSettingsView(updater: updater)
.tabItem {
Label("通用", systemImage: "gear")
}
@@ -19,7 +26,8 @@ struct AboutView: View {
Label("关于", systemImage: "info.circle")
}
}
.frame(width: 500, height: 400)
.background(Color(NSColor.windowBackgroundColor))
.frame(width: 500, height: 350)
}
}
@@ -32,7 +40,17 @@ struct GeneralSettingsView: View {
@AppStorage("downloadAppleSilicon") private var downloadAppleSilicon: Bool = true
@State private var showLanguagePicker = false
@EnvironmentObject private var networkManager: NetworkManager
private let updater: SPUUpdater
@State private var automaticallyChecksForUpdates: Bool
@State private var automaticallyDownloadsUpdates: Bool
init(updater: SPUUpdater) {
self.updater = updater
_automaticallyChecksForUpdates = State(initialValue: updater.automaticallyChecksForUpdates)
_automaticallyDownloadsUpdates = State(initialValue: updater.automaticallyDownloadsUpdates)
}
var body: some View {
Form {
GroupBox(label: Text("下载设置").padding(.bottom, 8)) {
@@ -86,8 +104,29 @@ struct GeneralSettingsView: View {
networkManager.updateAllowedPlatform(useAppleSilicon: newValue)
}
}
.padding(.vertical, 4)
.padding(8)
}
GroupBox(label: Text("更新设置").padding(.bottom, 8)) {
VStack(alignment: .leading, spacing: 12) {
HStack {
Toggle("自动检查更新版本", isOn: $automaticallyChecksForUpdates)
.onChange(of: automaticallyChecksForUpdates) { newValue in
updater.automaticallyChecksForUpdates = newValue
}
Spacer()
CheckForUpdatesView(updater: updater)
}
Divider()
Toggle("自动下载最新版本", isOn: $automaticallyDownloadsUpdates)
.disabled(!automaticallyChecksForUpdates)
.onChange(of: automaticallyDownloadsUpdates) { newValue in
updater.automaticallyDownloadsUpdates = newValue
}
}.padding(8)
}
.padding(.vertical, 5)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -126,13 +165,20 @@ struct GeneralSettingsView: View {
}
struct AboutAppView: View {
private var appVersion: String {
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
// let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
// return "Version \(version) (\(build))"
return "\(version)"
}
var body: some View {
VStack(spacing: 12) {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 96, height: 96)
Text("Adobe Downloader")
Text("Adobe Downloader \(appVersion)")
.font(.title2)
.bold()
@@ -168,8 +214,39 @@ struct AboutAppView: View {
}
}
#Preview {
#Preview("About Tab") {
AboutAppView()
}
#Preview("General Settings") {
let networkManager = NetworkManager()
return AboutView()
.environmentObject(networkManager)
VStack {
GeneralSettingsView(updater: PreviewUpdater())
.environmentObject(networkManager)
}
}
private class PreviewUpdater: SPUUpdater {
init() {
let hostBundle = Bundle.main
let applicationBundle = Bundle.main
let userDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: nil)
super.init(
hostBundle: hostBundle,
applicationBundle: applicationBundle,
userDriver: userDriver,
delegate: nil
)
}
override var automaticallyChecksForUpdates: Bool {
get { true }
set { }
}
override var automaticallyDownloadsUpdates: Bool {
get { true }
set { }
}
}

View File

@@ -51,22 +51,24 @@ class AppCardViewModel: ObservableObject {
self.sap = sap
self.networkManager = networkManager
loadIcon()
}
func updateDownloadingStatus() {
Task { @MainActor in
isDownloading = networkManager?.downloadTasks.contains(where: isTaskDownloading) ?? false
}
}
private func isTaskDownloading(_ task: NewDownloadTask) -> Bool {
guard task.sapCode == sap.sapCode else { return false }
switch task.totalStatus {
case .downloading, .preparing, .waiting, .retrying:
return true
default:
return false
NotificationCenter.default.addObserver(
self,
selector: #selector(updateDownloadingStatus),
name: NSNotification.Name("UpdateDownloadStatus"),
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func updateDownloadingStatus() {
Task { @MainActor in
isDownloading = networkManager?.downloadTasks.contains { task in
return task.sapCode == sap.sapCode && task.status.isActive
} ?? false
}
}
@@ -203,6 +205,17 @@ class AppCardViewModel: ObservableObject {
guard let networkManager = networkManager,
let productInfo = sap.versions[pendingVersion] else { return }
let existingTask = await networkManager.downloadTasks.first { task in
return task.sapCode == sap.sapCode &&
task.version == pendingVersion &&
task.language == pendingLanguage &&
task.directory == path
}
if existingTask != nil {
return
}
var productsToDownload: [ProductsToDownload] = []
let mainProduct = ProductsToDownload(
sapCode: sap.sapCode,
@@ -446,38 +459,38 @@ private struct DownloadButton: View {
struct AlertModifier: ViewModifier {
@ObservedObject var viewModel: AppCardViewModel
let confirmRedownload: Bool
func body(content: Content) -> some View {
content
.alert("安装程序已存在", isPresented: $viewModel.showExistingFileAlert) {
Button("使用现有程序") {
if let path = viewModel.existingFilePath,
!viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
Task {
await viewModel.createCompletedTask(path)
}
}
}
Button("重新下载") {
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
if confirmRedownload {
viewModel.showRedownloadConfirm = true
} else {
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
}
}
}
Button("取消", role: .cancel) {}
} message: {
VStack(alignment: .leading) {
Text("在以下位置找到现有的安装程序:")
if let path = viewModel.existingFilePath {
Text(path.path)
.foregroundColor(.blue)
.onTapGesture {
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
.sheet(isPresented: $viewModel.showExistingFileAlert) {
if let path = viewModel.existingFilePath {
ExistingFileAlertView(
path: path,
onUseExisting: {
viewModel.showExistingFileAlert = false
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
Task {
await viewModel.createCompletedTask(path)
}
}
}
},
onRedownload: {
viewModel.showExistingFileAlert = false
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
if confirmRedownload {
viewModel.showRedownloadConfirm = true
} else {
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
}
}
},
onCancel: {
viewModel.showExistingFileAlert = false
},
iconImage: viewModel.iconImage
)
.background(Color.black.opacity(0.3))
.ignoresSafeArea()
}
}
.alert("确认重新下载", isPresented: $viewModel.showRedownloadConfirm) {

View File

@@ -112,6 +112,8 @@ struct DownloadManagerView: View {
Button("关闭") {
dismiss()
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
.padding(.horizontal)
.padding(.vertical, 8)

View File

@@ -443,10 +443,6 @@ struct ProductRow: View {
let isCurrentProduct: Bool
@Binding var expandedProducts: Set<String>
private var completedPackagesCount: Int {
product.packages.filter(\.downloaded).count
}
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Button(action: {
@@ -467,7 +463,7 @@ struct ProductRow: View {
Spacer()
Text("\(completedPackagesCount)/\(product.packages.count)")
Text("\(product.completedPackages)/\(product.totalPackages)")
.font(.caption)
.foregroundColor(.secondary)

View File

@@ -0,0 +1,106 @@
import SwiftUI
struct ExistingFileAlertView: View {
let path: URL
let onUseExisting: () -> Void
let onRedownload: () -> Void
let onCancel: () -> Void
let iconImage: NSImage?
var body: some View {
VStack(spacing: 20) {
ZStack(alignment: .bottomTrailing) {
Group {
if let iconImage = iconImage {
Image(nsImage: iconImage)
.resizable()
.interpolation(.high)
.scaledToFit()
} else {
Image(systemName: "app.fill")
.resizable()
.scaledToFit()
.foregroundColor(.secondary)
}
}
.frame(width: 64, height: 64)
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 24))
.foregroundColor(.orange)
.offset(x: 10, y: 4)
}
.padding(.bottom, 5)
Text("安装程序已存在")
.font(.headline)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(path.path)
.foregroundColor(.blue)
.onTapGesture {
NSWorkspace.shared.activateFileViewerSelecting([path])
}
}
}
VStack(spacing: 16) {
Button(action: onUseExisting) {
Label("使用现有程序", systemImage: "checkmark.circle")
.frame(minWidth: 0,maxWidth: 260)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.blue)
Button(action: onRedownload) {
Label("重新下载", systemImage: "arrow.down.circle")
.frame(minWidth: 0,maxWidth: 260)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.green)
Button(action: onCancel) {
Label("取消", systemImage: "xmark.circle")
.frame(minWidth: 0, maxWidth: 260)
.frame(height: 32)
.font(.system(size: 14))
}
.buttonStyle(.borderedProminent)
.tint(.red)
.keyboardShortcut(.cancelAction)
}
}
.padding()
.background(Color(NSColor.windowBackgroundColor))
.cornerRadius(12)
.shadow(radius: 10)
}
}
#Preview {
ExistingFileAlertView(
path: URL(fileURLWithPath: "/Users/username/Downloads/Adobe/Adobe Downloader PHSP_25.0-en_US-macuniversal"),
onUseExisting: {},
onRedownload: {},
onCancel: {},
iconImage: NSImage(named: "PHSP")
)
.background(Color.black.opacity(0.3))
}
#Preview("Dark Mode") {
ExistingFileAlertView(
path: URL(fileURLWithPath: "/Users/username/Downloads/Adobe/Adobe Downloader PHSP_25.0-en_US-macuniversal"),
onUseExisting: {},
onRedownload: {},
onCancel: {},
iconImage: NSImage(named: "PHSP")
)
.background(Color.black.opacity(0.3))
.preferredColorScheme(.dark)
}

View File

@@ -14,16 +14,16 @@ struct InstallProgressView: View {
let onRetry: (() -> Void)?
private var isCompleted: Bool {
progress >= 1.0 || status == "安装完成"
progress >= 1.0 || status == String(localized: "安装完成")
}
private var isFailed: Bool {
status.contains("失败")
status.contains(String(localized: "失败"))
}
private var progressText: String {
if isCompleted {
return "安装完成"
return String(localized: "安装完成")
} else {
return "\(Int(progress * 100))%"
}
@@ -51,11 +51,11 @@ struct InstallProgressView: View {
private var statusTitle: String {
if isCompleted {
return "\(productName) 安装完成"
return String(localized: "\(productName) 安装完成")
} else if isFailed {
return "\(productName) 安装失败"
return String(localized: "\(productName) 安装失败")
} else {
return "正在安装 \(productName)"
return String(localized: "正在安装 \(productName)")
}
}
@@ -78,7 +78,9 @@ struct InstallProgressView: View {
LogSection(logs: networkManager.installationLogs)
if isFailed {
ErrorSection(status: status)
ErrorSection(
status: status, isFailed: isFailed
)
}
ButtonSection(
@@ -89,7 +91,7 @@ struct InstallProgressView: View {
)
}
.padding()
.frame(minWidth: 500, minHeight: 300)
.frame(minWidth: 500, minHeight: 400)
.background(Color(NSColor.windowBackgroundColor))
.cornerRadius(8)
}
@@ -155,28 +157,61 @@ private struct LogSection: View {
private struct ErrorSection: View {
let status: String
let isFailed: Bool
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("错误详情:")
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.medium)
Text(status)
.font(.caption)
.foregroundColor(.secondary)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.background(Color.secondary.opacity(0.1))
.cornerRadius(6)
if isFailed {
HStack {
Text("自行安装命令:")
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.medium)
CommandPopover()
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
}
}
private struct CommandSection: View {
let command: String
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
Text("错误详情:")
Text("自行安装命令:")
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.medium)
Text(status)
Text(command)
.font(.caption)
.foregroundColor(.secondary)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(maxWidth: .infinity,alignment: .leading)
.frame(minHeight: 200)
.padding(8)
.background(Color.secondary.opacity(0.1))
.cornerRadius(6)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxHeight: 100)
.padding(.horizontal, 20)
}
}
@@ -220,6 +255,36 @@ private struct ButtonSection: View {
}
}
private struct CommandPopover: View {
@EnvironmentObject private var networkManager: NetworkManager
@State private var showPopover = false
var body: some View {
Button(action: { showPopover.toggle() }) {
Image(systemName: "terminal.fill")
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
VStack(alignment: .leading, spacing: 8) {
Button("复制命令") {
}
Text(networkManager.installCommand)
.font(.system(.caption, design: .monospaced))
.foregroundColor(.secondary)
.textSelection(.enabled)
.padding(8)
.background(Color.secondary.opacity(0.1))
.cornerRadius(6)
}
.padding()
.frame(width: 400)
}
}
}
#Preview("安装中带日志") {
let networkManager = NetworkManager()
return InstallProgressView(
@@ -260,6 +325,8 @@ private struct ButtonSection: View {
)
.environmentObject(networkManager)
.onAppear {
networkManager.installCommand = "sudo \"/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup\" --install=1 --driverXML=\"/Users/demo/Downloads/Adobe Photoshop/driver.xml\""
let previewLogs = [
"正在准备安装...",
"Progress: 10%",

View File

@@ -1,6 +1,16 @@
{
"sourceLanguage" : "en",
"strings" : {
"(退出代码: %d)" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Exit Code: %d)"
}
}
}
},
"/" : {
},
@@ -34,6 +44,26 @@
}
}
},
"%@ 安装失败" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ Install failed"
}
}
}
},
"%@ 安装完成" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@ Install completed"
}
}
}
},
"%lld" : {
},
@@ -72,6 +102,9 @@
},
"Adobe Downloader" : {
},
"Adobe Downloader %@" : {
},
"Adobe Downloader 已为你默认设定如下值" : {
"localizations" : {
@@ -355,12 +388,22 @@
}
}
},
"在以下位置找到现有的安装程序:" : {
"复制命令" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Locate the existing installer at:"
"value" : "Copy"
}
}
}
},
"失败" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Failed"
}
}
}
@@ -385,6 +428,26 @@
}
}
},
"安装完成" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Completed"
}
}
}
},
"安装已取消" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cancelled"
}
}
}
},
"安装程序已存在" : {
"localizations" : {
"en" : {
@@ -395,6 +458,16 @@
}
}
},
"安装程序调用失败请联系X1a0He" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The installation program failed to call, please contact X1a0He"
}
}
}
},
"尝试使用不同的搜索关键词" : {
"localizations" : {
"en" : {
@@ -476,6 +549,16 @@
}
}
},
"找不到安装程序" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Installer not found"
}
}
}
},
"按名称" : {
"localizations" : {
"en" : {
@@ -556,6 +639,16 @@
}
}
},
"更新设置" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Update settings"
}
}
}
},
"服务器响应无效" : {
"comment" : "Invalid response",
"localizations" : {
@@ -604,6 +697,26 @@
}
}
},
"权限被拒绝" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Permission denied"
}
}
}
},
"检查更新..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Check for updates…"
}
}
}
},
"检测到Setup文件尚未备份如果你需要安装程序则Setup必须被处理点击确定后你需要输入密码Adobe Downloader将自动处理并备份为Setup.original" : {
"localizations" : {
"en" : {
@@ -625,6 +738,26 @@
}
}
},
"正在准备..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparing..."
}
}
}
},
"正在准备安装..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Preparing..."
}
}
}
},
"正在加载..." : {
"localizations" : {
"en" : {
@@ -635,6 +768,46 @@
}
}
},
"正在处理 %@ 的包信息..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Processing package for %@..."
}
}
}
},
"正在安装 %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Installing %@"
}
}
}
},
"正在安装包..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Installing packages..."
}
}
}
},
"正在重试安装..." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Retrying installation..."
}
}
}
},
"没有找到产品" : {
"localizations" : {
"en" : {
@@ -769,6 +942,36 @@
}
}
},
"自动下载最新版本" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Automatically download updates"
}
}
}
},
"自动检查更新版本" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Automatically check for updates"
}
}
}
},
"自行安装命令:" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Self-installation command:"
}
}
}
},
"语言:" : {
"localizations" : {
"en" : {
@@ -779,6 +982,17 @@
}
}
},
"请求超时,请检查网络连接后重试" : {
"comment" : "Network timeout",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Request timed out, please check the network connection and try again"
}
}
}
},
"选择" : {
"localizations" : {
"en" : {
@@ -861,6 +1075,16 @@
}
}
},
"重试失败,需要重新输入密码" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Retry failed, need to re-enter password"
}
}
}
},
"错误详情:" : {
"localizations" : {
"en" : {

38
appcast.xml Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" standalone="yes"?>
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>Adobe Downloader</title>
<item>
<title>1.0.1</title>
<pubDate>Thu, 07 Nov 2024 16:06:42 +0800</pubDate>
<sparkle:version>101</sparkle:version>
<sparkle:shortVersionString>1.0.1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
<enclosure
url="https://raw.githubusercontent.com/X1a0He/Adobe-Downloader/refs/heads/main/Adobe%20Downloader.dmg"
length="2531087" type="application/octet-stream"
sparkle:edSignature="aEktEis1fZBGVe1BeYRkWtr+6pAYF1a3bjT9tjP3Jha+kdvft1xXAtUjX0m76emFCzPi6M+GYEYuxeAE44rFDQ=="/>
<description><![CDATA[
<style>ul{margin-top: 0;margin-bottom: 7;padding-left: 18;}</style>
<h5>Adobe Downloader 更新日志: </h1>
<ul>
<li>修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上</li>
<li>增加 Sparkle 用于检测更新</li>
<li>当默认目录为 未选择 时,将 下载 文件夹作为默认目录</li>
<li>当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装</li>
<li>调整了文件已存在的 UI 显示</li>
</ul>
<hr>
<h5>Adobe Downloader Changes: </h1>
<ul>
<li>Support macOS 13.0 and above</li>
<li>Added Sparkle for checking update</li>
<li>When the default directory is not selected, the Downloads folder will be used as the default directory</li>
<li>When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to</li>
<li>install by themselves</li>
<li>Adjusted the UI display of existing files</li>
</ul>
]]></description>
</item>
</channel>
</rss>

View File

@@ -6,7 +6,7 @@
## Before Use
**🍎Only for macOS 14+.**
**🍎Only for macOS 13.0+.**
> **If you like Adobe Downloader, or it helps you, please Star🌟 it.**
>
@@ -21,21 +21,21 @@
> 4. ⚠️⚠️⚠️ **All Adobe apps in Adobe Downloader are from official Adobe channels and are not cracked versions.**
> 5. ❌❌❌ **Do not use an external hard drive or any USB to store it, as this will cause permission issues, I do not have
the patience to solve any about permission issues**
> 6. ❌❌❌ **Due to permission reasons, there may be problems with installation on hackintosh**
## 📔Latest Log
- For historical update logs, please go to [Update Log](update-log.md)
- 2024-11-06 15:50 Update Log
- 2024-11-07 16:00 Update Log
```markdown
1. Added default configuration settings and prompts when the program is started for the first time
2. Added optional architecture downloads, please select in settings
3. Fixed the problem of version detection error \(only checks whether the file exists, not whether it is complete\)
4. Removed the language selection and directory selection on the main interface and moved them to settings
5. Added architecture prompts on the version selection page
6. Removed the installer mechanism, and now no installer will be generated
7. Added Adobe Creative Cloud installation detection, which cannot be used before installation
1. Support macOS 13.0 and above
2. Added Sparkle for checking update
3. When the default directory is not selected, the Downloads folder will be used as the default directory
4. When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to
install by themselves
5. Adjusted the UI display of existing files
```
### Language friendly

View File

@@ -6,7 +6,7 @@
## 使用须知
**🍎仅支持 macOS 14+.**
**🍎仅支持 macOS 13.0+**
> **如果你也喜欢 Adobe Downloader, 或者对你又帮助, 请 Star 仓库吧 🌟, 你的支持是我更新的动力**
>
@@ -20,21 +20,20 @@
的 [adobe-packager](https://github.com/Drovosek01/adobe-packager)
> 4. ⚠️⚠️⚠️ **Adobe Downloader 中的所有 Adobe 应用均来自 Adobe 官方渠道,并非破解版本。**
> 5. ❌❌❌ **不要将下载目录设置为外接移动硬盘或者USB设备这会导致出现权限问题我并没有时间也没有耐心处理任何权限问题**
> 6. ❌❌❌ **由于权限原因,可能会在黑苹果上出现无法安装的问题**
## 📔 最新日志
- 更多关于 App 的更新日志,请查看 [Update Log](update-log.md)
- 2024-11-06 15:50 更新日志
- 2024-11-07 16:00 更新日志
```markdown
1. 增加程序首次启动时的默认配置设定与提示
2. 增加可选架构下载,请在设置中进行选择
3. 修复了版本已存在检测错误的问题 \(仅检测文件是否存在,并不会检测是否完整\)
4. 移除主界面的语言选择和目录选择,移动到了设置中
5. 版本选择页面增加架构提
6. 移除了安装程序的机制,现在不会再生成安装程序
7. 增加了Adobe Creative Cloud安装检测未安装前无法使用
1. 修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上
2. 增加 Sparkle 用于检测更新
3. 当默认目录为 未选择 时,将 下载 文件夹作为默认目录
4. 当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装
5. 调整了文件已存在的 UI 显
```
### 语言支持

View File

@@ -1,11 +1,30 @@
# Change Log
- 2024-11-07 16:00 更新日志
```markdown
1. 修复了当系统版本低于 macOS 14.6 时无法打开程序的问题,现已支持 macOS 13.0 以上
2. 增加 Sparkle 用于检测更新
3. 当默认目录为 未选择 时,将 下载 文件夹作为默认目录
4. 当通过 Adobe Downloader 安装遇到权限问题时,提供终端命令让用户自行安装
5. 调整了文件已存在的 UI 显示
====================
1. Support macOS 13.0 and above
2. Added Sparkle for checking update
3. When the default directory is not selected, the Downloads folder will be used as the default directory
4. When installing via Adobe Downloader and encountering permission issues, provide terminal commands to allow users to
install by themselves
5. Adjusted the UI display of existing files
```
- 2024-11-06 15:50 更新日志
```markdown
1. 增加程序首次启动时的默认配置设定与提示
2. 增加可选架构下载,请在设置中进行选择
3. 修复了版本已存在检测错误的问题 \(仅检测文件是否存在,并不会检测是否完整\)
3. 修复了版本已存在检测错误的问题 (仅检测文件是否存在,并不会检测是否完整)
4. 移除主界面的语言选择和目录选择,移动到了设置中
5. 版本选择页面增加架构提示
6. 移除了安装程序的机制,现在不会再生成安装程序
@@ -15,7 +34,7 @@
1. Added default configuration settings and prompts when the program is started for the first time
2. Added optional architecture downloads, please select in settings
3. Fixed the problem of version detection error \(only checks whether the file exists, not whether it is complete\)
3. Fixed the problem of version detection error (only checks whether the file exists, not whether it is complete)
4. Removed the language selection and directory selection on the main interface and moved them to settings
5. Added architecture prompts on the version selection page
6. Removed the installer mechanism, and now no installer will be generated