Skip to content

Commit

Permalink
fix: 🐇 Support concurrent file browsing and transfers (#113)
Browse files Browse the repository at this point in the history
The PLP (and plptools) architecture allows for multiple parallel file
operaitons. This change takes advantage of that back backing each window
with a separate `FileServer` instance and creating an additional
instance for long-running file transfers.
  • Loading branch information
jbmorley authored Jul 11, 2024
1 parent 08cffdc commit b2791a9
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 73 deletions.
68 changes: 5 additions & 63 deletions Reconnect/Model/BrowserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class BrowserModel {

var transfersModel = TransfersModel()

let fileServer: FileServer
let fileServer = FileServer()

var drives: [FileServer.DriveInfo] = []
var files: [FileServer.DirectoryEntry] = []
Expand All @@ -68,8 +68,7 @@ class BrowserModel {

private var navigationStack = NavigationStack()

init(fileServer: FileServer) {
self.fileServer = fileServer
init() {
}

func start() async {
Expand Down Expand Up @@ -192,55 +191,7 @@ class BrowserModel {
if path.isWindowsDirectory {
downloadDirectory(path: path, convertFiles: convertFiles)
} else {
downloadFile(from: path, convertFiles: convertFiles)
}
}
}

private func downloadFile(from path: String, to destinationURL: URL? = nil, convertFiles: Bool) {
Task {
let fileManager = FileManager.default
let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
let filename = path.lastWindowsPathComponent
let downloadURL = destinationURL ?? downloadsURL.appendingPathComponent(filename)
print("Downloading file at path '\(path)' to destination path '\(downloadURL.path)'...")
transfersModel.add(filename) { transfer in

// Get the file information.
let directoryEntry = try await self.fileServer.getExtendedAttributes(path: path)

// Perform the file copy.
try await self.fileServer.copyFile(fromRemotePath: path, toLocalPath: downloadURL.path) { progress, size in
transfer.setStatus(.active(Float(progress) / Float(size)))
return .continue
}

// 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.
if convertFiles {
if directoryEntry.fileType == .mbm {
let directoryURL = (downloadURL as NSURL).deletingLastPathComponent!
let basename = (downloadURL.lastPathComponent as NSString).deletingPathExtension
let bitmaps = OpoInterpreter().getMbmBitmaps(path: downloadURL.path) ?? []
for (index, bitmap) in bitmaps.enumerated() {
let identifier = if index < 1 {
basename
} else {
"\(basename) \(index)"
}
let conversionURL = directoryURL
.appendingPathComponent(identifier)
.appendingPathExtension("png")
let image = CGImage.from(bitmap: bitmap)
try CGImageWritePNG(image, to: conversionURL)
}
try fileManager.removeItem(at: downloadURL)
}
}

// Mark the transfer as complete.
transfer.setStatus(.complete)
transfersModel.download(from: path, convertFiles: convertFiles)
}
}
}
Expand All @@ -263,7 +214,7 @@ class BrowserModel {
if file.isDirectory {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
} else {
self.downloadFile(from: file.path, to: destinationURL, convertFiles: convertFiles)
self.transfersModel.download(from: file.path, to: destinationURL, convertFiles: convertFiles)
}
}
}
Expand All @@ -274,16 +225,7 @@ class BrowserModel {
guard let path = self.path else {
throw ReconnectError.invalidFilePath
}
let destinationPath = path + url.lastPathComponent
print("Uploading file at path '\(url.path)' to destination path '\(destinationPath)'...")
self.transfersModel.add(url.lastPathComponent) { transfer in
try await self.fileServer.copyFile(fromLocalPath: url.path, toRemotePath: destinationPath) { progress, size in
transfer.setStatus(.active(Float(progress) / Float(size)))
return .continue
}
transfer.setStatus(.complete)
self.update()
}
self.transfersModel.upload(from: url, to: path + url.lastPathComponent)
}
}

Expand Down
68 changes: 65 additions & 3 deletions Reconnect/Model/TransfersModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@

import SwiftUI

import OpoLua

@MainActor @Observable
class TransfersModel {

var transfers: [Transfer] = []
var selection: UUID? = nil

var isActive: Bool {
return transfers
.map { $0.isActive }
.reduce(false) { $0 || $1 }
}

var transfers: [Transfer] = []
var selection: UUID? = nil

let fileServer = FileServer()

init() {
}

Expand All @@ -42,4 +46,62 @@ class TransfersModel {
.removeAll { !$0.isActive }
}

func download(from sourcePath: String, to destinationURL: URL? = nil, convertFiles: Bool) {
let fileManager = FileManager.default
let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
let filename = sourcePath.lastWindowsPathComponent
let downloadURL = destinationURL ?? downloadsURL.appendingPathComponent(filename)
print("Downloading file at path '\(sourcePath)' to destination path '\(downloadURL.path)'...")

add(filename) { transfer in

// Get the file information.
let directoryEntry = try await self.fileServer.getExtendedAttributes(path: sourcePath)

// Perform the file copy.
try await self.fileServer.copyFile(fromRemotePath: sourcePath, toLocalPath: downloadURL.path) { progress, size in
transfer.setStatus(.active(Float(progress) / Float(size)))
return .continue
}

// 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.
if convertFiles {
if directoryEntry.fileType == .mbm {
let directoryURL = (downloadURL as NSURL).deletingLastPathComponent!
let basename = (downloadURL.lastPathComponent as NSString).deletingPathExtension
let bitmaps = OpoInterpreter().getMbmBitmaps(path: downloadURL.path) ?? []
for (index, bitmap) in bitmaps.enumerated() {
let identifier = if index < 1 {
basename
} else {
"\(basename) \(index)"
}
let conversionURL = directoryURL
.appendingPathComponent(identifier)
.appendingPathExtension("png")
let image = CGImage.from(bitmap: bitmap)
try CGImageWritePNG(image, to: conversionURL)
}
try fileManager.removeItem(at: downloadURL)
}
}

// Mark the transfer as complete.
transfer.setStatus(.complete)
}
}

func upload(from sourceURL: URL, to destinationPath: String) {
print("Uploading file at path '\(sourceURL.path)' to destination path '\(destinationPath)'...")
add(sourceURL.lastPathComponent) { transfer in
try await self.fileServer.copyFile(fromLocalPath: sourceURL.path, toRemotePath: destinationPath) { progress, size in
transfer.setStatus(.active(Float(progress) / Float(size)))
return .continue
}
transfer.setStatus(.complete)
}
}

}
2 changes: 1 addition & 1 deletion Reconnect/PLP/FileServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class FileServer {

var client = RFSVClient()

init(host: String, port: Int32) {
init(host: String = "127.0.0.1", port: Int32 = 7501) {
self.host = host
self.port = port
}
Expand Down
7 changes: 3 additions & 4 deletions Reconnect/Views/BrowserView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ import SwiftUI
struct BrowserView: View {

@Environment(ApplicationModel.self) var applicationModel

@State private var browserModel: BrowserModel

init(fileServer: FileServer) {
_browserModel = State(initialValue: BrowserModel(fileServer: fileServer))
@State private var browserModel = BrowserModel()

init() {
}

var body: some View {
Expand Down
3 changes: 1 addition & 2 deletions Reconnect/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import SwiftUI
struct ContentView: View {

var applicationModel: ApplicationModel
let fileServer = FileServer(host: "127.0.0.1", port: 7501)

var body: some View {
VStack {
BrowserView(fileServer: fileServer)
BrowserView()
}
.showsDockIcon()
}
Expand Down

0 comments on commit b2791a9

Please sign in to comment.