aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Models/Hotline.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Hotline/Models/Hotline.swift')
-rw-r--r--Hotline/Models/Hotline.swift699
1 files changed, 373 insertions, 326 deletions
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift
index 9ea29c5..1989d46 100644
--- a/Hotline/Models/Hotline.swift
+++ b/Hotline/Models/Hotline.swift
@@ -1,25 +1,27 @@
import SwiftUI
@Observable
-class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelegate, HotlineFilePreviewClientDelegate, HotlineFileUploadClientDelegate {
+class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelegate,
+ HotlineFilePreviewClientDelegate, HotlineFileUploadClientDelegate
+{
let id: UUID = UUID()
let trackerClient: HotlineTrackerClient
let client: HotlineClient
-
+
static func == (lhs: Hotline, rhs: Hotline) -> Bool {
return lhs.id == rhs.id
}
-
+
#if os(macOS)
- static func getClassicIcon(_ index: Int) -> NSImage? {
- return NSImage(named: "Classic/\(index)")
- }
+ static func getClassicIcon(_ index: Int) -> NSImage? {
+ return NSImage(named: "Classic/\(index)")
+ }
#elseif os(iOS)
- static func getClassicIcon(_ index: Int) -> UIImage? {
- return UIImage(named: "Classic/\(index)")
- }
+ static func getClassicIcon(_ index: Int) -> UIImage? {
+ return UIImage(named: "Classic/\(index)")
+ }
#endif
-
+
// The icon ordering here was painsakenly pulled manually
// from the original Hotline client to display the classic icons
// in the same order as the original client.
@@ -104,11 +106,11 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
6001, 6002, 6003, 6004, 6005, 6008, 6009,
6010, 6011, 6012, 6013, 6014, 6015, 6016,
6017, 6018, 6023, 6025, 6026, 6027, 6028,
- 6029, 6030, 6031, 6032, 6033, 6034, 6035
+ 6029, 6030, 6031, 6032, 6033, 6034, 6035,
]
-
+
var status: HotlineClientStatus = .disconnected
- var server: Server? {
+ var server: Server? {
didSet {
self.updateServerTitle()
}
@@ -132,258 +134,271 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
var files: [FileInfo] = []
var filesLoaded: Bool = false
var news: [NewsInfo] = []
- private var newsLookup: [String:NewsInfo] = [:]
+ private var newsLookup: [String: NewsInfo] = [:]
var newsLoaded: Bool = false
var accountsLoaded: Bool = false
- var instantMessages: [UInt16:[InstantMessage]] = [:]
+ var instantMessages: [UInt16: [InstantMessage]] = [:]
var transfers: [TransferInfo] = []
var downloads: [HotlineTransferClient] = []
- var unreadInstantMessages: [UInt16:UInt16] = [:]
+ var unreadInstantMessages: [UInt16: UInt16] = [:]
var unreadPublicChat: Bool = false
var errorDisplayed: Bool = false
var errorMessage: String? = nil
-
+
@ObservationIgnored var bannerClient: HotlineFilePreviewClient?
#if os(macOS)
- var bannerImage: NSImage? = nil
+ var bannerImage: NSImage? = nil
#elseif os(iOS)
- var bannerImage: UIImage? = nil
+ var bannerImage: UIImage? = nil
#endif
-
-
+
// MARK: -
-
+
init(trackerClient: HotlineTrackerClient, client: HotlineClient) {
self.trackerClient = trackerClient
self.client = client
self.client.delegate = self
}
-
+
// MARK: -
-
- @MainActor func getServerList(tracker: String, port: Int = HotlinePorts.DefaultTrackerPort) async -> [Server] {
+
+ @MainActor func getServerList(tracker: String, port: Int = HotlinePorts.DefaultTrackerPort) async
+ -> [Server]
+ {
var servers: [Server] = []
-
- if let fetchedServers: [HotlineServer] = try? await self.trackerClient.fetchServers(address: tracker, port: port) {
+
+ if let fetchedServers: [HotlineServer] = try? await self.trackerClient.fetchServers(
+ address: tracker, port: port)
+ {
for s in fetchedServers {
if let serverName = s.name {
- servers.append(Server(name: serverName, description: s.description, address: s.address, port: Int(s.port), users: Int(s.users)))
+ servers.append(
+ Server(
+ name: serverName, description: s.description, address: s.address, port: Int(s.port),
+ users: Int(s.users)))
}
}
}
-
+
return servers
}
-
+
@MainActor func disconnectTracker() {
self.trackerClient.close()
}
-
- @MainActor func login(server: Server, 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(address: server.address, port: server.port, login: server.login, password: server.password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in
+
+ self.client.login(
+ address: server.address, port: server.port, login: server.login, password: server.password,
+ username: username, iconID: UInt16(iconID)
+ ) { [weak self] err, serverName, serverVersion in
self?.serverVersion = serverVersion ?? 123
if serverName != nil {
self?.serverName = serverName
}
-
+
callback?(err == nil)
}
}
-
- @MainActor func sendUserInfo(username: String, iconID: Int, options: HotlineUserOptions = [], autoresponse: String? = nil) {
+
+ @MainActor func sendUserInfo(
+ username: String, iconID: Int, options: HotlineUserOptions = [], autoresponse: String? = nil
+ ) {
self.username = username
self.iconID = iconID
-
- self.client.sendSetClientUserInfo(username: username, iconID: UInt16(iconID), options: options, autoresponse: autoresponse)
+
+ self.client.sendSetClientUserInfo(
+ username: username, iconID: UInt16(iconID), options: options, autoresponse: autoresponse)
}
-
+
@MainActor func getUserList() {
self.client.sendGetUserList()
}
-
+
@MainActor func disconnect() {
self.client.disconnect()
self.bannerClient?.cancel()
}
-
+
@MainActor func sendAgree() {
self.client.sendAgree(username: self.username, iconID: UInt16(self.iconID), options: .none)
}
-
+
@MainActor func sendInstantMessage(_ text: String, userID: UInt16) {
- let message = InstantMessage(direction: .outgoing, text: text.convertingLinksToMarkdown(), type: .message, date: Date())
-
+ let message = InstantMessage(
+ direction: .outgoing, text: text.convertingLinksToMarkdown(), type: .message, date: Date())
+
if self.instantMessages[userID] == nil {
self.instantMessages[userID] = [message]
- }
- else {
+ } else {
self.instantMessages[userID]!.append(message)
}
-
+
self.client.sendInstantMessage(message: text, userID: userID)
-
+
if Prefs.shared.playPrivateMessageSound && Prefs.shared.playPrivateMessageSound {
SoundEffectPlayer.shared.playSoundEffect(.chatMessage)
}
}
-
+
func markPublicChatAsRead() {
self.unreadPublicChat = false
}
-
+
func hasUnreadInstantMessages(userID: UInt16) -> Bool {
return self.unreadInstantMessages[userID] != nil
}
-
+
func markInstantMessagesAsRead(userID: UInt16) {
self.unreadInstantMessages.removeValue(forKey: userID)
}
-
+
@MainActor func sendChat(_ text: String, announce: Bool = false) {
self.client.sendChat(message: text, announce: announce)
}
-
+
@MainActor func getMessageBoard() async -> [String] {
self.messageBoard = await withCheckedContinuation { [weak self] continuation in
- self?.client.sendGetMessageBoard() { err, messages in
+ self?.client.sendGetMessageBoard { err, messages in
continuation.resume(returning: (err != nil ? [] : messages))
}
}
-
+
self.messageBoardLoaded = true
-
+
return self.messageBoard
}
-
+
@MainActor func postToMessageBoard(text: String) {
self.client.sendPostMessageBoard(text: text)
}
-
+
@MainActor func getFileList(path: [String] = []) async -> [FileInfo] {
return await withCheckedContinuation { [weak self] continuation in
self?.client.sendGetFileList(path: path) { [weak self] files in
let parentFile = self?.findFile(in: self?.files ?? [], at: path)
-
+
var newFiles: [FileInfo] = []
for f in files {
newFiles.append(FileInfo(hotlineFile: f))
}
-
+
DispatchQueue.main.async {
if let parent = parentFile {
parent.children = newFiles
- }
- else if path.isEmpty {
+ } else if path.isEmpty {
self?.filesLoaded = true
self?.files = newFiles
}
-
+
continuation.resume(returning: newFiles)
}
}
}
}
-
- @MainActor func getNewsArticle(id articleID: UInt, at path: [String], flavor: String) async -> String? {
+
+ @MainActor func getNewsArticle(id articleID: UInt, at path: [String], flavor: String) async
+ -> String?
+ {
return await withCheckedContinuation { [weak self] continuation in
- self?.client.sendGetNewsArticle(id: UInt32(articleID), path: path, flavor: flavor) { articleText in
-// let parentNews = self?.findNews(in: self?.news ?? [], at: path)
-
-// var newCategories: [NewsInfo] = []
-// for category in categories {
-// newCategories.append(NewsInfo(hotlineNewsCategory: category))
-// }
-//
-// if let parent = existingNewsItem {
-// parent.children = newCategories
-// }
-// else if path.isEmpty {
-// self?.news = newCategories
-// }
-
+ self?.client.sendGetNewsArticle(id: UInt32(articleID), path: path, flavor: flavor) {
+ articleText in
+ // let parentNews = self?.findNews(in: self?.news ?? [], at: path)
+
+ // var newCategories: [NewsInfo] = []
+ // for category in categories {
+ // newCategories.append(NewsInfo(hotlineNewsCategory: category))
+ // }
+ //
+ // if let parent = existingNewsItem {
+ // parent.children = newCategories
+ // }
+ // else if path.isEmpty {
+ // self?.news = newCategories
+ // }
+
continuation.resume(returning: articleText)
}
}
-
+
}
-
+
@MainActor func getAccounts() async -> [HotlineAccount] {
return await withCheckedContinuation { [weak self] continuation in
- self?.client.sendGetAccounts() { articles in
+ self?.client.sendGetAccounts { articles in
continuation.resume(returning: articles)
}
}
}
-
+
@MainActor func getNewsList(at path: [String] = []) async {
return await withCheckedContinuation { [weak self] continuation in
let parentNewsGroup = self?.findNews(in: self?.news ?? [], at: path)
-
+
// Send a categories request for bundle paths or root (empty path)
if path.isEmpty || parentNewsGroup?.type == .bundle {
print("Hotline: Requesting categories at: /\(path.joined(separator: "/"))")
-
+
self?.client.sendGetNewsCategories(path: path) { @MainActor [weak self] categories in
// Create info for each category returned.
var newCategoryInfos: [NewsInfo] = []
-
+
// Transform hotline categories into NewsInfo objects.
for category in categories {
var newsCategoryInfo = NewsInfo(hotlineNewsCategory: category)
-
+
if let lookupPath = newsCategoryInfo.lookupPath {
// Merge returned category info with existing category info.
if let existingCategoryInfo = self?.newsLookup[lookupPath] {
print("Hotline: Merging category into existing category at \(lookupPath)")
-
+
existingCategoryInfo.count = newsCategoryInfo.count
existingCategoryInfo.name = newsCategoryInfo.name
existingCategoryInfo.path = newsCategoryInfo.path
existingCategoryInfo.categoryID = newsCategoryInfo.categoryID
newsCategoryInfo = existingCategoryInfo
- }
- else {
+ } else {
print("Hotline: New category added at \(lookupPath)")
self?.newsLookup[lookupPath] = newsCategoryInfo
}
}
-
+
newCategoryInfos.append(newsCategoryInfo)
}
-
+
if let parent = parentNewsGroup {
parent.children = newCategoryInfos
- }
- else if path.isEmpty {
+ } else if path.isEmpty {
self?.newsLoaded = true
self?.news = newCategoryInfos
}
-
+
continuation.resume()
}
- }
- else {
+ } else {
print("Hotline: Requesting articles at: /\(path.joined(separator: "/"))")
-
+
self?.client.sendGetNewsArticles(path: path) { @MainActor [weak self] articles in
print("Hotline: Organizing news at \(path.joined(separator: "/"))")
// Create info for each article returned.
var newArticleInfos: [NewsInfo] = []
-
+
for article in articles {
var newsArticleInfo = NewsInfo(hotlineNewsArticle: article)
-
+
if let lookupPath = newsArticleInfo.lookupPath {
// Merge returned category info with existing category info.
if let existingArticleInfo = self?.newsLookup[lookupPath] {
print("Hotline: Merging article into existing article at \(lookupPath)")
-
+
existingArticleInfo.count = newsArticleInfo.count
existingArticleInfo.name = newsArticleInfo.name
existingArticleInfo.path = newsArticleInfo.path
@@ -392,94 +407,96 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
existingArticleInfo.articleFlavors = newsArticleInfo.articleFlavors
existingArticleInfo.articleID = newsArticleInfo.articleID
newsArticleInfo = existingArticleInfo
- }
- else {
+ } else {
print("Hotline: New article added at \(lookupPath)")
self?.newsLookup[lookupPath] = newsArticleInfo
}
}
-
+
newArticleInfos.append(newsArticleInfo)
}
-
+
let organizedNewsArticles: [NewsInfo] = self?.organizeNewsArticles(newArticleInfos) ?? []
if let parent = parentNewsGroup {
parent.children = organizedNewsArticles
}
-
+
continuation.resume()
}
}
}
}
-
+
func organizeNewsArticles(_ flatArticles: [NewsInfo]) -> [NewsInfo] {
// Place articles under their parent.
var organized: [NewsInfo] = []
for article in flatArticles {
if let parentLookupPath = article.parentArticleLookupPath,
- let parentArticle = self.newsLookup[parentLookupPath] {
-// article.expanded = true
+ let parentArticle = self.newsLookup[parentLookupPath]
+ {
+ // article.expanded = true
if parentArticle.children.firstIndex(of: article) == nil {
article.expanded = true
parentArticle.children.append(article)
}
- }
- else {
+ } else {
organized.append(article)
}
}
-
+
return organized
}
-
- @MainActor func postNewsArticle(title: String, body: String, at path: [String], parentID: UInt32 = 0) async -> Bool {
-
-
+
+ @MainActor func postNewsArticle(
+ title: String, body: String, at path: [String], parentID: UInt32 = 0
+ ) async -> Bool {
+
return await withCheckedContinuation { [weak self] continuation in
guard let client = self?.client else {
continuation.resume(returning: false)
return
}
-
- client.postNewsArticle(title: title, text: body, path: path, parentID: parentID, callback: { success in
- print("Hotline: News article posted? \(success)")
- continuation.resume(returning: success)
- })
+
+ client.postNewsArticle(
+ title: title, text: body, path: path, parentID: parentID,
+ callback: { success in
+ print("Hotline: News article posted? \(success)")
+ continuation.resume(returning: success)
+ })
}
}
-
-// @MainActor func getNewsCategories(at path: [String] = []) async -> [NewsInfo] {
-// return await withCheckedContinuation { [weak self] continuation in
-// guard let client = self?.client else {
-// continuation.resume(returning: [])
-// return
-// }
-//
-// client.sendGetNewsCategories(path: path) { [weak self] categories in
-// let parentNews = self?.findNews(in: self?.news ?? [], at: path)
-//
-// var newCategories: [NewsInfo] = []
-// for category in categories {
-// let categoryInfo: NewsInfo = NewsInfo(hotlineNewsCategory: category)
-// newCategories.append(categoryInfo)
-// self?.newsLookup[categoryInfo.path.joined(separator: "/")] = categoryInfo
-// }
-//
-// DispatchQueue.main.async {
-// if let parent = parentNews {
-// parent.children = newCategories
-// }
-// else if path.isEmpty {
-// self?.news = newCategories
-// }
-//
-// continuation.resume(returning: newCategories)
-// }
-// }
-// }
-// }
-
+
+ // @MainActor func getNewsCategories(at path: [String] = []) async -> [NewsInfo] {
+ // return await withCheckedContinuation { [weak self] continuation in
+ // guard let client = self?.client else {
+ // continuation.resume(returning: [])
+ // return
+ // }
+ //
+ // client.sendGetNewsCategories(path: path) { [weak self] categories in
+ // let parentNews = self?.findNews(in: self?.news ?? [], at: path)
+ //
+ // var newCategories: [NewsInfo] = []
+ // for category in categories {
+ // let categoryInfo: NewsInfo = NewsInfo(hotlineNewsCategory: category)
+ // newCategories.append(categoryInfo)
+ // self?.newsLookup[categoryInfo.path.joined(separator: "/")] = categoryInfo
+ // }
+ //
+ // DispatchQueue.main.async {
+ // if let parent = parentNews {
+ // parent.children = newCategories
+ // }
+ // else if path.isEmpty {
+ // self?.news = newCategories
+ // }
+ //
+ // continuation.resume(returning: newCategories)
+ // }
+ // }
+ // }
+ // }
+
@MainActor func getArticles(at path: [String]) async -> [NewsInfo] {
return await withCheckedContinuation { [weak self] continuation in
self?.client.sendGetNewsArticles(path: path) { articles in
@@ -487,284 +504,315 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
}
}
}
-
- @MainActor func downloadFile(_ fileName: String, path: [String], complete callback: ((TransferInfo, URL) -> Void)? = nil) {
+
+ @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])
+ fullPath = Array(path[0..<path.count - 1])
}
-
- self.client.sendDownloadFile(name: fileName, path: fullPath) { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in
+
+ 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 server = self.server,
+
+ if let self = self,
+ // let server = self.server,
let address = self.server?.address,
let port = self.server?.port,
let referenceNumber = downloadReferenceNumber,
- let transferSize = downloadTransferSize {
-
- let fileClient = HotlineFileDownloadClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize))
-// let previewClient = HotlineFilePreviewClient(server: self.server, reference: referenceNumber, size: UInt32(transferSize), type: .fileDownload)
-// let fileClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .fileDownload)
+ let transferSize = downloadTransferSize
+ {
+
+ let fileClient = HotlineFileDownloadClient(
+ address: address, port: UInt16(port), reference: referenceNumber,
+ size: UInt32(transferSize))
+ // let previewClient = HotlineFilePreviewClient(server: self.server, reference: referenceNumber, size: UInt32(transferSize), type: .fileDownload)
+ // let fileClient = HotlineFileClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize), type: .fileDownload)
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.start()
}
}
}
-
- @MainActor func downloadFileTo(url fileURL: URL, fileName: String, path: [String], progress progressCallback: ((TransferInfo, Double) -> Void)? = nil, complete callback: ((TransferInfo, URL) -> Void)? = nil) {
+
+ @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])
+ fullPath = Array(path[0..<path.count - 1])
}
-
- self.client.sendDownloadFile(name: fileName, path: fullPath) { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in
+
+ 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,
+
+ if let self = self,
let address = self.server?.address,
let port = self.server?.port,
let referenceNumber = downloadReferenceNumber,
- let transferSize = downloadTransferSize {
-
- let fileClient = HotlineFileDownloadClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize))
+ let transferSize = downloadTransferSize
+ {
+
+ let fileClient = HotlineFileDownloadClient(
+ address: address, port: UInt16(port), reference: referenceNumber,
+ size: UInt32(transferSize))
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.start(to: fileURL)
}
}
}
-
- @MainActor func uploadFile(url fileURL: URL, path: [String], complete callback: ((TransferInfo) -> Void)? = nil) {
+
+ @MainActor func uploadFile(
+ url fileURL: URL, path: [String], complete callback: ((TransferInfo) -> Void)? = nil
+ ) {
let fileName = fileURL.lastPathComponent
-
+
guard fileURL.isFileURL, !fileName.isEmpty else {
print("NOT A FILE URL?")
return
}
-
+
let filePath = fileURL.path(percentEncoded: false)
-
+
var fileIsDirectory: ObjCBool = false
guard FileManager.default.fileExists(atPath: filePath, isDirectory: &fileIsDirectory),
- fileIsDirectory.boolValue == false else {
+ fileIsDirectory.boolValue == false
+ else {
print("FILE IS A DIRECTORY?")
return
}
-
+
var fileSize: UInt = 0
-
+
// Data size
let fileAttributes = try? FileManager.default.attributesOfItem(atPath: filePath)
if let sizeAttribute = fileAttributes?[.size] as? NSNumber {
print("DATA SIZE \(sizeAttribute.uintValue)")
fileSize += sizeAttribute.uintValue
}
-
+
// Resource size
let resourceURL = fileURL.appendingPathComponent("..namedfork/rsrc")
-
-
+
print("RESOURCE PATH \(resourceURL)")
- let resourceAttributes = try? FileManager.default.attributesOfItem(atPath: resourceURL.path(percentEncoded: false))
+ let resourceAttributes = try? FileManager.default.attributesOfItem(
+ atPath: resourceURL.path(percentEncoded: false))
if let sizeAttribute = resourceAttributes?[.size] as? NSNumber {
print("RESOURCE SIZE \(sizeAttribute.uintValue)")
fileSize += sizeAttribute.uintValue
}
-
+
print("FILE SIZE? \(fileSize)")
-
+
guard fileSize > 0 else {
print("FILE IS EMPTY??")
return
}
-
+
print("FILE SIZE: \(fileSize) NAME: \(fileName) PATH: \(path)")
-
- self.client.sendUploadFile(name: fileName, path: path) { [weak self] success, uploadReferenceNumber in
+
+ self.client.sendUploadFile(name: fileName, path: path) {
+ [weak self] success, uploadReferenceNumber in
print("UPLOAD REFERENCE: \(String(describing: uploadReferenceNumber))")
-
+
if let self = self,
- let address = self.server?.address,
- let port = self.server?.port,
- let referenceNumber = uploadReferenceNumber,
- let fileClient = HotlineFileUploadClient(upload: fileURL, address: address, port: UInt16(port), reference: referenceNumber) {
-
+ let address = self.server?.address,
+ let port = self.server?.port,
+ let referenceNumber = uploadReferenceNumber,
+ let fileClient = HotlineFileUploadClient(
+ upload: fileURL, address: address, port: UInt16(port), reference: referenceNumber)
+ {
+
print("GOING TO UPLOAD")
-
+
fileClient.delegate = self
self.downloads.append(fileClient)
-
+
let transfer = TransferInfo(id: referenceNumber, title: fileName, size: fileSize)
transfer.uploadCallback = callback
self.transfers.append(transfer)
-
+
fileClient.start()
}
}
-
+
}
-
+
@MainActor func getFileDetails(_ fileName: String, path: [String]) async -> FileDetails? {
var fullPath: [String] = []
if path.count > 1 {
- fullPath = Array(path[0..<path.count-1])
+ fullPath = Array(path[0..<path.count - 1])
}
-
+
return await withCheckedContinuation { [weak self] continuation in
self?.client.sendGetFileInfo(name: fileName, path: fullPath) { info in
continuation.resume(returning: info)
}
}
}
-
+
@MainActor func deleteFile(_ fileName: String, path: [String]) async -> Bool {
var fullPath: [String] = []
if path.count > 1 {
- fullPath = Array(path[0..<path.count-1])
+ fullPath = Array(path[0..<path.count - 1])
}
-
+
return await withCheckedContinuation { [weak self] continuation in
self?.client.sendDeleteFile(name: fileName, path: fullPath) { success in
continuation.resume(returning: success)
}
}
}
-
- @MainActor func previewFile(_ fileName: String, path: [String], complete callback: ((PreviewFileInfo?) -> 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])
+ fullPath = Array(path[0..<path.count - 1])
}
-
- self.client.sendDownloadFile(name: fileName, path: fullPath, preview: true) { [weak self] success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount in
+
+ self.client.sendDownloadFile(name: fileName, path: fullPath, preview: true) {
+ [weak self]
+ success, downloadReferenceNumber, downloadTransferSize, downloadFileSize, downloadWaitingCount
+ in
guard success else {
callback?(nil)
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)")
-
+
var info: PreviewFileInfo? = nil
-
- if
- let address = self?.server?.address,
+
+ if let address = self?.server?.address,
let port = self?.server?.port,
let referenceNumber = downloadReferenceNumber,
- let transferSize = downloadTransferSize {
-
- info = PreviewFileInfo(id: referenceNumber, address: address, port: port, size: transferSize, name: fileName)
+ let transferSize = downloadTransferSize
+ {
+
+ info = PreviewFileInfo(
+ id: referenceNumber, address: address, port: port, size: transferSize, name: fileName)
}
-
+
callback?(info)
}
}
-
+
@MainActor func deleteTransfer(id: UInt32) {
if let b = self.bannerClient, b.referenceNumber == id {
b.cancel()
self.bannerClient = nil
return
}
-
+
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()
}
}
-
+
@MainActor func deleteAllTransfers() {
self.transfers = []
-
+
let downloads = self.downloads
self.downloads = []
-
+
for fileClient in downloads {
fileClient.cancel()
}
}
-
+
@MainActor func downloadBanner(force: Bool = false) {
guard self.serverVersion >= 150 else {
return
}
-
+
if self.bannerClient != nil || force {
self.bannerClient?.delegate = nil
self.bannerClient?.cancel()
self.bannerClient = nil
-
+
if force {
self.bannerImage = nil
}
}
-
+
if self.bannerImage != nil {
return
}
-
- self.client.sendDownloadBanner { [weak self] success, downloadReferenceNumber, downloadTransferSize in
+
+ self.client.sendDownloadBanner {
+ [weak self] success, downloadReferenceNumber, downloadTransferSize in
if !success {
return
}
-
- if
- let self = self,
+
+ if let self = self,
let address = self.server?.address,
let port = self.server?.port,
let referenceNumber = downloadReferenceNumber,
- let transferSize = downloadTransferSize {
- self.bannerClient = HotlineFilePreviewClient(address: address, port: UInt16(port), reference: referenceNumber, size: UInt32(transferSize))
+ let transferSize = downloadTransferSize
+ {
+ self.bannerClient = HotlineFilePreviewClient(
+ address: address, port: UInt16(port), reference: referenceNumber,
+ size: UInt32(transferSize))
self.bannerClient?.delegate = self
self.bannerClient?.start()
}
}
}
-
+
// MARK: - Hotline Delegate
-
+
@MainActor func hotlineStatusChanged(status: HotlineClientStatus) {
print("Hotline: Connection status changed to: \(status)")
-
+
if status == .disconnected {
self.serverVersion = 123
self.serverName = nil
self.access = nil
-
+
self.users = []
self.chat = []
self.messageBoard = []
@@ -773,32 +821,31 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
self.filesLoaded = false
self.news = []
self.newsLoaded = false
-
+
self.bannerImage = nil
if let b = self.bannerClient {
b.cancel()
self.bannerClient = nil
}
-
+
self.deleteAllTransfers()
- }
- else if status == .loggedIn {
+ } else if status == .loggedIn {
if Prefs.shared.playSounds && Prefs.shared.playLoggedInSound {
SoundEffectPlayer.shared.playSoundEffect(.loggedIn)
}
}
-
+
self.status = status
}
-
+
func hotlineGetUserInfo() -> (String, UInt16) {
return (self.username, UInt16(self.iconID))
}
-
+
func hotlineReceivedAgreement(text: String) {
self.chat.append(ChatMessage(text: text, type: .agreement, date: Date()))
}
-
+
func hotlineReceivedNewsPost(message: String) {
let messageBoardRegex = /([\s\r\n]*[_\-]+[\s\r\n]+)/
let matches = message.matches(of: messageBoardRegex)
@@ -809,44 +856,44 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
} else {
self.messageBoard.insert(message, at: 0)
}
-
+
SoundEffectPlayer.shared.playSoundEffect(.newNews)
}
-
+
func hotlineReceivedServerMessage(message: String) {
if Prefs.shared.playChatSound && Prefs.shared.playChatSound {
SoundEffectPlayer.shared.playSoundEffect(.serverMessage)
}
-
+
print("Hotline: received server message:\n\(message)")
self.chat.append(ChatMessage(text: message, type: .server, date: Date()))
}
-
+
func hotlineReceivedPrivateMessage(userID: UInt16, message: String) {
if let existingUserIndex = self.users.firstIndex(where: { $0.id == UInt(userID) }) {
let user = self.users[existingUserIndex]
print("Hotline: received private message from \(user.name): \(message)")
-
+
if Prefs.shared.playPrivateMessageSound && Prefs.shared.playPrivateMessageSound {
if self.unreadInstantMessages[userID] == nil {
SoundEffectPlayer.shared.playSoundEffect(.serverMessage)
- }
- else {
+ } else {
SoundEffectPlayer.shared.playSoundEffect(.chatMessage)
}
}
-
- let instantMessage = InstantMessage(direction: .incoming, text: message.convertingLinksToMarkdown(), type: .message, date: Date())
+
+ let instantMessage = InstantMessage(
+ direction: .incoming, text: message.convertingLinksToMarkdown(), type: .message,
+ date: Date())
if self.instantMessages[userID] == nil {
self.instantMessages[userID] = [instantMessage]
- }
- else {
+ } else {
self.instantMessages[userID]!.append(instantMessage)
}
self.unreadInstantMessages[userID] = userID
}
}
-
+
func hotlineReceivedChatMessage(message: String) {
if Prefs.shared.playSounds && Prefs.shared.playChatSound {
SoundEffectPlayer.shared.playSoundEffect(.chatMessage)
@@ -854,11 +901,11 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
self.chat.append(ChatMessage(text: message, type: .message, date: Date()))
self.unreadPublicChat = true
}
-
+
func hotlineReceivedUserList(users: [HotlineUser]) {
var existingUserIDs: [UInt16] = []
var userList: [User] = []
-
+
for u in users {
if let i = self.users.firstIndex(where: { $0.id == u.id }) {
// If a user is already in the user list we have to assume
@@ -866,59 +913,62 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
// which means let's keep their existing info.
existingUserIDs.append(u.id)
userList.append(self.users[i])
- }
- else {
+ } else {
userList.append(User(hotlineUser: u))
}
}
-
+
if !existingUserIDs.isEmpty {
self.users = self.users.filter { !existingUserIDs.contains($0.id) }
}
-
+
self.users = userList + self.users
}
-
+
func hotlineUserChanged(user: HotlineUser) {
self.addOrUpdateHotlineUser(user)
}
-
+
func hotlineUserDisconnected(userID: UInt16) {
if let existingUserIndex = self.users.firstIndex(where: { $0.id == UInt(userID) }) {
let user = self.users.remove(at: existingUserIndex)
-
+
if Prefs.shared.showJoinLeaveMessages {
self.chat.append(ChatMessage(text: "\(user.name) left", type: .status, date: Date()))
}
-
+
if Prefs.shared.playSounds && Prefs.shared.playLeaveSound {
SoundEffectPlayer.shared.playSoundEffect(.userLogout)
}
}
}
-
+
func hotlineReceivedUserAccess(options: HotlineUserAccessOptions) {
print("Hotline: got access options")
HotlineUserAccessOptions.printAccessOptions(options)
-
+
self.access = options
}
-
+
func hotlineReceivedErrorMessage(code: UInt32, message: String?) {
print("Hotline: received error message \(code)", message.debugDescription)
-
- self.errorDisplayed = (message != nil) // Show error if there is a message to display.
+
+ self.errorDisplayed = (message != nil) // Show error if there is a message to display.
self.errorMessage = message
-
+
if self.errorDisplayed,
- Prefs.shared.playSounds && Prefs.shared.playErrorSound {
+ Prefs.shared.playSounds && Prefs.shared.playErrorSound
+ {
SoundEffectPlayer.shared.playSoundEffect(.error)
}
}
-
+
// MARK: - Hotline Transfer Delegate
-
- func hotlineTransferStatusChanged(client: HotlineTransferClient, reference: UInt32, status: HotlineTransferStatus, timeRemaining: TimeInterval) {
+
+ func hotlineTransferStatusChanged(
+ client: HotlineTransferClient, reference: UInt32, status: HotlineTransferStatus,
+ timeRemaining: TimeInterval
+ ) {
switch status {
case .unconnected:
break
@@ -953,34 +1003,34 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
}
}
}
-
- func hotlineFileDownloadReceivedInfo(client: HotlineFileDownloadClient, reference: UInt32, info: HotlineFileInfoFork) {
+
+ func hotlineFileDownloadReceivedInfo(
+ client: HotlineFileDownloadClient, reference: UInt32, info: HotlineFileInfoFork
+ ) {
if let transfer = self.transfers.first(where: { $0.id == reference }) {
transfer.title = info.name
}
}
-
+
func hotlineFilePreviewComplete(client: HotlineFilePreviewClient, reference: UInt32, data: Data) {
if let b = self.bannerClient, b.referenceNumber == reference {
#if os(macOS)
- self.bannerImage = NSImage(data: data)
+ self.bannerImage = NSImage(data: data)
#elseif os(iOS)
- self.bannerImage = UIImage(data: data)
+ self.bannerImage = UIImage(data: data)
#endif
- }
- else
- if let i = self.transfers.firstIndex(where: { $0.id == reference }) {
+ } 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 hotlineFileDownloadComplete(client: HotlineFileDownloadClient, reference: UInt32, at: URL) {
+
+ func hotlineFileDownloadComplete(client: HotlineFileDownloadClient, reference: UInt32, at: URL) {
if let i = self.transfers.firstIndex(where: { $0.id == reference }) {
let transfer = self.transfers[i]
transfer.fileURL = at
@@ -989,12 +1039,12 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
SoundEffectPlayer.shared.playSoundEffect(.transferComplete)
}
}
-
+
if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) {
self.downloads.remove(at: i)
}
}
-
+
func hotlineFileUploadComplete(client: HotlineFileUploadClient, reference: UInt32) {
if let i = self.transfers.firstIndex(where: { $0.id == reference }) {
let transfer = self.transfers[i]
@@ -1003,31 +1053,30 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
SoundEffectPlayer.shared.playSoundEffect(.transferComplete)
}
}
-
+
if let i = self.downloads.firstIndex(where: { $0.referenceNumber == reference }) {
self.downloads.remove(at: i)
}
}
-
+
// MARK: - Utilities
-
+
func updateServerTitle() {
self.serverTitle = self.serverName ?? self.server?.name ?? server?.address ?? "Server"
}
-
+
private func addOrUpdateHotlineUser(_ user: HotlineUser) {
print("Hotline: users: \n\(self.users)")
if let i = self.users.firstIndex(where: { $0.id == user.id }) {
print("Hotline: updating user \(self.users[i].name)")
self.users[i] = User(hotlineUser: user)
- }
- else {
+ } else {
if !self.users.isEmpty {
if Prefs.shared.playSounds && Prefs.shared.playJoinSound {
SoundEffectPlayer.shared.playSoundEffect(.userLogin)
}
}
-
+
print("Hotline: added user: \(user.name)")
self.users.append(User(hotlineUser: user))
if Prefs.shared.showJoinLeaveMessages {
@@ -1035,55 +1084,53 @@ class Hotline: Equatable, HotlineClientDelegate, HotlineFileDownloadClientDelega
}
}
}
-
+
private func findFile(in filesToSearch: [FileInfo], at path: [String]) -> FileInfo? {
guard !path.isEmpty, !filesToSearch.isEmpty else { return nil }
-
+
let currentName = path[0]
-
+
for file in filesToSearch {
if file.name == currentName {
if path.count == 1 {
return file
- }
- else if let subfiles = file.children {
+ } else if let subfiles = file.children {
let remainingPath = Array(path[1...])
return self.findFile(in: subfiles, at: remainingPath)
}
}
}
-
+
return nil
}
-
+
private func findNews(in newsToSearch: [NewsInfo], at path: [String]) -> NewsInfo? {
guard !path.isEmpty, !newsToSearch.isEmpty, let currentName = path.first else { return nil }
-
+
for news in newsToSearch {
if news.name == currentName {
if path.count == 1 {
return news
- }
- else if !news.children.isEmpty {
+ } else if !news.children.isEmpty {
let remainingPath = Array(path[1...])
return self.findNews(in: news.children, at: remainingPath)
}
}
}
-
+
return nil
}
-
+
private func findNewsArticle(id articleID: UInt32, at path: [String]) -> NewsInfo? {
guard let parent = self.findNews(in: self.news, at: path), !parent.children.isEmpty else {
return nil
}
-
+
return parent.children.first { child in
guard let childArticleID = child.articleID else {
return false
}
-
+
return child.type == .article && child.articleID == childArticleID
}
}