Skip to content

Commit

Permalink
fix: 📁 Download folders (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbmorley authored Jul 6, 2024
1 parent 87eea6f commit 72b0f5a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Reconnect/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension String {
return hasSuffix(.windowsPathSeparator)
}

var windowsLastPathComponent: String {
var lastWindowsPathComponent: String {
return windowsPathComponents.last ?? ""
}

Expand Down
6 changes: 6 additions & 0 deletions Reconnect/Extensions/URL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ extension URL {
static let about = URL(string: "x-reconnect://about")!
static let browser = URL(string: "x-reconnect://browser")!

func appendingPathComponents(_ pathComponents: [String]) -> URL {
return pathComponents.reduce(self) { url, pathComponent in
return url.appendingPathComponent(pathComponent)
}
}

}
149 changes: 85 additions & 64 deletions Reconnect/Model/BrowserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ class BrowserModel {
return name(for: path)
}

var nextItems: [NavigationStack.Item] {
return navigationStack.nextItems
}

var path: String? {
return navigationStack.path
}

var previousItems: [NavigationStack.Item] {
return navigationStack.previousItems.reversed()
}

var transfersModel = TransfersModel()

let fileServer: FileServer
Expand Down Expand Up @@ -66,7 +78,7 @@ class BrowserModel {
if path.isRoot, let drive = drives.first(where: { path.hasPrefix($0.drive) }) {
return drive.displayName
}
return path.windowsLastPathComponent
return path.lastWindowsPathComponent
}

func image(for path: String) -> String {
Expand Down Expand Up @@ -106,16 +118,16 @@ class BrowserModel {
}
}

var path: String? {
return navigationStack.path
}

var previousItems: [NavigationStack.Item] {
return navigationStack.previousItems.reversed()
}

var nextItems: [NavigationStack.Item] {
return navigationStack.nextItems
private func runAsync(task: @escaping () async throws -> Void) {
Task {
do {
try await task()
} catch {
await MainActor.run {
lastError = error
}
}
}
}

func canGoBack() -> Bool {
Expand All @@ -137,84 +149,93 @@ class BrowserModel {
}

func newFolder() {
guard let path = navigationStack.path else {
return
}
Task {
do {
let folderPath = path + "untitled folder"
try await fileServer.mkdir(path: folderPath)
let files = try await fileServer.dir(path: path)
.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
self.files = files
fileSelection = Set([folderPath + "\\"])
} catch {
print("Failed to create new folder with error \(error).")
lastError = error
runAsync {
guard let path = self.path else {
throw ReconnectError.invalidFilePath
}
let folderPath = path + "untitled folder"
try await self.fileServer.mkdir(path: folderPath)
let files = try await self.fileServer.dir(path: path)
.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
self.files = files
self.fileSelection = Set([folderPath + "\\"])
}
}

func delete(path: String) {
Task {
do {
if path.isWindowsDirectory {
try await fileServer.rmdir(path: path)
} else {
try await fileServer.remove(path: path)
}
update()
} catch {
print("Failed to delete item at path '\(path)' with error \(error).")
lastError = error
runAsync {
if path.isWindowsDirectory {
try await self.fileServer.rmdir(path: path)
} else {
try await self.fileServer.remove(path: path)
}
self.files.removeAll { $0.path == path }
}
}

func download(path: String) {
func download(from path: String) {
if path.isWindowsDirectory {
downloadDirectory(path: path)
} else {
downloadFile(from: path)
}
}

private func downloadFile(from path: String, to destinationURL: URL? = nil) {
Task {
let fileManager = FileManager.default
let downloadsUrl = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask)[0]

let filename = path.windowsLastPathComponent
let destinationURL = downloadsUrl.appendingPathComponent(filename)

print("Downloading file at path '\(path)' to destination path '\(destinationURL.path)'...")
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
try await self.fileServer.copyFile(fromRemotePath: path, toLocalPath: destinationURL.path) { progress, size in
try await self.fileServer.copyFile(fromRemotePath: path, toLocalPath: downloadURL.path) { progress, size in
transfer.setStatus(.active(Float(progress) / Float(size)))
return .continue
}
transfer.setStatus(.complete)
}
}
}

do {
if let directoryUrls = try? FileManager.default.contentsOfDirectory(at: downloadsUrl, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants) {
print(directoryUrls)
private func downloadDirectory(path: String) {
runAsync {
let fileManager = FileManager.default
let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
let parentPath = path.deletingLastWindowsPathComponent.ensuringTrailingWindowsPathSeparator(isPresent: true)

// Here we know we're downloading a directory, so we make sure the destination exists.
try fileManager.createDirectory(at: downloadsURL.appendingPathComponent(path.lastWindowsPathComponent),
withIntermediateDirectories: true)

// Iterate over the recursive directory listing creating directories where necessary and downloading files.
let files = try await self.fileServer.dir(path: path, recursive: true)
for file in files {
let relativePath = String(file.path.dropFirst(parentPath.count))
let destinationURL = downloadsURL.appendingPathComponents(relativePath.windowsPathComponents)
if file.isDirectory {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
} else {
self.downloadFile(from: file.path, to: destinationURL)
}
}
}
}

func upload(url: URL) {
Task {
do {
guard let path else {
throw ReconnectError.invalidFilePath
}
let destinationPath = path + url.lastPathComponent
print("Uploading file at path '\(url.path)' to destination path '\(destinationPath)'...")
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()
runAsync {
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
}
} catch {
print("Failed to upload file with error \(error).")
lastError = error
transfer.setStatus(.complete)
self.update()
}
}
}
Expand Down
23 changes: 19 additions & 4 deletions Reconnect/PLP/FileServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class FileServer {
return path
}

var isDirectory: Bool {
return attributes.contains(.directory)
}

let path: String
let name: String
let size: UInt32
Expand Down Expand Up @@ -164,7 +168,7 @@ class FileServer {
}
}

private func syncQueue_dir(path: String) throws -> [DirectoryEntry] {
private func syncQueue_dir(path: String, recursive: Bool) throws -> [DirectoryEntry] {
dispatchPrecondition(condition: .onQueue(workQueue))
try syncQueue_connect()
var details = PlpDir()
Expand All @@ -174,7 +178,18 @@ class FileServer {
for i in 0..<details.count {
entries.append(DirectoryEntry(directoryPath: path, entry: details[i]))
}
return entries
guard recursive else {
return entries
}

var result: [DirectoryEntry] = []
for entry in entries {
result.append(entry)
if entry.isDirectory {
result.append(contentsOf: try syncQueue_dir(path: entry.path, recursive: true))
}
}
return result
}

func syncQueue_getExtendedAttributes(path: String) throws -> DirectoryEntry {
Expand Down Expand Up @@ -274,9 +289,9 @@ class FileServer {
name: String(cString: name))
}

func dir(path: String) async throws -> [DirectoryEntry] {
func dir(path: String, recursive: Bool = false) async throws -> [DirectoryEntry] {
return try await perform {
return try self.syncQueue_dir(path: path)
return try self.syncQueue_dir(path: path, recursive: recursive)
}
}

Expand Down
6 changes: 3 additions & 3 deletions Reconnect/Views/BrowserDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct BrowserDetailView: View {
ZStack {
Table(model.files, selection: $model.fileSelection) {
TableColumn("") { file in
if file.attributes.contains(.directory) {
if file.isDirectory {
Image("Folder16")
} else {
switch file.uid3 {
Expand Down Expand Up @@ -61,7 +61,7 @@ struct BrowserDetailView: View {
.foregroundStyle(.secondary)
}
TableColumn("Size") { file in
if file.attributes.contains(.directory) {
if file.isDirectory {
Text("--")
.foregroundStyle(.secondary)
} else {
Expand All @@ -80,7 +80,7 @@ struct BrowserDetailView: View {

Button("Download") {
for item in items {
model.download(path: item)
model.download(from: item)
}
}

Expand Down

0 comments on commit 72b0f5a

Please sign in to comment.