54 lines
2.2 KiB
Swift
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
|
|
}
|
|
}
|