refactor: complete initial reconstruction

This commit is contained in:
X1a0He
2025-03-05 21:09:14 +08:00
parent b816dcf159
commit 4b9dc3d417
17 changed files with 1016 additions and 652 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
.DS_Store
*.DS_Store
.idea/
.fleet
## User settings
xcuserdata/

View File

@@ -12,7 +12,7 @@
<key>Adobe-Downloader.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
<key>AdobeDownloaderHelperTool.xcscheme_^#shared#^_</key>
<dict>

View File

@@ -105,7 +105,6 @@ struct Adobe_DownloaderApp: App {
Settings {
AboutView(updater: updaterController.updater)
.environmentObject(globalNetworkManager)
}
}

View File

@@ -55,6 +55,7 @@ enum NetworkError: Error, LocalizedError {
case unsupportedPlatform(String)
case incompatibleVersion(String, String)
case installError(String)
case productNotFound
private var errorGroup: Int {
switch self {
@@ -64,7 +65,7 @@ enum NetworkError: Error, LocalizedError {
case .httpError, .serverError, .clientError: return 4000
case .downloadError, .downloadCancelled, .insufficientStorage, .cancelled: return 5000
case .fileSystemError, .fileExists, .fileNotFound, .filePermissionDenied: return 6000
case .applicationInfoError, .unsupportedPlatform, .incompatibleVersion, .installError: return 7000
case .applicationInfoError, .unsupportedPlatform, .incompatibleVersion, .installError, .productNotFound: return 7000
}
}
@@ -94,6 +95,7 @@ enum NetworkError: Error, LocalizedError {
case .unsupportedPlatform: return 2
case .incompatibleVersion: return 3
case .installError: return 4
case .productNotFound: return 5
}
}
@@ -160,6 +162,8 @@ enum NetworkError: Error, LocalizedError {
return NSLocalizedString("下载已取消", value: "下载已取消", comment: "Download cancelled")
case .installError(let message):
return String(format: NSLocalizedString("安装错误: %@", value: "安装错误: %@", comment: "Install error"), message)
case .productNotFound:
return NSLocalizedString("产品未找到", value: "产品未找到", comment: "Product not found")
}
}

View File

@@ -8,6 +8,8 @@
//
private var _globalStiResult: NewParseResult?
private var _globalCcmResult: NewParseResult?
private var _globalProducts: [Product]?
private var _globalUniqueProducts: [UniqueProduct]?
private var _globalCdn: String = ""
private var _globalNetworkService: NewNetworkService?
private var _globalNetworkManager: NetworkManager?
@@ -96,6 +98,30 @@ var globalCancelTracker: CancelTracker {
}
}
var globalProducts: [Product] {
get {
if _globalProducts == nil {
_globalProducts = []
}
return _globalProducts!
}
set {
_globalProducts = newValue
}
}
var globalUniqueProducts: [UniqueProduct] {
get {
if _globalUniqueProducts == nil {
_globalUniqueProducts = []
}
return _globalUniqueProducts!
}
set {
_globalUniqueProducts = newValue
}
}
func getAllProducts() -> [Product] {
var allProducts = [Product]()
let stiProducts = globalStiResult.products
@@ -108,3 +134,93 @@ func getAllProducts() -> [Product] {
}
return allProducts
}
/// ID
/// - Parameters:
/// - id: ID
/// - version:
/// - Returns:
func findProduct(id: String, version: String? = nil) -> Product? {
// ID
guard let product = globalProducts.first(where: { $0.id == id }) else {
return nil
}
//
guard let version = version else {
return product
}
//
for platform in product.platforms {
for languageSet in platform.languageSet {
if languageSet.productVersion == version {
return product
}
}
}
return nil
}
///
/// - Parameter id: ID
/// - Returns:
func getProductVersions(id: String) -> [String] {
guard let product = globalProducts.first(where: { $0.id == id }) else {
return []
}
//
var versions = Set<String>()
for platform in product.platforms {
for languageSet in platform.languageSet {
versions.insert(languageSet.productVersion)
}
}
//
return Array(versions).sorted { version1, version2 in
version1.compare(version2, options: .numeric) == .orderedDescending
}
}
///
/// - Parameters:
/// - id: ID
/// - version:
/// - Returns:
func getProductLanguages(id: String, version: String) -> [String] {
guard let product = globalProducts.first(where: { $0.id == id }) else {
return []
}
var languages = Set<String>()
for platform in product.platforms {
for languageSet in platform.languageSet {
if languageSet.productVersion == version {
languages.insert(languageSet.name)
}
}
}
return Array(languages).sorted()
}
/// ID
/// - Parameter id: ID
/// - Returns:
func findProducts(id: String) -> [Product] {
var matchedProducts = [Product]()
// globalProducts
matchedProducts.append(contentsOf: globalProducts.filter { $0.id == id })
// globalCcmResult
matchedProducts.append(contentsOf: globalCcmResult.products.filter { $0.id == id })
// globalStiResult
matchedProducts.append(contentsOf: globalStiResult.products.filter { $0.id == id })
return matchedProducts
}

View File

@@ -6,7 +6,6 @@ struct ContentView: View {
@State private var showDownloadManager = false
@State private var searchText = ""
@State private var currentApiVersion = StorageData.shared.apiVersion
@State private var cachedProducts: [UniqueProduct] = []
private var apiVersion: String {
get { StorageData.shared.apiVersion }
@@ -17,13 +16,10 @@ struct ContentView: View {
}
private var filteredProducts: [UniqueProduct] {
if searchText.isEmpty {
return cachedProducts
}
if searchText.isEmpty { return globalUniqueProducts }
return cachedProducts.filter {
$0.displayName.localizedCaseInsensitiveContains(searchText) ||
$0.id.localizedCaseInsensitiveContains(searchText)
return globalUniqueProducts.filter {
$0.displayName.localizedCaseInsensitiveContains(searchText) || $0.id.localizedCaseInsensitiveContains(searchText)
}
}
@@ -31,22 +27,14 @@ struct ContentView: View {
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
}
private func updateProductsCache() {
//
let validProducts = globalCcmResult.products
.filter { $0.hasValidVersions(allowedPlatform: StorageData.shared.allowedPlatform) }
// 使ID
var uniqueProductsDict = [String: UniqueProduct]()
for product in validProducts {
uniqueProductsDict[product.id] = UniqueProduct(id: product.id, displayName: product.displayName)
private func refreshData() {
isRefreshing = true
errorMessage = nil
Task {
await globalNetworkManager.fetchProducts()
await MainActor.run { isRefreshing = false }
}
//
let uniqueProducts = Array(uniqueProductsDict.values)
.sorted { $0.displayName < $1.displayName }
cachedProducts = uniqueProducts
}
var body: some View {
@@ -60,9 +48,7 @@ struct ContentView: View {
await globalNetworkManager.fetchProducts()
}
}
)) {
Text("Apple Silicon")
}
)) { Text("Apple Silicon") }
.toggleStyle(.switch)
.tint(.green)
.disabled(isRefreshing)
@@ -137,7 +123,7 @@ struct ContentView: View {
HStack(spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text("Adobe Downloader 完全开源免费: https://github.com/X1a0He/Adobe-Downloader")
Text("Adobe Downloader 完全免费: https://github.com/X1a0He/Adobe-Downloader")
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal)
@@ -204,8 +190,8 @@ struct ContentView: View {
],
spacing: 20
) {
ForEach(filteredProducts, id: \.sapCode) { sap in
AppCardView(sap: sap)
ForEach(filteredProducts, id: \.id) { uniqueProduct in
AppCardView(uniqueProduct: uniqueProduct)
}
}
.padding()
@@ -224,63 +210,30 @@ struct ContentView: View {
}
}
}
.sheet(isPresented: $showDownloadManager) {
DownloadManagerView()
.environmentObject(globalNetworkManager)
}
.onAppear {
if globalCcmResult.products.isEmpty {
refreshData()
} else {
updateProductsCache()
}
}
.onChange(of: globalNetworkManager.saps) { _ in
updateProductsCache()
}
.sheet(isPresented: $showDownloadManager) { DownloadManagerView() }
.onAppear { if globalCcmResult.products.isEmpty { refreshData() } }
}
private func refreshData() {
isRefreshing = true
errorMessage = nil
struct SearchField: View {
@Binding var text: String
Task {
await globalNetworkManager.fetchProducts()
await MainActor.run {
updateProductsCache()
isRefreshing = false
}
}
}
}
struct SearchField: View {
@Binding var text: String
var body: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
TextField("搜索应用", text: $text)
.textFieldStyle(PlainTextFieldStyle())
if !text.isEmpty {
Button(action: { text = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
var body: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
TextField("搜索应用", text: $text)
.textFieldStyle(PlainTextFieldStyle())
if !text.isEmpty {
Button(action: { text = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
}
.buttonStyle(PlainButtonStyle())
}
.buttonStyle(PlainButtonStyle())
}
.padding(8)
.background(Color(NSColor.controlBackgroundColor))
.cornerRadius(8)
}
.padding(8)
.background(Color(NSColor.controlBackgroundColor))
.cornerRadius(8)
}
}
#Preview {
let networkManager = NetworkManager()
return ContentView()
.environmentObject(networkManager)
.frame(width: 792, height: 600)
}

View File

@@ -55,13 +55,9 @@ class NewNetworkService {
let products = globalCcmResult.products
if products.isEmpty {
return ([], [])
}
if products.isEmpty { return ([], []) }
let validProducts = products.filter {
$0.hasValidVersions(allowedPlatform: StorageData.shared.allowedPlatform)
}
let validProducts = products.filter { $0.hasValidVersions(allowedPlatform: StorageData.shared.allowedPlatform) }
var uniqueProductsDict = [String: UniqueProduct]()
for product in validProducts {

View File

@@ -49,16 +49,10 @@ class NetworkManager: ObservableObject {
func fetchProducts() async {
loadingState = .loading
do {
let (saps, sapCodes) = try await globalNetworkService.fetchProductsData()
let (newProducts, uniqueProducts) = try await globalNetworkService.fetchProductsData()
print("新产品数量: \(newProducts.count), 唯一产品数量: \(uniqueProducts.count), CDN: \(globalCdn)")
for uniqueProduct in uniqueProducts {
print("新唯一产品: \(uniqueProduct)")
}
let (products, uniqueProducts) = try await globalNetworkService.fetchProductsData()
await MainActor.run {
globalProducts = products
globalUniqueProducts = uniqueProducts.sorted { $0.displayName < $1.displayName }
self.loadingState = .success
}
} catch {
@@ -153,8 +147,10 @@ class NetworkManager: ObservableObject {
while retryCount < maxRetries {
do {
let (saps, sapCodes) = try await globalNetworkService.fetchProductsData()
let (products, uniqueProducts) = try await globalNetworkService.fetchProductsData()
await MainActor.run {
globalProducts = products
globalUniqueProducts = uniqueProducts
self.loadingState = .success
self.isFetchingProducts = false
}
@@ -290,18 +286,18 @@ class NetworkManager: ObservableObject {
return try await globalNetworkService.getApplicationInfo(buildGuid: buildGuid)
}
func isVersionDownloaded(product: Product, version: String, language: String) -> URL? {
func isVersionDownloaded(productId: String, version: String, language: String) -> URL? {
if let task = downloadTasks.first(where: {
$0.sapCode == sap.sapCode &&
$0.version == version &&
$0.productId == productId &&
$0.productVersion == version &&
$0.language == language &&
!$0.status.isCompleted
}) { return task.directory }
let platform = sap.versions[version]?.apPlatform ?? "unknown"
let fileName = sap.sapCode == "APRO"
? "Adobe Downloader \(sap.sapCode)_\(version)_\(platform).dmg"
: "Adobe Downloader \(sap.sapCode)_\(version)-\(language)-\(platform)"
let platform = globalProducts.first(where: { $0.id == productId })?.platforms.first?.id ?? "unknown"
let fileName = productId == "APRO"
? "Adobe Downloader \(productId)_\(version)_\(platform).dmg"
: "Adobe Downloader \(productId)_\(version)-\(language)-\(platform)"
if useDefaultDirectory && !defaultDirectory.isEmpty {
let defaultPath = URL(fileURLWithPath: defaultDirectory)
@@ -344,7 +340,7 @@ class NetworkManager: ObservableObject {
let savedTasks = await TaskPersistenceManager.shared.loadTasks()
await MainActor.run {
for task in savedTasks {
for product in task.productsToDownload {
for product in task.dependenciesToDownload {
product.updateCompletedPackages()
}
}
@@ -383,7 +379,7 @@ class NetworkManager: ObservableObject {
for task in downloadTasks {
if case .paused(let info) = task.status,
info.reason == .networkIssue {
await downloadUtils.resumeDownloadTask(taskId: task.id)
await globalNewDownloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
@@ -391,7 +387,7 @@ class NetworkManager: ObservableObject {
private func pauseActiveTasks() async {
for task in downloadTasks {
if case .downloading = task.status {
await downloadUtils.pauseDownloadTask(taskId: task.id, reason: .networkIssue)
await globalNewDownloadUtils.pauseDownloadTask(taskId: task.id, reason: .networkIssue)
}
}
}

View File

@@ -244,6 +244,9 @@ class NewDownloadUtils {
if condition.contains("[OSArchitecture]==\(AppStatics.architectureSymbol)") {
shouldDownload = true
}
if condition.contains("[OSArchitecture]==x64") {
shouldDownload = true
}
if condition.contains(installLanguage) || task.language == "ALL" {
shouldDownload = true
}
@@ -311,7 +314,6 @@ class NewDownloadUtils {
if !FileManager.default.fileExists(atPath: driverPath.path) {
if let productInfo = globalCcmResult.products.first(where: { $0.id == task.productId && $0.version == task.productVersion }) {
let driverXml = generateDriverXML(
sapCode: task.productId,
version: task.productVersion,
language: task.language,
productInfo: productInfo,
@@ -412,19 +414,19 @@ class NewDownloadUtils {
func handleError(_ taskId: UUID, _ error: Error) async {
let task = await globalNetworkManager.downloadTasks.first(where: { $0.id == taskId })
guard task != nil else { return }
guard let task = task else { return }
let (errorMessage, isRecoverable) = classifyError(error)
if isRecoverable,
let downloadTask = await globalCancelTracker?.downloadTasks[taskId] {
let downloadTask = await globalCancelTracker.downloadTasks[taskId] {
let resumeData = await withCheckedContinuation { continuation in
downloadTask.cancel(byProducingResumeData: { data in
continuation.resume(returning: data)
})
}
if let resumeData = resumeData {
await globalCancelTracker?.storeResumeData(taskId, data: resumeData)
await globalCancelTracker.storeResumeData(taskId, data: resumeData)
}
}
@@ -441,7 +443,7 @@ class NewDownloadUtils {
Task {
do {
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
if await !(globalCancelTracker?.isCancelled(taskId) ?? false) {
if await globalCancelTracker.isCancelled(taskId) == false {
await resumeDownloadTask(taskId: taskId)
}
} catch {
@@ -458,7 +460,7 @@ class NewDownloadUtils {
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("\(task.productId)")
.appendingPathComponent("\(task.productId ?? "")")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
@@ -473,16 +475,18 @@ class NewDownloadUtils {
func resumeDownloadTask(taskId: UUID) async {
let task = await globalNetworkManager.downloadTasks.first(where: { $0.id == taskId })
guard task != nil else { return }
guard let task = task else { return }
await MainActor.run {
let totalPackages = task.dependenciesToDownload.reduce(0) { $0 + $1.packages.count }
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.dependenciesToDownload.reduce(0) { $0 + $1.packages.count },
totalPackages: totalPackages,
startTime: Date(),
estimatedTimeRemaining: nil
)))
task.objectWillChange.send()
}
await globalNetworkManager.saveTask(task)
@@ -491,7 +495,7 @@ class NewDownloadUtils {
}
if task.productId == "APRO" {
if let resumeData = await globalCancelTracker?.getResumeData(taskId),
if let resumeData = await globalCancelTracker.getResumeData(taskId),
let currentPackage = task.currentPackage,
let product = task.dependenciesToDownload.first {
try? await downloadPackage(
@@ -598,9 +602,9 @@ class NewDownloadUtils {
product.updateCompletedPackages()
}
await globalNetworkManager.saveTask(task)
await globalNetworkManager?.saveTask(task)
await MainActor.run {
globalNetworkManager.objectWillChange.send()
globalNetworkManager?.objectWillChange.send()
}
continuation.resume()
}
@@ -643,7 +647,7 @@ class NewDownloadUtils {
lastUpdateTime = now
lastBytes = totalBytesWritten
globalNetworkManager.objectWillChange.send()
globalNetworkManager?.objectWillChange.send()
}
}
}
@@ -664,27 +668,27 @@ class NewDownloadUtils {
return
}
await globalCancelTracker?.registerTask(task.id, task: downloadTask, session: session)
await globalCancelTracker?.clearResumeData(task.id)
await globalCancelTracker.registerTask(task.id, task: downloadTask, session: session)
await globalCancelTracker.clearResumeData(task.id)
downloadTask.resume()
}
}
}
func generateDriverXML(sapCode: String, version: String, language: String, productInfo: Product, displayName: String) -> String {
func generateDriverXML(version: String, language: String, productInfo: Product, displayName: String) -> String {
// platform languageSet
guard let platform = productInfo.platforms.first(where: { $0.id == "mac" }),
let languageSet = platform.languageSet.first else {
guard let platform = globalProducts.first(where: { $0.id == productInfo.id })?.platforms.first?.id,
let languageSet = globalProducts.first(where: { $0.id == productInfo.id })?.platforms.first?.languageSet else {
return ""
}
//
let dependencies = languageSet.dependencies.map { dependency in
let dependencies = languageSet.first?.dependencies.map { dependency in
"""
<Dependency>
<SAPCode>\(dependency.sapCode)</SAPCode>
<BaseVersion>\(dependency.baseVersion)</BaseVersion>
<EsdDirectory>\(dependency.sapCode)</EsdDirectory>
<SAPCode>\(productInfo.id)</SAPCode>
<BaseVersion>\(languageSet.first?.baseVersion)</BaseVersion>
<EsdDirectory>\(productInfo.id)</EsdDirectory>
</Dependency>
"""
}.joined(separator: "\n")
@@ -693,10 +697,10 @@ class NewDownloadUtils {
<DriverInfo>
<ProductInfo>
<n>Adobe \(displayName)</n>
<SAPCode>\(sapCode)</SAPCode>
<SAPCode>\(productInfo.id)</SAPCode>
<CodexVersion>\(version)</CodexVersion>
<Platform>mac</Platform>
<EsdDirectory>\(sapCode)</EsdDirectory>
<EsdDirectory>\(productInfo.id)</EsdDirectory>
<Dependencies>
\(dependencies)
</Dependencies>
@@ -811,11 +815,11 @@ class NewDownloadUtils {
task.objectWillChange.send()
}
await globalNetworkManager.saveTask(task)
await globalNetworkManager?.saveTask(task)
await MainActor.run {
globalNetworkManager.updateDockBadge()
globalNetworkManager.objectWillChange.send()
globalNetworkManager?.updateDockBadge()
globalNetworkManager?.objectWillChange.send()
}
continuation.resume()
}
@@ -842,10 +846,10 @@ class NewDownloadUtils {
lastBytes = totalBytesWritten
task.objectWillChange.send()
globalNetworkManager.objectWillChange.send()
globalNetworkManager?.objectWillChange.send()
Task {
await globalNetworkManager.saveTask(task)
await globalNetworkManager?.saveTask(task)
}
}
}
@@ -860,20 +864,19 @@ class NewDownloadUtils {
let downloadTask = session.downloadTask(with: downloadRequest)
Task {
await globalCancelTracker?.registerTask(task.id, task: downloadTask, session: session)
if await (globalCancelTracker?.isCancelled(task.id) ?? false) {
await globalCancelTracker.registerTask(task.id, task: downloadTask, session: session)
if await globalCancelTracker.isCancelled(task.id) {
continuation.resume(throwing: NetworkError.cancelled)
return
}
downloadTask.resume()
}
}
}
func pauseDownloadTask(taskId: UUID, reason: DownloadStatus.PauseInfo.PauseReason) async {
let task = await globalCancelTracker?.downloadTasks[taskId]
let task = await globalCancelTracker.downloadTasks[taskId]
if let downloadTask = task {
let data = await withCheckedContinuation { continuation in
downloadTask.cancel(byProducingResumeData: { data in
@@ -881,7 +884,7 @@ class NewDownloadUtils {
})
}
if let data = data {
await globalCancelTracker?.storeResumeData(taskId, data: data)
await globalCancelTracker.storeResumeData(taskId, data: data)
}
}
@@ -944,4 +947,223 @@ class NewDownloadUtils {
return false
}
}
private func executePrivilegedCommand(_ command: String) async -> String {
return await withCheckedContinuation { continuation in
PrivilegedHelperManager.shared.executeCommand(command) { result in
if result.starts(with: "Error:") {
print("命令执行失败: \(command)")
print("错误信息: \(result)")
}
continuation.resume(returning: result)
}
}
}
func downloadX1a0HeCCPackages(
progressHandler: @escaping (Double, String) -> Void,
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")"
let tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 300
configuration.httpAdditionalHeaders = NetworkConstants.downloadHeaders
let session = URLSession(configuration: configuration)
do {
var request = URLRequest(url: URL(string: baseUrl)!)
NetworkConstants.downloadHeaders.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
let xmlDoc = try XMLDocument(data: data)
let packageSets = try xmlDoc.nodes(forXPath: "//packageSet[name='ADC']")
guard let adcPackageSet = packageSets.first else {
throw NetworkError.invalidData("找不到ADC包集")
}
let targetPackages = ["HDBox", "IPCBox"]
var packagesToDownload: [(name: String, url: URL, size: Int64)] = []
for packageName in targetPackages {
let packageNodes = try adcPackageSet.nodes(forXPath: ".//package[name='\(packageName)']")
guard let package = packageNodes.first else {
print("未找到包: \(packageName)")
continue
}
guard let manifestUrl = try package.nodes(forXPath: ".//manifestUrl").first?.stringValue,
let cdnBase = try xmlDoc.nodes(forXPath: "//cdn/secure").first?.stringValue else {
print("无法获取manifest URL或CDN基础URL")
continue
}
let manifestFullUrl = cdnBase + manifestUrl
var manifestRequest = URLRequest(url: URL(string: manifestFullUrl)!)
NetworkConstants.downloadHeaders.forEach { manifestRequest.setValue($0.value, forHTTPHeaderField: $0.key) }
let (manifestData, manifestResponse) = try await session.data(for: manifestRequest)
guard let manifestHttpResponse = manifestResponse as? HTTPURLResponse,
(200...299).contains(manifestHttpResponse.statusCode) else {
print("获取manifest失败: HTTP \(String(describing: (manifestResponse as? HTTPURLResponse)?.statusCode))")
continue
}
let manifestDoc = try XMLDocument(data: manifestData)
let assetPathNodes = try manifestDoc.nodes(forXPath: "//asset_path")
let sizeNodes = try manifestDoc.nodes(forXPath: "//asset_size")
guard let assetPath = assetPathNodes.first?.stringValue,
let sizeStr = sizeNodes.first?.stringValue,
let size = Int64(sizeStr),
let downloadUrl = URL(string: assetPath) else {
continue
}
packagesToDownload.append((packageName, downloadUrl, size))
}
guard !packagesToDownload.isEmpty else {
throw NetworkError.invalidData("没有找到可下载的包")
}
let totalCount = packagesToDownload.count
for (index, package) in packagesToDownload.enumerated() {
if cancellationHandler() {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.cancelled
}
await MainActor.run {
progressHandler(Double(index) / Double(totalCount), "正在下载 \(package.name)...")
}
let 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)
guard let httpResponse = downloadResponse as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("下载失败: HTTP \(String(describing: (downloadResponse as? HTTPURLResponse)?.statusCode))")
continue
}
try FileManager.default.moveItem(at: downloadURL, to: destinationURL)
}
await MainActor.run {
progressHandler(0.9, shouldProcess ? "正在安装组件..." : "正在完成下载...")
}
let targetDirectory = "/Library/Application\\ Support/Adobe/Adobe\\ Desktop\\ Common"
let rawTargetDirectory = "/Library/Application Support/Adobe/Adobe Desktop Common"
if !FileManager.default.fileExists(atPath: rawTargetDirectory) {
let createDirResult = await executePrivilegedCommand("/bin/mkdir -p \(targetDirectory)")
if createDirResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("创建目录失败: \(createDirResult)")
}
let chmodResult = await executePrivilegedCommand("/bin/chmod 755 \(targetDirectory)")
if chmodResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("设置权限失败: \(chmodResult)")
}
}
for package in packagesToDownload {
let packageDir = "\(targetDirectory)/\(package.name)"
let removeResult = await executePrivilegedCommand("/bin/rm -rf \(packageDir)")
if removeResult.starts(with: "Error:") {
print("移除旧目录失败: \(removeResult)")
}
let mkdirResult = await executePrivilegedCommand("/bin/mkdir -p \(packageDir)")
if mkdirResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("创建 \(package.name) 目录失败")
}
let unzipResult = await executePrivilegedCommand("cd \(packageDir) && /usr/bin/unzip -o '\(tempDirectory.path)/\(package.name).zip'")
if unzipResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("解压 \(package.name) 失败: \(unzipResult)")
}
let chmodResult = await executePrivilegedCommand("/bin/chmod -R 755 \(packageDir)")
if chmodResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("设置 \(package.name) 权限失败: \(chmodResult)")
}
let chownResult = await executePrivilegedCommand("/usr/sbin/chown -R root:wheel \(packageDir)")
if chownResult.starts(with: "Error:") {
try? FileManager.default.removeItem(at: tempDirectory)
throw NetworkError.installError("设置 \(package.name) 所有者失败: \(chownResult)")
}
}
try await Task.sleep(nanoseconds: 1_000_000_000)
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()
}
try? FileManager.default.removeItem(at: tempDirectory)
await MainActor.run {
progressHandler(1.0, shouldProcess ? "安装完成" : "下载完成")
}
} catch {
print("发生错误: \(error.localizedDescription)")
throw error
}
}
func cancelDownloadTask(taskId: UUID, removeFiles: Bool = false) async {
await globalCancelTracker.cancel(taskId)
if let task = await globalNetworkManager.downloadTasks.first(where: { $0.id == taskId }) {
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
}
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: String(localized: "下载已取消"),
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
await globalNetworkManager.saveTask(task)
await MainActor.run {
globalNetworkManager.updateDockBadge()
globalNetworkManager.objectWillChange.send()
}
}
}
}

View File

@@ -18,15 +18,15 @@ class TaskPersistenceManager {
self.cancelTracker = tracker
}
private func getTaskFileName(sapCode: String, version: String, language: String, platform: String) -> String {
return sapCode == "APRO"
? "Adobe Downloader \(sapCode)_\(version)_\(platform)-task.json"
: "Adobe Downloader \(sapCode)_\(version)-\(language)-\(platform)-task.json"
private func getTaskFileName(productId: String, version: String, language: String, platform: String) -> String {
return productId == "APRO"
? "Adobe Downloader \(productId)_\(version)_\(platform)-task.json"
: "Adobe Downloader \(productId)_\(version)-\(language)-\(platform)-task.json"
}
func saveTask(_ task: NewDownloadTask) async {
let fileName = getTaskFileName(
sapCode: task.productId,
productId: task.productId,
version: task.productVersion,
language: task.language,
platform: task.platform
@@ -209,7 +209,7 @@ class TaskPersistenceManager {
func removeTask(_ task: NewDownloadTask) {
let fileName = getTaskFileName(
sapCode: task.productId,
productId: task.productId,
version: task.productVersion,
language: task.language,
platform: task.platform
@@ -220,16 +220,16 @@ class TaskPersistenceManager {
try? fileManager.removeItem(at: fileURL)
}
func createExistingProgramTask(sapCode: String, version: String, language: String, displayName: String, platform: String, directory: URL) async {
func createExistingProgramTask(productId: String, version: String, language: String, displayName: String, platform: String, directory: URL) async {
let fileName = getTaskFileName(
sapCode: sapCode,
productId: productId,
version: version,
language: language,
platform: platform
)
let product = DependenciesToDownload(
sapCode: sapCode,
sapCode: productId,
version: version,
buildGuid: "",
applicationJson: ""
@@ -249,7 +249,7 @@ class TaskPersistenceManager {
product.packages = [package]
let task = NewDownloadTask(
productId: sapCode,
productId: productId,
productVersion: version,
language: language,
displayName: displayName,

View File

@@ -277,17 +277,19 @@ struct GeneralSettingsView: View {
}
var body: some View {
Form {
DownloadSettingsView(viewModel: viewModel)
HelperSettingsView(viewModel: viewModel,
showHelperAlert: $showHelperAlert,
helperAlertMessage: $helperAlertMessage,
helperAlertSuccess: $helperAlertSuccess)
CCSettingsView(viewModel: viewModel)
UpdateSettingsView(viewModel: viewModel)
CleanConfigView()
ScrollView {
VStack(spacing: 16) {
DownloadSettingsView(viewModel: viewModel)
HelperSettingsView(viewModel: viewModel,
showHelperAlert: $showHelperAlert,
helperAlertMessage: $helperAlertMessage,
helperAlertSuccess: $helperAlertSuccess)
CCSettingsView(viewModel: viewModel)
UpdateSettingsView(viewModel: viewModel)
CleanConfigView()
}
.padding()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.alert(helperAlertSuccess ? "操作成功" : "操作失败", isPresented: $showHelperAlert) {
Button("确定") { }
@@ -298,28 +300,7 @@ struct GeneralSettingsView: View {
Button("取消", role: .cancel) { }
Button("下载") {
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 }
)
viewModel.setupVersion = ModifySetup.checkComponentVersion()
viewModel.isSuccess = true
viewModel.alertMessage = String(localized: "Setup 组件安装成功")
} catch NetworkError.cancelled {
viewModel.isSuccess = false
viewModel.alertMessage = String(localized: "下载已取消")
} catch {
viewModel.isSuccess = false
viewModel.alertMessage = error.localizedDescription
}
viewModel.showAlert = true
viewModel.isDownloadingSetup = false
await downloadSetup(shouldProcess: false)
}
}
} message: {
@@ -329,29 +310,7 @@ struct GeneralSettingsView: View {
Button("取消", role: .cancel) { }
Button("确定") {
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: true
)
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
await downloadSetup(shouldProcess: true)
}
}
} message: {
@@ -361,29 +320,7 @@ struct GeneralSettingsView: View {
Button("取消", role: .cancel) { }
Button("确定") {
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
await downloadSetup(shouldProcess: false)
}
}
} message: {
@@ -401,6 +338,34 @@ struct GeneralSettingsView: View {
viewModel.objectWillChange.send()
}
}
private func downloadSetup(shouldProcess: Bool) async {
viewModel.isDownloadingSetup = true
viewModel.isCancelled = false
do {
try await globalNewDownloadUtils.downloadX1a0HeCCPackages(
progressHandler: { progress, status in
viewModel.setupDownloadProgress = progress
viewModel.setupDownloadStatus = status
},
cancellationHandler: { viewModel.isCancelled },
shouldProcess: shouldProcess
)
viewModel.setupVersion = ModifySetup.checkComponentVersion()
viewModel.isSuccess = true
viewModel.alertMessage = shouldProcess ?
String(localized: "X1a0He CC 下载并处理成功") :
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
}
}
struct DownloadSettingsView: View {

View File

@@ -51,8 +51,7 @@ final class AppCardViewModel: ObservableObject {
@Published var pendingLanguage = ""
@Published var showRedownloadConfirm = false
let sap: Sap
weak var networkManager: NetworkManager?
let uniqueProduct: UniqueProduct
@Published var isDownloading = false
private let userDefaults = UserDefaults.standard
@@ -67,10 +66,9 @@ final class AppCardViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
init(sap: Sap, networkManager: NetworkManager?) {
self.sap = sap
self.networkManager = networkManager
init(uniqueProduct: UniqueProduct) {
self.uniqueProduct = uniqueProduct
Task { @MainActor in
setupObservers()
}
@@ -78,12 +76,12 @@ final class AppCardViewModel: ObservableObject {
@MainActor
private func setupObservers() {
networkManager?.$downloadTasks
globalNetworkManager.$downloadTasks
.receive(on: RunLoop.main)
.sink { [weak self] tasks in
guard let self = self else { return }
let hasActiveTask = tasks.contains {
$0.sapCode == self.sap.sapCode && self.isTaskActive($0.status)
$0.productId == self.uniqueProduct.id && self.isTaskActive($0.status)
}
if hasActiveTask != self.isDownloading {
@@ -93,7 +91,7 @@ final class AppCardViewModel: ObservableObject {
}
.store(in: &cancellables)
networkManager?.objectWillChange
globalNetworkManager.objectWillChange
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.updateDownloadingStatus()
@@ -114,13 +112,8 @@ final class AppCardViewModel: ObservableObject {
@MainActor
func updateDownloadingStatus() {
guard let networkManager = networkManager else {
self.isDownloading = false
return
}
let hasActiveTask = networkManager.downloadTasks.contains {
$0.sapCode == sap.sapCode && isTaskActive($0.status)
let hasActiveTask = globalNetworkManager.downloadTasks.contains {
$0.productId == uniqueProduct.id && isTaskActive($0.status)
}
if hasActiveTask != self.isDownloading {
@@ -130,10 +123,10 @@ final class AppCardViewModel: ObservableObject {
}
func getDestinationURL(version: String, language: String) async throws -> URL {
let platform = sap.versions[version]?.apPlatform ?? "unknown"
let installerName = sap.sapCode == "APRO"
? "Adobe Downloader \(sap.sapCode)_\(version)_\(platform).dmg"
: "Adobe Downloader \(sap.sapCode)_\(version)-\(language)-\(platform)"
let platform = globalProducts.first(where: { $0.id == uniqueProduct.id })?.platforms.first?.id
let installerName = uniqueProduct.id == "APRO"
? "Adobe Downloader \(uniqueProduct.id)_\(version)_\(platform).dmg"
: "Adobe Downloader \(uniqueProduct.id)_\(version)-\(language)-\(platform)"
if useDefaultDirectory && !defaultDirectory.isEmpty {
return URL(fileURLWithPath: defaultDirectory)
@@ -167,10 +160,9 @@ final class AppCardViewModel: ObservableObject {
}
func loadIcon() {
if let bestIcon = sap.getBestIcon(),
let iconURL = URL(string: bestIcon.url) {
if let cachedImage = IconCache.shared.getIcon(for: bestIcon.url) {
if let bestIcon = globalProducts.first(where: { $0.id == uniqueProduct.id })?.getBestIcon(),
let iconURL = URL(string: bestIcon.value) {
if let cachedImage = IconCache.shared.getIcon(for: bestIcon.value) {
self.iconImage = cachedImage
return
}
@@ -189,20 +181,20 @@ final class AppCardViewModel: ObservableObject {
await MainActor.run {
if let image = NSImage(data: data) {
IconCache.shared.setIcon(image, for: bestIcon.url)
IconCache.shared.setIcon(image, for: bestIcon.value)
self.iconImage = image
}
}
} catch {
await MainActor.run {
if let localImage = NSImage(named: sap.sapCode) {
if let localImage = NSImage(named: uniqueProduct.id) {
self.iconImage = localImage
}
}
}
}
} else {
if let localImage = NSImage(named: sap.sapCode) {
if let localImage = NSImage(named: uniqueProduct.id) {
self.iconImage = localImage
}
}
@@ -222,37 +214,32 @@ final class AppCardViewModel: ObservableObject {
}
func checkAndStartDownload(version: String, language: String) async {
if let networkManager = networkManager {
if let existingPath = networkManager.isVersionDownloaded(sap: sap, version: version, language: language) {
await MainActor.run {
existingFilePath = existingPath
pendingVersion = version
pendingLanguage = language
showExistingFileAlert = true
}
} else {
do {
let destinationURL = try await getDestinationURL(version: version, language: language)
try await networkManager.startDownload(
sap: sap,
selectedVersion: version,
language: language,
destinationURL: destinationURL
)
} catch {
handleError(error)
}
if let existingPath = globalNetworkManager.isVersionDownloaded(productId: uniqueProduct.id, version: version, language: language) {
await MainActor.run {
existingFilePath = existingPath
pendingVersion = version
pendingLanguage = language
showExistingFileAlert = true
}
} else {
do {
let destinationURL = try await getDestinationURL(version: version, language: language)
try await globalNetworkManager.startDownload(
productId: uniqueProduct.id,
selectedVersion: version,
language: language,
destinationURL: destinationURL
)
} catch {
handleError(error)
}
}
}
func createCompletedTask(_ path: URL) async {
guard let networkManager = networkManager,
let productInfo = sap.versions[pendingVersion] else { return }
let existingTask = networkManager.downloadTasks.first { task in
return task.sapCode == sap.sapCode &&
task.version == pendingVersion &&
let existingTask = globalNetworkManager.downloadTasks.first { task in
return task.productId == uniqueProduct.id &&
task.productVersion == pendingVersion &&
task.language == pendingLanguage &&
task.directory == path
}
@@ -262,27 +249,24 @@ final class AppCardViewModel: ObservableObject {
}
await TaskPersistenceManager.shared.createExistingProgramTask(
sapCode: sap.sapCode,
productId: uniqueProduct.id,
version: pendingVersion,
language: pendingLanguage,
displayName: sap.displayName,
platform: productInfo.apPlatform,
displayName: uniqueProduct.displayName,
platform: globalProducts.first(where: { $0.id == uniqueProduct.id })?.platforms.first?.id ?? "unknown",
directory: path
)
let savedTasks = await TaskPersistenceManager.shared.loadTasks()
await MainActor.run {
networkManager.downloadTasks = savedTasks
networkManager.updateDockBadge()
networkManager.objectWillChange.send()
globalNetworkManager.downloadTasks = savedTasks
globalNetworkManager.updateDockBadge()
globalNetworkManager.objectWillChange.send()
}
}
var dependenciesCount: Int {
if let firstVersion = sap.versions.first?.value {
return firstVersion.dependencies.count
}
return 0
return globalProducts.first(where: { $0.id == uniqueProduct.id })?.platforms.first?.languageSet.first?.dependencies.count ?? 0
}
var hasValidIcon: Bool {
@@ -304,12 +288,11 @@ final class AppCardViewModel: ObservableObject {
struct AppCardView: View {
@StateObject private var viewModel: AppCardViewModel
@EnvironmentObject private var networkManager: NetworkManager
@StorageValue(\.useDefaultLanguage) private var useDefaultLanguage
@StorageValue(\.defaultLanguage) private var defaultLanguage
init(sap: Sap) {
_viewModel = StateObject(wrappedValue: AppCardViewModel(sap: sap, networkManager: nil))
init(uniqueProduct: UniqueProduct) {
_viewModel = StateObject(wrappedValue: AppCardViewModel(uniqueProduct: uniqueProduct))
}
var body: some View {
@@ -322,18 +305,19 @@ struct AppCardView: View {
}
}
.modifier(CardModifier())
.modifier(SheetModifier(viewModel: viewModel, networkManager: networkManager))
.modifier(SheetModifier(viewModel: viewModel))
.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
.onAppear(perform: setupViewModel)
.onChange(of: networkManager.downloadTasks, perform: updateDownloadStatus)
.onChange(of: globalNetworkManager.downloadTasks.count) { _ in
updateDownloadStatus()
}
}
private func setupViewModel() {
viewModel.networkManager = networkManager
viewModel.updateDownloadingStatus()
}
private func updateDownloadStatus(_ _: [NewDownloadTask]) {
private func updateDownloadStatus() {
viewModel.updateDownloadingStatus()
}
}
@@ -379,16 +363,19 @@ private struct ProductInfoView: View {
var body: some View {
VStack {
Text(viewModel.sap.displayName)
Text(viewModel.uniqueProduct.displayName)
.font(.system(size: AppCardConstants.titleFontSize))
.fontWeight(.bold)
.lineLimit(2)
.multilineTextAlignment(.center)
HStack(spacing: 4) {
Text("可用版本: \(viewModel.sap.versions.count)")
let product = findProduct(id: viewModel.uniqueProduct.id)
let versions = Set(product?.platforms.first?.languageSet.map { $0.productVersion } ?? [])
let dependenciesCount = product?.platforms.first?.languageSet.first?.dependencies.count ?? 0
Text("可用版本: \(versions.count)")
Text("|")
Text("依赖包: \(viewModel.dependenciesCount)")
Text("依赖包: \(dependenciesCount)")
}
.font(.caption)
.foregroundColor(.secondary)
@@ -437,23 +424,23 @@ private struct CardModifier: ViewModifier {
private struct SheetModifier: ViewModifier {
@ObservedObject var viewModel: AppCardViewModel
let networkManager: NetworkManager
@StorageValue(\.useDefaultLanguage) private var useDefaultLanguage
@StorageValue(\.defaultLanguage) private var defaultLanguage
func body(content: Content) -> some View {
content
.sheet(isPresented: $viewModel.showVersionPicker) {
VersionPickerView(sap: viewModel.sap) { version in
Task {
await viewModel.handleDownloadRequest(
version,
useDefaultLanguage: useDefaultLanguage,
defaultLanguage: defaultLanguage
)
if let product = findProduct(id: viewModel.uniqueProduct.id) {
VersionPickerView(product: product) { version in
Task {
await viewModel.handleDownloadRequest(
version,
useDefaultLanguage: useDefaultLanguage,
defaultLanguage: defaultLanguage
)
}
}
}
.environmentObject(networkManager)
}
.sheet(isPresented: $viewModel.showLanguagePicker) {
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
@@ -482,10 +469,9 @@ struct AlertModifier: ViewModifier {
viewModel.showExistingFileAlert = false
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
Task {
if let networkManager = viewModel.networkManager,
!networkManager.downloadTasks.contains(where: { task in
task.sapCode == viewModel.sap.sapCode &&
task.version == viewModel.pendingVersion &&
if !globalNetworkManager.downloadTasks.contains(where: { task in
task.productId == viewModel.uniqueProduct.id &&
task.productVersion == viewModel.pendingVersion &&
task.language == viewModel.pendingLanguage
}) {
await viewModel.createCompletedTask(path)
@@ -540,12 +526,10 @@ struct AlertModifier: ViewModifier {
}
private func startRedownload() async {
guard let networkManager = viewModel.networkManager else { return }
do {
networkManager.downloadTasks.removeAll { task in
task.sapCode == viewModel.sap.sapCode &&
task.version == viewModel.pendingVersion &&
globalNetworkManager.downloadTasks.removeAll { task in
task.productId == viewModel.uniqueProduct.id &&
task.productVersion == viewModel.pendingVersion &&
task.language == viewModel.pendingLanguage
}
@@ -558,8 +542,8 @@ struct AlertModifier: ViewModifier {
language: viewModel.pendingLanguage
)
try await networkManager.startDownload(
sap: viewModel.sap,
try await globalNetworkManager.startDownload(
productId: viewModel.uniqueProduct.id,
selectedVersion: viewModel.pendingVersion,
language: viewModel.pendingLanguage,
destinationURL: destinationURL

View File

@@ -7,7 +7,6 @@
import SwiftUI
struct DownloadManagerView: View {
@EnvironmentObject private var networkManager: NetworkManager
@Environment(\.dismiss) private var dismiss
@State private var sortOrder: SortOrder = .addTime
@@ -27,7 +26,7 @@ struct DownloadManagerView: View {
}
private func removeTask(_ task: NewDownloadTask) {
networkManager.removeTask(taskId: task.id)
globalNetworkManager.removeTask(taskId: task.id)
}
private func sortTasks(_ tasks: [NewDownloadTask]) -> [NewDownloadTask] {
@@ -47,123 +46,163 @@ struct DownloadManagerView: View {
var body: some View {
VStack(spacing: 0) {
HStack {
Text("下载管理")
.font(.headline)
Spacer()
HStack(){
Menu {
ForEach([SortOrder.addTime, .name, .status], id: \.self) { order in
Button(action: {
sortOrder = order
}) {
HStack {
Text(order.description)
if sortOrder == order {
Image(systemName: "checkmark")
}
}
}
}
} label: {
HStack {
Image(systemName: "arrow.up.arrow.down")
Text(sortOrder.description)
.font(.caption)
}
}
}
.frame(minWidth: 120)
.fixedSize()
Button("全部暂停") {
Task {
for task in networkManager.downloadTasks {
if case .downloading = task.status {
await networkManager.downloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
}
}
}
Button("全部继续") {
Task {
for task in networkManager.downloadTasks {
if case .paused = task.status {
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
}
Button("清理已完成") {
networkManager.downloadTasks.removeAll { task in
if case .completed = task.status {
return true
}
return false
}
networkManager.updateDockBadge()
}
Button("关闭") {
dismiss()
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
.padding(.horizontal)
.padding(.vertical, 8)
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 8) {
ForEach(sortTasks(networkManager.downloadTasks)) { task in
DownloadProgressView(
task: task,
onCancel: {
Task {
await networkManager.downloadUtils.cancelDownloadTask(taskId: task.id)
}
},
onPause: {
Task {
await networkManager.downloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
},
onResume: {
Task {
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
},
onRetry: {
Task {
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
}
},
onRemove: {
removeTask(task)
}
)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
.background(Color(NSColor.windowBackgroundColor))
DownloadManagerToolbar(
sortOrder: $sortOrder,
dismiss: dismiss
)
DownloadTaskList(
tasks: sortTasks(globalNetworkManager.downloadTasks),
removeTask: removeTask
)
}
.frame(width:800, height: 600)
}
}
private struct DownloadManagerToolbar: View {
@Binding var sortOrder: DownloadManagerView.SortOrder
let dismiss: DismissAction
var body: some View {
HStack {
Text("下载管理")
.font(.headline)
Spacer()
SortMenuView(sortOrder: $sortOrder)
.frame(minWidth: 120)
.fixedSize()
ToolbarButtons(dismiss: dismiss)
}
.padding(.horizontal)
.padding(.vertical, 8)
}
}
private struct ToolbarButtons: View {
let dismiss: DismissAction
var body: some View {
Group {
Button("全部暂停") {
Task {
for task in globalNetworkManager.downloadTasks {
if case .downloading = task.status {
await globalNewDownloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
}
}
}
Button("全部继续") {
Task {
for task in globalNetworkManager.downloadTasks {
if case .paused = task.status {
await globalNewDownloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
}
Button("清理已完成") {
globalNetworkManager.downloadTasks.removeAll { task in
if case .completed = task.status {
return true
}
return false
}
globalNetworkManager.updateDockBadge()
}
Button("关闭") {
dismiss()
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
}
}
private struct DownloadTaskList: View {
let tasks: [NewDownloadTask]
let removeTask: (NewDownloadTask) -> Void
var body: some View {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 8) {
ForEach(tasks) { task in
DownloadProgressView(
task: task,
onCancel: { TaskOperations.cancelTask(task) },
onPause: { TaskOperations.pauseTask(task) },
onResume: { TaskOperations.resumeTask(task) },
onRetry: { TaskOperations.resumeTask(task) },
onRemove: { removeTask(task) }
)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
.background(Color(NSColor.windowBackgroundColor))
}
}
private enum TaskOperations {
static func cancelTask(_ task: NewDownloadTask) {
Task {
await globalNewDownloadUtils.cancelDownloadTask(taskId: task.id)
}
}
static func pauseTask(_ task: NewDownloadTask) {
Task {
await globalNewDownloadUtils.pauseDownloadTask(
taskId: task.id,
reason: .userRequested
)
}
}
static func resumeTask(_ task: NewDownloadTask) {
Task {
await globalNewDownloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
extension DownloadManagerView.SortOrder: Hashable {}
struct SortMenuView: View {
@Binding var sortOrder: DownloadManagerView.SortOrder
var body: some View {
Menu {
ForEach([DownloadManagerView.SortOrder.addTime, .name, .status], id: \.self) { order in
Button(action: {
sortOrder = order
}) {
HStack {
Text(order.description)
if sortOrder == order {
Image(systemName: "checkmark")
}
}
}
}
} label: {
HStack {
Image(systemName: "arrow.up.arrow.down")
Text(sortOrder.description)
.font(.caption)
}
}
}
}
#Preview {
DownloadManagerView()
.environmentObject(NetworkManager())
}

View File

@@ -6,7 +6,6 @@
import SwiftUI
struct DownloadProgressView: View {
@EnvironmentObject private var networkManager: NetworkManager
@ObservedObject var task: NewDownloadTask
let onCancel: () -> Void
let onPause: () -> Void
@@ -26,7 +25,7 @@ struct DownloadProgressView: View {
private var statusLabel: some View {
Text(task.status.description)
.font(.caption)
.foregroundColor(statusColor)
.foregroundColor(.white)
.padding(.vertical, 2)
.padding(.horizontal, 6)
.background(statusBackgroundColor)
@@ -131,7 +130,7 @@ struct DownloadProgressView: View {
showInstallPrompt = false
isInstalling = true
Task {
await networkManager.installProduct(at: task.directory)
await globalNetworkManager.installProduct(at: task.directory)
}
} catch {
showSetupProcessAlert = true
@@ -145,7 +144,7 @@ struct DownloadProgressView: View {
showInstallPrompt = false
isInstalling = true
Task {
await networkManager.installProduct(at: task.directory)
await globalNetworkManager.installProduct(at: task.directory)
}
} catch {
showSetupProcessAlert = true
@@ -205,7 +204,7 @@ struct DownloadProgressView: View {
showInstallPrompt = false
isInstalling = true
Task {
await networkManager.installProduct(at: task.directory)
await globalNetworkManager.installProduct(at: task.directory)
}
}
.buttonStyle(.borderedProminent)
@@ -217,18 +216,18 @@ struct DownloadProgressView: View {
}
.sheet(isPresented: $isInstalling) {
Group {
if case .installing(let progress, let status) = networkManager.installationState {
if case .installing(let progress, let status) = globalNetworkManager.installationState {
InstallProgressView(
productName: task.displayName,
progress: progress,
status: status,
onCancel: {
networkManager.cancelInstallation()
globalNetworkManager.cancelInstallation()
isInstalling = false
},
onRetry: nil
)
} else if case .completed = networkManager.installationState {
} else if case .completed = globalNetworkManager.installationState {
InstallProgressView(
productName: task.displayName,
progress: 1.0,
@@ -238,7 +237,7 @@ struct DownloadProgressView: View {
},
onRetry: nil
)
} else if case .failed(let error) = networkManager.installationState {
} else if case .failed(let error) = globalNetworkManager.installationState {
InstallProgressView(
productName: task.displayName,
progress: 0,
@@ -248,7 +247,7 @@ struct DownloadProgressView: View {
},
onRetry: {
Task {
await networkManager.retryInstallation(at: task.directory)
await globalNetworkManager.retryInstallation(at: task.directory)
}
}
)
@@ -258,7 +257,7 @@ struct DownloadProgressView: View {
progress: 0,
status: String(localized: "准备安装..."),
onCancel: {
networkManager.cancelInstallation()
globalNetworkManager.cancelInstallation()
isInstalling = false
},
onRetry: nil
@@ -301,12 +300,12 @@ struct DownloadProgressView: View {
}
private func loadIcon() {
let product = globalCcmResult.products.first { $0.id == task.productId }
let product = findProduct(id: task.productId)
if product != nil {
if let bestIcon = product.getBestIcon(),
let iconURL = URL(string: bestIcon.url) {
if let cachedImage = IconCache.shared.getIcon(for: bestIcon.url) {
if let bestIcon = product?.getBestIcon(),
let iconURL = URL(string: bestIcon.value) {
if let cachedImage = IconCache.shared.getIcon(for: bestIcon.value) {
self.iconImage = cachedImage
return
}
@@ -324,13 +323,13 @@ struct DownloadProgressView: View {
throw URLError(.badServerResponse)
}
IconCache.shared.setIcon(image, for: bestIcon.url)
IconCache.shared.setIcon(image, for: bestIcon.value)
await MainActor.run {
self.iconImage = image
}
} catch {
if let localImage = NSImage(named: task.sapCode) {
if let localImage = NSImage(named: task.productId) {
await MainActor.run {
self.iconImage = localImage
}
@@ -357,184 +356,20 @@ struct DownloadProgressView: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 12) {
Group {
if let iconImage = iconImage {
Image(nsImage: iconImage)
.resizable()
.interpolation(.high)
.aspectRatio(contentMode: .fit)
} else {
Image(systemName: "app.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.secondary)
}
}
.frame(width: 32, height: 32)
.onAppear(perform: loadIcon)
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 8) {
HStack(spacing: 4) {
Text(task.displayName)
.font(.headline)
Text(task.productVersion)
.foregroundColor(.secondary)
}
statusLabel
Spacer()
}
Text(formatPath(task.directory.path))
.font(.caption)
.foregroundColor(.blue)
.lineLimit(1)
.truncationMode(.middle)
.onTapGesture {
openInFinder(task.directory.path)
}
.help(task.directory.path)
}
}
TaskHeaderView(iconImage: iconImage, task: task, loadIcon: loadIcon, formatPath: formatPath, openInFinder: openInFinder)
VStack(alignment: .leading, spacing: 4) {
HStack {
HStack(spacing: 4) {
Text(task.formattedDownloadedSize)
Text("/")
Text(task.formattedTotalSize)
}
Spacer()
if task.totalSpeed > 0 {
Text(formatRemainingTime(
totalSize: task.totalSize,
downloadedSize: task.totalDownloadedSize,
speed: task.totalSpeed
))
.foregroundColor(.secondary)
}
Text("\(Int(task.totalProgress * 100))%")
if task.totalSpeed > 0 {
Text(formatSpeed(task.totalSpeed))
.foregroundColor(.secondary)
}
}
.font(.caption)
ProgressView(value: task.totalProgress)
.progressViewStyle(.linear)
}
TaskProgressView(task: task, formatRemainingTime: formatRemainingTime, formatSpeed: formatSpeed)
if !task.dependenciesToDownload.isEmpty {
Divider()
VStack(alignment: .leading, spacing: 6) {
HStack {
Button(action: {
withAnimation {
isPackageListExpanded.toggle()
}
}) {
HStack {
Image(systemName: isPackageListExpanded ? "chevron.down" : "chevron.right")
.foregroundColor(.secondary)
Text("产品和包列表")
.font(.caption)
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Spacer()
#if DEBUG
Button(action: {
let containerURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let tasksDirectory = containerURL.appendingPathComponent("Adobe Downloader/tasks", isDirectory: true)
let fileName = "\(task.sapCode == "APRO" ? "Adobe Downloader \(task.sapCode)_\(task.version)_\(task.platform)" : "Adobe Downloader \(task.sapCode)_\(task.version)-\(task.language)-\(task.platform)")-task.json"
let fileURL = tasksDirectory.appendingPathComponent(fileName)
NSWorkspace.shared.selectFile(fileURL.path, inFileViewerRootedAtPath: tasksDirectory.path)
}) {
Label("查看持久化文件", systemImage: "doc.text.magnifyingglass")
}
.buttonStyle(.borderedProminent)
.tint(.blue)
.controlSize(.regular)
#endif
if case .completed = task.status, task.sapCode != "APRO" {
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)
}
}
actionButtons
}
if isPackageListExpanded {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 8) {
ForEach(task.productsToDownload, id: \.sapCode) { product in
ProductRow(
product: product,
isCurrentProduct: task.currentPackage?.id == product.packages.first?.id,
expandedProducts: $expandedProducts
)
}
}
}
.frame(maxHeight: 200)
}
}
PackageListView(
task: task,
isPackageListExpanded: $isPackageListExpanded,
showCommandLineInstall: $showCommandLineInstall,
showCopiedAlert: $showCopiedAlert,
expandedProducts: $expandedProducts,
actionButtons: AnyView(actionButtons)
)
}
}
.padding()
@@ -548,8 +383,256 @@ struct DownloadProgressView: View {
}
}
private struct TaskHeaderView: View {
let iconImage: NSImage?
let task: NewDownloadTask
let loadIcon: () -> Void
let formatPath: (String) -> String
let openInFinder: (String) -> Void
var body: some View {
HStack(spacing: 12) {
Group {
if let iconImage = iconImage {
Image(nsImage: iconImage)
.resizable()
.interpolation(.high)
.aspectRatio(contentMode: .fit)
} else {
Image(systemName: "app.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.secondary)
}
}
.frame(width: 32, height: 32)
.onAppear(perform: loadIcon)
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 8) {
HStack(spacing: 4) {
Text(task.displayName)
.font(.headline)
Text(task.productVersion)
.foregroundColor(.secondary)
}
statusLabel
Spacer()
}
Text(formatPath(task.directory.path))
.font(.caption)
.foregroundColor(.blue)
.lineLimit(1)
.truncationMode(.middle)
.onTapGesture {
openInFinder(task.directory.path)
}
.help(task.directory.path)
}
}
}
private var statusLabel: some View {
Text(task.status.description)
.font(.caption)
.foregroundColor(.white)
.padding(.vertical, 2)
.padding(.horizontal, 6)
.background(statusBackgroundColor)
.cornerRadius(4)
}
private var statusBackgroundColor: Color {
switch task.status {
case .downloading:
return Color.blue
case .preparing:
return Color.purple.opacity(0.8)
case .completed:
return Color.green.opacity(0.8)
case .failed:
return Color.red.opacity(0.8)
case .paused:
return Color.orange.opacity(0.8)
case .waiting:
return Color.gray.opacity(0.8)
case .retrying:
return Color.yellow.opacity(0.8)
}
}
}
private struct TaskProgressView: View {
let task: NewDownloadTask
let formatRemainingTime: (Int64, Int64, Double) -> String
let formatSpeed: (Double) -> String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
HStack(spacing: 4) {
Text(task.formattedDownloadedSize)
Text("/")
Text(task.formattedTotalSize)
}
Spacer()
if task.totalSpeed > 0 {
Text(formatRemainingTime(
task.totalSize,
task.totalDownloadedSize,
task.totalSpeed
))
.foregroundColor(.secondary)
}
Text("\(Int(task.totalProgress * 100))%")
if task.totalSpeed > 0 {
Text(formatSpeed(task.totalSpeed))
.foregroundColor(.secondary)
}
}
.font(.caption)
ProgressView(value: task.totalProgress)
.progressViewStyle(.linear)
}
}
}
private struct PackageListView: View {
let task: NewDownloadTask
@Binding var isPackageListExpanded: Bool
@Binding var showCommandLineInstall: Bool
@Binding var showCopiedAlert: Bool
@Binding var expandedProducts: Set<String>
let actionButtons: AnyView
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Button(action: {
withAnimation {
isPackageListExpanded.toggle()
}
}) {
HStack {
Image(systemName: isPackageListExpanded ? "chevron.down" : "chevron.right")
.foregroundColor(.secondary)
Text("产品和包列表")
.font(.caption)
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Spacer()
#if DEBUG
Button(action: {
let containerURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let tasksDirectory = containerURL.appendingPathComponent("Adobe Downloader/tasks", isDirectory: true)
let fileName = "\(task.productId == "APRO" ? "Adobe Downloader \(task.productId)_\(task.productVersion)_\(task.platform)" : "Adobe Downloader \(task.productId)_\(task.productVersion)-\(task.language)-\(task.platform)")-task.json"
let fileURL = tasksDirectory.appendingPathComponent(fileName)
NSWorkspace.shared.selectFile(fileURL.path, inFileViewerRootedAtPath: tasksDirectory.path)
}) {
Label("查看持久化文件", systemImage: "doc.text.magnifyingglass")
}
.buttonStyle(.borderedProminent)
.tint(.blue)
.controlSize(.regular)
#endif
if case .completed = task.status, task.productId != "APRO" {
CommandLineInstallButton(
task: task,
showCommandLineInstall: $showCommandLineInstall,
showCopiedAlert: $showCopiedAlert
)
}
actionButtons
}
if isPackageListExpanded {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 8) {
ForEach(task.dependenciesToDownload, id: \.sapCode) { product in
ProductRow(
product: product,
isCurrentProduct: task.currentPackage?.id == product.packages.first?.id,
expandedProducts: $expandedProducts
)
}
}
}
.frame(maxHeight: 200)
}
}
}
}
private struct CommandLineInstallButton: View {
let task: NewDownloadTask
@Binding var showCommandLineInstall: Bool
@Binding var showCopiedAlert: Bool
var body: some View {
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)
}
}
}
struct ProductRow: View {
@ObservedObject var dependencies: DependenciesToDownload
@ObservedObject var product: DependenciesToDownload
let isCurrentProduct: Bool
@Binding var expandedProducts: Set<String>

View File

@@ -20,7 +20,7 @@ struct ShouldExistsSetUpView: View {
var body: some View {
VStack(spacing: 20) {
HeaderView()
SetupAlertHeaderView()
MessageView()
ButtonsView(
isDownloading: $isDownloading,
@@ -47,17 +47,19 @@ struct ShouldExistsSetUpView: View {
}
}
private struct HeaderView: View {
private struct SetupAlertHeaderView: View {
var body: some View {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 64))
.foregroundColor(.orange)
.padding(.bottom, 5)
.frame(alignment: .bottomTrailing)
VStack {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 64))
.foregroundColor(.orange)
.padding(.bottom, 5)
.frame(alignment: .bottomTrailing)
Text("未检测到 Adobe CC 组件")
.font(.system(size: 24))
.bold()
Text("未检测到 Adobe CC 组件")
.font(.system(size: 24))
.bold()
}
}
}
@@ -176,7 +178,7 @@ private struct ButtonsView: View {
isCancelled = false
Task {
do {
try await networkManager.downloadUtils.downloadX1a0HeCCPackages(
try await globalNewDownloadUtils.downloadX1a0HeCCPackages(
progressHandler: { progress, status in
Task { @MainActor in
downloadProgress = progress

View File

@@ -21,7 +21,6 @@ private enum VersionPickerConstants {
}
struct VersionPickerView: View {
@EnvironmentObject private var networkManager: NetworkManager
@Environment(\.dismiss) private var dismiss
@StorageValue(\.defaultLanguage) private var defaultLanguage
@StorageValue(\.downloadAppleSilicon) private var downloadAppleSilicon
@@ -37,7 +36,7 @@ struct VersionPickerView: View {
var body: some View {
VStack(spacing: 0) {
HeaderView(product: product, downloadAppleSilicon: downloadAppleSilicon)
VersionPickerHeaderView(product: product, downloadAppleSilicon: downloadAppleSilicon)
VersionListView(
product: product,
expandedVersions: $expandedVersions,
@@ -49,7 +48,7 @@ struct VersionPickerView: View {
}
}
private struct HeaderView: View {
private struct VersionPickerHeaderView: View {
let product: Product
let downloadAppleSilicon: Bool
@Environment(\.dismiss) private var dismiss
@@ -160,7 +159,7 @@ private struct VersionRow: View {
private var existingPath: URL? {
globalNetworkManager.isVersionDownloaded(
product: product,
productId: product.id,
version: version,
language: defaultLanguage
)

View File

@@ -11,7 +11,6 @@
},
"(可能导致处理 Setup 组件失败)" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -22,7 +21,6 @@
}
},
"(将导致无法使用安装功能)" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -157,8 +155,12 @@
},
"Adobe Downloader %@" : {
},
"Adobe Downloader 完全免费: https://github.com/X1a0He/Adobe-Downloader" : {
},
"Adobe Downloader 完全开源免费: https://github.com/X1a0He/Adobe-Downloader" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -288,6 +290,7 @@
},
"Debug 模式" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -422,6 +425,7 @@
},
"Setup 组件安装成功" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -769,6 +773,9 @@
}
}
},
"产品未找到" : {
"comment" : "Product not found"
},
"仅下载" : {
"localizations" : {
"en" : {
@@ -1385,6 +1392,7 @@
}
},
"将执行的命令:" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1425,6 +1433,7 @@
}
},
"展开全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1445,7 +1454,6 @@
}
},
"已处理" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1456,7 +1464,6 @@
}
},
"已备份" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1662,6 +1669,7 @@
}
},
"折叠全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -2099,9 +2107,6 @@
}
}
}
},
"查看持久化文件" : {
},
"检查中" : {
"localizations" : {