diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-01-01 11:35:49 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-01-01 11:35:49 -0800 |
| commit | 5d2aadbc834393b532d043d6298d6a23b80c2a5c (patch) | |
| tree | dcf139b67179d5bc3ad3ed69e949a1e7c1d18177 /Hotline/Models | |
| parent | a4cbaea0b12b7dd42133b4bd14f1371f1000fcc0 (diff) | |
File downloads now working in macOS client.1.0beta4
Diffstat (limited to 'Hotline/Models')
| -rw-r--r-- | Hotline/Models/FilePreview.swift | 41 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 259 | ||||
| -rw-r--r-- | Hotline/Models/Server.swift | 32 | ||||
| -rw-r--r-- | Hotline/Models/TransferInfo.swift | 33 | ||||
| -rw-r--r-- | Hotline/Models/User.swift | 5 |
5 files changed, 321 insertions, 49 deletions
diff --git a/Hotline/Models/FilePreview.swift b/Hotline/Models/FilePreview.swift new file mode 100644 index 0000000..552ccff --- /dev/null +++ b/Hotline/Models/FilePreview.swift @@ -0,0 +1,41 @@ +import SwiftUI + +//struct FilePreview: Identifiable, Hashable { +// let id: UUID +// +// let path: [String] +// let name: String +// +// let type: String +// let creator: String +// let fileSize: UInt +// +// let isFolder: Bool +// let isUnavailable: Bool +// var expanded: Bool = false +// var children: [FileInfo]? = nil +// +// init(hotlineFile: HotlineFile) { +// self.id = UUID() +// self.path = hotlineFile.path +// self.name = hotlineFile.name +// self.type = hotlineFile.type +// self.creator = hotlineFile.creator +// self.fileSize = UInt(hotlineFile.fileSize) +// self.isFolder = hotlineFile.isFolder +// self.isUnavailable = (!self.isFolder && (self.fileSize == 0)) +// +// print(self.name, self.type, self.creator, self.isUnavailable) +// if self.isFolder { +// self.children = [] +// } +// } +// +// static func == (lhs: FileInfo, rhs: FileInfo) -> Bool { +// return lhs.id == rhs.id +// } +// +// func hash(into hasher: inout Hasher) { +// hasher.combine(self.id) +// } +//} diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index 6aafdfe..fcf6c00 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -1,6 +1,6 @@ import SwiftUI -@Observable final class Hotline: HotlineClientDelegate { +@Observable final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { let trackerClient: HotlineTrackerClient let client: HotlineClient @@ -71,12 +71,6 @@ import SwiftUI 2561: "πΈπͺ", ] -// @AppStorage("username") private var username: String = "guest" -// @AppStorage("refuse private messages") private var refusePrivateMessages = false -// @AppStorage("refuse private chat") private var refusePrivateChat = false -// @AppStorage("enable automatic response") private var enableAutomaticResponse = false -// @AppStorage("automatic response") private var automaticResponse = "" - var status: HotlineClientStatus = .disconnected var server: Server? { @@ -109,6 +103,7 @@ import SwiftUI var news: [NewsInfo] = [] var newsLoaded: Bool = false + var transfers: [TransferInfo] = [] var downloads: [HotlineFileClient] = [] @ObservationIgnored var bannerClient: HotlineFileClient? @@ -180,7 +175,7 @@ import SwiftUI @MainActor func disconnect() { self.client.disconnect() - self.bannerClient?.disconnect() + self.bannerClient?.cancel() } @MainActor func sendAgree() { @@ -391,18 +386,130 @@ import SwiftUI } } - func downloadFile(path: [String], callback: ((Bool) -> Void)?) { + @MainActor func downloadFile(_ fileName: String, path: [String], 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, sent: { _ in + }, reply: { [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 + self.transfers.append(transfer) + + fileClient.downloadToFile() + } + }) } - @MainActor func downloadBanner(force: Bool = false, callback: ((Bool) -> Void)?) { - if let b = self.bannerClient { - b.disconnect() + @MainActor func previewFile(_ fileName: String, path: [String], addTransfer: Bool = false, complete callback: ((TransferInfo, Data) -> Void)? = nil) { + var fullPath: [String] = [] + if path.count > 1 { + fullPath = Array(path[0..<path.count-1]) + } + + self.client.sendDownloadFile(name: fileName, path: fullPath, preview: true, sent: { _ in + + }, reply: { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in + guard success else { + return + } + + print("GOT DOWNLOAD REPLY:") + print("SUCCESS?", success) + print("TRANSFER SIZE: \(downloadTransferSize.debugDescription)") + print("FILE SIZE: \(downloadFileSize.debugDescription)") + print("REFERENCE NUM: \(downloadReferenceNumber.debugDescription)") + print("WAITING COUNT: \(downloadWaitingCount.debugDescription)") + + if + let self = self, + let address = self.server?.address, + let port = self.server?.port, + let referenceNumber = downloadReferenceNumber, + let transferSize = downloadTransferSize { + + let fileClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .preview) + fileClient.delegate = self + self.downloads.append(fileClient) + + if addTransfer { + let transfer = TransferInfo(id: referenceNumber, title: fileName, size: UInt(transferSize)) + transfer.previewCallback = callback + self.transfers.append(transfer) + } + + fileClient.downloadToMemory() + + print("DOWNLOADING TO MEMORY") +// fileClient.downloadToMemory({ [weak self] fileData in +// print("DOWNLOADED PREVIEW DATA", fileData?.count) +// self?.downloads.removeAll { $0.referenceNumber == referenceNumber } +// callback?(fileData != nil, fileData) +// }) + +// self.downloads.append(fileClient) + } + }) + } + + @MainActor func deleteTransfer(id: UInt32) { + if let b = self.bannerClient, b.referenceNumber == id { + b.cancel() self.bannerClient = nil + return } - if force { - self.bannerImage = nil + if let i = self.transfers.firstIndex(where: { $0.id == id }) { + self.transfers.remove(at: i) + } + + if let i = self.downloads.firstIndex(where: { $0.referenceNumber == id }) { + let fileClient = self.downloads.remove(at: i) + fileClient.cancel(deleteIncompleteFile: true) + } + } + + @MainActor func deleteAllTransfers() { + self.transfers = [] + + let downloads = self.downloads + self.downloads = [] + + for fileClient in downloads { + fileClient.cancel(deleteIncompleteFile: true) + } + } + + @MainActor func downloadBanner(force: Bool = false, callback: ((Bool) -> Void)?) { + if self.bannerClient != nil || force { + self.bannerClient?.delegate = nil + self.bannerClient?.cancel() + self.bannerClient = nil + + if force { + self.bannerImage = nil + } } if self.bannerImage != nil { @@ -416,37 +523,44 @@ import SwiftUI callback?(false) return } - }, reply: { [weak self] downloadReferenceNumber, downloadTransferSize in + }, reply: { [weak self] success, downloadReferenceNumber, downloadTransferSize in + if !success { + callback?(false) + return + } + if let self = self, let address = self.server?.address, let port = self.server?.port, let referenceNumber = downloadReferenceNumber, let transferSize = downloadTransferSize { - self.bannerClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .banner) - self.bannerClient?.downloadToMemory({ [weak self] data in - if let b = self?.bannerClient { - b.disconnect() - self?.bannerClient = nil - } - - if data != nil { - #if os(macOS) - self?.bannerImage = NSImage(data: data!) - #elseif os(iOS) - self?.bannerImage = UIImage(data: data!) - #endif - } - - callback?(data != nil) - }) + self.bannerClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .preview) + self.bannerClient?.downloadToMemory() + +// self.bannerClient?.downloadToMemory({ [weak self] data in +// if let b = self?.bannerClient { +// b.disconnect() +// self?.bannerClient = nil +// } +// +// if data != nil { +// #if os(macOS) +// self?.bannerImage = NSImage(data: data!) +// #elseif os(iOS) +// self?.bannerImage = UIImage(data: data!) +// #endif +// } +// +// callback?(data != nil) +// }) } }) } // MARK: - Hotline Delegate - func hotlineStatusChanged(status: HotlineClientStatus) { + @MainActor func hotlineStatusChanged(status: HotlineClientStatus) { print("Hotline: Connection status changed to: \(status)") if status == .disconnected { @@ -464,11 +578,12 @@ import SwiftUI self.newsLoaded = false self.bannerImage = nil - if let b = self.bannerClient { - b.disconnect() + b.cancel() self.bannerClient = nil } + + self.deleteAllTransfers() } self.status = status @@ -539,6 +654,80 @@ import SwiftUI } + // MARK: - Hotline File Delegate + + func hotlineFileStatusChanged(client: HotlineFileClient, reference: UInt32, status: HotlineFileClientStatus, timeRemaining: TimeInterval) { + switch status { + case .unconnected: + break + case .connecting: + break + case .connected: + break + case .progress(let progress): + if let transfer = self.transfers.first(where: { $0.id == reference }) { + transfer.progress = progress + transfer.timeRemaining = timeRemaining + } + case .failed(_): + if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) { + self.downloads.remove(at: i) + } + if let transfer = self.transfers.first(where: { $0.id == reference }) { + transfer.failed = true + transfer.timeRemaining = 0.0 + } + if let b = self.bannerClient, reference == b.referenceNumber { + b.delegate = nil + self.bannerClient = nil + } + case .completed: + if let transfer = self.transfers.first(where: { $0.id == reference }) { + transfer.completed = true + transfer.timeRemaining = 0.0 + } + break + } + } + + func hotlineFileReceivedInfo(client: HotlineFileClient, reference: UInt32, info: HotlineFileInfoFork) { + if let transfer = self.transfers.first(where: { $0.id == reference }) { + transfer.title = info.name + } + } + + func hotlineFileDownloadedData(client: HotlineFileClient, reference: UInt32, data: Data) { + if let b = self.bannerClient, reference == b.referenceNumber { + #if os(macOS) + self.bannerImage = NSImage(data: data) + #elseif os(iOS) + self.bannerImage = UIImage(data: data) + #endif + } + else + if let i = self.transfers.firstIndex(where: { $0.id == reference }) { + let transfer = self.transfers[i] + transfer.previewCallback?(transfer, data) + self.transfers.remove(at: i) + } + + if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) { + self.downloads.remove(at: i) + } + } + + func hotlineFileDownloadedFile(client: HotlineFileClient, reference: UInt32, at: URL) { + if let i = self.transfers.firstIndex(where: { $0.id == reference }) { + let transfer = self.transfers[i] + transfer.fileURL = at + transfer.downloadCallback?(transfer, at) + } + + if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) { + self.downloads.remove(at: i) + } + } + // MARK: - Utilities func updateServerTitle() { diff --git a/Hotline/Models/Server.swift b/Hotline/Models/Server.swift index b39f1c4..2275036 100644 --- a/Hotline/Models/Server.swift +++ b/Hotline/Models/Server.swift @@ -1,29 +1,35 @@ import SwiftUI -struct Server: Identifiable, Equatable, Hashable, Codable { +struct Server: Codable { static let defaultPort: Int = 5500 - let id: UUID - let name: String? - let description: String? - let users: Int - let address: String - let port: Int + var name: String? + var description: String? + var users: Int + var address: String + var port: Int init(name: String?, description: String?, address: String, port: Int, users: Int = 0) { - self.id = UUID() self.name = name self.description = description self.address = address.lowercased() self.port = port self.users = users } - - func hash(into hasher: inout Hasher) { - hasher.combine(self.id) - } - +} + +extension Server: Identifiable { + var id: String { "\(address):\(port)" } +} + +extension Server: Equatable { static func == (lhs: Server, rhs: Server) -> Bool { return (lhs.address == rhs.address) && (lhs.port == rhs.port) } } + +extension Server: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } +} diff --git a/Hotline/Models/TransferInfo.swift b/Hotline/Models/TransferInfo.swift new file mode 100644 index 0000000..546efd3 --- /dev/null +++ b/Hotline/Models/TransferInfo.swift @@ -0,0 +1,33 @@ +import SwiftUI + +@Observable +class TransferInfo: Identifiable, Equatable, Hashable { + var id: UInt32 + + var title: String + var size: UInt + var progress: Double = 0.0 + var timeRemaining: TimeInterval = 0.0 + var completed: Bool = false + var failed: Bool = false + + // For file based transfers (i.e. not previews) + var fileURL: URL? = nil + + var downloadCallback: ((TransferInfo, URL) -> Void)? = nil + var previewCallback: ((TransferInfo, Data) -> Void)? = nil + + init(id: UInt32, title: String, size: UInt) { + self.id = id + self.title = title + self.size = size + } + + static func == (lhs: TransferInfo, rhs: TransferInfo) -> Bool { + return lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } +} diff --git a/Hotline/Models/User.swift b/Hotline/Models/User.swift index 1a921e2..7fbecb0 100644 --- a/Hotline/Models/User.swift +++ b/Hotline/Models/User.swift @@ -8,11 +8,14 @@ struct UserStatus: OptionSet { } struct User: Identifiable { - let id: UInt + var id: UInt var name: String var iconID: UInt var status: UserStatus + var isAdmin: Bool { self.status.contains(.admin) } + var isIdle: Bool { self.status.contains(.idle) } + init(hotlineUser: HotlineUser) { var status: UserStatus = UserStatus() if hotlineUser.isIdle { status.update(with: .idle) } |