Avoid doing too many writes when synchronizing

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

@ -254,6 +254,20 @@ final public class Store {
// 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
@MainActor
func addOrUpdateIfNewer<T: SyncedStorable>(_ instance: T, shared: SharingStatus?) {
@ -261,6 +275,52 @@ final public class Store {
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
// func deleteNoSync<T: Storable>(instance: T) {
// do {
@ -272,9 +332,9 @@ final public class Store {
// }
/// 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()
collection.deleteNoSyncNoCascade(id: id)
collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
}
/// Calls deleteById from the collection corresponding to the instance

@ -680,36 +680,65 @@ public class StoreCenter {
/// - updateArrays: the server updates
/// - shared: indicates if the content should be flagged as shared
@MainActor
func _syncAddOrUpdate(_ updateArrays: [SyncedStorableArray], shared: SharingStatus? = nil) async {
fileprivate func _syncAddOrUpdate(_ updateArrays: [SyncedStorableArray], shared: SharingStatus? = nil) async {
for updateArray in updateArrays {
for item in updateArray.items {
let storeId: String? = item.getStoreId()
await self.synchronizationAddOrUpdate(item, storeId: storeId, shared: shared)
await self._syncAddOrUpdate(updateArray, type: updateArray.type, shared: shared)
// for item in updateArray.items {
// 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
fileprivate func _syncDelete(_ deletionArrays: [ObjectIdentifierArray]) async {
for deletionArray in deletionArrays {
for deletedObject in deletionArray.items {
await self.synchronizationDelete(id: deletedObject.modelId, type: deletionArray.type, storeId: deletedObject.storeId)
await self._syncDelete(deletionArray, type: deletionArray.type)
// 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
fileprivate func syncRevoke(_ revokedArrays: [ObjectIdentifierArray], parents: [[ObjectIdentifierArray]]) async {
await self._syncRevoke(revokedArrays)
for revokedArray in revokedArrays {
for revoked in revokedArray.items {
await self.synchronizationDelete(id: revoked.modelId, type: revokedArray.type, storeId: revoked.storeId) // or synchronizationRevoke ?
}
await self._syncDelete(revokedArray, type: revokedArray.type)
// for revoked in revokedArray.items {
// await self.synchronizationDelete(id: revoked.modelId, type: revokedArray.type, storeId: revoked.storeId) // or synchronizationRevoke ?
// }
}
for level in parents {
@ -719,10 +748,28 @@ public class StoreCenter {
fileprivate func _syncRevoke(_ revokeArrays: [ObjectIdentifierArray]) async {
for revokeArray in revokeArrays {
for revoked in revokeArray.items {
await self.synchronizationRevoke(id: revoked.modelId, type: revokeArray.type, storeId: revoked.storeId)
await self._syncRevoke(revokeArray: revokeArray)
// 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
@ -740,52 +787,45 @@ public class StoreCenter {
}
/// 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: {
$0.dataId == instance.stringId && $0.operation == .delete
})
}
/// Adds or updates an instance into the store
func synchronizationAddOrUpdate<T: SyncedStorable>(_ instance: T, storeId: String?, shared: SharingStatus?) async {
let hasAlreadyBeenDeleted: Bool = self._hasAlreadyBeenDeleted(instance)
if !hasAlreadyBeenDeleted {
await self._requestStore(id: storeId).addOrUpdateIfNewer(instance, shared: shared)
}
}
// func synchronizationAddOrUpdate<T: SyncedStorable>(_ instance: T, storeId: String?, shared: SharingStatus?) async {
// let hasAlreadyBeenDeleted: Bool = self.hasAlreadyBeenDeleted(instance)
// if !hasAlreadyBeenDeleted {
// await self._requestStore(id: storeId).addOrUpdateIfNewer(instance, shared: shared)
// }
// }
/// Deletes an instance with the given parameters
@MainActor
func synchronizationDelete<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
do {
try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
} catch {
Logger.error(error)
}
self._cleanupDataLog(dataId: id)
}
// @MainActor
// func synchronizationDelete<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
// do {
// try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
// } catch {
// Logger.error(error)
// }
// self.cleanupDataLog(dataId: id)
// }
/// Revokes a data that has been shared with the user
@MainActor
func synchronizationRevoke<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
do {
if let instance = self._instance(id: id, type: type, storeId: storeId) {
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 {
// @MainActor
// func synchronizationRevoke<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
//
// do {
// if let instance = self._instance(id: id, type: type, storeId: storeId) {
// if instance.sharing != nil && !self.isReferenced(instance: instance) {
// try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
// }
// }
} catch {
Logger.error(error)
}
}
// } catch {
// Logger.error(error)
// }
// }
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> T? {
let realId: T.ID = T.buildRealId(id: id)
@ -800,7 +840,7 @@ public class StoreCenter {
// }
/// Deletes a data log by data id
fileprivate func _cleanupDataLog(dataId: String) {
func cleanupDataLog(dataId: String) {
let logs = self._deleteLogs.filter { $0.dataId == dataId }
self._deleteLogs.delete(contentOfs: logs)
}

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

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

Loading…
Cancel
Save