@@ -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]) |
@@ -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 |
|
@@ -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 |
} |