| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import Foundation
- /// Attention! Do not use this wrapper for mutating structure with `didSet` handler into property owner!
- /// `didSet` will never called if structure mutate into itself (by "mutating functions").
- @propertyWrapper struct Persisted<Value: Codable & Equatable> {
- var wrappedValue: Value {
- get { getValue() ?? initialValue }
- set { setValue(newValue) }
- }
- private func getValue() -> Value? {
- lock?.lock()
- defer { lock?.unlock() }
- return storage.getValue(Value.self, forKey: key)
- }
- private mutating func setValue(_ value: Value) {
- lock?.lock()
- defer { lock?.unlock() }
- storage.setValue(value, forKey: key)
- }
- private let key: String
- private let storage: KeyValueStorage
- private let lock: NSRecursiveLock?
- private let initialValue: Value
- var isInitialValue: Bool {
- if let value = getValue() {
- return value == initialValue
- }
- return true
- }
- init(
- wrappedValue: Value,
- key: String,
- storage: KeyValueStorage = UserDefaults.standard,
- lock: NSRecursiveLock? = nil
- ) {
- self.storage = storage
- self.key = key
- self.lock = lock
- initialValue = wrappedValue
- lock?.lock()
- defer { lock?.unlock() }
- if storage.getValue(Value.self, forKey: key) == nil {
- setValue(wrappedValue)
- }
- }
- }
- @propertyWrapper public struct PersistedProperty<Value> {
- let key: String
- let storageURL: URL
- public init(key: String) {
- self.key = key
- let documents: URL
- guard let localDocuments = try? FileManager.default.url(
- for: .documentDirectory,
- in: .userDomainMask,
- appropriateFor: nil,
- create: true
- ) else {
- preconditionFailure("Could not get a documents directory URL.")
- }
- documents = localDocuments
- storageURL = documents.appendingPathComponent(key + ".plist")
- }
- public var wrappedValue: Value? {
- get {
- do {
- let data = try Data(contentsOf: storageURL)
- guard let value = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? Value
- else {
- debug(.storage, "[PersistedProperty:\(key)] Could not cast property list to expected type.")
- return nil
- }
- return value
- } catch {
- debug(.storage, "❌ [PersistedProperty:\(key)] Failed to read value: \(error)")
- return nil
- }
- }
- set {
- guard let newValue = newValue else {
- do {
- try FileManager.default.removeItem(at: storageURL)
- debug(.storage, "[PersistedProperty:\(key)] Removed value.")
- } catch {
- debug(.storage, "❌ [PersistedProperty:\(key)] Failed to remove value: \(error)")
- }
- return
- }
- do {
- let data = try PropertyListSerialization.data(fromPropertyList: newValue, format: .binary, options: 0)
- try data.write(to: storageURL, options: .atomic)
- // Ensure appropriate protection level
- try FileManager.default.setAttributes(
- [.protectionKey: FileProtectionType.none],
- ofItemAtPath: storageURL.path
- )
- debug(.storage, "✅ [PersistedProperty:\(key)] Saved value successfully.")
- } catch {
- debug(.storage, "❌ [PersistedProperty:\(key)] Failed to write value: \(error)")
- }
- }
- }
- }
- import Foundation
- enum FileProtectionFixer {
- /// Ensures only critical persisted flags have safe file protection set.
- static func fixFlagFileProtectionForPropertyPersistentFlags() {
- let flagFiles = [
- "onboardingCompleted.plist",
- "diagnosticsSharing.plist",
- "lastCleanupDate.plist",
- "hasSeenFatProteinOrderChange.plist",
- "telemetryEnabled.plist",
- "telemetryConsentDecisionMade.plist",
- "telemetryLastSentAt.plist",
- "telemetryLastSentSha.plist",
- "telemetryColdLaunchTimes.plist",
- "telemetryInstallId.plist",
- "telemetryAttestForbidden.plist",
- "telemetryDebugServerURL.plist"
- ]
- let fileManager = FileManager.default
- guard let documentsURL = try? fileManager.url(
- for: .documentDirectory,
- in: .userDomainMask,
- appropriateFor: nil,
- create: false
- ) else {
- debug(.storage, "⚠️ Could not access the documents directory.")
- return
- }
- for fileName in flagFiles {
- let fileURL = documentsURL.appendingPathComponent(fileName)
- guard fileManager.fileExists(atPath: fileURL.path) else {
- continue // Skip if file doesn’t exist yet
- }
- do {
- try fileManager.setAttributes(
- [.protectionKey: FileProtectionType.none],
- ofItemAtPath: fileURL.path
- )
- debug(.storage, "✅ Updated protection for \(fileName)")
- } catch {
- debug(.storage, "❌ Failed to update protection for \(fileName): \(error)")
- }
- }
- }
- }
|