aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2023-12-17 12:17:33 -0700
committerNavan Chauhan <navanchauhan@gmail.com>2023-12-17 12:17:33 -0700
commit8b877890927d1841f07f83f6b15edc1a012f4cb3 (patch)
tree83c95bdca67d8ad33efde25960e4267ccb2781b0
parentdd94f794224e9527ef410956934e9fc70afe53d9 (diff)
stable iOS/macOS/iPadOS
-rw-r--r--iGopherBrowser.xcodeproj/project.pbxproj8
-rw-r--r--iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved9
-rw-r--r--iGopherBrowser/BrowserView.swift294
-rw-r--r--iGopherBrowser/ContentView.swift274
-rw-r--r--iGopherBrowser/FileView.swift112
-rw-r--r--iGopherBrowser/Helpers.swift45
-rw-r--r--iGopherBrowser/SidebarView.swift13
7 files changed, 473 insertions, 282 deletions
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 = "<group>"; };
3EFB9C0C2B2E4F06005EAD7C /* SearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchInputView.swift; sourceTree = "<group>"; };
3EFB9C0E2B2E6325005EAD7C /* FileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileView.swift; sourceTree = "<group>"; };
+ 3EFB9C102B2E869C005EAD7C /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = "<group>"; };
+ 3EFB9C122B2E872A005EAD7C /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@@ -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")
}
}