Avoid doing too many writes when synchronizing

sync3
Laurent 5 months ago
parent 5910bfffd4
commit 007f7d6bf1
  1. 64
      LeStorage/Store.swift
  2. 138
      LeStorage/StoreCenter.swift
  3. 38
      LeStorage/StoredCollection.swift
  4. 19
      LeStorage/SyncedCollection.swift

@ -254,6 +254,20 @@ final public class Store {
// MARK: - Synchronization // MARK: - Synchronization
fileprivate func _requestWrite<T: SyncedStorable>(type: T.Type) {
self._baseCollections[T.resourceName()]?.requestWrite()
}
@MainActor
func synchronizationAddOrUpdate<T: SyncedStorable>(_ instances: [T], shared: SharingStatus?) {
for item in instances {
if !self.storeCenter.hasAlreadyBeenDeleted(item) {
self.addOrUpdateIfNewer(item, shared: shared)
}
}
self._requestWrite(type: T.self)
}
/// Calls addOrUpdateIfNewer from the collection corresponding to the instance /// Calls addOrUpdateIfNewer from the collection corresponding to the instance
@MainActor @MainActor
func addOrUpdateIfNewer<T: SyncedStorable>(_ instance: T, shared: SharingStatus?) { func addOrUpdateIfNewer<T: SyncedStorable>(_ instance: T, shared: SharingStatus?) {
@ -261,6 +275,52 @@ final public class Store {
collection.addOrUpdateIfNewer(instance, shared: shared) collection.addOrUpdateIfNewer(instance, shared: shared)
} }
@MainActor
func synchronizationDelete<T: SyncedStorable>(_ identifiers: [ObjectIdentifier], type: T.Type) {
for identifier in identifiers {
do {
try self.deleteNoSyncNoCascadeNoWrite(type: type, id: identifier.modelId)
} catch {
Logger.error(error)
}
self.storeCenter.cleanupDataLog(dataId: identifier.modelId)
}
self._requestWrite(type: T.self)
}
@MainActor
func synchronizationRevoke<T: SyncedStorable>(_ identifiers: [ObjectIdentifier], type: T.Type) {
for identifier in identifiers {
do {
if let instance = self._instance(id: identifier.modelId, type: type) {
if instance.sharing != nil && !self.storeCenter.isReferenced(instance: instance) {
try self.deleteNoSyncNoCascadeNoWrite(type: type, id: identifier.modelId)
}
}
} catch {
Logger.error(error)
}
}
self._requestWrite(type: T.self)
// for identifier in identifiers {
// do {
// try self.deleteNoSyncNoCascade(type: type, id: identifier.modelId)
// } catch {
// Logger.error(error)
// }
// self.storeCenter.cleanupDataLog(dataId: identifier.modelId)
// }
// self._requestWrite(type: T.self)
}
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type) -> T? {
let realId: T.ID = T.buildRealId(id: id)
return self.findById(realId)
}
/// Calls deleteById from the collection corresponding to the instance /// Calls deleteById from the collection corresponding to the instance
// func deleteNoSync<T: Storable>(instance: T) { // func deleteNoSync<T: Storable>(instance: T) {
// do { // do {
@ -272,9 +332,9 @@ final public class Store {
// } // }
/// Calls deleteById from the collection corresponding to the instance /// Calls deleteById from the collection corresponding to the instance
func deleteNoSyncNoCascade<T: SyncedStorable>(type: T.Type, id: String) throws { func deleteNoSyncNoCascadeNoWrite<T: SyncedStorable>(type: T.Type, id: String) throws {
let collection: SyncedCollection<T> = try self.syncedCollection() let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteNoSyncNoCascade(id: id) collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
} }
/// Calls deleteById from the collection corresponding to the instance /// Calls deleteById from the collection corresponding to the instance

@ -680,36 +680,65 @@ public class StoreCenter {
/// - updateArrays: the server updates /// - updateArrays: the server updates
/// - shared: indicates if the content should be flagged as shared /// - shared: indicates if the content should be flagged as shared
@MainActor @MainActor
func _syncAddOrUpdate(_ updateArrays: [SyncedStorableArray], shared: SharingStatus? = nil) async { fileprivate func _syncAddOrUpdate(_ updateArrays: [SyncedStorableArray], shared: SharingStatus? = nil) async {
for updateArray in updateArrays { for updateArray in updateArrays {
for item in updateArray.items { await self._syncAddOrUpdate(updateArray, type: updateArray.type, shared: shared)
let storeId: String? = item.getStoreId() // for item in updateArray.items {
await self.synchronizationAddOrUpdate(item, storeId: storeId, shared: shared) // let storeId: String? = item.getStoreId()
} // await self.synchronizationAddOrUpdate(item, storeId: storeId, shared: shared)
// }
} }
} }
@MainActor
fileprivate func _syncAddOrUpdate<T: SyncedStorable>(_ updateArray: SyncedStorableArray, type: T.Type, shared: SharingStatus? = nil) async {
let itemsByStore = updateArray.items.group { $0.getStoreId() }
for (storeId, items) in itemsByStore {
let store = self._requestStore(id: storeId)
store.synchronizationAddOrUpdate(items as! [T], shared: shared)
}
}
/// Processes data that should be deleted inside the app /// Processes data that should be deleted inside the app
fileprivate func _syncDelete(_ deletionArrays: [ObjectIdentifierArray]) async { fileprivate func _syncDelete(_ deletionArrays: [ObjectIdentifierArray]) async {
for deletionArray in deletionArrays { for deletionArray in deletionArrays {
for deletedObject in deletionArray.items { await self._syncDelete(deletionArray, type: deletionArray.type)
await self.synchronizationDelete(id: deletedObject.modelId, type: deletionArray.type, storeId: deletedObject.storeId)
} // for deletedObject in deletionArray.items {
// await self.synchronizationDelete(id: deletedObject.modelId, type: deletionArray.type, storeId: deletedObject.storeId)
// }
} }
}
fileprivate func _syncDelete<T : SyncedStorable>(_ deletionArray: ObjectIdentifierArray, type: T.Type) async {
for deletedObject in deletionArray.items {
let itemsByStore = deletionArray.items.group { $0.storeId }
for (storeId, items) in itemsByStore {
let store = self._requestStore(id: storeId)
await store.synchronizationDelete(items, type: T.self)
}
// await self.synchronizationDelete(id: deletedObject.modelId, type: deletionArray.type, storeId: deletedObject.storeId)
}
} }
/// Processes data that has been revoked /// Processes data that has been revoked
fileprivate func syncRevoke(_ revokedArrays: [ObjectIdentifierArray], parents: [[ObjectIdentifierArray]]) async { fileprivate func syncRevoke(_ revokedArrays: [ObjectIdentifierArray], parents: [[ObjectIdentifierArray]]) async {
await self._syncRevoke(revokedArrays) await self._syncRevoke(revokedArrays)
for revokedArray in revokedArrays { for revokedArray in revokedArrays {
for revoked in revokedArray.items { await self._syncDelete(revokedArray, type: revokedArray.type)
await self.synchronizationDelete(id: revoked.modelId, type: revokedArray.type, storeId: revoked.storeId) // or synchronizationRevoke ?
} // for revoked in revokedArray.items {
// await self.synchronizationDelete(id: revoked.modelId, type: revokedArray.type, storeId: revoked.storeId) // or synchronizationRevoke ?
// }
} }
for level in parents { for level in parents {
@ -719,12 +748,30 @@ public class StoreCenter {
fileprivate func _syncRevoke(_ revokeArrays: [ObjectIdentifierArray]) async { fileprivate func _syncRevoke(_ revokeArrays: [ObjectIdentifierArray]) async {
for revokeArray in revokeArrays { for revokeArray in revokeArrays {
for revoked in revokeArray.items { await self._syncRevoke(revokeArray: revokeArray)
await self.synchronizationRevoke(id: revoked.modelId, type: revokeArray.type, storeId: revoked.storeId) // for revoked in revokeArray.items {
} // await self.synchronizationRevoke(id: revoked.modelId, type: revokeArray.type, storeId: revoked.storeId)
// }
} }
} }
@MainActor
fileprivate func _syncRevoke(revokeArray: ObjectIdentifierArray) async {
let itemsByStore = revokeArray.items.group { $0.storeId }
for (storeId, items) in itemsByStore {
let store = self._requestStore(id: storeId)
await store.synchronizationRevoke(items, type: revokeArray.type)
}
// for revoked in revokeArray.items {
//
//
//
// }
}
/// Returns a Type object for a class name /// Returns a Type object for a class name
func classFromName(_ className: String) throws -> any SyncedStorable.Type { func classFromName(_ className: String) throws -> any SyncedStorable.Type {
if let type = ClassLoader.getClass(className, classProject: self.classProject) { if let type = ClassLoader.getClass(className, classProject: self.classProject) {
@ -740,52 +787,45 @@ public class StoreCenter {
} }
/// Returns whether a data has already been deleted by, to avoid inserting it again /// Returns whether a data has already been deleted by, to avoid inserting it again
fileprivate func _hasAlreadyBeenDeleted<T: Storable>(_ instance: T) -> Bool { func hasAlreadyBeenDeleted<T: Storable>(_ instance: T) -> Bool {
return self._deleteLogs.contains(where: { return self._deleteLogs.contains(where: {
$0.dataId == instance.stringId && $0.operation == .delete $0.dataId == instance.stringId && $0.operation == .delete
}) })
} }
/// Adds or updates an instance into the store /// Adds or updates an instance into the store
func synchronizationAddOrUpdate<T: SyncedStorable>(_ instance: T, storeId: String?, shared: SharingStatus?) async { // func synchronizationAddOrUpdate<T: SyncedStorable>(_ instance: T, storeId: String?, shared: SharingStatus?) async {
let hasAlreadyBeenDeleted: Bool = self._hasAlreadyBeenDeleted(instance) // let hasAlreadyBeenDeleted: Bool = self.hasAlreadyBeenDeleted(instance)
if !hasAlreadyBeenDeleted { // if !hasAlreadyBeenDeleted {
await self._requestStore(id: storeId).addOrUpdateIfNewer(instance, shared: shared) // await self._requestStore(id: storeId).addOrUpdateIfNewer(instance, shared: shared)
} // }
} // }
/// Deletes an instance with the given parameters /// Deletes an instance with the given parameters
@MainActor // @MainActor
func synchronizationDelete<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) { // func synchronizationDelete<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
do { // do {
try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id) // try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
} catch { // } catch {
Logger.error(error) // Logger.error(error)
} // }
self._cleanupDataLog(dataId: id) // self.cleanupDataLog(dataId: id)
} // }
/// Revokes a data that has been shared with the user /// Revokes a data that has been shared with the user
@MainActor // @MainActor
func synchronizationRevoke<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) { // func synchronizationRevoke<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
//
do { // do {
if let instance = self._instance(id: id, type: type, storeId: storeId) { // if let instance = self._instance(id: id, type: type, storeId: storeId) {
if instance.sharing != nil && !self.isReferenced(instance: instance) { // if instance.sharing != nil && !self.isReferenced(instance: instance) {
try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
}
}
// if self._instanceShared(id: id, type: type, storeId: storeId) {
// let count = self.isReferenced(type: type, id: id)
// if count == 0 {
// try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id) // try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
// } // }
// } // }
} catch { // } catch {
Logger.error(error) // Logger.error(error)
} // }
} // }
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> T? { fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> T? {
let realId: T.ID = T.buildRealId(id: id) let realId: T.ID = T.buildRealId(id: id)
@ -800,7 +840,7 @@ public class StoreCenter {
// } // }
/// Deletes a data log by data id /// Deletes a data log by data id
fileprivate func _cleanupDataLog(dataId: String) { func cleanupDataLog(dataId: String) {
let logs = self._deleteLogs.filter { $0.dataId == dataId } let logs = self._deleteLogs.filter { $0.dataId == dataId }
self._deleteLogs.delete(contentOfs: logs) self._deleteLogs.delete(contentOfs: logs)
} }

@ -25,7 +25,7 @@ public protocol SomeCollection<Item>: Identifiable {
func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool) func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool)
func findById(_ id: Item.ID) -> Item? func findById(_ id: Item.ID) -> Item?
func requestWrite()
} }
protocol CollectionDelegate<Item> { protocol CollectionDelegate<Item> {
@ -49,11 +49,12 @@ public struct ActionResult<T> {
public struct ActionOption: Codable { public struct ActionOption: Codable {
var synchronize: Bool var synchronize: Bool
var cascade: Bool var cascade: Bool
var write: Bool
static let standard: ActionOption = ActionOption(synchronize: false, cascade: false) static let standard: ActionOption = ActionOption(synchronize: false, cascade: false, write: true)
static let cascade: ActionOption = ActionOption(synchronize: false, cascade: true) static let noCascadeNoWrite: ActionOption = ActionOption(synchronize: false, cascade: false, write: false)
static let syncedCascade: ActionOption = ActionOption(synchronize: true, cascade: true) static let cascade: ActionOption = ActionOption(synchronize: false, cascade: true, write: true)
static let syncedCascade: ActionOption = ActionOption(synchronize: true, cascade: true, write: true)
} }
public class StoredCollection<T: Storable>: SomeCollection { public class StoredCollection<T: Storable>: SomeCollection {
@ -146,7 +147,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
// MARK: - Loading // MARK: - Loading
/// Sets the collection as changed to trigger a write /// Sets the collection as changed to trigger a write
fileprivate func requestWrite() { public func requestWrite() {
self._triggerWrite = true self._triggerWrite = true
} }
@ -203,8 +204,9 @@ public class StoredCollection<T: Storable>: SomeCollection {
func setItems(_ items: [T]) { func setItems(_ items: [T]) {
self.items.removeAll() self.items.removeAll()
for item in items { for item in items {
self.addItem(instance: item) self._addItem(instance: item)
} }
self.requestWrite()
} }
@MainActor @MainActor
@ -217,7 +219,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.addOrUpdate(contentOfs: items) self.addOrUpdate(contentOfs: items)
} }
self.requestWrite()
} }
/// Updates the whole index with the items array /// Updates the whole index with the items array
@ -254,10 +255,10 @@ public class StoredCollection<T: Storable>: SomeCollection {
fileprivate func _rawAddOrUpdate(instance: T) -> ActionResult<T> { fileprivate func _rawAddOrUpdate(instance: T) -> ActionResult<T> {
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { if let index = self.items.firstIndex(where: { $0.id == instance.id }) {
let updated = self.updateItem(instance, index: index, actionOption: .standard) let updated = self._updateItem(instance, index: index, actionOption: .standard)
return ActionResult(instance: instance, method: .update, pending: !updated) return ActionResult(instance: instance, method: .update, pending: !updated)
} else { } else {
let added = self.addItem(instance: instance) let added = self._addItem(instance: instance)
return ActionResult(instance: instance, method: .insert, pending: !added) return ActionResult(instance: instance, method: .insert, pending: !added)
} }
} }
@ -268,7 +269,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.requestWrite() self.requestWrite()
} }
self.items.removeAll() self.items.removeAll()
self.addItem(instance: instance) self._addItem(instance: instance)
} }
/// Deletes the instance in the collection and sets the collection as changed to trigger a write /// Deletes the instance in the collection and sets the collection as changed to trigger a write
@ -313,12 +314,11 @@ public class StoredCollection<T: Storable>: SomeCollection {
} }
func add(instance: T, actionOption: ActionOption) { func add(instance: T, actionOption: ActionOption) {
self.addItem(instance: instance, actionOption: actionOption) self._addItem(instance: instance, actionOption: actionOption)
self.requestWrite()
} }
/// Adds an instance to the collection /// Adds an instance to the collection
@discardableResult fileprivate func addItem(instance: T, actionOption: ActionOption = .standard) -> Bool { @discardableResult fileprivate func _addItem(instance: T, actionOption: ActionOption = .standard) -> Bool {
if !self.hasLoaded { if !self.hasLoaded {
self.addPendingOperation(method: .add, instance: instance, actionOption: actionOption) self.addPendingOperation(method: .add, instance: instance, actionOption: actionOption)
@ -339,12 +339,12 @@ public class StoredCollection<T: Storable>: SomeCollection {
} }
func update(_ instance: T, index: Int, actionOption: ActionOption) { func update(_ instance: T, index: Int, actionOption: ActionOption) {
self.updateItem(instance, index: index, actionOption: actionOption) self._updateItem(instance, index: index, actionOption: actionOption)
self.requestWrite() // self.requestWrite()
} }
/// Updates an instance to the collection by index /// Updates an instance to the collection by index
@discardableResult fileprivate func updateItem(_ instance: T, index: Int, actionOption: ActionOption) -> Bool { @discardableResult fileprivate func _updateItem(_ instance: T, index: Int, actionOption: ActionOption) -> Bool {
if !self.hasLoaded { if !self.hasLoaded {
self.addPendingOperation(method: .update, instance: instance, actionOption: actionOption) self.addPendingOperation(method: .update, instance: instance, actionOption: actionOption)
@ -415,7 +415,9 @@ public class StoredCollection<T: Storable>: SomeCollection {
if let instance = self.findById(realId) { if let instance = self.findById(realId) {
self.deleteItem(instance, actionOption: actionOption) self.deleteItem(instance, actionOption: actionOption)
} }
self.requestWrite() if actionOption.write {
self.requestWrite()
}
} }
/// Returns the instance corresponding to the provided [id] /// Returns the instance corresponding to the provided [id]

@ -338,13 +338,22 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
func deleteUnusedGranted(instance: T) { func deleteUnusedGranted(instance: T) {
guard instance.sharing != nil else { return } guard instance.sharing != nil else { return }
self.deleteNoSyncNoCascade(id: instance.stringId) self.deleteByStringId(instance.stringId)
instance.deleteUnusedSharedDependencies(store: self.store) instance.deleteUnusedSharedDependencies(store: self.store)
} }
/// Deletes the instance in the collection without synchronization /// Deletes the instance in the collection without synchronization
func deleteNoSyncNoCascade(id: String) { // func deleteNoSyncNoCascade(id: String) {
self.collection.deleteByStringId(id, actionOption: .standard) // self.collection.deleteByStringId(id, actionOption: .standard)
// }
//
// /// Deletes the instance in the collection without synchronization
// func deleteNoSyncNoCascadeNoWrite(id: String) {
// self.collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
// }
func deleteByStringId(_ id: String, actionOption: ActionOption = .standard) {
self.collection.deleteByStringId(id, actionOption: actionOption)
} }
// MARK: - Collection Delegate // MARK: - Collection Delegate
@ -472,6 +481,10 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return self.collection.items return self.collection.items
} }
public func requestWrite() {
self.collection.requestWrite()
}
} }
class OperationBatch<T> { class OperationBatch<T> {

Loading…
Cancel
Save