From 0758b84603c96fbf9bf138979b7178f06062b588 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 19 Oct 2022 12:38:01 +0200
Subject: [PATCH] Implement ReceiveFileHandler

---
 .../ReceiveFileHandler.swift                  |  98 +++++++-
 .../AppFeature/AppEnvironment+Live.swift      |   4 +-
 .../ReceiveFileHandlerTests.swift             | 214 ++++++++++++++++++
 3 files changed, 311 insertions(+), 5 deletions(-)
 create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/ReceiveFileHandler/ReceiveFileHandlerTests.swift

diff --git a/Examples/xx-messenger/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift b/Examples/xx-messenger/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift
index 92c266da..84abe3c5 100644
--- a/Examples/xx-messenger/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift
+++ b/Examples/xx-messenger/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift
@@ -1,8 +1,18 @@
+import Foundation
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct ReceiveFileHandler {
+  public struct ProgressError: Error {
+    public init(message: String) {
+      self.message = message
+    }
+
+    public var message: String
+  }
+
   public typealias OnError = (Error) -> Void
 
   public var run: (@escaping OnError) -> Cancellable
@@ -14,14 +24,94 @@ public struct ReceiveFileHandler {
 
 extension ReceiveFileHandler {
   public static func live(
-    messenger: Messenger
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    now: @escaping () -> Date
   ) -> ReceiveFileHandler {
     ReceiveFileHandler { onError in
-      messenger.registerReceiveFileCallback(.init { result in
+      func receiveFile(_ file: ReceivedFile) {
+        do {
+          let date = now()
+          try db().saveFileTransfer(XXModels.FileTransfer(
+            id: file.transferId,
+            contactId: file.senderId,
+            name: file.name,
+            type: file.type,
+            data: nil,
+            progress: 0,
+            isIncoming: true,
+            createdAt: date
+          ))
+          try db().saveMessage(XXModels.Message(
+            senderId: file.senderId,
+            recipientId: try messenger.e2e.tryGet().getContact().getId(),
+            groupId: nil,
+            date: date,
+            status: .received,
+            isUnread: false,
+            text: "",
+            fileTransferId: file.transferId
+          ))
+          try messenger.receiveFile(.init(
+            transferId: file.transferId,
+            callbackIntervalMS: 500
+          )) { info in
+            switch info {
+            case .progress(let transmitted, let total):
+              updateProgress(
+                transferId: file.transferId,
+                transmitted: transmitted,
+                total: total
+              )
+
+            case .finished(let data):
+              saveData(
+                transferId: file.transferId,
+                data: data
+              )
+
+            case .failed(.receiveError(let error)):
+              onError(error)
+
+            case .failed(.callbackError(let error)):
+              onError(error)
+
+            case .failed(.progressError(let message)):
+              onError(ProgressError(message: message))
+            }
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      func updateProgress(transferId: Data, transmitted: Int, total: Int) {
+        do {
+          if var transfer = try db().fetchFileTransfers(.init(id: [transferId])).first {
+            transfer.progress = total > 0 ? Float(transmitted) / Float(total) : 0
+            try db().saveFileTransfer(transfer)
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      func saveData(transferId: Data, data: Data) {
+        do {
+          if var transfer = try db().fetchFileTransfers(.init(id: [transferId])).first {
+            transfer.progress = 1
+            transfer.data = data
+            try db().saveFileTransfer(transfer)
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      return messenger.registerReceiveFileCallback(.init { result in
         switch result {
         case .success(let file):
-          // TODO:
-          break
+          receiveFile(file)
 
         case .failure(let error):
           onError(error)
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 62ea9c13..be768aba 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -114,7 +114,9 @@ extension AppEnvironment {
         db: dbManager.getDB
       ),
       receiveFileHandler: .live(
-        messenger: messenger
+        messenger: messenger,
+        db: dbManager.getDB,
+        now: Date.init
       ),
       backupStorage: backupStorage,
       log: .live(),
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/ReceiveFileHandler/ReceiveFileHandlerTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/ReceiveFileHandler/ReceiveFileHandlerTests.swift
new file mode 100644
index 00000000..6d54a9cf
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/ReceiveFileHandler/ReceiveFileHandlerTests.swift
@@ -0,0 +1,214 @@
+import CustomDump
+import XCTest
+import XXMessengerClient
+import XXClient
+import XXModels
+@testable import AppCore
+
+final class ReceiveFileHandlerTests: XCTestCase {
+  func testReceiveFile() {
+    let currentDate = Date(timeIntervalSince1970: 123)
+    let myContactId = "my-contact-id".data(using: .utf8)!
+    let receivedFile = ReceivedFile.stub()
+
+    var actions: [Action] = []
+    var receiveFileCallback: ReceiveFileCallback?
+    var receivingFileCallback: MessengerReceiveFile.Callback?
+
+    var messenger: Messenger = .unimplemented
+    messenger.registerReceiveFileCallback.run = { callback in
+      actions.append(.didRegisterReceiveFileCallback)
+      receiveFileCallback = callback
+      return Cancellable { actions.append(.didCancelReceiveFileCallback) }
+    }
+    messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in myContactId }
+        return contact
+      }
+      return e2e
+    }
+    messenger.receiveFile.run = { params, callback in
+      actions.append(.didReceiveFile(params))
+      receivingFileCallback = callback
+    }
+
+    var db: DBManagerGetDB = .unimplemented
+    db.run = {
+      var db: Database = .unimplemented
+      db.saveFileTransfer.run = { model in
+        actions.append(.didSaveFileTransfer(model))
+        return model
+      }
+      db.saveMessage.run = { model in
+        actions.append(.didSaveMessage(model))
+        return model
+      }
+      db.fetchFileTransfers.run = { query in
+        actions.append(.didFetchFileTransfers(query))
+        return [
+          FileTransfer(
+            id: receivedFile.transferId,
+            contactId: receivedFile.senderId,
+            name: receivedFile.name,
+            type: receivedFile.type,
+            data: nil,
+            progress: 0,
+            isIncoming: true,
+            createdAt: currentDate
+          )
+        ]
+      }
+      return db
+    }
+
+    let handler = ReceiveFileHandler.live(
+      messenger: messenger,
+      db: db,
+      now: { currentDate }
+    )
+
+    XCTAssertNoDifference(actions, [])
+
+    actions = []
+    let cancellable = handler(onError: { error in
+      actions.append(.didCatchError(error as NSError))
+    })
+
+    XCTAssertNoDifference(actions, [
+      .didRegisterReceiveFileCallback
+    ])
+
+    actions = []
+    let error = NSError(domain: "receive-file", code: 1)
+    receiveFileCallback?.handle(.failure(error))
+
+    XCTAssertNoDifference(actions, [
+      .didCatchError(error)
+    ])
+
+    actions = []
+    receiveFileCallback?.handle(.success(receivedFile))
+
+    XCTAssertNoDifference(actions, [
+      .didSaveFileTransfer(FileTransfer(
+        id: receivedFile.transferId,
+        contactId: receivedFile.senderId,
+        name: receivedFile.name,
+        type: receivedFile.type,
+        data: nil,
+        progress: 0,
+        isIncoming: true,
+        createdAt: currentDate
+      )),
+      .didSaveMessage(Message(
+        networkId: nil,
+        senderId: receivedFile.senderId,
+        recipientId: myContactId,
+        groupId: nil,
+        date: currentDate,
+        status: .received,
+        isUnread: false,
+        text: "",
+        replyMessageId: nil,
+        roundURL: nil,
+        fileTransferId: receivedFile.transferId
+      )),
+      .didReceiveFile(MessengerReceiveFile.Params(
+        transferId: receivedFile.transferId,
+        callbackIntervalMS: 500
+      )),
+    ])
+
+    actions = []
+    let receivingFileError = NSError(domain: "receiving-file", code: 2)
+    receivingFileCallback?(.failed(.receiveError(receivingFileError)))
+
+    XCTAssertNoDifference(actions, [
+      .didCatchError(receivingFileError)
+    ])
+
+    actions = []
+    let receivingFileCallbackError = NSError(domain: "receiving-file-callback", code: 3)
+    receivingFileCallback?(.failed(.callbackError(receivingFileCallbackError)))
+
+    XCTAssertNoDifference(actions, [
+      .didCatchError(receivingFileCallbackError)
+    ])
+
+    actions = []
+    let receivingFileProgressError = "receiving-file-progress"
+    receivingFileCallback?(.failed(.progressError(receivingFileProgressError)))
+
+    XCTAssertNoDifference(actions, [
+      .didCatchError(ReceiveFileHandler.ProgressError(message: receivingFileProgressError) as NSError)
+    ])
+
+    actions = []
+    receivingFileCallback?(.progress(transmitted: 1, total: 2))
+
+    XCTAssertNoDifference(actions, [
+      .didFetchFileTransfers(.init(id: [receivedFile.transferId])),
+      .didSaveFileTransfer(FileTransfer(
+        id: receivedFile.transferId,
+        contactId: receivedFile.senderId,
+        name: receivedFile.name,
+        type: receivedFile.type,
+        data: nil,
+        progress: 0.5,
+        isIncoming: true,
+        createdAt: currentDate
+      )),
+    ])
+
+    actions = []
+    let fileData = "file-data".data(using: .utf8)!
+    receivingFileCallback?(.finished(fileData))
+
+    XCTAssertNoDifference(actions, [
+      .didFetchFileTransfers(.init(id: [receivedFile.transferId])),
+      .didSaveFileTransfer(FileTransfer(
+        id: receivedFile.transferId,
+        contactId: receivedFile.senderId,
+        name: receivedFile.name,
+        type: receivedFile.type,
+        data: fileData,
+        progress: 1,
+        isIncoming: true,
+        createdAt: currentDate
+      )),
+    ])
+
+    actions = []
+    cancellable.cancel()
+
+    XCTAssertNoDifference(actions, [
+      .didCancelReceiveFileCallback
+    ])
+  }
+}
+
+private enum Action: Equatable {
+  case didRegisterReceiveFileCallback
+  case didCancelReceiveFileCallback
+  case didCatchError(NSError)
+  case didSaveFileTransfer(XXModels.FileTransfer)
+  case didSaveMessage(XXModels.Message)
+  case didReceiveFile(MessengerReceiveFile.Params)
+  case didFetchFileTransfers(XXModels.FileTransfer.Query)
+}
+
+private extension ReceivedFile {
+  static func stub() -> ReceivedFile {
+    ReceivedFile(
+      transferId: "received-file-transferId".data(using: .utf8)!,
+      senderId: "received-file-senderId".data(using: .utf8)!,
+      preview: "received-file-preview".data(using: .utf8)!,
+      name: "received-file-name",
+      type: "received-file-type",
+      size: 1234
+    )
+  }
+}
-- 
GitLab