aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-01-14 19:52:32 -0800
committerDustin Mierau <dustin@mierau.me>2024-01-14 19:52:32 -0800
commitfffb072dcd736a061186f44511d2a10cb41e8b42 (patch)
treebd856778e6cd9710d20d2889b904ff3ba5a33404 /Hotline
parentff7445f1c273a79d0da70845073b58e30680c778 (diff)
Add support for downloading files with resource forks! Also properly set HFS creator code and type on downloaded files when specified by the server.
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Hotline/HotlineFileClient.swift110
-rw-r--r--Hotline/Models/Hotline.swift37
-rw-r--r--Hotline/Models/TransferInfo.swift1
-rw-r--r--Hotline/macOS/FilesView.swift3
4 files changed, 136 insertions, 15 deletions
diff --git a/Hotline/Hotline/HotlineFileClient.swift b/Hotline/Hotline/HotlineFileClient.swift
index 5b2d05a..8202a61 100644
--- a/Hotline/Hotline/HotlineFileClient.swift
+++ b/Hotline/Hotline/HotlineFileClient.swift
@@ -1,5 +1,6 @@
import Foundation
import Network
+import UniformTypeIdentifiers
enum HotlineFileClientError: Error {
case failedToConnect
@@ -39,7 +40,8 @@ enum HotlineFileTransferStage: Int {
case fileForkHeader = 2
case fileInfoFork = 3
case fileDataFork = 4
- case fileUnsupportedFork = 5
+ case fileResourceFork = 5
+ case fileUnsupportedFork = 6
}
struct HotlineFileHeader {
@@ -186,6 +188,7 @@ class HotlineFileClient {
private var transferStage: HotlineFileTransferStage = .fileHeader
private var fileBytes = Data()
+ private var fileResourceBytes = Data()
private var fileHeader: HotlineFileHeader? = nil
private var fileCurrentForkHeader: HotlineFileForkHeader? = nil
@@ -225,6 +228,16 @@ class HotlineFileClient {
return
}
+ self.filePath = nil
+ self.connect()
+ }
+
+ func downloadToFile(to fileURL: URL) {
+ guard self.status == .unconnected else {
+ return
+ }
+
+ self.filePath = fileURL.path
self.connect()
}
@@ -395,7 +408,11 @@ class HotlineFileClient {
}
else if forkHeader.forkType == "MACR".fourCharCode() {
print("RESOURCE FORK!")
- self.transferStage = .fileUnsupportedFork
+ self.fileResourceBytes = Data()
+// if self.prepareResourceFile() {
+// print("BOOYAH")
+// }
+ self.transferStage = .fileResourceFork
}
else {
print("UNKNOWN FORK??")
@@ -418,6 +435,7 @@ class HotlineFileClient {
print("INFO FORK STUFF:", infoFork)
+ // Create file in Downloads folder if we don't have a destination already.
if !self.prepareDownloadFile(name: infoFork.name) {
print("FAILED TO CREATE FILE ON DISK")
}
@@ -458,6 +476,37 @@ class HotlineFileClient {
}
}
}
+ case .fileResourceFork:
+ if self.fileBytes.count > 0 {
+// if let f = self.fileResourceHandle {
+// do {
+ var dataToWrite = self.fileBytes
+
+ if dataToWrite.count >= self.fileCurrentForkBytesLeft {
+ dataToWrite = self.fileBytes.subdata(in: 0..<self.fileCurrentForkBytesLeft)
+ self.fileBytes.removeSubrange(0..<self.fileCurrentForkBytesLeft)
+
+ self.transferStage = .fileForkHeader
+ self.fileCurrentForkBytesLeft = 0
+ self.fileCurrentForkHeader = nil
+
+ keepProcessing = true
+ }
+ else {
+ self.fileCurrentForkBytesLeft -= dataToWrite.count
+ self.fileBytes = Data()
+ }
+
+ print("WRITING TO RESOURCE FORK", dataToWrite.count)
+
+ self.fileResourceBytes.append(dataToWrite)
+// try f.write(contentsOf: dataToWrite)
+// }
+// catch {
+// print("DOWNLOAD WRITE ERROR", error)
+// }
+// }
+ }
case .fileUnsupportedFork:
if self.fileBytes.count > 0 {
var dataToWrite = self.fileBytes
@@ -486,15 +535,14 @@ class HotlineFileClient {
return
}
else {
- print("FILE COMPLETE")
- if let h = self.fileHandle {
- try? h.close()
- self.fileHandle = nil
+ self.invalidate()
+
+ if self.fileResourceBytes.count > 0 {
+ let _ = self.writeResourceFork()
+ self.fileResourceBytes = Data()
}
- self.fileBytes = Data()
self.status = .completed
- self.invalidate()
if let downloadPath = self.filePath {
DispatchQueue.main.sync {
@@ -561,11 +609,28 @@ class HotlineFileClient {
// MARK: - Utility
- private func prepareDownloadFile(name: String) -> Bool {
- let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
- let filePath = folderURL.generateUniqueFilePath(filename: name)
+ private func writeResourceFork() -> Bool {
+ guard let filePath = self.filePath else {
+ return false
+ }
+
+ var resolvedFileURL = URL(filePath: filePath)
+ resolvedFileURL.resolveSymlinksInPath()
+
+ let resourceFilePath = resolvedFileURL.appendingPathComponent("..namedfork/rsrc")
+
+ do {
+ try self.fileResourceBytes.write(to: resourceFilePath)
+ }
+ catch {
+ return false
+ }
- if FileManager.default.createFile(atPath: filePath, contents: nil) {
+ return true
+ }
+
+ private func prepareDownloadFile(name: String) -> Bool {
+ if let filePath = self.filePath {
if let h = FileHandle(forWritingAtPath: filePath) {
self.filePath = filePath
self.fileHandle = h
@@ -573,6 +638,27 @@ class HotlineFileClient {
return true
}
}
+ else {
+ let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
+ let filePath = folderURL.generateUniqueFilePath(filename: name)
+
+ var fileAttributes: [FileAttributeKey : Any] = [:]
+ if let creatorCode = self.fileInfoFork?.creator {
+ fileAttributes[.hfsCreatorCode] = creatorCode as NSNumber
+ }
+ if let typeCode = self.fileInfoFork?.type {
+ fileAttributes[.hfsTypeCode] = typeCode as NSNumber
+ }
+
+ if FileManager.default.createFile(atPath: filePath, contents: nil, attributes: fileAttributes) {
+ if let h = FileHandle(forWritingAtPath: filePath) {
+ self.filePath = filePath
+ self.fileHandle = h
+ self.fileProgress?.fileURL = URL(filePath: filePath).resolvingSymlinksInPath()
+ return true
+ }
+ }
+ }
return false
}
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift
index f15bbb6..814ade5 100644
--- a/Hotline/Models/Hotline.swift
+++ b/Hotline/Models/Hotline.swift
@@ -473,6 +473,42 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate {
}
}
+ @MainActor func downloadFileTo(url fileURL: URL, fileName: String, path: [String], progress progressCallback: ((TransferInfo, Double) -> Void)? = nil, complete callback: ((TransferInfo, URL) -> Void)? = nil) {
+ var fullPath: [String] = []
+ if path.count > 1 {
+ fullPath = Array(path[0..<path.count-1])
+ }
+
+ self.client.sendDownloadFile(name: fileName, path: fullPath) { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in
+ print("GOT DOWNLOAD REPLY:")
+ print("\tSUCCESS?", success)
+ print("\tTRANSFER SIZE: \(downloadTransferSize.debugDescription)")
+ print("\tFILE SIZE: \(downloadFileSize.debugDescription)")
+ print("\tREFERENCE NUM: \(downloadReferenceNumber.debugDescription)")
+ print("\tWAITING COUNT: \(downloadWaitingCount.debugDescription)")
+
+ if
+ let self = self,
+ let address = self.server?.address,
+ let port = self.server?.port,
+ let referenceNumber = downloadReferenceNumber,
+ let transferSize = downloadTransferSize {
+
+ print("DOWNLOADING TO MEMORY")
+ let fileClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .file)
+ fileClient.delegate = self
+ self.downloads.append(fileClient)
+
+ let transfer = TransferInfo(id: referenceNumber, title: fileName, size: UInt(transferSize))
+ transfer.downloadCallback = callback
+ transfer.progressCallback = progressCallback
+ self.transfers.append(transfer)
+
+ fileClient.downloadToFile(to: fileURL)
+ }
+ }
+ }
+
@MainActor func previewFile(_ fileName: String, path: [String], complete callback: ((PreviewFileInfo?) -> Void)? = nil) {
var fullPath: [String] = []
if path.count > 1 {
@@ -748,6 +784,7 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate {
if let transfer = self.transfers.first(where: { $0.id == reference }) {
transfer.progress = progress
transfer.timeRemaining = timeRemaining
+ transfer.progressCallback?(transfer, progress)
}
case .failed(_):
if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) {
diff --git a/Hotline/Models/TransferInfo.swift b/Hotline/Models/TransferInfo.swift
index 546efd3..8b8d97a 100644
--- a/Hotline/Models/TransferInfo.swift
+++ b/Hotline/Models/TransferInfo.swift
@@ -14,6 +14,7 @@ class TransferInfo: Identifiable, Equatable, Hashable {
// For file based transfers (i.e. not previews)
var fileURL: URL? = nil
+ var progressCallback: ((TransferInfo, Double) -> Void)? = nil
var downloadCallback: ((TransferInfo, URL) -> Void)? = nil
var previewCallback: ((TransferInfo, Data) -> Void)? = nil
diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift
index b9f15db..397feb3 100644
--- a/Hotline/macOS/FilesView.swift
+++ b/Hotline/macOS/FilesView.swift
@@ -42,9 +42,6 @@ struct FileView: View {
}
else {
FileIconView(filename: file.name)
-// .resizable()
-// .aspectRatio(contentMode: .fit)
-// .scaledToFill()
.frame(width: 16, height: 16)
}
}