diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift index cdb2746..adda335 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift @@ -98,6 +98,17 @@ extension DiskPersistence.Datastore.Index { case .secondary(let index, let manifest): self = .secondary(index: index, manifest: manifest) } } + + init(_ id: DatastoreRootManifest.IndexManifestID) { + switch id { + case .primary(let manifest): + self = .primary(manifest: manifest) + case .direct(let index, let manifest): + self = .direct(index: index, manifest: manifest) + case .secondary(let index, let manifest): + self = .secondary(index: index, manifest: manifest) + } + } } } diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/PersistenceDatastore.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/PersistenceDatastore.swift index f1ac416..7d754ed 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/PersistenceDatastore.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/PersistenceDatastore.swift @@ -217,14 +217,16 @@ extension DiskPersistence.Datastore { extension DiskPersistence.Datastore { /// Load the root object from disk for the given identifier. - func loadRootObject(for rootIdentifier: DatastoreRootIdentifier) throws -> DatastoreRootManifest { + func loadRootObject(for rootIdentifier: DatastoreRootIdentifier, shouldCache: Bool = true) throws -> DatastoreRootManifest { let rootObjectURL = rootURL(for: rootIdentifier) let data = try Data(contentsOf: rootObjectURL) let root = try JSONDecoder.shared.decode(DatastoreRootManifest.self, from: data) - cachedRootObject = root + if shouldCache { + cachedRootObject = root + } return root } diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift index ba11304..c789d82 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift @@ -291,7 +291,7 @@ extension DiskPersistence { return snapshot } - let snapshot = Snapshot(id: snapshotID, persistence: self) + let snapshot = Snapshot(id: snapshotID, persistence: self, isExtendedIterationCacheEnabled: !_transactionRetentionPolicy.isIndefinite) snapshots[snapshotID] = snapshot return snapshot diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift index 15e15dd..0b9ab92 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift @@ -30,8 +30,9 @@ actor Snapshot { /// A cached instance of the manifest as last loaded from disk. var cachedManifest: SnapshotManifest? - /// A cached instance of the current iteration as last loaded from disk. - var cachedIteration: SnapshotIteration? + /// Cache for the loaded iterations as last loaded from disk. ``isExtendedIterationCacheEnabled`` controls if multiple iterations are cached or not. + var cachedIterations: [SnapshotIterationIdentifier : SnapshotIteration] = [:] + var isExtendedIterationCacheEnabled: Bool /// A pointer to the last manifest updater, so updates can be serialized after the last request. var lastUpdateManifestTask: Task? @@ -42,11 +43,13 @@ actor Snapshot { init( id: SnapshotIdentifier, persistence: DiskPersistence, - isBackup: Bool = false + isBackup: Bool = false, + isExtendedIterationCacheEnabled: Bool = false ) { self.id = id self.persistence = persistence self.isBackup = isBackup + self.isExtendedIterationCacheEnabled = isExtendedIterationCacheEnabled } } @@ -124,14 +127,25 @@ extension Snapshot { } } + func setExtendedIterationCacheEnabled(_ isEnabled: Bool) { + isExtendedIterationCacheEnabled = isEnabled + } + /// Load an iteration from disk, or create a suitable starting value if such a file does not exist. - private func loadIteration(for iterationID: SnapshotIterationIdentifier) throws -> SnapshotIteration { + func loadIteration(for iterationID: SnapshotIterationIdentifier?) async throws -> SnapshotIteration? { + guard let iterationID else { return nil } + if let iteration = cachedIterations[iterationID] { + return iteration + } do { let data = try Data(contentsOf: iterationURL(for: iterationID)) let iteration = try JSONDecoder.shared.decode(SnapshotIteration.self, from: data) - cachedIteration = iteration + if !isExtendedIterationCacheEnabled { + cachedIterations.removeAll() + } + cachedIterations[iteration.id] = iteration return iteration } catch { throw error @@ -155,7 +169,7 @@ extension Snapshot { cachedManifest = manifest } - /// Write the specified iteration to the store, and cache the results in ``Snapshot/cachedIteration``. + /// Write the specified iteration to the store, and cache the results in ``Snapshot/cachedIterations``. private func write(iteration: SnapshotIteration) throws where AccessMode == ReadWrite { let iterationURL = iterationURL(for: iteration.id) /// Make sure the directories exists first. @@ -166,7 +180,10 @@ extension Snapshot { try data.write(to: iterationURL, options: .atomic) /// Update the cache since we know what it should be. - cachedIteration = iteration + if !isExtendedIterationCacheEnabled { + cachedIterations.removeAll() + } + cachedIterations[iteration.id] = iteration } /// Load and update the manifest in an updater, returning the task for the updater. @@ -200,15 +217,8 @@ extension Snapshot { /// Load the manifest so we have a fresh copy, unless we have a cached copy already. var manifest = try cachedManifest ?? self.loadManifest() - var iteration: SnapshotIteration - if let cachedIteration, cachedIteration.id == manifest.currentIteration { - iteration = cachedIteration - } else if let iterationID = manifest.currentIteration { - iteration = try self.loadIteration(for: iterationID) - } else { - let date = Date() - iteration = SnapshotIteration(id: SnapshotIterationIdentifier(date: date), creationDate: date) - } + let precedingIteration = try await self.loadIteration(for: manifest.currentIteration) + var iteration = precedingIteration ?? SnapshotIteration() /// Let the updater do something with the manifest, storing the variable on the Task Local stack. let returnValue = try await SnapshotTaskLocals.$manifest.withValue((manifest, iteration)) { @@ -216,10 +226,10 @@ extension Snapshot { } /// Only write to the store if we changed the manifest for any reason - if iteration.isMeaningfullyChanged(from: cachedIteration) { + if iteration.isMeaningfullyChanged(from: precedingIteration) { iteration.creationDate = Date() iteration.id = SnapshotIterationIdentifier(date: iteration.creationDate) - iteration.precedingIteration = cachedIteration?.id + iteration.precedingIteration = precedingIteration?.id try write(iteration: iteration) } @@ -260,15 +270,7 @@ extension Snapshot { /// Load the manifest so we have a fresh copy, unless we have a cached copy already. let manifest = try cachedManifest ?? self.loadManifest() - var iteration: SnapshotIteration - if let cachedIteration, cachedIteration.id == manifest.currentIteration { - iteration = cachedIteration - } else if let iterationID = manifest.currentIteration { - iteration = try self.loadIteration(for: iterationID) - } else { - let date = Date() - iteration = SnapshotIteration(id: SnapshotIterationIdentifier(date: date), creationDate: date) - } + let iteration = try await self.loadIteration(for: manifest.currentIteration) ?? SnapshotIteration() /// Let the accessor do something with the manifest, storing the variable on the Task Local stack. return try await SnapshotTaskLocals.$manifest.withValue((manifest, iteration)) { diff --git a/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift b/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift index 4feb622..a3f8b16 100644 --- a/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift +++ b/Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift @@ -68,6 +68,12 @@ extension SnapshotIteration { } extension SnapshotIteration { + /// Initialize a snapshot iteration with a date + /// - Parameter date: The date to base the identifier and creation date off of. + init(date: Date = Date()) { + self.init(id: SnapshotIterationIdentifier(date: date), creationDate: date) + } + /// Internal method to check if an instance should be persisted based on iff it changed significantly from a previous iteration /// - Parameter existingInstance: The previous iteration to check /// - Returns: `true` if the iteration should be persisted, `false` if it represents the same data from `existingInstance`.