diff --git a/AMSMB2/AMSMB2.swift b/AMSMB2/AMSMB2.swift index 71f02e4..cf0d86b 100644 --- a/AMSMB2/AMSMB2.swift +++ b/AMSMB2/AMSMB2.swift @@ -16,7 +16,7 @@ public typealias AMSMB2 = SMB2Manager /// Implements SMB2 File operations. @objc(AMSMB2Manager) public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomReflectable, @unchecked Sendable { - public typealias SimpleCompletionHandler = (@Sendable (_ error: Error?) -> Void)? + public typealias SimpleCompletionHandler = (@Sendable (_ error: (any Error)?) -> Void)? public typealias ReadProgressHandler = (@Sendable (_ bytes: Int64, _ total: Int64) -> Bool)? public typealias WriteProgressHandler = (@Sendable (_ bytes: Int64) -> Bool)? fileprivate typealias CopyProgressHandler = (@Sendable @@ -181,7 +181,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe case user, password, timeout } - public required init(from decoder: Decoder) throws { + public required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let url = try container.decode(URL.self, forKey: .url) guard url.scheme?.lowercased() == "smb" else { @@ -203,7 +203,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe super.init() } - open func encode(to encoder: Encoder) throws { + open func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(url, forKey: .url) try container.encode(_domain, forKey: .domain) @@ -346,7 +346,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe */ open func listShares( enumerateHidden: Bool = false, - completionHandler: @Sendable @escaping (_ result: Result<[(name: String, comment: String)], Error>) -> Void + completionHandler: @Sendable @escaping (_ result: Result<[(name: String, comment: String)], any Error>) -> Void ) { // Connecting to Interprocess Communication share with(shareName: "IPC$", encrypted: false, completionHandler: completionHandler) { context in @@ -377,7 +377,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe /// Only for test case coverage func _swift_listShares( enumerateHidden: Bool = false, - completionHandler: @Sendable @escaping (_ result: Result<[(name: String, comment: String)], Error>) -> Void + completionHandler: @Sendable @escaping (_ result: Result<[(name: String, comment: String)], any Error>) -> Void ) { with(shareName: "IPC$", encrypted: false, completionHandler: completionHandler) { context in try context.shareEnumSwift().map(enumerateHidden: enumerateHidden) @@ -406,7 +406,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe */ open func contentsOfDirectory( atPath path: String, recursive: Bool = false, - completionHandler: @Sendable @escaping (_ result: Result<[[URLResourceKey: Any]], Error>) -> Void + completionHandler: @Sendable @escaping (_ result: Result<[[URLResourceKey: Any]], any Error>) -> Void ) { with(completionHandler: completionHandler) { context in try self.listDirectory(context: context, path: path, recursive: recursive) @@ -443,7 +443,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe */ open func attributesOfFileSystem( forPath path: String, - completionHandler: @Sendable @escaping (_ result: Result<[FileAttributeKey: Any], Error>) -> Void + completionHandler: @Sendable @escaping (_ result: Result<[FileAttributeKey: Any], any Error>) -> Void ) { with(completionHandler: completionHandler) { context in // This exactly matches implementation of Swift Foundation. @@ -487,7 +487,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe */ open func attributesOfItem( atPath path: String, - completionHandler: @Sendable @escaping (_ result: Result<[URLResourceKey: Any], Error>) -> Void + completionHandler: @Sendable @escaping (_ result: Result<[URLResourceKey: Any], any Error>) -> Void ) { with(completionHandler: completionHandler) { context in let stat = try context.stat(path) @@ -606,7 +606,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe */ open func destinationOfSymbolicLink( atPath path: String, - completionHandler: @Sendable @escaping (_ result: Result) -> Void + completionHandler: @Sendable @escaping (_ result: Result) -> Void ) { with(completionHandler: completionHandler) { context in try context.readlink(path) @@ -834,7 +834,7 @@ public class SMB2Manager: NSObject, NSSecureCoding, Codable, NSCopying, CustomRe open func contents( atPath path: String, range: R? = Range?.none, progress: ReadProgressHandler, - completionHandler: @Sendable @escaping (_ result: Result) -> Void + completionHandler: @Sendable @escaping (_ result: Result) -> Void ) where R.Bound: FixedWidthInteger { let range = range?.int64Range ?? 0..( atPath path: String, range: R? = Range?.none - ) -> AsyncThrowingStream where R.Bound: FixedWidthInteger { + ) -> AsyncThrowingStream where R.Bound: FixedWidthInteger { let range = range?.int64Range ?? 0..( - completionHandler: @Sendable @escaping (Result) -> Void, + completionHandler: @Sendable @escaping (Result) -> Void, handler: @Sendable @escaping (_ context: SMB2Context) throws -> T ) { queue { @@ -1451,7 +1451,7 @@ extension SMB2Manager { } private func with( - shareName: String, encrypted: Bool, completionHandler: @Sendable @escaping (Result) -> Void, + shareName: String, encrypted: Bool, completionHandler: @Sendable @escaping (Result) -> Void, handler: @Sendable @escaping (_ context: SMB2Context) throws -> T ) { queue { diff --git a/AMSMB2/Context.swift b/AMSMB2/Context.swift index 5484bd9..6103916 100644 --- a/AMSMB2/Context.swift +++ b/AMSMB2/Context.swift @@ -375,7 +375,7 @@ extension SMB2Context { try withThreadSafeContext { context -> (Int32, DataType) in var cb = CBData() var resultData: DataType? - var dataHandlerError: Error? + var dataHandlerError: (any Error)? cb.dataHandler = { ptr in do { resultData = try dataHandler(self, ptr) @@ -413,7 +413,7 @@ extension SMB2Context { try withThreadSafeContext { context -> (UInt32, DataType) in var cb = CBData() var resultData: DataType? - var dataHandlerError: Error? + var dataHandlerError: (any Error)? cb.dataHandler = { ptr in do { resultData = try dataHandler(self, ptr) diff --git a/AMSMB2/Extensions.swift b/AMSMB2/Extensions.swift index 8c9687e..088afae 100644 --- a/AMSMB2/Extensions.swift +++ b/AMSMB2/Extensions.swift @@ -270,7 +270,7 @@ extension OutputStream { } } -func asyncHandler(_ continuation: CheckedContinuation) -> @Sendable (_ error: Error?) -> Void { +func asyncHandler(_ continuation: CheckedContinuation) -> @Sendable (_ error: (any Error)?) -> Void { { error in if let error = error { continuation.resume(throwing: error) @@ -280,7 +280,7 @@ func asyncHandler(_ continuation: CheckedContinuation) -> @Sendable } } -func asyncHandler(_ continuation: CheckedContinuation) -> @Sendable (Result) -> Void { +func asyncHandler(_ continuation: CheckedContinuation) -> @Sendable (Result) -> Void { { result in continuation.resume(with: result) } diff --git a/AMSMB2/ObjCCompat.swift b/AMSMB2/ObjCCompat.swift index 27e706b..f66fd54 100644 --- a/AMSMB2/ObjCCompat.swift +++ b/AMSMB2/ObjCCompat.swift @@ -20,7 +20,7 @@ extension SMB2Manager { */ @available(swift, obsoleted: 1.0) @objc(connectShareWithName:completionHandler:) - open func __connectShare(name: String, completionHandler: @Sendable @escaping (_ error: Error?) -> Void) { + open func __connectShare(name: String, completionHandler: @Sendable @escaping (_ error: (any Error)?) -> Void) { connectShare(name: name, completionHandler: completionHandler) } @@ -61,7 +61,7 @@ extension SMB2Manager { @available(swift, obsoleted: 1.0) @objc(listSharesWithCompletionHandler:) public func __listShares( - completionHandler: @Sendable @escaping (_ names: [String], _ comments: [String], _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ names: [String], _ comments: [String], _ error: (any Error)?) -> Void ) { listShares(enumerateHidden: false) { result in switch result { @@ -88,7 +88,7 @@ extension SMB2Manager { @objc(listSharesWithEnumerateHidden:completionHandler:) public func __listShares( enumerateHidden: Bool, - completionHandler: @Sendable @escaping (_ names: [String], _ comments: [String], _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ names: [String], _ comments: [String], _ error: (any Error)?) -> Void ) { listShares(enumerateHidden: enumerateHidden) { result in switch result { @@ -114,7 +114,7 @@ extension SMB2Manager { @objc(contentsOfDirectoryAtPath:recursive:completionHandler:) public func __contentsOfDirectory( atPath path: String, recursive: Bool = false, - completionHandler: @Sendable @escaping (_ contents: [[URLResourceKey: Any]]?, _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ contents: [[URLResourceKey: Any]]?, _ error: (any Error)?) -> Void ) { contentsOfDirectory( atPath: path, recursive: recursive, completionHandler: convert(completionHandler) @@ -135,7 +135,7 @@ extension SMB2Manager { @objc(attributesOfFileSystemForPath:completionHandler:) public func __attributesOfFileSystem( forPath path: String, - completionHandler: @Sendable @escaping (_ attributes: [FileAttributeKey: Any]?, _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ attributes: [FileAttributeKey: Any]?, _ error: (any Error)?) -> Void ) { attributesOfFileSystem(forPath: path, completionHandler: convert(completionHandler)) } @@ -153,7 +153,7 @@ extension SMB2Manager { @objc(attributesOfItemAtPath:completionHandler:) public func __attributesOfItem( atPath path: String, - completionHandler: @Sendable @escaping (_ file: [URLResourceKey: Any]?, _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ file: [URLResourceKey: Any]?, _ error: (any Error)?) -> Void ) { attributesOfItem(atPath: path, completionHandler: convert(completionHandler)) } @@ -172,7 +172,7 @@ extension SMB2Manager { @objc(destinationOfSymbolicLinkAtPath:completionHandler:) open func __destinationOfSymbolicLink( atPath path: String, - completionHandler: @Sendable @escaping (_ destinationPath: String?, _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ destinationPath: String?, _ error: (any Error)?) -> Void ) { destinationOfSymbolicLink(atPath: path, completionHandler: convert(completionHandler)) } @@ -200,7 +200,7 @@ extension SMB2Manager { @objc(contentsAtPath:fromOffset:toLength:progress:completionHandler:) open func __contents( atPath path: String, offset: Int64 = 0, length: Int = -1, progress: ReadProgressHandler, - completionHandler: @Sendable @escaping (_ contents: Data?, _ error: Error?) -> Void + completionHandler: @Sendable @escaping (_ contents: Data?, _ error: (any Error)?) -> Void ) { guard offset >= 0 else { let error = POSIXError(.EINVAL, description: "Invalid content offset.") @@ -239,8 +239,8 @@ extension SMB2Manager { } extension SMB2Manager { - private func convert(_ resultCompletion: @Sendable @escaping (T?, Error?) -> Void) -> ( - @Sendable (Result) -> Void + private func convert(_ resultCompletion: @Sendable @escaping (T?, (any Error)?) -> Void) -> ( + @Sendable (Result) -> Void ) { { result in switch result { diff --git a/AMSMB2Tests/SMB2ManagerTests.swift b/AMSMB2Tests/SMB2ManagerTests.swift index 29a717e..f1e82d5 100644 --- a/AMSMB2Tests/SMB2ManagerTests.swift +++ b/AMSMB2Tests/SMB2ManagerTests.swift @@ -386,6 +386,59 @@ class SMB2ManagerTests: XCTestCase { XCTAssert(outputStream.streamStatus == .closed) XCTAssert(FileManager.default.contentsEqual(atPath: url.path, andPath: dlURL.path)) } + + func testSimultaneousUpload() async throws { + let redownload = false + let fileNums = 5 + let files = (1...fileNums).map { "uploadsimtest\($0).dat" } + let urls = (1...fileNums).map { + let size: Int = random(max: 0xf00000) + print(#function, "test size \($0):", size) + return self.dummyFile(size: size) + } + + let smb = SMB2Manager(url: server, credential: credential)! + try await smb.connectShare(name: share, encrypted: encrypted) + + addTeardownBlock { + try? urls.forEach(FileManager.default.removeItem(at:)) + try? urls + .map { $0.appendingPathExtension("download") } + .forEach(FileManager.default.removeItem(at:)) + await withTaskGroup(of: Void.self) { group in + for file in files { + group.addTask{ + try? await smb.removeFile(atPath: file) + } + } + await group.waitForAll() + } + for file in files { + try? await smb.removeFile(atPath: file) + } + } + + try await withThrowingTaskGroup(of: Void.self) { group in + for (file, url) in zip(files, urls) { + group.addTask{ + try await smb.uploadItem(at: url, toPath: file, progress: nil) + } + } + + try await group.waitForAll() + } + + guard redownload else { return } + try await withThrowingTaskGroup(of: Void.self) { group in + for (file, url) in zip(files, urls) { + group.addTask{ + try await smb.downloadItem(atPath: file, to: url.appendingPathExtension("download"), progress: nil) + } + } + + try await group.waitForAll() + } + } func testTruncate() async throws { let smb = SMB2Manager(url: server, credential: credential)!