Files
2026-09-17 17:59:34 +02:00

54 lines
2.2 KiB
Swift

import Darwin
import FamilyControls
import Foundation
import PolicyCore
struct NativeState: Codable {
var snapshot: Snapshot?
var selection = FamilyActivitySelection()
var selectionRevision = UUID().uuidString
var observation = Observation()
var generation: String?
var policyKey: String?
var thresholds: [Int] = []
var monitoringReady = false
var shielded = false
var lastEvent = "Not configured"
var monitoringError: String?
}
enum LocalError: LocalizedError {
case message(String)
var errorDescription: String? {
switch self { case .message(let value): return value }
}
}
enum StateStore {
/// A process-wide UserDefaults cache is not sufficient: the app and extension both write.
/// Lock one file while atomically replacing another. Corrupt state is never reset to defaults.
static func withState<T>(_ operation: (inout NativeState) throws -> T) throws -> T {
guard let group = Bundle.main.object(forInfoDictionaryKey: "APCAppGroup") as? String,
let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group) else {
throw LocalError.message("App Group unavailable. Check signing and APC_APP_GROUP on all targets.")
}
let lockPath = directory.appendingPathComponent("state.lock").path
let fd = open(lockPath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)
guard fd >= 0 else { throw LocalError.message("Cannot open the shared-state lock") }
defer { close(fd) }
guard flock(fd, LOCK_EX) == 0 else { throw LocalError.message("Cannot lock shared state") }
defer { flock(fd, LOCK_UN) }
let file = directory.appendingPathComponent("state.json")
var state = NativeState()
if FileManager.default.fileExists(atPath: file.path) {
state = try WireCoding.decoder().decode(NativeState.self, from: Data(contentsOf: file))
}
let result = try operation(&state)
try WireCoding.encoder().encode(state).write(to: file, options: .atomic)
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], atPath: file.path
)
return result
}
}