diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-01-05 19:46:13 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-01-05 19:46:37 -0800 |
| commit | 2f8f71d0410f59d5d303a4c221a628630168bfe2 (patch) | |
| tree | 6a18ae2e4dbd72a688667bd9e819ecee72d76b52 /Hotline/Models | |
| parent | 57c33eb798f6802fe5af8fcd3d1992a4b63a448f (diff) | |
Add image preview to Files so you don't have to download images to view them, works with animated GIFs too. Added cmd-R shortcut for Servers window. More work on login view.
Diffstat (limited to 'Hotline/Models')
| -rw-r--r-- | Hotline/Models/FileInfo.swift | 11 | ||||
| -rw-r--r-- | Hotline/Models/FilePreview.swift | 110 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 98 | ||||
| -rw-r--r-- | Hotline/Models/PreviewFileInfo.swift | 20 | ||||
| -rw-r--r-- | Hotline/Models/Server.swift | 33 |
5 files changed, 212 insertions, 60 deletions
diff --git a/Hotline/Models/FileInfo.swift b/Hotline/Models/FileInfo.swift index 9caa976..0cd9b2b 100644 --- a/Hotline/Models/FileInfo.swift +++ b/Hotline/Models/FileInfo.swift @@ -1,4 +1,5 @@ import SwiftUI +import UniformTypeIdentifiers @Observable class FileInfo: Identifiable, Hashable { let id: UUID @@ -15,6 +16,16 @@ import SwiftUI var expanded: Bool = false var children: [FileInfo]? = nil + var isImage: Bool { + let fileExtension = (self.name as NSString).pathExtension + if let fileType = UTType(filenameExtension: fileExtension) { + if fileType.isSubtype(of: .image) { + return true + } + } + return false + } + init(hotlineFile: HotlineFile) { self.id = UUID() self.path = hotlineFile.path diff --git a/Hotline/Models/FilePreview.swift b/Hotline/Models/FilePreview.swift index 552ccff..a7b183f 100644 --- a/Hotline/Models/FilePreview.swift +++ b/Hotline/Models/FilePreview.swift @@ -1,41 +1,73 @@ import SwiftUI +import UniformTypeIdentifiers -//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) -// } -//} +enum FilePreviewState: Equatable { + case unloaded + case loading + case loaded + case failed +} + +@Observable +final class FilePreview: HotlineFileClientDelegate { + @ObservationIgnored let info: PreviewFileInfo + @ObservationIgnored var client: HotlineFileClient? = nil + + var state: FilePreviewState = .unloaded + var progress: Double = 0.0 + + #if os(macOS) + var image: NSImage? + #endif + + init(info: PreviewFileInfo) { + self.info = info + + self.client = HotlineFileClient(address: info.address, port: UInt16(info.port), reference: info.id, size: UInt32(info.size), type: .preview) + self.client?.delegate = self + } + + func download() { + self.client?.downloadToMemory() + } + + func cancel() { + self.client?.cancel(deleteIncompleteFile: true) + } + + func hotlineFileStatusChanged(client: HotlineFileClient, reference: UInt32, status: HotlineFileClientStatus, timeRemaining: TimeInterval) { + print("FILE STATUS CHANGED", status) + + switch status { + case .unconnected: + state = .unloaded + progress = 0.0 + case .connecting: + state = .loading + progress = 0.0 + case .connected: + state = .loading + progress = 0.0 + case .progress(let p): + state = .loading + progress = p + case .failed(_): + state = .failed + progress = 0.0 + case .completed: + state = .loaded + progress = 1.0 + } + } + + func hotlineFileDownloadedData(client: HotlineFileClient, reference: UInt32, data: Data) { + self.state = .loaded + + let fileExtension = (info.name as NSString).pathExtension + if let fileType = UTType(filenameExtension: fileExtension) { + if fileType.isSubtype(of: .image) { + self.image = NSImage(data: data) + } + } + } +} diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index 3e3bf26..e88c267 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -1,6 +1,7 @@ import SwiftUI -@Observable final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { +@Observable +final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate { let trackerClient: HotlineTrackerClient let client: HotlineClient @@ -239,13 +240,15 @@ import SwiftUI self.trackerClient.disconnect() } - @MainActor func login(server: Server, login: String, password: String, username: String, iconID: Int, callback: ((Bool) -> Void)? = nil) { + @MainActor func login(server: Server, username: String, iconID: Int, callback: ((Bool) -> Void)? = nil) { self.server = server self.serverName = server.name self.username = username self.iconID = iconID - self.client.login(server.address, port: UInt16(server.port), login: login, password: password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in + print("CLIENT LOGIN: '\(server.login)' '\(server.password == nil)'") + + self.client.login(server.address, port: UInt16(server.port), login: server.login, password: server.password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in self?.serverVersion = serverVersion if serverName != nil { self?.serverName = serverName @@ -519,16 +522,21 @@ import SwiftUI }) } - @MainActor func previewFile(_ fileName: String, path: [String], addTransfer: Bool = false, complete callback: ((TransferInfo, Data) -> Void)? = nil) { + @MainActor func previewFile(_ fileName: String, path: [String], complete callback: ((PreviewFileInfo?) -> 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 + self.client.sendDownloadFile(name: fileName, path: fullPath, preview: true, sent: { success in + if !success { + callback?(nil) + return + } }, reply: { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in guard success else { + callback?(nil) return } @@ -539,24 +547,27 @@ import SwiftUI print("REFERENCE NUM: \(downloadReferenceNumber.debugDescription)") print("WAITING COUNT: \(downloadWaitingCount.debugDescription)") + var info: PreviewFileInfo? = nil + if - let self = self, - let address = self.server?.address, - let port = self.server?.port, + 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) + info = PreviewFileInfo(id: referenceNumber, address: address, port: port, size: transferSize, name: fileName) - if addTransfer { - let transfer = TransferInfo(id: referenceNumber, title: fileName, size: UInt(transferSize)) - transfer.previewCallback = callback - self.transfers.append(transfer) - } +// let fileClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .preview) +// fileClient.delegate = self +// self.downloads.append(fileClient) - fileClient.downloadToMemory() +// 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 @@ -567,9 +578,62 @@ import SwiftUI // self.downloads.append(fileClient) } + + callback?(info) }) } +// @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() diff --git a/Hotline/Models/PreviewFileInfo.swift b/Hotline/Models/PreviewFileInfo.swift new file mode 100644 index 0000000..9a4c980 --- /dev/null +++ b/Hotline/Models/PreviewFileInfo.swift @@ -0,0 +1,20 @@ + +struct PreviewFileInfo: Identifiable, Codable { + var id: UInt32 + var address: String + var port: Int + var size: Int + var name: String +} + +extension PreviewFileInfo: Equatable { + static func == (lhs: PreviewFileInfo, rhs: PreviewFileInfo) -> Bool { + return lhs.id == rhs.id + } +} + +extension PreviewFileInfo: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } +} diff --git a/Hotline/Models/Server.swift b/Hotline/Models/Server.swift index 62c6867..27d45c6 100644 --- a/Hotline/Models/Server.swift +++ b/Hotline/Models/Server.swift @@ -4,22 +4,47 @@ struct Server: Codable { var name: String? var description: String? var users: Int + var address: String var port: Int + var login: String + var password: String - init(name: String?, description: String?, address: String, port: Int = HotlinePorts.DefaultServerPort, users: Int = 0) { + init(name: String?, description: String?, address: String, port: Int = HotlinePorts.DefaultServerPort, users: Int = 0, login: String = "", password: String = "") { self.name = name self.description = description self.address = address.lowercased() self.port = port self.users = users + self.login = login + self.password = password + } + + init?(url: URL) { + guard url.scheme?.lowercased() == "hotline" else { + return nil + } + + guard let host = url.host(percentEncoded: false) else { + return nil + } + + self.name = nil + self.description = nil + self.users = 0 + + self.address = host.lowercased() + self.port = url.port ?? HotlinePorts.DefaultServerPort + + self.login = url.user(percentEncoded: false) ?? "" + self.password = url.password(percentEncoded: false) ?? "" } static func parseServerAddressAndPort(_ address: String) -> (String, Int) { let url = URL(string: "hotline://\(address)") let port = url?.port ?? HotlinePorts.DefaultServerPort - let host = url?.host(percentEncoded: false) ?? address - return (host, port) + let host = url?.host(percentEncoded: false) ?? "" + return (host.lowercased().trimmingCharacters(in: .whitespacesAndNewlines), port) } } @@ -29,7 +54,7 @@ extension Server: Identifiable { extension Server: Equatable { static func == (lhs: Server, rhs: Server) -> Bool { - return (lhs.address == rhs.address) && (lhs.port == rhs.port) + return (lhs.address == rhs.address) && (lhs.port == rhs.port) && (lhs.login == rhs.login) && (lhs.password == rhs.password) } } |