diff options
| author | Dustin Mierau <dustin@mierau.me> | 2023-12-07 09:20:15 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2023-12-07 09:20:15 -0800 |
| commit | 154763b8e2b14ff2a92d5a1f1f3e14a18167e6fc (patch) | |
| tree | 11217bdecc78f47a0dfc597c90a6ff882b13cbde | |
| parent | e93eea234f294551c20787718cead4877429a5b7 (diff) | |
Some work on file display.
| -rw-r--r-- | Hotline/Hotline/HotlineClient.swift | 61 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineProtocol.swift | 46 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineTrackerClient.swift | 21 | ||||
| -rw-r--r-- | Hotline/Utility/FoundationExtensions.swift | 12 | ||||
| -rw-r--r-- | Hotline/Views/FilesView.swift | 141 | ||||
| -rw-r--r-- | Hotline/Views/TrackerView.swift | 4 |
6 files changed, 169 insertions, 116 deletions
diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift index c325fb7..59a1f9d 100644 --- a/Hotline/Hotline/HotlineClient.swift +++ b/Hotline/Hotline/HotlineClient.swift @@ -9,36 +9,6 @@ enum HotlineClientStatus: Int { case loggedIn } -enum HotlineChatType: Int { - case message - case agreement - case status -} - -struct HotlineChat: Identifiable { - let id = UUID() - let text: String - let username: String - let type: HotlineChatType - - static let parser = /^\s*([^\:]+)\:\s*(.*)/ - - init(text: String, type: HotlineChatType = .message) { - self.type = type - - if - type == .message, - let match = text.firstMatch(of: HotlineChat.parser) { - self.username = String(match.1) - self.text = String(match.2) - } - else { - self.username = "" - self.text = text - } - } -} - @Observable class HotlineClient { // static let shared = HotlineClient() @@ -64,7 +34,7 @@ class HotlineClient { var server: HotlineServer? @ObservationIgnored private var connection: NWConnection? - @ObservationIgnored private var transactionLog: [UInt32:HotlineTransactionType] = [:] + @ObservationIgnored private var transactionLog: [UInt32:(HotlineTransactionType, (() -> Void)?)] = [:] init() { // let downloadsPath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask) @@ -136,14 +106,14 @@ class HotlineClient { // MARK: - - private func sendTransaction(_ t: HotlineTransaction, autodisconnect disconnectOnError: Bool = true, callback: (() -> Void)? = nil) { + private func sendTransaction(_ t: HotlineTransaction, autodisconnect disconnectOnError: Bool = true, callback: (() -> Void)? = nil, reply: (() -> Void)? = nil) { guard let c = connection else { return } print("HotlineClient => \(t.id) \(t.type)") - self.transactionLog[t.id] = t.type + self.transactionLog[t.id] = (t.type, reply) c.send(content: t.encoded(), completion: .contentProcessed { [weak self] (error) in if disconnectOnError, error != nil { @@ -155,6 +125,10 @@ class HotlineClient { }) } + private func sendTransaction(_ t: HotlineTransaction, autodisconnect disconnectOnError: Bool = true, callback: (() -> Void)? = nil) { + sendTransaction(t, autodisconnect: disconnectOnError, callback: callback, reply: nil) + } + private func receiveTransaction() { guard let c = connection else { print("HotlineClient: no connection for transaction.") @@ -379,12 +353,16 @@ class HotlineClient { self.sendTransaction(t, callback: callback) } - func sendGetFileList(path: String? = nil, callback: (() -> Void)? = nil) { - let t = HotlineTransaction(type: .getFileNameList) + func sendGetFileList(path: [String] = [], callback: (() -> Void)? = nil, reply: (() -> Void)?) { + var t = HotlineTransaction(type: .getFileNameList) + + if !path.isEmpty { + t.setFieldPath(type: .filePath, val: path) + } // if let p = path { // t.setFieldString(type: .filePath) // } - self.sendTransaction(t, callback: callback) + self.sendTransaction(t, callback: callback, reply: reply) } // func sendGetNews(callback: (() -> Void)? = nil) { @@ -409,11 +387,18 @@ class HotlineClient { return } + defer { + let replyCallback = repliedTransactionType.1 + DispatchQueue.main.async { + replyCallback?() + } + } + self.transactionLog[transaction.id] = nil print("HotlineClient reply in response to \(repliedTransactionType)") - switch(repliedTransactionType) { + switch(repliedTransactionType.0) { case .login: print("GOT REPLY TO LOGIN!") @@ -483,6 +468,8 @@ class HotlineClient { default: break } + + } private func processTransaction(_ transaction: HotlineTransaction) { diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift index a0e129e..92d7449 100644 --- a/Hotline/Hotline/HotlineProtocol.swift +++ b/Hotline/Hotline/HotlineProtocol.swift @@ -17,15 +17,33 @@ struct HotlineServer: Identifiable, Hashable { } } -extension UInt32 { - func decodeFromUInt32() -> String { - let bytes = [ - UInt8((self >> 24) & 0xFF), - UInt8((self >> 16) & 0xFF), - UInt8((self >> 8) & 0xFF), - UInt8(self & 0xFF) - ] - return String(bytes: bytes, encoding: .utf8) ?? "" +enum HotlineChatType: Int { + case message + case agreement + case status +} + +struct HotlineChat: Identifiable { + let id = UUID() + let text: String + let username: String + let type: HotlineChatType + + static let parser = /^\s*([^\:]+)\:\s*(.*)/ + + init(text: String, type: HotlineChatType = .message) { + self.type = type + + if + type == .message, + let match = text.firstMatch(of: HotlineChat.parser) { + self.username = String(match.1) + self.text = String(match.2) + } + else { + self.username = "" + self.text = text + } } } @@ -109,8 +127,8 @@ struct HotlineFile: Identifiable, Hashable { let typeRaw = data.readUInt32(at: 0)! let creatorRaw = data.readUInt32(at: 4)! - self.type = typeRaw.decodeFromUInt32() - self.creator = creatorRaw.decodeFromUInt32() + self.type = typeRaw.toStringLiteral() + self.creator = creatorRaw.toStringLiteral() self.fileSize = data.readUInt32(at: 8)! self.isFolder = (self.type == "fldr") @@ -435,6 +453,7 @@ enum HotlineTransactionFieldType: UInt16 { case communityBannerID = 161 // Integer case serverName = 162 // String case fileNameWithInfo = 200 // Data { type: 4, creator: 4, file size: 4, reserved: 4, name script: 2, name size: 2, name data: size } + case filePath = 202 // Path // TODO: Add file field types case quotingMessage = 214 // String? case automaticResponse = 215 // String @@ -460,6 +479,11 @@ enum HotlineTransactionFieldType: UInt16 { case newsArticleRecursiveDelete = 337 // Integer } +func transactionTypeHasReply(_ type: HotlineTransactionType) -> Bool { + + return false +} + enum HotlineTransactionType: UInt16 { case reply = 0 case error = 100 diff --git a/Hotline/Hotline/HotlineTrackerClient.swift b/Hotline/Hotline/HotlineTrackerClient.swift index 78f681f..4a24520 100644 --- a/Hotline/Hotline/HotlineTrackerClient.swift +++ b/Hotline/Hotline/HotlineTrackerClient.swift @@ -255,31 +255,10 @@ class HotlineTrackerClient { if let name = serverName, let desc = serverDescription { let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) - - print("SERVER: \(server)") - foundServers.append(server) - - } cursor += 10 + nameByteCount + descByteCount - -// let nameLength = Int(nameLengthByte) -// if let name = self.bytes.readString(at: cursor + 11, length: nameLength) { -// if let descLengthByte = self.bytes.readUInt8(at: cursor + 11 + nameLength) { -// let descLength = Int(descLengthByte) -// if let desc = self.bytes.readString(at: cursor + 11 + nameLength + 1, length: descLength) { -// let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) -// -// print("SERVER: \(server)") -// -// foundServers.append(server) -// -// cursor += 11 + nameLength + 1 + descLength -// } -// } -// } } print(cursor) diff --git a/Hotline/Utility/FoundationExtensions.swift b/Hotline/Utility/FoundationExtensions.swift index c6ca251..000314e 100644 --- a/Hotline/Utility/FoundationExtensions.swift +++ b/Hotline/Utility/FoundationExtensions.swift @@ -94,3 +94,15 @@ extension Data { // append(&val, count: MemoryLayout<UInt32>.size) } } + +extension UInt32 { + func toStringLiteral() -> String { + let bytes = [ + UInt8((self >> 24) & 0xFF), + UInt8((self >> 16) & 0xFF), + UInt8((self >> 8) & 0xFF), + UInt8(self & 0xFF) + ] + return String(bytes: bytes, encoding: .utf8) ?? "" + } +} diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift index 1395ca6..52963e0 100644 --- a/Hotline/Views/FilesView.swift +++ b/Hotline/Views/FilesView.swift @@ -1,22 +1,25 @@ import SwiftUI import UniformTypeIdentifiers -struct FilesView: View { +struct FileListView: View { @Environment(HotlineClient.self) private var hotline @State private var fetched = false + @State var fileList: [HotlineFile] = [] + + var path: [String] = [] static let byteFormatter = ByteCountFormatter() private func formattedFileSize(_ fileSize: UInt32) -> String { -// let bcf = ByteCountFormatter() - FilesView.byteFormatter.allowedUnits = [.useAll] - FilesView.byteFormatter.countStyle = .file - return FilesView.byteFormatter.string(fromByteCount: Int64(fileSize)) + // let bcf = ByteCountFormatter() + FileListView.byteFormatter.allowedUnits = [.useAll] + FileListView.byteFormatter.countStyle = .file + return FileListView.byteFormatter.string(fromByteCount: Int64(fileSize)) } private func fileIcon(name: String) -> UIImage { -// func utTypeForFilename(_ filename: String) -> UTType? { + // func utTypeForFilename(_ filename: String) -> UTType? { let fileExtension = (name as NSString).pathExtension if let fileType = UTType(filenameExtension: fileExtension) { print("\(name) \(fileExtension) = \(fileType)") @@ -42,57 +45,103 @@ struct FilesView: View { } var body: some View { - NavigationStack { - List(hotline.fileList, id: \.self, children: \.files) { tree in - if tree.isFolder { - DisclosureGroup { - HStack { - HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { - Image(systemName: "folder.fill") - } - .frame(minWidth: 25) - Text("File").fontWeight(.medium) - Spacer() - Text("4").foregroundStyle(.secondary) -// HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { -// } -// .frame(minWidth: 25) -// ProgressView(value: 0.8) - } - } label: { - HStack { - HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { - Image(systemName: "folder.fill") - } - .frame(minWidth: 25) - Text(tree.name).fontWeight(.medium) - Spacer() - Text("\(tree.fileSize)").foregroundStyle(.secondary) - } + List { + ForEach(fileList, id: \.self) { file in + if file.isFolder { + DisclosureGroup { + if !fetched { + ProgressView() } - } - else { + else { + FileListView(path: []) + } + } label: { HStack { HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { - Image(uiImage: fileIcon(name: tree.name)) - .renderingMode(.template) - .foregroundColor(.accentColor) + Image(systemName: "folder.fill") } .frame(minWidth: 25) - Text(tree.name) + Text(file.name).fontWeight(.medium) Spacer() - Text(formattedFileSize(tree.fileSize)).foregroundStyle(.gray) + Text("\(file.fileSize)").foregroundStyle(.secondary) } } - } - .listStyle(.plain) - .task { - if !fetched { - hotline.sendGetFileList() { - fetched = true + } + else { + HStack { + HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { + Image(uiImage: fileIcon(name: file.name)) + .renderingMode(.template) + .foregroundColor(.accentColor) + } + .frame(minWidth: 25) + Text(file.name) + Spacer() + Text(formattedFileSize(file.fileSize)).foregroundStyle(.gray) } } } + } + .listStyle(.plain) + .task { + if !fetched { + hotline.sendGetFileList() { + print("FETCHED!") + fetched = true + } reply: { + print("GOT FILES REPLY?") + fileList = hotline.fileList + } + } + } + } +} + +struct FilesView: View { + @Environment(HotlineClient.self) private var hotline + + @State private var fetched = false + +// let fileList: [HotlineFile] + +// static let byteFormatter = ByteCountFormatter() +// +// private func formattedFileSize(_ fileSize: UInt32) -> String { +// // let bcf = ByteCountFormatter() +// FilesView.byteFormatter.allowedUnits = [.useAll] +// FilesView.byteFormatter.countStyle = .file +// return FilesView.byteFormatter.string(fromByteCount: Int64(fileSize)) +// } +// +// private func fileIcon(name: String) -> UIImage { +// // func utTypeForFilename(_ filename: String) -> UTType? { +// let fileExtension = (name as NSString).pathExtension +// if let fileType = UTType(filenameExtension: fileExtension) { +// print("\(name) \(fileExtension) = \(fileType)") +// +// if fileType.isSubtype(of: .movie) { +// return UIImage(systemName: "play.rectangle")! +// } +// else if fileType.isSubtype(of: .image) { +// return UIImage(systemName: "photo")! +// } +// else if fileType.isSubtype(of: .archive) { +// return UIImage(systemName: "doc.zipper")! +// } +// else if fileType.isSubtype(of: .text) { +// return UIImage(systemName: "doc.text")! +// } +// else { +// return UIImage(systemName: "doc")! +// } +// } +// +// return UIImage(systemName: "doc")! +// } + + var body: some View { + NavigationStack { + FileListView(path: []) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift index 7f59118..c8d0f2e 100644 --- a/Hotline/Views/TrackerView.swift +++ b/Hotline/Views/TrackerView.swift @@ -92,7 +92,9 @@ struct TrackerView: View { Text("\(server.address)").opacity(0.3).font(.system(size: 13)) } Spacer() - Text("\(server.users)").opacity(0.3).font(.system(size: 16)).fontWeight(.medium) + if server.users > 0 { + Text("\(server.users)").opacity(0.3).font(.system(size: 16)).fontWeight(.medium) + } } if server == selectedServer { Spacer(minLength: 16) |