From 1c4963a3bcea81d4b62c685e742d04ea48aff03f Mon Sep 17 00:00:00 2001 From: Jason Morley Date: Fri, 13 Dec 2024 17:54:58 -1000 Subject: [PATCH] fix: Refresh the browser when uploads complete (#163) This changes makes `TransfersModel.upload` and `TransfersModel.download` async to allow callers to await upload and download operations. --- Reconnect/Model/BrowserModel.swift | 13 +++++++++--- Reconnect/Model/Transfer.swift | 17 ++++++++++----- Reconnect/Model/TransfersModel.swift | 28 +++++++++++++++---------- Reconnect/Windows/TransfersWindow.swift | 4 +++- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/Reconnect/Model/BrowserModel.swift b/Reconnect/Model/BrowserModel.swift index 25d557d..96ca2eb 100644 --- a/Reconnect/Model/BrowserModel.swift +++ b/Reconnect/Model/BrowserModel.swift @@ -220,7 +220,9 @@ class BrowserModel { if file.path.isWindowsDirectory { downloadDirectory(path: file.path, convertFiles: convertFiles) } else { - transfersModel.download(from: file, convertFiles: convertFiles) + Task { + try? await transfersModel.download(from: file, convertFiles: convertFiles) + } } } } @@ -243,7 +245,11 @@ class BrowserModel { if file.isDirectory { try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true) } else { - self.transfersModel.download(from: file, to: destinationURL, convertFiles: convertFiles) + Task { + try? await self.transfersModel.download(from: file, + to: destinationURL, + convertFiles: convertFiles) + } } } } @@ -255,7 +261,8 @@ class BrowserModel { guard let path = self.path else { throw ReconnectError.invalidFilePath } - self.transfersModel.upload(from: url, to: path + url.lastPathComponent) + try? await self.transfersModel.upload(from: url, to: path + url.lastPathComponent) + self.refresh() } } diff --git a/Reconnect/Model/Transfer.swift b/Reconnect/Model/Transfer.swift index 8d3b64d..b3251ea 100644 --- a/Reconnect/Model/Transfer.swift +++ b/Reconnect/Model/Transfer.swift @@ -73,7 +73,8 @@ class Transfer: Identifiable { let id = UUID() let item: FileReference - + let action: (Transfer) async throws -> Void + var status: Status private var task: Task? = nil @@ -89,18 +90,24 @@ class Transfer: Identifiable { } } - init(item: FileReference, status: Status = .waiting, action: @escaping ((Transfer) async throws -> Void) = { _ in }) { + init(item: FileReference, + status: Status = .waiting, + action: @escaping ((Transfer) async throws -> Void) = { _ in }) { self.item = item self.status = status - let task = Task { + self.action = action + } + + func run() async throws { + self.task = Task { do { - try await action(self) + return try await action(self) } catch { print("Failed with error \(error).") self.setStatus(.failed(error)) } } - self.task = task + await task?.value } func setStatus(_ status: Status) { diff --git a/Reconnect/Model/TransfersModel.swift b/Reconnect/Model/TransfersModel.swift index f8c7c8d..bc6f597 100644 --- a/Reconnect/Model/TransfersModel.swift +++ b/Reconnect/Model/TransfersModel.swift @@ -39,20 +39,22 @@ class TransfersModel { init() { } - func download(from source: FileServer.DirectoryEntry, to destinationURL: URL? = nil, convertFiles: Bool) { + func download(from source: FileServer.DirectoryEntry, + to destinationURL: URL? = nil, + convertFiles: Bool) async throws { let fileManager = FileManager.default let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask)[0] let filename = source.path.lastWindowsPathComponent - let downloadURL = destinationURL ?? downloadsURL.appendingPathComponent(filename) - print("Downloading file at path '\(source.path)' to destination path '\(downloadURL.path)'...") + let destinationURL = destinationURL ?? downloadsURL.appendingPathComponent(filename) + print("Downloading file at path '\(source.path)' to destination path '\(destinationURL.path)'...") - transfers.append(Transfer(item: .remote(source)) { transfer in + let download = Transfer(item: .remote(source)) { transfer in // Get the file information. let directoryEntry = try await self.fileServer.getExtendedAttributes(path: source.path) // Perform the file copy. - try await self.fileServer.copyFile(fromRemotePath: source.path, toLocalPath: downloadURL.path) { progress, size in + try await self.fileServer.copyFile(fromRemotePath: source.path, toLocalPath: destinationURL.path) { progress, size in transfer.setStatus(.active(progress, size)) return transfer.isCancelled ? .cancel : .continue } @@ -60,10 +62,10 @@ class TransfersModel { // Convert known types. // N.B. This would be better implemented as a user-configurable and extensible pipeline, but this is a // reasonable point to hook an initial implementation. - var urls: [URL] = [downloadURL] + var urls: [URL] = [destinationURL] if convertFiles { if directoryEntry.fileType == .mbm || directoryEntry.pathExtension.lowercased() == "mbm" { - urls = try PsiLuaEnv().convertMultiBitmap(at: downloadURL, removeSource: true) + urls = try PsiLuaEnv().convertMultiBitmap(at: destinationURL, removeSource: true) } } @@ -75,18 +77,22 @@ class TransfersModel { // Mark the transfer as complete. transfer.setStatus(.complete(details.first)) - }) + } + transfers.append(download) + try await download.run() } - func upload(from sourceURL: URL, to destinationPath: String) { + func upload(from sourceURL: URL, to destinationPath: String) async throws { print("Uploading file at path '\(sourceURL.path)' to destination path '\(destinationPath)'...") - transfers.append(Transfer(item: .local(sourceURL)) { transfer in + let upload = Transfer(item: .local(sourceURL)) { transfer in try await self.fileServer.copyFile(fromLocalPath: sourceURL.path, toRemotePath: destinationPath) { progress, size in transfer.setStatus(.active(progress, size)) return transfer.isCancelled ? .cancel : .continue } transfer.setStatus(.complete(nil)) - }) + } + transfers.append(upload) + try await upload.run() } func clear() { diff --git a/Reconnect/Windows/TransfersWindow.swift b/Reconnect/Windows/TransfersWindow.swift index 876b49d..c9732ee 100644 --- a/Reconnect/Windows/TransfersWindow.swift +++ b/Reconnect/Windows/TransfersWindow.swift @@ -38,7 +38,9 @@ struct TransfersWindow: Scene { return } let filename = installerURL.lastPathComponent - transfersModel.upload(from: installerURL, to: "C:".appendingWindowsPathComponent(filename)) + Task { + try? await transfersModel.upload(from: installerURL, to: "C:".appendingWindowsPathComponent(filename)) + } } .handlesExternalEvents(preferring: [.install], allowing: []) }