From 8b877890927d1841f07f83f6b15edc1a012f4cb3 Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Sun, 17 Dec 2023 12:17:33 -0700 Subject: stable iOS/macOS/iPadOS --- iGopherBrowser.xcodeproj/project.pbxproj | 8 + .../xcshareddata/swiftpm/Package.resolved | 9 + iGopherBrowser/BrowserView.swift | 294 ++++++++++++++++++++- iGopherBrowser/ContentView.swift | 274 ++----------------- iGopherBrowser/FileView.swift | 112 ++++++-- iGopherBrowser/Helpers.swift | 45 ++++ iGopherBrowser/SidebarView.swift | 13 +- 7 files changed, 473 insertions(+), 282 deletions(-) create mode 100644 iGopherBrowser/Helpers.swift diff --git a/iGopherBrowser.xcodeproj/project.pbxproj b/iGopherBrowser.xcodeproj/project.pbxproj index 4aad221..a5bc525 100644 --- a/iGopherBrowser.xcodeproj/project.pbxproj +++ b/iGopherBrowser.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 3E1BCC862B299E9F00A4CB69 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */; }; 3EFB9C0D2B2E4F06005EAD7C /* SearchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFB9C0C2B2E4F06005EAD7C /* SearchInputView.swift */; }; 3EFB9C0F2B2E6325005EAD7C /* FileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFB9C0E2B2E6325005EAD7C /* FileView.swift */; }; + 3EFB9C112B2E869C005EAD7C /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFB9C102B2E869C005EAD7C /* BrowserView.swift */; }; + 3EFB9C132B2E872A005EAD7C /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFB9C122B2E872A005EAD7C /* Helpers.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +32,8 @@ 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 3EFB9C0C2B2E4F06005EAD7C /* SearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchInputView.swift; sourceTree = ""; }; 3EFB9C0E2B2E6325005EAD7C /* FileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileView.swift; sourceTree = ""; }; + 3EFB9C102B2E869C005EAD7C /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = ""; }; + 3EFB9C122B2E872A005EAD7C /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,6 +78,8 @@ 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */, 3EFB9C0C2B2E4F06005EAD7C /* SearchInputView.swift */, 3EFB9C0E2B2E6325005EAD7C /* FileView.swift */, + 3EFB9C102B2E869C005EAD7C /* BrowserView.swift */, + 3EFB9C122B2E872A005EAD7C /* Helpers.swift */, ); path = iGopherBrowser; sourceTree = ""; @@ -171,6 +177,8 @@ files = ( 3EFB9C0F2B2E6325005EAD7C /* FileView.swift in Sources */, 3E1BCC582B297E9B00A4CB69 /* ContentView.swift in Sources */, + 3EFB9C112B2E869C005EAD7C /* BrowserView.swift in Sources */, + 3EFB9C132B2E872A005EAD7C /* Helpers.swift in Sources */, 3E1BCC862B299E9F00A4CB69 /* SidebarView.swift in Sources */, 3E1BCC5A2B297E9B00A4CB69 /* Item.swift in Sources */, 3E1BCC562B297E9B00A4CB69 /* iGopherBrowserApp.swift in Sources */, diff --git a/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 85a7d8c..8b9be02 100644 --- a/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -26,6 +26,15 @@ "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", "version" : "2.62.0" } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "ebf8b9c365a6ce043bf6e6326a04b15589bd285e", + "version" : "1.20.0" + } } ], "version" : 2 diff --git a/iGopherBrowser/BrowserView.swift b/iGopherBrowser/BrowserView.swift index 31e6dcb..6a6c221 100644 --- a/iGopherBrowser/BrowserView.swift +++ b/iGopherBrowser/BrowserView.swift @@ -6,13 +6,299 @@ // import SwiftUI +import swiftGopherClient +import GopherHelpers struct BrowserView: View { + + @State var url: String = "" + @State private var gopherItems: [gopherItem] = [] + + @Binding public var hosts: [GopherNode] + @Binding var selectedNode: GopherNode? + + @State private var backwardStack: [GopherNode] = [] + @State private var forwardStack: [GopherNode] = [] + + @State private var searchText: String = "" + @State private var showSearchInput = false + @State var selectedSearchItem: Int? + + let client = GopherClient() + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + NavigationStack { + VStack(spacing: 0) { + List { + ForEach(Array(gopherItems.enumerated()), id: \.offset) { idx, item in + if item.parsedItemType == .info { + Text(item.message) + .font(.system(size: 12, design: .monospaced)) + .frame(height: 20) + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) + } else if item.parsedItemType == .directory { + HStack { + Text(Image(systemName: "folder")) + Text(item.message) + Spacer() + }.onTapGesture { + performGopherRequest(host: item.host, port: item.port, selector: item.selector) + } + } else if item.parsedItemType == .search { + HStack { + Text(Image(systemName: "magnifyingglass")) + Text(item.message) + Spacer() + }.onTapGesture { + self.selectedSearchItem = idx + self.showSearchInput = true + } + } else if item.parsedItemType == .text { + NavigationLink(destination: FileView(item: item)) { + HStack { + Text(Image(systemName: "doc.plaintext")) + Text(item.message) + Spacer() + } + } + } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains(item.parsedItemType) { + NavigationLink(destination: FileView(item: item)) { + HStack { + Text(Image(systemName: itemToImageType(item))) + Text(item.message) + Spacer() + } + } + } else { + Text(item.message) + .onTapGesture { + performGopherRequest(host: item.host, port: item.port, selector: item.selector) + } + + } + } + } + .background(Color.white) + .cornerRadius(10) + .sheet(isPresented: $showSearchInput) { + if let index = selectedSearchItem, gopherItems.indices.contains(index) { + let searchItem = gopherItems[index] + SearchInputView( + host: searchItem.host, + port: searchItem.port, + selector: searchItem.selector, + searchText: $searchText, + onSearch: { query in + performGopherRequest(host: searchItem.host, port: searchItem.port, selector: "\(searchItem.selector)\t\(query)") + showSearchInput = false + } + ) + } else { + + Text("Weird bug. Please Dismiss -> Press Go -> Try Again") + } + } + #if os(iOS) + VStack { + HStack(spacing: 10) { + HStack { + Spacer() + + + + TextField("Enter a URL", text: $url) + #if !os(OSX) + .keyboardType(.URL) + .autocapitalization(.none) + #endif + .padding(10) + Spacer() + } + //.background(Color.white) + .cornerRadius(30) + + Button("Go", action: { + performGopherRequest(clearForward: false) + }) + .keyboardShortcut(.defaultAction) + .onSubmit { + performGopherRequest() + } + Spacer() + } + HStack { + Spacer() + Button { + performGopherRequest(host:"gopher.navan.dev",port: 70,selector: "/") + } label: { + Label("Home", systemImage: "house") + .labelStyle(.iconOnly) + } + Spacer() + Button { + if let curNode = backwardStack.popLast() { + forwardStack.append(curNode) + if let prevNode = backwardStack.popLast() { + performGopherRequest(host: prevNode.host, port: prevNode.port, selector: prevNode.selector, clearForward: false) + } + } + } label: { + Label("Back", systemImage: "chevron.left") + .labelStyle(.iconOnly) + } + .disabled(backwardStack.count < 2) + Spacer() + Button { + if let nextNode = forwardStack.popLast() { + //backwardStack.append(nextNode) + performGopherRequest(host: nextNode.host, port: nextNode.port, selector: nextNode.selector, clearForward: false) + } + } label: { + Label("Forward", systemImage: "chevron.right") + .labelStyle(.iconOnly) + } + .disabled(forwardStack.isEmpty) + Spacer() + } + } + #else + HStack(spacing: 10) { + HStack { + Spacer() + Button { + performGopherRequest(host:"gopher.navan.dev",port: 70,selector: "/") + } label: { + Label("Home", systemImage: "house") + .labelStyle(.iconOnly) + } + + Button { + if let curNode = backwardStack.popLast() { + forwardStack.append(curNode) + if let prevNode = backwardStack.popLast() { + performGopherRequest(host: prevNode.host, port: prevNode.port, selector: prevNode.selector, clearForward: false) + } + } + } label: { + Label("Back", systemImage: "chevron.left") + .labelStyle(.iconOnly) + } + .disabled(backwardStack.count < 2) + + Button { + if let nextNode = forwardStack.popLast() { + //backwardStack.append(nextNode) + performGopherRequest(host: nextNode.host, port: nextNode.port, selector: nextNode.selector, clearForward: false) + } + } label: { + Label("Forward", systemImage: "chevron.right") + .labelStyle(.iconOnly) + } + .disabled(forwardStack.isEmpty) + + + TextField("Enter a URL", text: $url) +#if !os(OSX) + .keyboardType(.URL) + .autocapitalization(.none) +#endif + .padding(10) + Spacer() + } + //.background(Color.white) + .cornerRadius(30) + + Button("Go", action: { + performGopherRequest(clearForward: false) + }) + .keyboardShortcut(.defaultAction) + .onSubmit { + performGopherRequest() + } + Spacer() + } + #endif + } + } + .onChange(of: selectedNode) { + if let node = selectedNode { + performGopherRequest(host: node.host, port: node.port, selector: node.selector) + } + } + } + + private func performGopherRequest(host: String = "", port: Int = -1, selector: String = "", clearForward: Bool = true) { + // TODO: Remove getHostandPort call here, and call it before calling performGopherRequest + print("recieved ", host, port, selector) + var res = getHostAndPort(from: self.url) + + if host != "" { + res.host = host + if selector != "" { + res.selector = selector + } else { + res.selector = "" + } + } + + if port != -1 { + res.port = port + } + + + + self.url = "\(res.host):\(res.port)\(res.selector)" + + client.sendRequest(to: res.host, port: res.port, message: "\(res.selector)\r\n") { result in + switch result { + case .success(let resp): + //print(resp) + var newNode = GopherNode(host: res.host, port: res.port, selector: selector, item: nil, children: convertToHostNodes(resp)) + backwardStack.append(newNode) + if clearForward { + forwardStack.removeAll() + } + print(newNode.selector) + if let index = self.hosts.firstIndex(where: { $0.host == res.host && $0.port == res.port }) { + // TODO: Handle case where first link visited is a subdirectory, should the sidebar auto fetch the rest? + print("parent already exists") + //hosts[index] = newNode + hosts[index].children = hosts[index].children?.map { child in + if child.selector == newNode.selector { + newNode.message = child.message + return newNode + } else { + return child + } + } + + } else { + newNode.selector = "/" + hosts.append(newNode) + print("created new") + } + self.gopherItems = resp + + + case .failure(let error): + print("Error \(error)") + var item = gopherItem(rawLine: "Error \(error)") + item.message = "Error \(error)" + self.gopherItems = [item] + } + } + } + + private func convertToHostNodes(_ responseItems: [gopherItem]) -> [GopherNode] { + var returnItems: [GopherNode] = [] + responseItems.forEach { item in + if item.parsedItemType != .info { + returnItems.append(GopherNode(host: item.host, port: item.port, selector: item.selector, message: item.message, item: item, children: nil)) + //print("found: \(item.message)") + } + } + return returnItems } -} -#Preview { - BrowserView() } diff --git a/iGopherBrowser/ContentView.swift b/iGopherBrowser/ContentView.swift index cbf1247..26dd941 100644 --- a/iGopherBrowser/ContentView.swift +++ b/iGopherBrowser/ContentView.swift @@ -9,8 +9,13 @@ import SwiftUI import SwiftData import swiftGopherClient +import GopherHelpers -struct GopherNode: Identifiable { +struct GopherNode: Identifiable, Equatable { + static func == (lhs: GopherNode, rhs: GopherNode) -> Bool { + return lhs.host == rhs.host && lhs.port == rhs.port && lhs.selector == rhs.selector + } + let id = UUID() var host: String let port: Int @@ -24,280 +29,35 @@ struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] - @State var url: String = "" - @State private var gopherItems: [gopherItem] = [] @State public var hosts: [GopherNode] = [] + @State private var selectedNode: GopherNode? - @State private var backwardStack: [GopherNode] = [] - @State private var forwardStack: [GopherNode] = [] - - @State private var searchText: String = "" - @State private var showSearchInput = false - @State var selectedSearchItem: Int? - - let client = GopherClient() + @State private var columnVisibility = NavigationSplitViewVisibility.automatic var body: some View { - NavigationSplitView { -#if os(iOS) -#else + + #if os(iOS) + BrowserView(hosts: $hosts, selectedNode: $selectedNode) + #else + + NavigationSplitView(columnVisibility: $columnVisibility) { SidebarView(hosts: hosts, onSelect: { node in - performGopherRequest(host: node.host, port: node.port, selector: node.selector) + selectedNode = node }) .listStyle(.sidebar) -#endif } detail: { - - ZStack(alignment: .bottom) { - - - - VStack(spacing: 0) { - List { - ForEach(Array(gopherItems.enumerated()), id: \.offset) { idx, item in - if item.parsedItemType == .info { - Text(item.message) - .font(.system(size: 12, design: .monospaced)) - .frame(height: 20) - .listRowInsets(EdgeInsets()) - .listRowSeparator(.hidden) - } else if item.parsedItemType == .directory { - HStack { - Text(Image(systemName: "folder")) - Text(item.message) - Spacer() - }.onTapGesture { - performGopherRequest(host: item.host, port: item.port, selector: item.selector) - } - } else if item.parsedItemType == .search { - HStack { - Text(Image(systemName: "magnifyingglass")) - Text(item.message) - Spacer() - }.onTapGesture { - self.selectedSearchItem = idx - self.showSearchInput = true - } - } else if item.parsedItemType == .text { - NavigationLink(destination: FileView(item: item)) { - HStack { - Text(Image(systemName: "doc.text")) - Text(item.message) - Spacer() - } - } - } - else { - Text(item.message) - .onTapGesture { - performGopherRequest(host: item.host, port: item.port, selector: item.selector) - } - - } - } - } - .background(Color.white) - .cornerRadius(10) - .sheet(isPresented: $showSearchInput) { - if let index = selectedSearchItem, gopherItems.indices.contains(index) { - let searchItem = gopherItems[index] - SearchInputView( - host: searchItem.host, - port: searchItem.port, - selector: searchItem.selector, - searchText: $searchText, - onSearch: { query in - performGopherRequest(host: searchItem.host, port: searchItem.port, selector: "\(searchItem.selector)\t\(query)") - showSearchInput = false - } - ) - } else { - - Text("Search is Broken.") - } - } - HStack(spacing: 10) { - HStack { - Spacer() - Button { - performGopherRequest(host:"gopher.navan.dev",port: 70,selector: "/") - } label: { - Label("Home", systemImage: "house") - .labelStyle(.iconOnly) - } - - Button { - if let curNode = backwardStack.popLast() { - forwardStack.append(curNode) - if let prevNode = backwardStack.popLast() { - performGopherRequest(host: prevNode.host, port: prevNode.port, selector: prevNode.selector, clearForward: false) - } - } - } label: { - Label("Back", systemImage: "chevron.left") - .labelStyle(.iconOnly) - } - .disabled(backwardStack.count < 2) - - Button { - if let nextNode = forwardStack.popLast() { - //backwardStack.append(nextNode) - performGopherRequest(host: nextNode.host, port: nextNode.port, selector: nextNode.selector, clearForward: false) - } - } label: { - Label("Forward", systemImage: "chevron.right") - .labelStyle(.iconOnly) - } - .disabled(forwardStack.isEmpty) - - - TextField("Enter a URL", text: $url) -#if os(iOS) - .keyboardType(.URL) - .autocapitalization(.none) -#endif - .padding(10) - Spacer() - } - //.background(Color.white) - .cornerRadius(30) - - Button("Go", action: { - performGopherRequest(clearForward: false) - }) - .keyboardShortcut(.defaultAction) - .onSubmit { - performGopherRequest() - } - Spacer() - } - } - } - }.toolbar { - ToolbarItem(placement: .navigation) { - Button(action: toggleSidebar, label: { - Image(systemName: "sidebar.leading") - }) - } + BrowserView(hosts: $hosts, selectedNode: $selectedNode) } - } - - private func toggleSidebar() { - #if os(iOS) - #else - NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) #endif } - public func getHostAndPort(from urlString: String, defaultPort: Int = 70, defaultHost: String = "gopher.navan.dev") -> (host: String, port: Int, selector: String) { - if let urlComponents = URLComponents(string: urlString), - let host = urlComponents.host { - let port = urlComponents.port ?? defaultPort - let selector = urlComponents.path - print("Mainmain, ", urlComponents, host, port, selector) - return (host, port, selector) - } else { - // Fallback for simpler formats like "localhost:8080" - let components = urlString.split(separator: ":") - let host = components.first.map(String.init) ?? defaultHost - - var port = (components.count > 1 ? Int(components[1]) : nil) ?? defaultPort - var selector = "/" - - if (components.count > 1) { - let portCompString = components[1] - let portCompComponents = portCompString.split(separator: "/", maxSplits: 1) - if portCompComponents.count > 1 { - port = Int(portCompComponents[0]) ?? defaultPort - selector = "/" + portCompComponents[1] - - } - } - - - print("Else Else",components, host, port, selector) - return (host, port, selector) - } - } - private func performGopherRequest(host: String = "", port: Int = -1, selector: String = "", clearForward: Bool = true) { - // TODO: Remove getHostandPort call here, and call it before calling performGopherRequest - print("recieved ", host, port, selector) - var res = getHostAndPort(from: self.url) - - if host != "" { - res.host = host - if selector != "" { - res.selector = selector - } else { - res.selector = "" - } - } - - if port != -1 { - res.port = port - } - - - - self.url = "\(res.host):\(res.port)\(res.selector)" - - client.sendRequest(to: res.host, port: res.port, message: "\(res.selector)\r\n") { result in - DispatchQueue.main.async { - switch result { - case .success(let resp): - //print(resp) - var newNode = GopherNode(host: res.host, port: res.port, selector: selector, item: nil, children: convertToHostNodes(resp)) - backwardStack.append(newNode) - if clearForward { - forwardStack.removeAll() - } - print(newNode.selector) - if let index = self.hosts.firstIndex(where: { $0.host == res.host && $0.port == res.port }) { - // TODO: Handle case where first link visited is a subdirectory, should the sidebar auto fetch the rest? - print("parent already exists") - //hosts[index] = newNode - hosts[index].children = hosts[index].children?.map { child in - if child.selector == newNode.selector { - newNode.message = child.message - return newNode - } else { - return child - } - } - - } else { - newNode.selector = "/" - hosts.append(newNode) - print("created new") - } - self.gopherItems = resp - - - case .failure(let error): - print("Error \(error)") - var item = gopherItem(rawLine: "Error \(error)") - item.message = "Error \(error)" - self.gopherItems = [item] - } - } - } - } } -private func convertToHostNodes(_ responseItems: [gopherItem]) -> [GopherNode] { - var returnItems: [GopherNode] = [] - responseItems.forEach { item in - if item.parsedItemType != .info { - returnItems.append(GopherNode(host: item.host, port: item.port, selector: item.selector, message: item.message, item: item, children: nil)) - //print("found: \(item.message)") - } - } - return returnItems -} + #Preview { ContentView() diff --git a/iGopherBrowser/FileView.swift b/iGopherBrowser/FileView.swift index 52e2081..2d96526 100644 --- a/iGopherBrowser/FileView.swift +++ b/iGopherBrowser/FileView.swift @@ -6,14 +6,53 @@ // import Foundation import SwiftUI - import swiftGopherClient +import GopherHelpers +import QuickLook + + +func determineFileType(data: Data) -> String? { + let signatures: [Data: String] = [ + Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]): "png", + Data([0xFF, 0xD8, 0xFF]): "jpeg", + Data("GIF87a".utf8): "gif", + Data("GIF89a".utf8): "gif", + Data("BM".utf8): "bmp", + Data("%PDF-".utf8): "pdf", + Data([0x50, 0x4B, 0x03, 0x04]): "docx", + Data([0x50, 0x4B, 0x05, 0x06]): "docx", + Data([0x50, 0x4B, 0x07, 0x08]): "docx", + Data([0x49, 0x44, 0x33]): "mp3", + Data([0x52, 0x49, 0x46, 0x46]): "wav", + Data([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70]): "mp4", + Data([0x6D, 0x6F, 0x6F, 0x76]): "mov", + // Add other file signatures as needed + ] + + // Check for each signature + for (signature, fileType) in signatures { + if data.starts(with: signature) { + return fileType + } + } + + return nil +} + struct FileView: View { var item: gopherItem let client = GopherClient() @State private var fileContent: String = "Loading..." - @Environment(\.dismiss) var dismiss + @State private var fileURL: URL? + @State private var QLURL: URL? + + let fileSignatures: [String: [UInt8]] = [ + "jpg": [0xFF, 0xD8, 0xFF], + "png": [0x89, 0x50, 0x4E, 0x47], + "gif": [0x47, 0x49, 0x46], + "pdf": [0x25, 0x50, 0x44, 0x46], + ] var body: some View { if item.parsedItemType == .text { @@ -22,36 +61,77 @@ struct FileView: View { .onAppear { readFile(item) } - } .toolbar { - ToolbarItem() { - Button(action: { - dismiss() - }) { - Label("Back", systemImage: "arrow.left.circle") - } - } } + } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains(item.parsedItemType) { // Preview Document: .pdf, .docx, e.t.c + // Quicklook + if let url = fileURL { + Button("Preview Document") { + print(url) + QLURL = url + }.quickLookPreview($QLURL) + } else { + Text("Loading Document...") + .onAppear { + readFile(item) + } + } } } private func readFile(_ item: gopherItem) { // Execute the network request on a background thread - DispatchQueue.global(qos: .userInitiated).async { self.client.sendRequest(to: item.host, port: item.port, message: "\(item.selector)\r\n") { result in // Dispatch the result handling back to the main thread - DispatchQueue.main.async { + switch result { case .success(let resp): - if let firstLine = resp.first?.rawLine { - self.fileContent = firstLine + if item.parsedItemType == .text { + if let firstLine = resp.first?.rawLine { + self.fileContent = firstLine + } else { + self.fileContent = "File is empty or couldn't be read." + } } else { - self.fileContent = "File is empty or couldn't be read." + if var data = resp.first?.rawData { + let tempDirURL = FileManager.default.temporaryDirectory + + do { + var fileData = Data() + print("Readable byees", data.readableBytes) + while data.readableBytes > 0 { + if let bytes = data.readBytes(length: data.readableBytes) { + fileData.append(contentsOf: bytes) + } + } + + let fileURL = tempDirURL.appendingPathComponent(UUID().uuidString + ".\(determineFileType(data: fileData) ?? "unkown")") + print(fileURL) + try fileData.write(to: fileURL) + self.fileURL = fileURL + } catch { + print("Error writing file to temp directory: \(error)") + } + } } case .failure(_): self.fileContent = "Unable to fetch file due to network error." } } - } + + + } + + private func getTempFileURL(_ data: [UInt8]) -> URL? { + let tempDirURL = FileManager.default.temporaryDirectory + let fileURL = tempDirURL.appendingPathComponent(UUID().uuidString + ".pdf") + print(fileURL) + do { + let fileData = Data(data) + try fileData.write(to: fileURL) + return fileURL + } catch { + print("Error writing file to temp directory: \(error)") + return nil } } } diff --git a/iGopherBrowser/Helpers.swift b/iGopherBrowser/Helpers.swift new file mode 100644 index 0000000..ef42905 --- /dev/null +++ b/iGopherBrowser/Helpers.swift @@ -0,0 +1,45 @@ +// +// Helpers.swift +// iGopherBrowser +// +// Created by Navan Chauhan on 12/16/23. +// + +import Foundation +import swiftGopherClient + +public func getHostAndPort(from urlString: String, defaultPort: Int = 70, defaultHost: String = "gopher.navan.dev") -> (host: String, port: Int, selector: String) { + if let urlComponents = URLComponents(string: urlString), + let host = urlComponents.host { + let port = urlComponents.port ?? defaultPort + let selector = urlComponents.path + print("Mainmain, ", urlComponents, host, port, selector) + return (host, port, selector) + } else { + // Fallback for simpler formats like "localhost:8080" + let components = urlString.split(separator: ":") + let host = components.first.map(String.init) ?? defaultHost + + var port = (components.count > 1 ? Int(components[1]) : nil) ?? defaultPort + var selector = "/" + + if (components.count > 1) { + let portCompString = components[1] + let portCompComponents = portCompString.split(separator: "/", maxSplits: 1) + if portCompComponents.count > 1 { + port = Int(portCompComponents[0]) ?? defaultPort + selector = "/" + portCompComponents[1] + + } + } + + + print("Else Else",components, host, port, selector) + return (host, port, selector) + } +} + + + + + diff --git a/iGopherBrowser/SidebarView.swift b/iGopherBrowser/SidebarView.swift index 61c1360..301f45e 100644 --- a/iGopherBrowser/SidebarView.swift +++ b/iGopherBrowser/SidebarView.swift @@ -13,11 +13,14 @@ struct SidebarView: View { var onSelect: (GopherNode) -> Void var body: some View { - List(hosts, children: \.children) { node in - Text(node.message ?? node.host) - .onTapGesture { - onSelect(node) - } + VStack { + List(hosts, children: \.children) { node in + Text(node.message ?? node.host) + .onTapGesture { + onSelect(node) + } } + } + .navigationTitle("Your Gophertree") } } -- cgit v1.2.3