Showing 3 changed files with 44 additions and 11 deletions
+4 -8
HealthProbe/HealthProbeApp.swift
@@ -50,16 +50,12 @@ struct HealthProbeApp: App {
50 50
         do {
51 51
             return try ModelContainer(for: fullSchema, configurations: [uiCacheConfig])
52 52
         } catch {
53
-            let candidates: [URL] = [
54
-                uiCacheStoreURL,
55
-                appSupportURL.appending(path: "HealthProbeRecords.store.shm"),
56
-                appSupportURL.appending(path: "HealthProbeRecords.store.wal"),
57
-                appSupportURL.appending(path: "HealthProbeCloud.store"),
58
-                appSupportURL.appending(path: "HealthProbeCloud.store.shm"),
59
-                appSupportURL.appending(path: "HealthProbeCloud.store.wal"),
53
+            let candidates = PrototypeStoreResetPolicy.destructiveResetURLs(appSupportURL: appSupportURL) + [
60 54
                 appSupportURL.appending(path: "HealthProbeLocal.store"),
55
+                appSupportURL.appending(path: "HealthProbeLocal.store-shm"),
56
+                appSupportURL.appending(path: "HealthProbeLocal.store-wal"),
61 57
                 appSupportURL.appending(path: "HealthProbeLocal.store.shm"),
62
-                appSupportURL.appending(path: "HealthProbeLocal.store.wal"),
58
+                appSupportURL.appending(path: "HealthProbeLocal.store.wal")
63 59
             ]
64 60
             for url in candidates { try? FileManager.default.removeItem(at: url) }
65 61
             return try ModelContainer(for: fullSchema, configurations: [uiCacheConfig])
+7 -3
HealthProbe/Services/PrototypeStoreResetPolicy.swift
@@ -8,13 +8,15 @@ struct PrototypeStoreResetResult: Equatable {
8 8
 enum PrototypeStoreResetPolicy {
9 9
     static let currentGeneration = 1
10 10
     static let defaultsKey = "hp_prototypeStoreResetGeneration"
11
+    static let manualResetDefaultsKey = "hp_prototypeStoreResetScheduled"
11 12
 
12 13
     static func isResetScheduled(defaults: UserDefaults = .standard) -> Bool {
13
-        defaults.integer(forKey: defaultsKey) < currentGeneration
14
+        defaults.bool(forKey: manualResetDefaultsKey) || defaults.integer(forKey: defaultsKey) < currentGeneration
14 15
     }
15 16
 
16 17
     static func requestResetOnNextLaunch(defaults: UserDefaults = .standard) {
17
-        defaults.set(max(currentGeneration - 1, 0), forKey: defaultsKey)
18
+        defaults.set(true, forKey: manualResetDefaultsKey)
19
+        defaults.synchronize()
18 20
     }
19 21
 
20 22
     static func applyIfNeeded(
@@ -22,7 +24,7 @@ enum PrototypeStoreResetPolicy {
22 24
         defaults: UserDefaults = .standard,
23 25
         fileManager: FileManager = .default
24 26
     ) throws -> PrototypeStoreResetResult {
25
-        guard defaults.integer(forKey: defaultsKey) < currentGeneration else {
27
+        guard isResetScheduled(defaults: defaults) else {
26 28
             return PrototypeStoreResetResult(didReset: false, removedURLs: [])
27 29
         }
28 30
 
@@ -34,6 +36,8 @@ enum PrototypeStoreResetPolicy {
34 36
         }
35 37
 
36 38
         defaults.set(currentGeneration, forKey: defaultsKey)
39
+        defaults.set(false, forKey: manualResetDefaultsKey)
40
+        defaults.synchronize()
37 41
         return PrototypeStoreResetResult(didReset: true, removedURLs: removedURLs)
38 42
     }
39 43
 
+33 -0
HealthProbeTests/PrototypeStoreResetPolicyTests.swift
@@ -68,4 +68,37 @@ final class PrototypeStoreResetPolicyTests: XCTestCase {
68 68
 
69 69
         XCTAssertTrue(PrototypeStoreResetPolicy.isResetScheduled(defaults: defaults))
70 70
     }
71
+
72
+    func testManualResetDeletesStoresEvenWhenGenerationIsCurrent() throws {
73
+        defaults.set(PrototypeStoreResetPolicy.currentGeneration, forKey: PrototypeStoreResetPolicy.defaultsKey)
74
+        let archiveURL = temporaryDirectory.appending(path: "HealthProbeArchive.sqlite")
75
+        let cacheWALURL = temporaryDirectory.appending(path: "HealthProbeCache.sqlite-wal")
76
+        try Data("archive".utf8).write(to: archiveURL)
77
+        try Data("cache".utf8).write(to: cacheWALURL)
78
+
79
+        PrototypeStoreResetPolicy.requestResetOnNextLaunch(defaults: defaults)
80
+
81
+        let result = try PrototypeStoreResetPolicy.applyIfNeeded(
82
+            appSupportURL: temporaryDirectory,
83
+            defaults: defaults
84
+        )
85
+
86
+        XCTAssertTrue(result.didReset)
87
+        XCTAssertEqual(Set(result.removedURLs.map(\.lastPathComponent)), Set([
88
+            "HealthProbeArchive.sqlite",
89
+            "HealthProbeCache.sqlite-wal"
90
+        ]))
91
+        XCTAssertFalse(FileManager.default.fileExists(atPath: archiveURL.path))
92
+        XCTAssertFalse(FileManager.default.fileExists(atPath: cacheWALURL.path))
93
+        XCTAssertFalse(PrototypeStoreResetPolicy.isResetScheduled(defaults: defaults))
94
+    }
95
+
96
+    func testManualResetFlagSurvivesWithoutChangingGenerationKey() throws {
97
+        defaults.set(PrototypeStoreResetPolicy.currentGeneration, forKey: PrototypeStoreResetPolicy.defaultsKey)
98
+
99
+        PrototypeStoreResetPolicy.requestResetOnNextLaunch(defaults: defaults)
100
+
101
+        XCTAssertEqual(defaults.integer(forKey: PrototypeStoreResetPolicy.defaultsKey), PrototypeStoreResetPolicy.currentGeneration)
102
+        XCTAssertTrue(defaults.bool(forKey: PrototypeStoreResetPolicy.manualResetDefaultsKey))
103
+    }
71 104
 }