diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-01-14 19:52:32 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-01-14 19:52:32 -0800 |
| commit | fffb072dcd736a061186f44511d2a10cb41e8b42 (patch) | |
| tree | bd856778e6cd9710d20d2889b904ff3ba5a33404 /Hotline | |
| parent | ff7445f1c273a79d0da70845073b58e30680c778 (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.swift | 110 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 37 | ||||
| -rw-r--r-- | Hotline/Models/TransferInfo.swift | 1 | ||||
| -rw-r--r-- | Hotline/macOS/FilesView.swift | 3 |
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) } } |