diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-05-16 17:40:27 -0700 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-05-16 17:40:27 -0700 |
| commit | 34fef2f5eca64586aace2c83723135c1eca346f7 (patch) | |
| tree | c1932cd9e17a4d8574952f43eee611fda4c09485 /Hotline/Models | |
| parent | bba5122797f47f463c2949a79876e2514694ca02 (diff) | |
Move tracker items and bookmarks to SwiftData for iCloud sync.
Diffstat (limited to 'Hotline/Models')
| -rw-r--r-- | Hotline/Models/Bookmark.swift | 269 | ||||
| -rw-r--r-- | Hotline/Models/BookmarksOld.swift (renamed from Hotline/Models/Bookmarks.swift) | 36 | ||||
| -rw-r--r-- | Hotline/Models/Server.swift | 6 |
3 files changed, 290 insertions, 21 deletions
diff --git a/Hotline/Models/Bookmark.swift b/Hotline/Models/Bookmark.swift new file mode 100644 index 0000000..160de30 --- /dev/null +++ b/Hotline/Models/Bookmark.swift @@ -0,0 +1,269 @@ +import Foundation +import SwiftData + +enum BookmarkType: Codable { + case tracker + case server + case temporary +} + +@Model +final class Bookmark { + var type: BookmarkType + var order: Int = 0 + + var name: String + var address: String + var port: Int + + @Attribute(.allowsCloudEncryption) + var login: String? + + @Attribute(.allowsCloudEncryption) + var password: String? + + @Attribute(.ephemeral) + var expanded: Bool = false + + @Attribute(.ephemeral) + var loading: Bool = false + + @Attribute(.ephemeral) + var serverDescription: String? = nil + + @Transient + var servers: [Bookmark] = [] + + @Transient + var displayAddress: String { + switch self.type { + case .tracker: + if self.port == HotlinePorts.DefaultTrackerPort { + return self.address + } + else { + return "\(self.address):\(String(self.port))" + } + + case .server, .temporary: + if self.port == HotlinePorts.DefaultServerPort { + return self.address + } + else { + return "\(self.address):\(String(self.port))" + } + } + +// if let s = server { +// if s.port == HotlinePorts.DefaultServerPort { +// return s.address +// } +// else { +// return "\(s.address):\(s.port)" +// } +// } +// else if let b = bookmark { +// if b.port == HotlinePorts.DefaultServerPort { +// return b.address +// } +// else { +// return "\(b.address):\(b.port)" +// } +// } + +// return nil + } + + @Transient + var server: Server? { + switch self.type { + case .tracker: + return nil + + case .server, .temporary: + return Server(name: self.name, description: self.serverDescription, address: self.address, port: self.port, login: self.login, password: self.password) + } + } + + static let DefaultBookmarks: [Bookmark] = [ + Bookmark(type: .server, name: "The Mobius Strip", address: "67.174.208.111", port: HotlinePorts.DefaultServerPort), + Bookmark(type: .server, name: "System 7 Today", address: "hotline.system7today.com", port: HotlinePorts.DefaultServerPort), + Bookmark(type: .tracker, name: "Featured Servers", address: "hltracker.com", port: HotlinePorts.DefaultTrackerPort) + ] + + init(type: BookmarkType, name: String, address: String, port: Int, login: String? = nil, password: String? = nil) { + self.type = type + self.name = name + self.address = address + self.port = port + + self.login = login + self.password = password + } + + init(temporaryServer server: Server) { + self.type = .temporary + + self.name = server.name ?? server.address + self.address = server.address + self.port = server.port + + self.serverDescription = server.description + } + + static func populateDefaults(force: Bool = false, context: ModelContext) { + if force || Bookmark.fetchCount(context: context) == 0 { + Bookmark.add(Bookmark.DefaultBookmarks, context: context) + } + } + + static func fetchAll(context: ModelContext) -> [Bookmark] { + let fetchDescriptor = FetchDescriptor<Bookmark>(sortBy: [.init(\.order)]) + do { + let bookmarks: [Bookmark] = try context.fetch(fetchDescriptor) + return bookmarks + } + catch { + return [] + } + } + + static func fetchCount(context: ModelContext) -> Int { + let descriptor = FetchDescriptor<Bookmark>() + return (try? context.fetchCount(descriptor)) ?? 0 + } + + static func deleteAll(context: ModelContext) { + try? context.delete(model: Bookmark.self) + } + + static func add(_ bookmark: Bookmark, context: ModelContext) { + guard bookmark.type != .temporary else { + print("Bookmark: Attempting to add temporary bookmark to store. Aborting.") + return + } + + let existingBookmarks = Bookmark.fetchAll(context: context) + + // Reindex bookmarks before insert. + for existingBookmark in existingBookmarks { + existingBookmark.order += 1 + } + + // Insert new bookmark at start. + bookmark.order = 0 + context.insert(bookmark) + } + + static func add(_ bookmarks: [Bookmark], context: ModelContext) { + let existingBookmarks = Bookmark.fetchAll(context: context) + + // Reindex bookmarks before insert. + for existingBookmark in existingBookmarks { + existingBookmark.order += bookmarks.count + } + + // Insert new bookmarks at start. + var bookmarkIndex = 0 + for newBookmark in bookmarks { + newBookmark.order = bookmarkIndex + context.insert(newBookmark) + bookmarkIndex += 1 + + print("Bookmark: added \(newBookmark.name)") + } + } + + static func delete(_ bookmark: Bookmark, context: ModelContext) { + // Delete bookmark + context.delete(bookmark) + + // Reindex bookmarks + let existingBookmarks = Bookmark.fetchAll(context: context) + var index = 0 + for existingBookmark in existingBookmarks { + existingBookmark.order = index + index += 1 + } + } + + static func delete(at indexes: IndexSet, context: ModelContext) { + var existingBookmarks = Bookmark.fetchAll(context: context) +// existingBookmarks.remove(atOffsets: indexes) + let bookmarksToDelete = indexes.map { existingBookmarks[$0] } + + // Delete bookmark + for bookmark in bookmarksToDelete { + context.delete(bookmark) + } + + // Reindex bookmarks + var index = 0 + existingBookmarks.remove(atOffsets: indexes) + for existingBookmark in existingBookmarks { + existingBookmark.order = index + index += 1 + } + + do { + try context.save() + } + catch { + print("Bookmark: Failed to save bookmark deletions") + } + } + + static func move(_ indexes: IndexSet, to newIndex: Int, context: ModelContext) { + guard Bookmark.fetchCount(context: context) >= indexes.count else { + print("Bookmark: Not enough bookmarks to move requested set") + return + } + + // Perform move + var existingBookmarks = Bookmark.fetchAll(context: context) + existingBookmarks.move(fromOffsets: indexes, toOffset: newIndex) + + // Reindex bookmarks + var index = 0 + for existingBookmark in existingBookmarks { + existingBookmark.order = index + index += 1 + } + + do { + try context.save() + } + catch { + print("Bookmark: Failed to save bookmark reordering") + } + } + + func fetchServers() async { + guard self.type == .tracker else { + // self.loading = false + return + } + + DispatchQueue.main.sync { + self.loading = true + } + + var fetchedBookmarks: [Bookmark] = [] + + let client = HotlineTrackerClient() + if let fetchedServers: [HotlineServer] = try? await client.fetchServers(address: self.address, port: self.port) { + for fetchedServer in fetchedServers { + if let serverName = fetchedServer.name { + let server = Server(name: serverName, description: fetchedServer.description, address: fetchedServer.address, port: Int(fetchedServer.port), users: Int(fetchedServer.users)) + fetchedBookmarks.append(Bookmark(temporaryServer: server)) + } + } + } + + let newServers = fetchedBookmarks + DispatchQueue.main.sync { + self.servers = newServers + self.loading = false + } + } +} diff --git a/Hotline/Models/Bookmarks.swift b/Hotline/Models/BookmarksOld.swift index 3f4971c..0a7bb16 100644 --- a/Hotline/Models/Bookmarks.swift +++ b/Hotline/Models/BookmarksOld.swift @@ -5,20 +5,20 @@ extension NSNotification { static let BookmarkRemoved = Notification.Name("BookmarkRemoved") } -enum BookmarkType: String, Codable { +enum BookmarkOldType: String, Codable { case tracker = "tracker" case server = "server" } -struct Bookmark: Codable, Equatable { - let type: BookmarkType +struct BookmarkOld: Codable, Equatable { + let type: BookmarkOldType let name: String let address: String let port: Int let login: String? let password: String? - init(type: BookmarkType, name: String, address: String, port: Int = HotlinePorts.DefaultServerPort, login: String? = nil, password: String? = nil) { + init(type: BookmarkOldType, name: String, address: String, port: Int = HotlinePorts.DefaultServerPort, login: String? = nil, password: String? = nil) { self.type = type self.name = name self.address = address @@ -28,20 +28,20 @@ struct Bookmark: Codable, Equatable { } } -@Observable final class Bookmarks { - var bookmarks: [Bookmark]? = nil +@Observable final class BookmarksOld { + var bookmarks: [BookmarkOld]? = nil - static let DefaultBookmarks: [Bookmark] = [ - Bookmark(type: .server, name: "System 7 Today", address: "hotline.system7today.com"), - Bookmark(type: .server, name: "The Mobius Strip", address: "67.174.208.111"), - Bookmark(type: .tracker, name: "Featured Servers", address: "hltracker.com"), + static let DefaultBookmarks: [BookmarkOld] = [ + BookmarkOld(type: .server, name: "System 7 Today", address: "hotline.system7today.com"), + BookmarkOld(type: .server, name: "The Mobius Strip", address: "67.174.208.111"), + BookmarkOld(type: .tracker, name: "Featured Servers", address: "hltracker.com"), ] init() { self.load() } - func apply(_ newBookmarks: [Bookmark], save shouldSave: Bool = true) { + func apply(_ newBookmarks: [BookmarkOld], save shouldSave: Bool = true) { self.bookmarks = newBookmarks if shouldSave { self.save() @@ -53,21 +53,21 @@ struct Bookmark: Codable, Equatable { let jsonData: Data? = jsonString?.data(using: .utf8, allowLossyConversion: false) let decoder = JSONDecoder() - var decodedBookmarks = try? decoder.decode([Bookmark].self, from: jsonData ?? Data()) + var decodedBookmarks = try? decoder.decode([BookmarkOld].self, from: jsonData ?? Data()) if decodedBookmarks == nil || decodedBookmarks?.isEmpty == true { print("Bookmarks: using default bookmarks") - decodedBookmarks = Bookmarks.DefaultBookmarks + decodedBookmarks = BookmarksOld.DefaultBookmarks } else { print("Bookmarks: using saved bookmarks") } - self.bookmarks = [Bookmark](decodedBookmarks!) + self.bookmarks = [BookmarkOld](decodedBookmarks!) } func save() { var bookmarksToSave = self.bookmarks - if bookmarksToSave == Bookmarks.DefaultBookmarks { + if bookmarksToSave == BookmarksOld.DefaultBookmarks { print("Bookmarks: skipping saving default bookmarks") bookmarksToSave = [] } @@ -83,7 +83,7 @@ struct Bookmark: Codable, Equatable { // MARK: - - func add(_ bookmark: Bookmark, save shouldSave: Bool = true) { + func add(_ bookmark: BookmarkOld, save shouldSave: Bool = true) { self.bookmarks?.insert(bookmark, at: 0) if shouldSave { @@ -95,7 +95,7 @@ struct Bookmark: Codable, Equatable { } } - func delete(_ bookmark: Bookmark, save shouldSave: Bool = true) -> Bool { + func delete(_ bookmark: BookmarkOld, save shouldSave: Bool = true) -> Bool { if let i = self.bookmarks?.firstIndex(where: { b in b.address.lowercased() == bookmark.address.lowercased() && b.port == bookmark.port }) { self.bookmarks?.remove(at: i) @@ -112,7 +112,7 @@ struct Bookmark: Codable, Equatable { return false } - func update(_ bookmark: Bookmark, save shouldSave: Bool = true) -> Bool { + func update(_ bookmark: BookmarkOld, save shouldSave: Bool = true) -> Bool { if let i = self.bookmarks?.firstIndex(where: { b in b.address.lowercased() == bookmark.address.lowercased() && b.port == bookmark.port }) { self.bookmarks?[i] = bookmark if shouldSave { diff --git a/Hotline/Models/Server.swift b/Hotline/Models/Server.swift index 27d45c6..e0fe38b 100644 --- a/Hotline/Models/Server.swift +++ b/Hotline/Models/Server.swift @@ -10,14 +10,14 @@ struct Server: Codable { var login: String var password: String - init(name: String?, description: String?, address: String, port: Int = HotlinePorts.DefaultServerPort, users: Int = 0, login: String = "", password: String = "") { + init(name: String?, description: String?, address: String, port: Int = HotlinePorts.DefaultServerPort, users: Int = 0, login: String? = nil, password: String? = nil) { self.name = name self.description = description self.address = address.lowercased() self.port = port self.users = users - self.login = login - self.password = password + self.login = login ?? "" + self.password = password ?? "" } init?(url: URL) { |