Skip to content

Commit 06ff780

Browse files
Updated edit history to allow for faster retention pruning
Closes #230
1 parent 60adb41 commit 06ff780

File tree

5 files changed

+67
-10
lines changed

5 files changed

+67
-10
lines changed

Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreRoot.swift

+26
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,38 @@ import Foundation
1010

1111
typealias DatastoreRootIdentifier = DatedIdentifier<DiskPersistence<ReadOnly>.Datastore.RootObject>
1212

13+
struct DatastoreRootReference: Codable, Hashable {
14+
var datastoreID: DatastoreIdentifier?
15+
var datastoreRootID: DatastoreRootIdentifier
16+
17+
init(datastoreID: DatastoreIdentifier, datastoreRootID: DatastoreRootIdentifier) {
18+
self.datastoreID = datastoreID
19+
self.datastoreRootID = datastoreRootID
20+
}
21+
22+
init(from decoder: any Decoder) throws {
23+
/// Attempt to decode a full object, otherwise fall back to a single value as it was prior to version 0.4 (2024-10-11)
24+
do {
25+
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
26+
self.datastoreID = try container.decodeIfPresent(DatastoreIdentifier.self, forKey: .datastoreID)
27+
self.datastoreRootID = try container.decode(DatastoreRootIdentifier.self, forKey: .datastoreRootID)
28+
} catch {
29+
self.datastoreID = nil
30+
self.datastoreRootID = try decoder.singleValueContainer().decode(DatastoreRootIdentifier.self)
31+
}
32+
}
33+
}
34+
1335
extension DiskPersistence.Datastore {
1436
actor RootObject: Identifiable {
1537
let datastore: DiskPersistence<AccessMode>.Datastore
1638

1739
let id: DatastoreRootIdentifier
1840

41+
nonisolated var referenceID: DatastoreRootReference {
42+
DatastoreRootReference(datastoreID: datastore.id, datastoreRootID: id)
43+
}
44+
1945
var _rootObject: DatastoreRootManifest?
2046

2147
var isPersisted: Bool

Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,8 @@ extension DiskPersistence {
505505
func persist(
506506
actionName: String?,
507507
roots: [DatastoreKey : Datastore.RootObject],
508-
addedDatastoreRoots: Set<DatastoreRootIdentifier>,
509-
removedDatastoreRoots: Set<DatastoreRootIdentifier>
508+
addedDatastoreRoots: Set<DatastoreRootReference>,
509+
removedDatastoreRoots: Set<DatastoreRootReference>
510510
) async throws {
511511
let containsEdits = try await readingCurrentSnapshot { snapshot in
512512
try await snapshot.readingManifest { manifest, iteration in

Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift

+34-3
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,36 @@ extension Snapshot {
162162

163163
let fileManager = FileManager()
164164

165-
/// Start by deleting and pruning roots as needed.
165+
/// Start by deleting and pruning roots as needed. We attempt to do this twice, as older versions of the persistence (prior to 0.4) didn't record the datastore ID along with the root id, which would therefor require extra work.
166+
/// First, delete the root entries we know to be removed.
167+
for datastoreRoot in datastoreRootsToPruneAndDelete {
168+
guard let datastoreID = datastoreRoot.datastoreID else { continue }
169+
let datastore = datastores[datastoreID] ?? DiskPersistence<AccessMode>.Datastore(id: datastoreID, snapshot: self)
170+
do {
171+
try await datastore.pruneRootObject(with: datastoreRoot.datastoreRootID, mode: mode, shouldDelete: true)
172+
} catch URLError.fileDoesNotExist, CocoaError.fileReadNoSuchFile, CocoaError.fileNoSuchFile, POSIXError.ENOENT {
173+
/// This datastore root is already gone.
174+
} catch {
175+
print("Could not delete datastore root \(datastoreRoot): \(error)")
176+
throw error
177+
}
178+
datastoreRootsToPruneAndDelete.remove(datastoreRoot)
179+
}
180+
/// Prune the root entries that were just added, as they themselves refer to other deleted assets.
181+
for datastoreRoot in datastoreRootsToPrune {
182+
guard let datastoreID = datastoreRoot.datastoreID else { continue }
183+
let datastore = datastores[datastoreID] ?? DiskPersistence<AccessMode>.Datastore(id: datastoreID, snapshot: self)
184+
do {
185+
try await datastore.pruneRootObject(with: datastoreRoot.datastoreRootID, mode: mode, shouldDelete: false)
186+
} catch URLError.fileDoesNotExist, CocoaError.fileReadNoSuchFile, CocoaError.fileNoSuchFile, POSIXError.ENOENT {
187+
/// This datastore root is already gone.
188+
} catch {
189+
print("Could not prune datastore root \(datastoreRoot): \(error)")
190+
throw error
191+
}
192+
datastoreRootsToPrune.remove(datastoreRoot)
193+
}
194+
/// If any regerences remain, funnel into this code path for very old persistences.
166195
if !datastoreRootsToPruneAndDelete.isEmpty || !datastoreRootsToPrune.isEmpty {
167196
for (_, datastoreInfo) in iteration.dataStores {
168197
/// Skip any roots for datastores being deleted, since we'll just unlink the whole directory in that case.
@@ -174,21 +203,23 @@ extension Snapshot {
174203
for datastoreRoot in datastoreRootsToPruneAndDelete {
175204
// TODO: Clean this up by also storing the datastore ID in with the root ID…
176205
do {
177-
try await datastore.pruneRootObject(with: datastoreRoot, mode: mode, shouldDelete: true)
206+
try await datastore.pruneRootObject(with: datastoreRoot.datastoreRootID, mode: mode, shouldDelete: true)
178207
datastoreRootsToPruneAndDelete.remove(datastoreRoot)
179208
} catch {
180209
/// This datastore did not contain the specified root, skip it for now.
210+
print("Could not delete datastore root \(datastoreRoot): \(error). Will probably try again.")
181211
}
182212
}
183213

184214
/// Prune the root entries that were just added, as they themselves refer to other deleted assets.
185215
for datastoreRoot in datastoreRootsToPrune {
186216
// TODO: Clean this up by also storing the datastore ID in with the root ID…
187217
do {
188-
try await datastore.pruneRootObject(with: datastoreRoot, mode: mode, shouldDelete: false)
218+
try await datastore.pruneRootObject(with: datastoreRoot.datastoreRootID, mode: mode, shouldDelete: false)
189219
datastoreRootsToPrune.remove(datastoreRoot)
190220
} catch {
191221
/// This datastore did not contain the specified root, skip it for now.
222+
print("Could not prune datastore root \(datastoreRoot): \(error). Will probably try again.")
192223
}
193224
}
194225
}

Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ struct SnapshotIteration: Codable, Equatable, Identifiable {
4848
var removedDatastores: Set<DatastoreIdentifier> = []
4949

5050
/// The datastore roots that have been added in this iteration of the snapshot.
51-
var addedDatastoreRoots: Set<DatastoreRootIdentifier> = []
51+
var addedDatastoreRoots: Set<DatastoreRootReference> = []
5252

5353
/// The datastore roots that have been replaced in this iteration of the snapshot.
54-
var removedDatastoreRoots: Set<DatastoreRootIdentifier> = []
54+
var removedDatastoreRoots: Set<DatastoreRootReference> = []
5555
}
5656

5757
extension SnapshotIteration {
@@ -94,7 +94,7 @@ extension SnapshotIteration {
9494
func datastoreRootsToPrune(
9595
for mode: SnapshotPruneMode,
9696
options: SnapshotPruneOptions
97-
) -> Set<DatastoreRootIdentifier> {
97+
) -> Set<DatastoreRootReference> {
9898
switch (mode, options) {
9999
case (.pruneRemoved, .pruneAndDelete): removedDatastoreRoots
100100
case (.pruneAdded, .pruneAndDelete): addedDatastoreRoots

Sources/CodableDatastore/Persistence/Disk Persistence/Transaction/Transaction.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ extension DiskPersistence {
172172
try await root.persistIfNeeded()
173173
}
174174

175-
let addedDatastoreRoots = Set(createdRootObjects.map(\.id))
176-
let removedDatastoreRoots = Set(deletedRootObjects.map(\.id))
175+
let addedDatastoreRoots = Set(createdRootObjects.map(\.referenceID))
176+
let removedDatastoreRoots = Set(deletedRootObjects.map(\.referenceID))
177177

178178
try await persistence.persist(
179179
actionName: actionName,

0 commit comments

Comments
 (0)