From 9dfe864677b1f32a81f64bcd5d9245ae5aa93740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Fri, 26 Dec 2025 15:41:22 +0100 Subject: [PATCH] Fix outgoing transfer to Windows --- .../Connection/InboundNearbyConnection.swift | 2 +- NearbyShare/Connection/NearbyConnection.swift | 4 +-- .../Connection/NearbyConnectionManager.swift | 2 +- .../Connection/OutboundNearbyConnection.swift | 27 +++++++++++++++---- NearbyShare/Model/DataModel.swift | 2 +- QuickDrop/ViewModel/ReceiveModel.swift | 20 +++++++------- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index 4e3551f..db7e103 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -547,7 +547,7 @@ class InboundNearbyConnection: NearbyConnection { TrustStore.shared.addTrusted(certificate: peerCertificate, device: remoteDeviceInfo) } - DispatchQueue.global(qos: .utility).async { + NearbyConnection.dispatchQueue.async { if accepted { self.acceptTransfer() } else { diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index a68e28d..d93fe25 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -17,7 +17,7 @@ import LUI class NearbyConnection { private let maxFrameLength = 5 * 1024 * 1024 - private static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) + static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) let id: String let connection: NWConnection @@ -118,7 +118,7 @@ class NearbyConnection { func connectionClosedByPeer(onError: @escaping () -> Void) { - NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { + NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 1) { // If already closed, ignore the error, as file transfer has been processed if !self.connectionClosed { diff --git a/NearbyShare/Connection/NearbyConnectionManager.swift b/NearbyShare/Connection/NearbyConnectionManager.swift index 492328a..2dfd537 100644 --- a/NearbyShare/Connection/NearbyConnectionManager.swift +++ b/NearbyShare/Connection/NearbyConnectionManager.swift @@ -276,7 +276,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb if !connection.wasUserRejected { inboundAppDelegates.forEach { delegate in - delegate.connectionWasTerminated(connectionID: connection.id, from: connection.remoteDeviceInfo ?? RemoteDeviceInfo(name: "UnknownDevice".localized(), type: .unknown), savedFiles: savedFiles, wasPlainTextTransfer: connection.isPlainTextTransfer, error: error) + delegate.connectionWasTerminated(connectionID: connection.id, from: connection.remoteDeviceInfo, savedFiles: savedFiles, wasPlainTextTransfer: connection.isPlainTextTransfer, error: error) } } } diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index a71c1ac..b32df01 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -347,6 +347,7 @@ class OutboundNearbyConnection: NearbyConnection { meta.textTitle = textToSend meta.size = Int64(textToSend.utf8.count) meta.payloadID = textPayloadID + meta.id = Int64.random(in: Int64.min ... Int64.max) introduction.v1.introduction.textMetadata.append(meta) } else if hasURL() { @@ -355,6 +356,7 @@ class OutboundNearbyConnection: NearbyConnection { meta.textTitle = urlsToSend[0].host ?? "URL" meta.size = Int64(urlsToSend[0].absoluteString.utf8.count) meta.payloadID = textPayloadID + meta.id = Int64.random(in: Int64.min ... Int64.max) introduction.v1.introduction.textMetadata.append(meta) } else { for url in urlsToSend { @@ -382,12 +384,15 @@ class OutboundNearbyConnection: NearbyConnection { } else { meta.type = .unknown } + meta.payloadID = Int64.random(in: Int64.min ... Int64.max) + meta.id = Int64.random(in: Int64.min ... Int64.max) + try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0)) introduction.v1.introduction.fileMetadata.append(meta) totalBytesToSend += meta.size - log("[OutboundNearbyConnection \(self.id)] Sending file with \(meta.size) bytes") + log("[OutboundNearbyConnection \(self.id)] Sending file with \(meta.size) bytes and mime type \(meta.mimeType) and type \(meta.type) and name \(meta.name)") } } try sendTransferSetupFrame(introduction) @@ -454,8 +459,18 @@ class OutboundNearbyConnection: NearbyConnection { } if queue.isEmpty { log("[OutboundNearbyConnection \(self.id)] Disconnecting because all files have been transferred") - try sendDisconnectionAndDisconnect() - delegate?.transferFinished(connection: self) + + // Delay disconnection to ensure EOF frame is processed at peer + NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { + do { + try self.sendDisconnectionAndDisconnect() + self.delegate?.transferFinished(connection: self) + } + catch { + log("[OutboundNearbyConnection \(self.id)] Error while sending disconnection: \(error)") + } + } + return } currentTransfer = queue.removeFirst() @@ -476,6 +491,7 @@ class OutboundNearbyConnection: NearbyConnection { transfer.payloadHeader.type = .file transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) transfer.payloadHeader.isSensitive = false + transfer.payloadHeader.fileName = OutboundNearbyConnection.sanitizeFileName(name: currentTransfer!.url.lastPathComponent) currentTransfer!.currentOffset += Int64(fileBuffer.count) var wrapper = Location_Nearby_Connections_OfflineFrame() @@ -498,11 +514,12 @@ class OutboundNearbyConnection: NearbyConnection { delegate?.updatedTransferProgress(connection: self, progress: Double(bytesTransferred) / Double(totalBytesToSend)) if currentTransfer!.currentOffset == currentTransfer!.totalBytes { - // Signal end of file (yes, all this for one bit) + + // Signal end of file var transfer = Location_Nearby_Connections_PayloadTransferFrame() transfer.packetType = .data transfer.payloadChunk.offset = currentTransfer!.currentOffset - transfer.payloadChunk.flags = 1 // <- this one here + transfer.payloadChunk.flags = 1 transfer.payloadHeader.id = currentTransfer!.payloadID transfer.payloadHeader.type = .file transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) diff --git a/NearbyShare/Model/DataModel.swift b/NearbyShare/Model/DataModel.swift index ea719cc..fc22cd8 100644 --- a/NearbyShare/Model/DataModel.swift +++ b/NearbyShare/Model/DataModel.swift @@ -33,7 +33,7 @@ protocol OutboundNearbyConnectionDelegate { public protocol InboundAppDelegate: AnyObject { func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo) func obtainedUserConsentAutomatically(transfer: TransferMetadata, device: RemoteDeviceInfo) - func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo, savedFiles: [URL], wasPlainTextTransfer: Bool, error: Error?) + func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo?, savedFiles: [URL], wasPlainTextTransfer: Bool, error: Error?) func transferProgress(connectionID: String, progress: Double) func showPlusScreen() } diff --git a/QuickDrop/ViewModel/ReceiveModel.swift b/QuickDrop/ViewModel/ReceiveModel.swift index 4de2cce..5a160fe 100644 --- a/QuickDrop/ViewModel/ReceiveModel.swift +++ b/QuickDrop/ViewModel/ReceiveModel.swift @@ -172,7 +172,7 @@ class ReceiveModel: ObservableObject, InboundAppDelegate { } - func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo, savedFiles: [URL], wasPlainTextTransfer: Bool, error: (any Error)?) { + func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo?, savedFiles: [URL], wasPlainTextTransfer: Bool, error: (any Error)?) { processes.removeValue(forKey: connectionID) @@ -193,16 +193,16 @@ class ReceiveModel: ObservableObject, InboundAppDelegate { func finish() { if let error = error { - - #if os(macOS) - DispatchQueue.main.async { - self.hideQuickDropToast() - } - #endif + if let name = device?.name { + + #if os(macOS) + DispatchQueue.main.async { + self.hideQuickDropToast() + } + #endif - controlPlusScreen(false) - - if let name = device.name { + controlPlusScreen(false) + errorVibration() ErrorAlertHandler.shared.showErrorAlert(for: name, error: error) }