import Foundation import ValueBox import Table import PostboxCoding public enum PreferencesOperation { case update(ValueBoxKey, PreferencesEntry?) } private struct CachedEntry { let entry: PreferencesEntry? } public final class PreferencesTable: Table { private var cachedEntries: [ValueBoxKey: CachedEntry] = [:] private var updatedEntryKeys = Set() public static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) } public func enumerateEntries(_ f: (PreferencesEntry) -> Bool) { self.valueBox.scan(self.table, values: { _, value in if let object = PostboxDecoder(buffer: value).decodeRootObject() as? PreferencesEntry { return f(object) } else { return true } }) } public func get(key: ValueBoxKey) -> PreferencesEntry? { if let cached = self.cachedEntries[key] { return cached.entry } else { if let value = self.valueBox.get(self.table, key: key), let object = PostboxDecoder(buffer: value).decodeRootObject() as? PreferencesEntry { self.cachedEntries[key] = CachedEntry(entry: object) return object } else { self.cachedEntries[key] = CachedEntry(entry: nil) return nil } } } public func set(key: ValueBoxKey, value: PreferencesEntry?, operations: inout [PreferencesOperation]) { self.cachedEntries[key] = CachedEntry(entry: value) updatedEntryKeys.insert(key) operations.append(.update(key, value)) } override public func clearMemoryCache() { assert(self.updatedEntryKeys.isEmpty) } override public func beforeCommit() { if !self.updatedEntryKeys.isEmpty { for key in self.updatedEntryKeys { if let value = self.cachedEntries[key]?.entry { let encoder = PostboxEncoder() encoder.encodeRootObject(value) withExtendedLifetime(encoder, { self.valueBox.set(self.table, key: key, value: encoder.readBufferNoCopy()) }) } else { self.valueBox.remove(self.table, key: key, secure: false) } } self.updatedEntryKeys.removeAll() } } }