From 7e2e0eeb77fa3da73f8ef62e09d1d1045024082a Mon Sep 17 00:00:00 2001 From: NimBold Date: Wed, 10 Jun 2026 23:36:16 +0330 Subject: [PATCH] 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. --- Sources/Firelink/AddDownloadsView.swift | 2 +- Sources/Firelink/AppSettings.swift | 16 +++++----------- Sources/Firelink/MediaDownloadEngine.swift | 20 ++++++-------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/Sources/Firelink/AddDownloadsView.swift b/Sources/Firelink/AddDownloadsView.swift index 38d3236..704e8ff 100644 --- a/Sources/Firelink/AddDownloadsView.swift +++ b/Sources/Firelink/AddDownloadsView.swift @@ -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 } diff --git a/Sources/Firelink/AppSettings.swift b/Sources/Firelink/AppSettings.swift index 4bb0a6f..fba94df 100644 --- a/Sources/Firelink/AppSettings.swift +++ b/Sources/Firelink/AppSettings.swift @@ -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 = "" diff --git a/Sources/Firelink/MediaDownloadEngine.swift b/Sources/Firelink/MediaDownloadEngine.swift index 3266033..96051b8 100644 --- a/Sources/Firelink/MediaDownloadEngine.swift +++ b/Sources/Firelink/MediaDownloadEngine.swift @@ -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)))) } }