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
let base = URL(fileURLWithPath: newName).deletingPathExtension().lastPathComponent
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)")
count += 1
}
+5 -11
View File
@@ -93,9 +93,6 @@ struct ProxySettings: Codable, Equatable, Sendable {
var copy = self
copy.host = copy.host.trimmingCharacters(in: .whitespacesAndNewlines)
copy.port = min(max(copy.port, 1), 65_535)
if copy.type != .http {
copy.type = .http
}
return copy
}
@@ -301,16 +298,13 @@ final class AppSettings: ObservableObject {
}
if granted {
if let token = KeychainCredentialStore.extensionToken() {
extensionPairingToken = token
} else {
extensionPairingToken = Self.generateSecureToken()
}
if needsPrimer {
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 {
extensionPairingToken = ""
+6 -14
View File
@@ -100,16 +100,10 @@ final class MediaDownloadEngine: @unchecked Sendable {
messageUpdate: messageUpdate
)
let group = DispatchGroup()
group.enter() // output
group.enter() // error
group.enter() // process
outputPipe.fileHandleForReading.readabilityHandler = { handle in
let data = handle.availableData
if data.isEmpty {
handle.readabilityHandler = nil
group.leave()
} else if let text = String(data: data, encoding: .utf8) {
outputHandler.handle(text)
}
@@ -119,7 +113,6 @@ final class MediaDownloadEngine: @unchecked Sendable {
let data = handle.availableData
if data.isEmpty {
handle.readabilityHandler = nil
group.leave()
} else {
errorBuffer.append(data)
if let text = String(data: data, encoding: .utf8) {
@@ -128,16 +121,15 @@ final class MediaDownloadEngine: @unchecked Sendable {
}
}
process.terminationHandler = { _ in
group.leave()
}
group.notify(queue: .global()) {
if process.terminationStatus == 0 {
process.terminationHandler = { finishedProcess in
outputPipe.fileHandleForReading.readabilityHandler = nil
errorPipe.fileHandleForReading.readabilityHandler = nil
if finishedProcess.terminationStatus == 0 {
completionGate.complete(.success(Self.resolvedOutputURL(for: item, tracker: outputPathTracker)))
} else {
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))))
}
}