fix: resolve critical download bugs and configuration issues

- proxy: Prevent SOCKS5 proxy type from being silently converted to HTTP.
- core: Remove DispatchGroup from MediaDownloadEngine to fix a hang caused by unclosed output pipes when child processes inherit file descriptors.
- settings: Fix keychain primer logic to prevent wiping extension credentials on debug builds.
- ui: Resolve a batch rename collision in AddDownloadsView by properly checking new filenames against the current batch sequence.
This commit is contained in:
NimBold
2026-06-10 23:36:16 +03:30
parent 2a5452b7c6
commit 7e2e0eeb77
3 changed files with 12 additions and 26 deletions
+1 -1
View File
@@ -701,7 +701,7 @@ struct AddDownloadsView: View {
var count = 1 var count = 1
let base = URL(fileURLWithPath: newName).deletingPathExtension().lastPathComponent let base = URL(fileURLWithPath: newName).deletingPathExtension().lastPathComponent
let ext = URL(fileURLWithPath: newName).pathExtension let ext = URL(fileURLWithPath: newName).pathExtension
while controller.downloads.contains(where: { $0.destinationDirectory == destURL && $0.fileName == newName }) || FileManager.default.fileExists(atPath: destURL.appendingPathComponent(newName).path) { while controller.downloads.contains(where: { $0.destinationDirectory == destURL && $0.fileName == newName }) || FileManager.default.fileExists(atPath: destURL.appendingPathComponent(newName).path) || finalDownloads.prefix(upTo: index).contains(where: { (overrideDirectory ?? $0.defaultDirectory) == destURL && $0.fileName == newName }) {
newName = "\(base) (\(count))" + (ext.isEmpty ? "" : ".\(ext)") newName = "\(base) (\(count))" + (ext.isEmpty ? "" : ".\(ext)")
count += 1 count += 1
} }
+5 -11
View File
@@ -93,9 +93,6 @@ struct ProxySettings: Codable, Equatable, Sendable {
var copy = self var copy = self
copy.host = copy.host.trimmingCharacters(in: .whitespacesAndNewlines) copy.host = copy.host.trimmingCharacters(in: .whitespacesAndNewlines)
copy.port = min(max(copy.port, 1), 65_535) copy.port = min(max(copy.port, 1), 65_535)
if copy.type != .http {
copy.type = .http
}
return copy return copy
} }
@@ -301,16 +298,13 @@ final class AppSettings: ObservableObject {
} }
if granted { if granted {
if let token = KeychainCredentialStore.extensionToken() {
extensionPairingToken = token
} else {
extensionPairingToken = Self.generateSecureToken()
}
if needsPrimer { if needsPrimer {
showKeychainPrimer = true showKeychainPrimer = true
extensionPairingToken = ""
} else {
if let token = KeychainCredentialStore.extensionToken() {
extensionPairingToken = token
} else {
extensionPairingToken = Self.generateSecureToken()
// The didSet of extensionPairingToken will handle setting it in the keychain since isKeychainAccessGranted is true.
}
} }
} else { } else {
extensionPairingToken = "" extensionPairingToken = ""
+5 -13
View File
@@ -100,16 +100,10 @@ final class MediaDownloadEngine: @unchecked Sendable {
messageUpdate: messageUpdate messageUpdate: messageUpdate
) )
let group = DispatchGroup()
group.enter() // output
group.enter() // error
group.enter() // process
outputPipe.fileHandleForReading.readabilityHandler = { handle in outputPipe.fileHandleForReading.readabilityHandler = { handle in
let data = handle.availableData let data = handle.availableData
if data.isEmpty { if data.isEmpty {
handle.readabilityHandler = nil handle.readabilityHandler = nil
group.leave()
} else if let text = String(data: data, encoding: .utf8) { } else if let text = String(data: data, encoding: .utf8) {
outputHandler.handle(text) outputHandler.handle(text)
} }
@@ -119,7 +113,6 @@ final class MediaDownloadEngine: @unchecked Sendable {
let data = handle.availableData let data = handle.availableData
if data.isEmpty { if data.isEmpty {
handle.readabilityHandler = nil handle.readabilityHandler = nil
group.leave()
} else { } else {
errorBuffer.append(data) errorBuffer.append(data)
if let text = String(data: data, encoding: .utf8) { if let text = String(data: data, encoding: .utf8) {
@@ -128,16 +121,15 @@ final class MediaDownloadEngine: @unchecked Sendable {
} }
} }
process.terminationHandler = { _ in process.terminationHandler = { finishedProcess in
group.leave() outputPipe.fileHandleForReading.readabilityHandler = nil
} errorPipe.fileHandleForReading.readabilityHandler = nil
group.notify(queue: .global()) { if finishedProcess.terminationStatus == 0 {
if process.terminationStatus == 0 {
completionGate.complete(.success(Self.resolvedOutputURL(for: item, tracker: outputPathTracker))) completionGate.complete(.success(Self.resolvedOutputURL(for: item, tracker: outputPathTracker)))
} else { } else {
let errorString = String(data: errorBuffer.data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown Error" let errorString = String(data: errorBuffer.data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown Error"
completionGate.complete(.failure(EngineError.launchFailed(Self.cleanErrorMessage(errorString, status: process.terminationStatus)))) completionGate.complete(.failure(EngineError.launchFailed(Self.cleanErrorMessage(errorString, status: finishedProcess.terminationStatus))))
} }
} }