From c34548d00b2b3fae005a1983b3514774f9df7813 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 19 Sep 2022 08:43:22 +0200 Subject: [PATCH 1/6] Add Params struct to InitFileTransfer function --- .../XXClient/Functions/InitFileTransfer.swift | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Sources/XXClient/Functions/InitFileTransfer.swift b/Sources/XXClient/Functions/InitFileTransfer.swift index e232ef74..f56b5680 100644 --- a/Sources/XXClient/Functions/InitFileTransfer.swift +++ b/Sources/XXClient/Functions/InitFileTransfer.swift @@ -2,28 +2,40 @@ import Bindings import XCTestDynamicOverlay public struct InitFileTransfer { - public var run: (Int, Data, Data, ReceiveFileCallback) throws -> FileTransfer + public struct Params: Equatable { + public init( + e2eId: Int, + e2eFileTransferParamsJSON: Data = GetE2EFileTransferParams.liveDefault(), + fileTransferParamsJSON: Data = GetFileTransferParams.liveDefault() + ) { + self.e2eId = e2eId + self.e2eFileTransferParamsJSON = e2eFileTransferParamsJSON + self.fileTransferParamsJSON = fileTransferParamsJSON + } + + public var e2eId: Int + public var e2eFileTransferParamsJSON: Data = GetE2EFileTransferParams.liveDefault() + public var fileTransferParamsJSON: Data = GetFileTransferParams.liveDefault() + } + + public var run: (Params, ReceiveFileCallback) throws -> FileTransfer public func callAsFunction( - e2eId: Int, - e2eFileTransferParamsJSON: Data = GetE2EFileTransferParams.liveDefault(), - fileTransferParamsJSON: Data = GetFileTransferParams.liveDefault(), + params: Params, callback: ReceiveFileCallback ) throws -> FileTransfer { - try run(e2eId, e2eFileTransferParamsJSON, fileTransferParamsJSON, callback) + try run(params, callback) } } extension InitFileTransfer { - public static let live = InitFileTransfer { - e2eId, e2eFileTransferParamsJSON, fileTransferParamsJSON, callback in - + public static let live = InitFileTransfer { params, callback in var error: NSError? let bindingsFileTransfer = BindingsInitFileTransfer( - e2eId, + params.e2eId, callback.makeBindingsReceiveFileCallback(), - e2eFileTransferParamsJSON, - fileTransferParamsJSON, + params.e2eFileTransferParamsJSON, + params.fileTransferParamsJSON, &error ) if let error = error { -- GitLab From 8d40ae015d4d420f92a0bc9f166be28219470965 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 19 Sep 2022 08:47:54 +0200 Subject: [PATCH 2/6] Fix GetFileTransferParams.unimplemented --- Sources/XXClient/Functions/GetFileTransferParams.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XXClient/Functions/GetFileTransferParams.swift b/Sources/XXClient/Functions/GetFileTransferParams.swift index 013536d9..515a3ca1 100644 --- a/Sources/XXClient/Functions/GetFileTransferParams.swift +++ b/Sources/XXClient/Functions/GetFileTransferParams.swift @@ -19,7 +19,7 @@ extension GetFileTransferParams { } extension GetFileTransferParams { - public static let unimplemented = GetCMixParams( + public static let unimplemented = GetFileTransferParams( run: XCTUnimplemented("\(Self.self)", placeholder: "unimplemented".data(using: .utf8)!) ) } -- GitLab From 5acba553db91065e0e21f9227e925c9d7ae1cedc Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 19 Sep 2022 10:09:44 +0200 Subject: [PATCH 3/6] WIP --- .../Functions/MessengerSendFile.swift | 82 +++++++++++++++++++ .../Messenger/MessengerEnvironment.swift | 9 ++ 2 files changed, 91 insertions(+) create mode 100644 Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift new file mode 100644 index 00000000..f665ace4 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift @@ -0,0 +1,82 @@ +import Foundation +import XCTestDynamicOverlay +import XXClient + +public struct MessengerSendFile { + public struct CallbackData { + public init( + transferId: Data, + result: Result<FileTransferProgressCallback.Callback, NSError> + ) { + self.transferId = transferId + self.result = result + } + + public var transferId: Data + public var result: Result<FileTransferProgressCallback.Callback, NSError> + } + + public typealias Callback = (CallbackData) -> Void + + public enum Error: Swift.Error, Equatable { + case notConnected + } + + public var run: (FileTransferSend.Params, @escaping Callback) throws -> Void + + public func callAsFunction( + params: FileTransferSend.Params, + callback: @escaping Callback + ) throws -> Void { + try run(params, callback) + } +} + +extension MessengerSendFile { + public static func live(_ env: MessengerEnvironment) -> MessengerSendFile { + MessengerSendFile { params, callback in + guard let e2e = env.e2e() else { + throw Error.notConnected + } + let fileTransfer = try env.initFileTransfer( + params: InitFileTransfer.Params( + e2eId: e2e.getId(), + e2eFileTransferParamsJSON: env.getE2EFileTransferParams(), + fileTransferParamsJSON: env.getFileTransferParams() + ), + callback: .unimplemented + ) + let semaphore = DispatchSemaphore(value: 0) + var transferId: Data! = nil + transferId = try fileTransfer.send( + params: params, + callback: FileTransferProgressCallback { result in + callback(CallbackData( + transferId: transferId, + result: result + )) + switch result { + case .failure(_): + + semaphore.signal() + case .success(let callback): + if callback.progress.error != nil { + + } + if callback.progress.completed { + semaphore.signal() + } + } + } + ) + semaphore.wait() + try? fileTransfer.closeSend(transferId: transferId) + } + } +} + +extension MessengerSendFile { + public static let unimplemented = MessengerSendFile( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift index f125c1ef..4c136806 100644 --- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift +++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift @@ -10,8 +10,11 @@ public struct MessengerEnvironment { public var fileManager: MessengerFileManager public var generateSecret: GenerateSecret public var getCMixParams: GetCMixParams + public var getE2EFileTransferParams: GetE2EFileTransferParams public var getE2EParams: GetE2EParams + public var getFileTransferParams: GetFileTransferParams public var getSingleUseParams: GetSingleUseParams + public var initFileTransfer: InitFileTransfer public var isRegisteredWithUD: IsRegisteredWithUD public var loadCMix: LoadCMix public var login: Login @@ -48,8 +51,11 @@ extension MessengerEnvironment { fileManager: .live(), generateSecret: .live, getCMixParams: .liveDefault, + getE2EFileTransferParams: .liveDefault, getE2EParams: .liveDefault, + getFileTransferParams: .liveDefault, getSingleUseParams: .liveDefault, + initFileTransfer: .live, isRegisteredWithUD: .live, loadCMix: .live, login: .live, @@ -81,8 +87,11 @@ extension MessengerEnvironment { fileManager: .unimplemented, generateSecret: .unimplemented, getCMixParams: .unimplemented, + getE2EFileTransferParams: .unimplemented, getE2EParams: .unimplemented, + getFileTransferParams: .unimplemented, getSingleUseParams: .unimplemented, + initFileTransfer: .unimplemented, isRegisteredWithUD: .unimplemented, loadCMix: .unimplemented, login: .unimplemented, -- GitLab From a688a03e24b92a9a285a01464318d9e0bd2e1b33 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 12 Oct 2022 13:26:59 +0200 Subject: [PATCH 4/6] Refactor MessengerSendFile --- .../Functions/MessengerSendFile.swift | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift index f665ace4..4732eccf 100644 --- a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift @@ -3,20 +3,7 @@ import XCTestDynamicOverlay import XXClient public struct MessengerSendFile { - public struct CallbackData { - public init( - transferId: Data, - result: Result<FileTransferProgressCallback.Callback, NSError> - ) { - self.transferId = transferId - self.result = result - } - - public var transferId: Data - public var result: Result<FileTransferProgressCallback.Callback, NSError> - } - - public typealias Callback = (CallbackData) -> Void + public typealias Callback = (Data, XXClient.Progress) -> Void public enum Error: Swift.Error, Equatable { case notConnected @@ -25,7 +12,7 @@ public struct MessengerSendFile { public var run: (FileTransferSend.Params, @escaping Callback) throws -> Void public func callAsFunction( - params: FileTransferSend.Params, + _ params: FileTransferSend.Params, callback: @escaping Callback ) throws -> Void { try run(params, callback) @@ -44,33 +31,37 @@ extension MessengerSendFile { e2eFileTransferParamsJSON: env.getE2EFileTransferParams(), fileTransferParamsJSON: env.getFileTransferParams() ), - callback: .unimplemented + callback: ReceiveFileCallback { _ in + fatalError("Bindings issue: ReceiveFileCallback called when sending file.") + } ) let semaphore = DispatchSemaphore(value: 0) - var transferId: Data! = nil + var transferId: Data! + var error: Swift.Error? transferId = try fileTransfer.send( params: params, callback: FileTransferProgressCallback { result in - callback(CallbackData( - transferId: transferId, - result: result - )) + guard let transferId else { + fatalError("Bindings issue: file transfer progress callback was called before send function returned transfer id.") + } switch result { - case .failure(_): - + case .failure(let err): + error = err semaphore.signal() - case .success(let callback): - if callback.progress.error != nil { - } - if callback.progress.completed { + case .success(let cb): + callback(transferId, cb.progress) + if cb.progress.completed || cb.progress.error != nil { semaphore.signal() } } } ) semaphore.wait() - try? fileTransfer.closeSend(transferId: transferId) + try fileTransfer.closeSend(transferId: transferId) + if let error { + throw error + } } } } -- GitLab From 73a6adff1efbb96fb1731ae788a9196f5875d6d6 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 12 Oct 2022 15:02:29 +0200 Subject: [PATCH 5/6] Add missing public initializer --- .../XXClient/Callbacks/FileTransferProgressCallback.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/XXClient/Callbacks/FileTransferProgressCallback.swift b/Sources/XXClient/Callbacks/FileTransferProgressCallback.swift index faf5a700..9c77d7af 100644 --- a/Sources/XXClient/Callbacks/FileTransferProgressCallback.swift +++ b/Sources/XXClient/Callbacks/FileTransferProgressCallback.swift @@ -3,6 +3,14 @@ import XCTestDynamicOverlay public struct FileTransferProgressCallback { public struct Callback { + public init( + progress: Progress, + partTracker: FilePartTracker + ) { + self.progress = progress + self.partTracker = partTracker + } + public var progress: Progress public var partTracker: FilePartTracker } -- GitLab From 8e15bc028e5e99019f7cede33c470d7b22fe5dfe Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 12 Oct 2022 15:02:51 +0200 Subject: [PATCH 6/6] Refactor MessengerSendFile and add tests --- .../Functions/MessengerSendFile.swift | 82 ++++-- .../Functions/MessengerSendFileTests.swift | 261 ++++++++++++++++++ 2 files changed, 325 insertions(+), 18 deletions(-) create mode 100644 Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift index 4732eccf..8aca65c4 100644 --- a/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSendFile.swift @@ -3,18 +3,49 @@ import XCTestDynamicOverlay import XXClient public struct MessengerSendFile { - public typealias Callback = (Data, XXClient.Progress) -> Void + public struct Params: Equatable { + public init( + file: FileSend, + recipientId: Data, + retry: Int = 3, + callbackIntervalMS: Int = 250 + ) { + self.file = file + self.recipientId = recipientId + self.retry = retry + self.callbackIntervalMS = callbackIntervalMS + } + + public var file: FileSend + public var recipientId: Data + public var retry: Int + public var callbackIntervalMS: Int + } + + public enum CallbackInfo: Equatable { + public enum Failure: Equatable { + case error(NSError) + case progressError(String) + case close(NSError) + } + + case progress(id: Data, transmitted: Int, total: Int) + case finished(id: Data) + case failed(id: Data, Failure) + } + + public typealias Callback = (CallbackInfo) -> Void public enum Error: Swift.Error, Equatable { case notConnected } - public var run: (FileTransferSend.Params, @escaping Callback) throws -> Void + public var run: (Params, @escaping Callback) throws -> Data public func callAsFunction( - _ params: FileTransferSend.Params, + _ params: Params, callback: @escaping Callback - ) throws -> Void { + ) throws -> Data { try run(params, callback) } } @@ -35,33 +66,48 @@ extension MessengerSendFile { fatalError("Bindings issue: ReceiveFileCallback called when sending file.") } ) - let semaphore = DispatchSemaphore(value: 0) + func close(id: Data) { + do { + try fileTransfer.closeSend(transferId: id) + } catch { + callback(.failed(id: id, .close(error as NSError))) + } + } var transferId: Data! - var error: Swift.Error? transferId = try fileTransfer.send( - params: params, + params: FileTransferSend.Params( + payload: params.file, + recipientId: params.recipientId, + retry: Float(params.retry), + period: params.callbackIntervalMS + ), callback: FileTransferProgressCallback { result in guard let transferId else { fatalError("Bindings issue: file transfer progress callback was called before send function returned transfer id.") } switch result { - case .failure(let err): - error = err - semaphore.signal() + case .failure(let error): + callback(.failed(id: transferId, .error(error))) + close(id: transferId) case .success(let cb): - callback(transferId, cb.progress) - if cb.progress.completed || cb.progress.error != nil { - semaphore.signal() + if let error = cb.progress.error { + callback(.failed(id: transferId, .progressError(error))) + close(id: transferId) + } else if cb.progress.completed { + callback(.finished(id: transferId)) + close(id: transferId) + } else { + callback(.progress( + id: transferId, + transmitted: cb.progress.transmitted, + total: cb.progress.total + )) } } } ) - semaphore.wait() - try fileTransfer.closeSend(transferId: transferId) - if let error { - throw error - } + return transferId } } } diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift new file mode 100644 index 00000000..9adc15b1 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSendFileTests.swift @@ -0,0 +1,261 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerSendFileTests: XCTestCase { + func testSendFile() throws { + let e2eId = 123 + let e2eFileTransferParams = "e2eFileTransferParams".data(using: .utf8)! + let fileTransferParams = "fileTransferParams".data(using: .utf8)! + let newTransferId = "transferId".data(using: .utf8)! + + var didInitFileTransfer: [InitFileTransfer.Params] = [] + var didSendFile: [FileTransferSend.Params] = [] + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.getE2EFileTransferParams.run = { e2eFileTransferParams } + env.getFileTransferParams.run = { fileTransferParams } + env.initFileTransfer.run = { params, callback in + didInitFileTransfer.append(params) + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { params, callback in + didSendFile.append(params) + fileTransferProgressCallback = callback + return newTransferId + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + + let sendFile: MessengerSendFile = .live(env) + let params = MessengerSendFile.Params.stub + + let transferId = try sendFile(params) { info in + didReceiveCallback.append(info) + } + + XCTAssertNoDifference(transferId, newTransferId) + XCTAssertNoDifference(didInitFileTransfer, [ + .init( + e2eId: e2eId, + e2eFileTransferParamsJSON: e2eFileTransferParams, + fileTransferParamsJSON: fileTransferParams + ) + ]) + XCTAssertNoDifference(didSendFile, [ + .init( + payload: params.file, + recipientId: params.recipientId, + retry: Float(params.retry), + period: params.callbackIntervalMS + ) + ]) + + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: false, + transmitted: 1, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: false, + transmitted: 6, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + fileTransferProgressCallback.handle(.success(.init( + progress: Progress( + completed: true, + transmitted: 10, + total: 10, + error: nil + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .progress(id: transferId, transmitted: 1, total: 10), + .progress(id: transferId, transmitted: 6, total: 10), + .finished(id: transferId), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileWhenNotConnected() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { nil } + let sendFile: MessengerSendFile = .live(env) + + XCTAssertThrowsError(try sendFile(.stub) { _ in }) { error in + XCTAssertNoDifference( + error as? MessengerSendFile.Error, + MessengerSendFile.Error.notConnected + ) + } + } + + func testSendFileCallbackFailure() throws { + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + let error = NSError(domain: "test", code: 1234) + fileTransferProgressCallback.handle(.failure(error)) + + XCTAssertNoDifference(didReceiveCallback, [ + .failed(id: transferId, .error(error)), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileProgressError() throws { + var didCloseSend: [Data] = [] + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + didCloseSend.append(id) + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + let error = "something went wrong" + fileTransferProgressCallback.handle(.success(.init( + progress: .init( + completed: false, + transmitted: 0, + total: 0, + error: error + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .failed(id: transferId, .progressError(error)), + ]) + XCTAssertNoDifference(didCloseSend, [transferId]) + } + + func testSendFileCloseError() throws { + let closeError = NSError(domain: "test", code: 1234) + + var didReceiveCallback: [MessengerSendFile.CallbackInfo] = [] + var fileTransferProgressCallback: FileTransferProgressCallback! + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.getE2EFileTransferParams.run = { Data() } + env.getFileTransferParams.run = { Data() } + env.initFileTransfer.run = { params, callback in + var fileTransfer: FileTransfer = .unimplemented + fileTransfer.send.run = { _, callback in + fileTransferProgressCallback = callback + return "transferId".data(using: .utf8)! + } + fileTransfer.closeSend.run = { id in + throw closeError + } + return fileTransfer + } + let sendFile: MessengerSendFile = .live(env) + + let transferId = try sendFile(.stub) { info in + didReceiveCallback.append(info) + } + + fileTransferProgressCallback.handle(.success(.init( + progress: .init( + completed: true, + transmitted: 1, + total: 1, + error: nil + ), + partTracker: .unimplemented + ))) + + XCTAssertNoDifference(didReceiveCallback, [ + .finished(id: transferId), + .failed(id: transferId, .close(closeError)), + ]) + } +} + +private extension MessengerSendFile.Params { + static let stub = MessengerSendFile.Params( + file: FileSend( + name: "file-name", + type: "file-type", + preview: "file-preview".data(using: .utf8)!, + contents: "file-contents".data(using: .utf8)! + ), + recipientId: "recipient-id".data(using: .utf8)!, + retry: 123, + callbackIntervalMS: 321 + ) +} -- GitLab