From fb7dda88240defb2240e9d3e44e429caeb70e75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Sat, 26 Jul 2025 19:27:59 +0200 Subject: [PATCH] Refactoring --- NearbyShare/DataModel.swift | 188 ++++++++++++++++++++ NearbyShare/Helpers.swift | 24 +-- NearbyShare/InboundNearbyConnection.swift | 6 - NearbyShare/NearbyConnection.swift | 23 +-- NearbyShare/NearbyConnectionManager.swift | 192 ++------------------- NearbyShare/OutboundNearbyConnection.swift | 29 ++-- QuickDrop.xcodeproj/project.pbxproj | 4 + 7 files changed, 244 insertions(+), 222 deletions(-) create mode 100644 NearbyShare/DataModel.swift diff --git a/NearbyShare/DataModel.swift b/NearbyShare/DataModel.swift new file mode 100644 index 0000000..1d0927f --- /dev/null +++ b/NearbyShare/DataModel.swift @@ -0,0 +1,188 @@ +// +// DataModel.swift +// QuickDrop +// +// Created by Leon Böttger on 26.07.25. +// + +import Foundation +import Network + + +protocol InboundNearbyConnectionDelegate { + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) + func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) +} + + +protocol OutboundNearbyConnectionDelegate { + func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) + func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) + func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) + func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) + func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) +} + + +public protocol MainAppDelegate { + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) + func incomingTransfer(id: String, didFinishWith error: Error?) + func showFirewallAlert() + func showCopiedToClipboardAlert() + func showUnsupportedFileAlert(for: RemoteDeviceInfo?) +} + + +public protocol ShareExtensionDelegate: AnyObject { + func addDevice(device: RemoteDeviceInfo) + func removeDevice(id: String) + func connectionWasEstablished(pinCode: String) + func connectionFailed(with error: Error) + func transferAccepted() + func transferProgress(progress: Double) + func transferFinished() +} + + +public struct RemoteDeviceInfo { + public let name: String + public let type: DeviceType + public var id: String? + + init(name: String, type: DeviceType, id: String? = nil) { + self.name = name + self.type = type + self.id = id + } + + init(info: EndpointInfo) { + name = info.name + type = info.deviceType + } + + + public enum DeviceType: Int32 { + case unknown = 0 + case phone + case tablet + case computer + + public static func fromRawValue(value: Int) -> DeviceType { + switch value { + case 0: + return .unknown + case 1: + return .phone + case 2: + return .tablet + case 3: + return .computer + default: + return .unknown + } + } + } +} + + +public struct TransferMetadata { + public let files: [FileMetadata] + public let id: String + public let pinCode: String? + public let textDescription: String? + public let type: TransferType + + init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file) { + self.files = files + self.id = id + self.pinCode = pinCode + self.textDescription = textDescription + self.type = transferType + } + + public enum TransferType { + case file + case text + case url + } +} + + +public struct FileMetadata { + public let name: String + public let size: Int64 + public let mimeType: String +} + + +struct EndpointInfo { + let name: String + let deviceType: RemoteDeviceInfo.DeviceType + + + init(name: String, deviceType: RemoteDeviceInfo.DeviceType) { + self.name = name + self.deviceType = deviceType + } + + + init?(data: Data) { + guard data.count > 17 else { return nil } + let deviceNameLength = Int(data[17]) + guard data.count >= deviceNameLength + 18 else { return nil } + guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil } + let rawDeviceType = Int(data[0] & 7) >> 1 + name = deviceName + deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType) + } + + + func serialize() -> Data { + // 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits) + // Device types: unknown=0, phone=1, tablet=2, laptop=3 + var endpointInfo: [UInt8] = [UInt8(deviceType.rawValue << 1)] + // 16 bytes: unknown random bytes + for _ in 0 ... 15 { + endpointInfo.append(UInt8.random(in: 0 ... 255)) + } + // Device name in UTF-8 prefixed with 1-byte length + var nameChars = [UInt8](name.utf8) + if nameChars.count > 255 { + nameChars = [UInt8](nameChars[0 ..< 255]) + } + endpointInfo.append(UInt8(nameChars.count)) + for ch in nameChars { + endpointInfo.append(UInt8(ch)) + } + return Data(endpointInfo) + } +} + + +public enum NearbyError: Error { + case protocolError(_ message: String) + case requiredFieldMissing(_ message: String) + case ukey2 + case packetFilterError + case inputOutput + case canceled(reason: CancellationReason) + + public enum CancellationReason { + case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut + + public func localizedDescription() -> String { + switch self { + case .userRejected: + "TransferDeclined".localized() + case .userCanceled: + "TransferCanceled".localized() + case .notEnoughSpace: + "NotEnoughSpace".localized() + case .unsupportedType: + "UnsupportedType".localized() + case .timedOut: + "TransferTimedOut".localized() + } + } + } +} diff --git a/NearbyShare/Helpers.swift b/NearbyShare/Helpers.swift index 6fbc468..4ac8d26 100644 --- a/NearbyShare/Helpers.swift +++ b/NearbyShare/Helpers.swift @@ -73,6 +73,18 @@ extension String { } +public enum UserDefaultsKeys: String, CaseIterable { + case isEligibleForIap = "isEligibleForIap" + case appLaunchedBefore = "ShowedWelcomeScreen" + case plusVersion = "isPlusVersion" + case transmissionCount = "reviewRequestCountKey" + case automaticallyAcceptFiles = "automaticallyAcceptFiles" + case saveFolderBookmark = "saveFolderBookmark" + case openFinderAfterReceiving = "openFinderAfterReceiving" + case endpointID = "endpointID" +} + + public func isPlusVersion() -> Bool { return UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue) } @@ -86,15 +98,3 @@ public func isFileTransferRestricted() -> Bool { public func transmissionCount() -> Int { return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue) } - - -public enum UserDefaultsKeys: String, CaseIterable { - case isEligibleForIap = "isEligibleForIap" - case appLaunchedBefore = "ShowedWelcomeScreen" - case plusVersion = "isPlusVersion" - case transmissionCount = "reviewRequestCountKey" - case automaticallyAcceptFiles = "automaticallyAcceptFiles" - case saveFolderBookmark = "saveFolderBookmark" - case openFinderAfterReceiving = "openFinderAfterReceiving" - case endpointID = "endpointID" -} diff --git a/NearbyShare/InboundNearbyConnection.swift b/NearbyShare/InboundNearbyConnection.swift index e07198e..80fab66 100644 --- a/NearbyShare/InboundNearbyConnection.swift +++ b/NearbyShare/InboundNearbyConnection.swift @@ -499,9 +499,3 @@ class InboundNearbyConnection: NearbyConnection { } } } - - -protocol InboundNearbyConnectionDelegate { - func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) - func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) -} diff --git a/NearbyShare/NearbyConnection.swift b/NearbyShare/NearbyConnection.swift index 73332a9..06dd4ec 100644 --- a/NearbyShare/NearbyConnection.swift +++ b/NearbyShare/NearbyConnection.swift @@ -593,14 +593,17 @@ class NearbyConnection { } } } -} - -struct InternalFileInfo { - let meta: FileMetadata - let payloadID: Int64 - let destinationURL: URL - var bytesTransferred: Int64 = 0 - var fileHandle: FileHandle? - var progress: Progress? - var created: Bool = false + + + // -- MARK: - Internal Data Model + + struct InternalFileInfo { + let meta: FileMetadata + let payloadID: Int64 + let destinationURL: URL + var bytesTransferred: Int64 = 0 + var fileHandle: FileHandle? + var progress: Progress? + var created: Bool = false + } } diff --git a/NearbyShare/NearbyConnectionManager.swift b/NearbyShare/NearbyConnectionManager.swift index f916042..a5ee046 100644 --- a/NearbyShare/NearbyConnectionManager.swift +++ b/NearbyShare/NearbyConnectionManager.swift @@ -9,182 +9,6 @@ import Foundation import Network import System -public struct RemoteDeviceInfo { - public let name: String - public let type: DeviceType - public var id: String? - - init(name: String, type: DeviceType, id: String? = nil) { - self.name = name - self.type = type - self.id = id - } - - init(info: EndpointInfo) { - name = info.name - type = info.deviceType - } - - - public enum DeviceType: Int32 { - case unknown = 0 - case phone - case tablet - case computer - - public static func fromRawValue(value: Int) -> DeviceType { - switch value { - case 0: - return .unknown - case 1: - return .phone - case 2: - return .tablet - case 3: - return .computer - default: - return .unknown - } - } - } -} - - -public enum NearbyError: Error { - case protocolError(_ message: String) - case requiredFieldMissing(_ message: String) - case ukey2 - case packetFilterError - case inputOutput - case canceled(reason: CancellationReason) - - public enum CancellationReason { - case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut - - public func localizedDescription() -> String { - switch self { - case .userRejected: - "TransferDeclined".localized() - case .userCanceled: - "TransferCanceled".localized() - case .notEnoughSpace: - "NotEnoughSpace".localized() - case .unsupportedType: - "UnsupportedType".localized() - case .timedOut: - "TransferTimedOut".localized() - } - } - } -} - - -public struct TransferMetadata { - public let files: [FileMetadata] - public let id: String - public let pinCode: String? - public let textDescription: String? - public let type: TransferType - - init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file) { - self.files = files - self.id = id - self.pinCode = pinCode - self.textDescription = textDescription - self.type = transferType - } - - public enum TransferType { - case file - case text - case url - } -} - - -public struct FileMetadata { - public let name: String - public let size: Int64 - public let mimeType: String -} - - -struct FoundServiceInfo { - let service: NWBrowser.Result - var device: RemoteDeviceInfo? -} - - -struct OutgoingTransferInfo { - let service: NWBrowser.Result - let device: RemoteDeviceInfo - let connection: OutboundNearbyConnection - let delegate: ShareExtensionDelegate -} - - -struct EndpointInfo { - let name: String - let deviceType: RemoteDeviceInfo.DeviceType - - - init(name: String, deviceType: RemoteDeviceInfo.DeviceType) { - self.name = name - self.deviceType = deviceType - } - - - init?(data: Data) { - guard data.count > 17 else { return nil } - let deviceNameLength = Int(data[17]) - guard data.count >= deviceNameLength + 18 else { return nil } - guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil } - let rawDeviceType = Int(data[0] & 7) >> 1 - name = deviceName - deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType) - } - - - func serialize() -> Data { - // 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits) - // Device types: unknown=0, phone=1, tablet=2, laptop=3 - var endpointInfo: [UInt8] = [UInt8(deviceType.rawValue << 1)] - // 16 bytes: unknown random bytes - for _ in 0 ... 15 { - endpointInfo.append(UInt8.random(in: 0 ... 255)) - } - // Device name in UTF-8 prefixed with 1-byte length - var nameChars = [UInt8](name.utf8) - if nameChars.count > 255 { - nameChars = [UInt8](nameChars[0 ..< 255]) - } - endpointInfo.append(UInt8(nameChars.count)) - for ch in nameChars { - endpointInfo.append(UInt8(ch)) - } - return Data(endpointInfo) - } -} - - -public protocol MainAppDelegate { - func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) - func incomingTransfer(id: String, didFinishWith error: Error?) - func showFirewallAlert() - func showCopiedToClipboardAlert() - func showUnsupportedFileAlert(for: RemoteDeviceInfo?) -} - -public protocol ShareExtensionDelegate: AnyObject { - func addDevice(device: RemoteDeviceInfo) - func removeDevice(id: String) - func connectionWasEstablished(pinCode: String) - func connectionFailed(with error: Error) - func transferAccepted() - func transferProgress(progress: Double) - func transferFinished() -} - public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate { @@ -482,4 +306,20 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb public func getActiveConnectionsCount() -> Int { return activeConnections.count } + + + // -- MARK: - Internal Data Model + + private struct FoundServiceInfo { + let service: NWBrowser.Result + var device: RemoteDeviceInfo? + } + + + private struct OutgoingTransferInfo { + let service: NWBrowser.Result + let device: RemoteDeviceInfo + let connection: OutboundNearbyConnection + let delegate: ShareExtensionDelegate + } } diff --git a/NearbyShare/OutboundNearbyConnection.swift b/NearbyShare/OutboundNearbyConnection.swift index a97b032..db7a3f8 100644 --- a/NearbyShare/OutboundNearbyConnection.swift +++ b/NearbyShare/OutboundNearbyConnection.swift @@ -470,22 +470,15 @@ class OutboundNearbyConnection: NearbyConnection { private static func sanitizeFileName(name: String) -> String { return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression) } -} - - -private struct OutgoingFileTransfer { - let url: URL - let payloadID: Int64 - let handle: FileHandle? - let totalBytes: Int64 - var currentOffset: Int64 -} - - -protocol OutboundNearbyConnectionDelegate { - func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) - func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) - func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) - func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) - func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) + + + // -- MARK: - Internal Data Model + + private struct OutgoingFileTransfer { + let url: URL + let payloadID: Int64 + let handle: FileHandle? + let totalBytes: Int64 + var currentOffset: Int64 + } } diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 17b4a23..38ac6d7 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; EB5C36E52E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; + EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; EB6E743E2DD5C77700331466 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; @@ -185,6 +186,7 @@ EB5C36952E34EBBD0052EFD7 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/ShareViewController.xcstrings; sourceTree = ""; }; EB5C36E32E34EC0E0052EFD7 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFilesManager.swift; sourceTree = ""; }; + EB5C36E82E3546100052EFD7 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; }; EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = ""; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -322,6 +324,7 @@ 69DA9A2729E18CB500A442DA /* Protobuf */, 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */, 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */, + EB5C36E82E3546100052EFD7 /* DataModel.swift */, 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */, EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */, EB2F9BFA2DA04B67004010A2 /* Helpers.swift */, @@ -640,6 +643,7 @@ 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */, 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */, EB2B41E42DC63ACC0070FC39 /* SleepManager.swift in Sources */, + EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */, EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */, 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */, );