aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Models
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-01-01 11:35:49 -0800
committerDustin Mierau <dustin@mierau.me>2024-01-01 11:35:49 -0800
commit5d2aadbc834393b532d043d6298d6a23b80c2a5c (patch)
treedcf139b67179d5bc3ad3ed69e949a1e7c1d18177 /Hotline/Models
parenta4cbaea0b12b7dd42133b4bd14f1371f1000fcc0 (diff)
File downloads now working in macOS client.1.0beta4
Diffstat (limited to 'Hotline/Models')
-rw-r--r--Hotline/Models/FilePreview.swift41
-rw-r--r--Hotline/Models/Hotline.swift259
-rw-r--r--Hotline/Models/Server.swift32
-rw-r--r--Hotline/Models/TransferInfo.swift33
-rw-r--r--Hotline/Models/User.swift5
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) }