70 lines
2.4 KiB
Swift
70 lines
2.4 KiB
Swift
import Foundation
|
|
import TermindDomain
|
|
|
|
/// Device-local connection intent only. Credentials, commands and live session state never enter this store.
|
|
@MainActor
|
|
final class MobileWorkspaceStore {
|
|
static let key = "mobile.workspaces.v1"
|
|
|
|
private struct Snapshot: Codable {
|
|
var version = 1
|
|
var workspaces: [MobileWorkspaceDescriptor]
|
|
}
|
|
|
|
private struct LegacyWorkspace: Decodable {
|
|
var id: UUID
|
|
var hostID: UUID
|
|
var title: String
|
|
var kind: ConnectionHistoryEntry.Kind
|
|
}
|
|
|
|
private enum StoreError: Error { case invalidSnapshot }
|
|
|
|
private let defaults: UserDefaults?
|
|
private var canSave = true
|
|
|
|
init(defaults: UserDefaults?) {
|
|
self.defaults = defaults
|
|
}
|
|
|
|
func load() throws -> [MobileWorkspaceDescriptor] {
|
|
do {
|
|
guard let value = defaults?.object(forKey: Self.key) else {
|
|
canSave = true
|
|
return []
|
|
}
|
|
guard let data = value as? Data else { throw StoreError.invalidSnapshot }
|
|
let decoder = JSONDecoder()
|
|
let snapshot: Snapshot
|
|
let needsMigration: Bool
|
|
if let current = try? decoder.decode(Snapshot.self, from: data) {
|
|
snapshot = current
|
|
needsMigration = false
|
|
} else {
|
|
let legacy = try decoder.decode([LegacyWorkspace].self, from: data)
|
|
snapshot = Snapshot(
|
|
workspaces: legacy.map {
|
|
MobileWorkspaceDescriptor(id: $0.id, hostID: $0.hostID, title: $0.title, kind: $0.kind)
|
|
})
|
|
needsMigration = true
|
|
}
|
|
guard snapshot.version == 1,
|
|
Set(snapshot.workspaces.map(\.id)).count == snapshot.workspaces.count
|
|
else { throw StoreError.invalidSnapshot }
|
|
canSave = true
|
|
if needsMigration { try save(snapshot.workspaces) }
|
|
return snapshot.workspaces
|
|
} catch {
|
|
// Keep the original bytes for recovery instead of replacing an unreadable snapshot with an empty list.
|
|
canSave = false
|
|
throw error
|
|
}
|
|
}
|
|
|
|
func save(_ workspaces: [MobileWorkspaceDescriptor]) throws {
|
|
guard canSave else { throw StoreError.invalidSnapshot }
|
|
guard let defaults else { return }
|
|
defaults.set(try JSONEncoder().encode(Snapshot(workspaces: workspaces)), forKey: Self.key)
|
|
}
|
|
}
|