aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-05-23 12:50:16 -0700
committerDustin Mierau <dustin@mierau.me>2024-05-23 12:50:16 -0700
commit23b3f338f0818a53b2499692e999a0528e5d9753 (patch)
tree19f2d018824cdc41c0aa37dde6bde5d9054be9f2 /Hotline
parent842611754debec7d1814e44e6a2cf302e84c0d66 (diff)
Add Bookmark exporting to Server window context menu.
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Application-macOS.swift3
-rw-r--r--Hotline/Info.plist14
-rw-r--r--Hotline/Models/Bookmark.swift100
-rw-r--r--Hotline/Utility/BookmarkDocument.swift32
-rw-r--r--Hotline/macOS/TrackerView.swift26
5 files changed, 153 insertions, 22 deletions
diff --git a/Hotline/Application-macOS.swift b/Hotline/Application-macOS.swift
index 016cb60..9aec187 100644
--- a/Hotline/Application-macOS.swift
+++ b/Hotline/Application-macOS.swift
@@ -22,9 +22,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
AppLaunchState.shared.launchState = .launched
- let blah = NSFileTypeForHFSTypeCode("HTbm".fourCharCode())
- print("FILETYPE: \(blah)", blah!, "'HTbm'")
-
CKContainer.default().accountStatus { status, error in
switch status {
case .noAccount:
diff --git a/Hotline/Info.plist b/Hotline/Info.plist
index ac7a2c9..b43d991 100644
--- a/Hotline/Info.plist
+++ b/Hotline/Info.plist
@@ -8,10 +8,10 @@
<key>CFBundleTypeName</key>
<string>Hotline Bookmark</string>
<key>LSHandlerRank</key>
- <string>Alternate</string>
+ <string>None</string>
<key>LSItemContentTypes</key>
<array>
- <string>'HTbm'</string>
+ <string>&apos;HTbm&apos;</string>
</array>
</dict>
</array>
@@ -38,25 +38,21 @@
</array>
</dict>
</array>
- <key>UTImportedTypeDeclarations</key>
+ <key>UTExportedTypeDeclarations</key>
<array>
<dict>
- <key>UTTypeConformsTo</key>
- <array/>
<key>UTTypeDescription</key>
<string>Hotline Bookmark</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
- <string>'HTbm'</string>
+ <string>&apos;HTbm&apos;</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
- <string>.htbm</string>
+ <string>.hlbm</string>
</array>
- <key>public.mime-type</key>
- <array/>
</dict>
</dict>
</array>
diff --git a/Hotline/Models/Bookmark.swift b/Hotline/Models/Bookmark.swift
index 234a196..115e5a9 100644
--- a/Hotline/Models/Bookmark.swift
+++ b/Hotline/Models/Bookmark.swift
@@ -96,16 +96,11 @@ final class Bookmark {
self.serverDescription = server.description
}
- init?(fileURL bookmarkFileURL: URL) {
- guard let fileAttributes = try? FileManager.default.attributesOfItem(atPath: bookmarkFileURL.path(percentEncoded: false)),
- let fileSize = fileAttributes[FileAttributeKey.size] as? UInt64,
- fileSize <= 2000,
- let fileData = try? Data(contentsOf: bookmarkFileURL) else {
+ init?(fileData: Data, name: String? = nil) {
+ guard fileData.count <= 2000 else {
return nil
}
-
- print("Bookmark: Parsing Hotline bookmark file...")
-
+
var fileDataArray: [UInt8] = [UInt8](fileData)
guard let headerValue = fileDataArray.consumeUInt32(),
@@ -128,13 +123,100 @@ final class Bookmark {
let (addressHost, addressPort) = Server.parseServerAddressAndPort(addressString)
self.type = .server
- self.name = addressHost
+ if let name = name, !name.isEmpty {
+ self.name = name
+ }
+ else {
+ self.name = addressHost
+ }
self.address = addressHost
self.port = addressPort
self.login = loginString.isEmpty ? nil : loginString
self.password = passwordString.isEmpty ? nil : passwordString
}
+ convenience init?(fileURL bookmarkFileURL: URL) {
+ guard bookmarkFileURL.isFileURL,
+ let fileAttributes = try? FileManager.default.attributesOfItem(atPath: bookmarkFileURL.path(percentEncoded: false)),
+ let fileSize = fileAttributes[FileAttributeKey.size] as? UInt64,
+ fileSize <= 2000,
+ let fileData = try? Data(contentsOf: bookmarkFileURL) else {
+ return nil
+ }
+
+ print("Bookmark: Parsing Hotline bookmark file...")
+
+ let fileName = bookmarkFileURL.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
+
+ self.init(fileData: fileData, name: fileName)
+
+// var fileDataArray: [UInt8] = [UInt8](fileData)
+//
+// guard let headerValue = fileDataArray.consumeUInt32(),
+// headerValue.fourCharCode() == "HTsc",
+// let versionNumber = fileDataArray.consumeUInt16(),
+// versionNumber == 1,
+// fileDataArray.consume(128), // Skip 128 reserved bytes.
+// let loginLength = fileDataArray.consumeUInt16(),
+// let loginData: Data = fileDataArray.consumeBytes(32),
+// let passwordLength = fileDataArray.consumeUInt16(),
+// let passwordData: Data = fileDataArray.consumeBytes(32),
+// let addressLength = fileDataArray.consumeUInt16(),
+// let addressData: Data = fileDataArray.consumeBytes(256),
+// let addressString = String(data: addressData[0..<Int(addressLength)], encoding: .ascii),
+// let loginString = String(data: loginData[0..<Int(loginLength)], encoding: .ascii),
+// let passwordString = String(data: passwordData[0..<Int(passwordLength)], encoding: .ascii) else {
+// return nil
+// }
+//
+// let (addressHost, addressPort) = Server.parseServerAddressAndPort(addressString)
+//
+// self.type = .server
+// self.name = fileName.isEmpty ? addressHost : fileName
+// self.address = addressHost
+// self.port = addressPort
+// self.login = loginString.isEmpty ? nil : loginString
+// self.password = passwordString.isEmpty ? nil : passwordString
+ }
+
+ func bookmarkFileData() -> Data? {
+ guard let addressData = self.displayAddress.data(using: .ascii) else {
+ return nil
+ }
+
+ let loginData: Data = self.login?.data(using: .ascii) ?? Data()
+ let passwordData: Data = self.password?.data(using: .ascii) ?? Data()
+
+ var fileData: Data = Data()
+
+ fileData.appendUInt32("HTsc".fourCharCode()) // magic
+ fileData.appendUInt16(0x0001) // version
+ fileData.append(Data(repeating: 0x00, count: 128)) // reserved
+
+ fileData.appendUInt16(UInt16(loginData.count))
+ fileData.append(loginData)
+ if loginData.count < 32 {
+ // Pad login data to 32 bytes
+ fileData.append(Data(repeating: 0x00, count: 32 - loginData.count))
+ }
+
+ fileData.appendUInt16(UInt16(passwordData.count))
+ fileData.append(passwordData)
+ if passwordData.count < 32 {
+ // Pad password data to 32 bytes
+ fileData.append(Data(repeating: 0x00, count: 32 - passwordData.count))
+ }
+
+ fileData.appendUInt16(UInt16(addressData.count))
+ fileData.append(addressData)
+ if passwordData.count < 256 {
+ // Pad address data to 256 bytes
+ fileData.append(Data(repeating: 0x00, count: 256 - addressData.count))
+ }
+
+ return fileData
+ }
+
static func populateDefaults(force: Bool = false, context: ModelContext) {
if force || Bookmark.fetchCount(context: context) == 0 {
Bookmark.add(Bookmark.DefaultBookmarks, context: context)
diff --git a/Hotline/Utility/BookmarkDocument.swift b/Hotline/Utility/BookmarkDocument.swift
new file mode 100644
index 0000000..47fa442
--- /dev/null
+++ b/Hotline/Utility/BookmarkDocument.swift
@@ -0,0 +1,32 @@
+import SwiftUI
+import Foundation
+import UniformTypeIdentifiers
+
+struct BookmarkDocument: FileDocument {
+ static var readableContentTypes: [UTType] { [.data, UTType(filenameExtension: "hlbm")!] }
+ static var writableContentTypes: [UTType] { [.data, UTType(filenameExtension: "hlbm")!] }
+
+ var bookmark: Bookmark
+
+ init(bookmark: Bookmark) {
+ self.bookmark = bookmark
+ }
+
+ init(configuration: ReadConfiguration) throws {
+ guard configuration.file.isRegularFile,
+ let data = configuration.file.regularFileContents,
+ let fileName = configuration.file.preferredFilename,
+ let bookmark = Bookmark(fileData: data, name: (fileName as NSString).deletingPathExtension)
+ else {
+ throw CocoaError(.fileReadCorruptFile)
+ }
+ self.bookmark = bookmark
+ }
+
+ func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
+ let wrapper = FileWrapper(regularFileWithContents: self.bookmark.bookmarkFileData()!)
+ wrapper.fileAttributes[FileAttributeKey.hfsCreatorCode.rawValue] = "HTLC".fourCharCode()
+ wrapper.fileAttributes[FileAttributeKey.hfsTypeCode.rawValue] = "HTbm".fourCharCode()
+ return wrapper
+ }
+}
diff --git a/Hotline/macOS/TrackerView.swift b/Hotline/macOS/TrackerView.swift
index 5a9db9b..5ab4c65 100644
--- a/Hotline/macOS/TrackerView.swift
+++ b/Hotline/macOS/TrackerView.swift
@@ -1,9 +1,9 @@
import SwiftUI
import SwiftData
+import Foundation
import UniformTypeIdentifiers
struct TrackerView: View {
-// @Environment(BookmarksOld.self) private var bookmarks: BookmarksOld
@Environment(\.colorScheme) private var colorScheme
@Environment(\.openWindow) private var openWindow
@Environment(\.controlActiveState) private var controlActiveState
@@ -14,6 +14,8 @@ struct TrackerView: View {
@State private var trackerSheetBookmark: Bookmark? = nil
@State private var attemptedPrepopulate: Bool = false
@State private var fileDropActive = false
+ @State private var bookmarkExportActive = false
+ @State private var bookmarkExport: BookmarkDocument? = nil
@Query(sort: \Bookmark.order) private var bookmarks: [Bookmark]
@State private var selection: Bookmark? = nil
@@ -96,6 +98,17 @@ struct TrackerView: View {
}
}
+ if item.type == .server {
+ Button {
+ bookmarkExport = BookmarkDocument(bookmark: item)
+ bookmarkExportActive = true
+ } label: {
+ Label("Export Bookmark...", systemImage: "bookmark.square")
+ }
+ }
+
+ Divider()
+
Button {
Bookmark.delete(item, context: modelContext)
} label: {
@@ -121,6 +134,17 @@ struct TrackerView: View {
openWindow(id: "server", value: server)
}
}
+ .fileExporter(isPresented: $bookmarkExportActive, document: bookmarkExport, contentTypes: [.data], defaultFilename: "\(bookmarkExport?.bookmark.name ?? "Hotline Bookmark").hlbm", onCompletion: { result in
+ switch result {
+ case .success(let fileURL):
+ print("Hotline Bookmark: Successfully exported:", fileURL)
+ case .failure(let err):
+ print("Hotline Bookmark: Failed to export:", err)
+ }
+
+ bookmarkExport = nil
+ bookmarkExportActive = false
+ }, onCancellation: {})
.onKeyPress(.rightArrow) {
if
let bookmark = selection,