Files
Termind/TFI/Infrastructure/MobileWorkspaceStore.swift

56 lines
1.9 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
/// Device-local connection intent and recovery references, never credentials or one-shot commands.
@MainActor
final class MobileWorkspaceStore {
static let key = "mobile.workspaces.v1"
static let recentlyClosedKey = "mobile.recentlyClosed.v2"
static let restorationKey = "mobile.restorableWorkspaces.v1"
struct Snapshot: Codable, Equatable {
var version = 1
var sessions: [String: [MobileWorkspaceDescriptor]] = [:]
}
private let defaults: UserDefaults?
private var canSave = true
init(defaults: UserDefaults?) {
self.defaults = defaults
}
func load() throws -> Snapshot {
do {
guard let value = defaults?.object(forKey: Self.restorationKey) else { return Snapshot() }
guard let data = value as? Data else { throw CocoaError(.coderReadCorrupt) }
let snapshot = try JSONDecoder().decode(Snapshot.self, from: data)
try validate(snapshot)
return snapshot
} catch {
canSave = false
throw error
}
}
func save(_ snapshot: Snapshot) throws {
guard canSave else { throw CocoaError(.coderReadCorrupt) }
try validate(snapshot)
defaults?.set(try JSONEncoder().encode(snapshot), forKey: Self.restorationKey)
}
private func validate(_ snapshot: Snapshot) throws {
let descriptors = snapshot.sessions.values.flatMap { $0 }
guard snapshot.version == 1, snapshot.sessions.keys.allSatisfy({ !$0.isEmpty }),
Set(descriptors.map(\.id)).count == descriptors.count
else { throw CocoaError(.coderReadCorrupt) }
}
static func retire(in defaults: UserDefaults) {
defaults.removeObject(forKey: key)
defaults.removeObject(forKey: recentlyClosedKey)
}
}