Refactor reference counting

sync3
Laurent 5 months ago
parent 0a83bddaf6
commit a3ec0820e4
  1. 12
      LeStorage/Relationship.swift
  2. 31
      LeStorage/Store.swift
  3. 53
      LeStorage/StoreCenter.swift
  4. 25
      LeStorage/StoredCollection.swift
  5. 33
      LeStorage/SyncedCollection.swift

@ -5,12 +5,18 @@
// Created by Laurent Morvillier on 27/11/2024. // Created by Laurent Morvillier on 27/11/2024.
// //
public enum StoreLookup {
case same
case main
case child
}
public struct Relationship { public struct Relationship {
public init(type: any Storable.Type, keyPath: AnyKeyPath, mainStoreLookup: Bool) { public init(type: any Storable.Type, keyPath: AnyKeyPath, storeLookup: StoreLookup) {
self.type = type self.type = type
self.keyPath = keyPath self.keyPath = keyPath
self.mainStoreLookup = mainStoreLookup self.storeLookup = storeLookup
} }
/// The type of the relationship /// The type of the relationship
@ -20,6 +26,6 @@ public struct Relationship {
var keyPath: AnyKeyPath var keyPath: AnyKeyPath
/// Indicates whether the linked object is on the main Store /// Indicates whether the linked object is on the main Store
var mainStoreLookup: Bool var storeLookup: StoreLookup
} }

@ -19,6 +19,7 @@ public enum StoreError: Error, LocalizedError {
case synchronizationInactive case synchronizationInactive
case storeNotRegistered(id: String) case storeNotRegistered(id: String)
case castIssue(type: String) case castIssue(type: String)
case invalidStoreLookup(from: any Storable.Type, to: any Storable.Type)
public var errorDescription: String? { public var errorDescription: String? {
switch self { switch self {
@ -42,6 +43,8 @@ public enum StoreError: Error, LocalizedError {
return "The store with identifier \(id) is not registered" return "The store with identifier \(id) is not registered"
case .castIssue(let type): case .castIssue(let type):
return "Can't cast to \(type)" return "Can't cast to \(type)"
case .invalidStoreLookup(let from, let to):
return "invalid store lookup from \(from) to \(to)"
} }
} }
@ -275,19 +278,27 @@ final public class Store {
} }
/// Calls deleteById from the collection corresponding to the instance /// Calls deleteById from the collection corresponding to the instance
func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int { // func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int {
var count: Int = 0 // var count: Int = 0
for collection in self._collections.values { // for collection in self._collections.values {
count += collection.referenceCount(type: type, id: id) // count += collection.referenceCount(type: type, id: id)
// }
// return count
// }
func isReferenced<T: Storable, S: Storable>(collectionType: S.Type, type: T.Type, id: String) -> Bool {
if let collection = self._baseCollections[S.resourceName()] {
return collection.isReferenced(type: type, id: id)
} else {
return false
} }
return count
} }
public func deleteUnusedSharedIfNecessary<T: SyncedStorable>(_ instance: T) { public func deleteUnusedGrantedIfNecessary<T: SyncedStorable>(_ instance: T, originStoreId: String?) {
if self.referenceCount(type: T.self, id: instance.stringId) == 0 { if !self.storeCenter.isReferenced(instance: instance) {
do { do {
let collection: SyncedCollection<T> = try self.syncedCollection() let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteUnusedShared(instance: instance) collection.deleteUnusedGranted(instance: instance)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -320,11 +331,11 @@ final public class Store {
do { do {
for item in items { for item in items {
guard item.sharing != nil else { continue } guard item.sharing != nil else { continue }
if self.referenceCount(type: T.self, id: item.stringId) == 0 { if !self.storeCenter.isReferenced(instance: item) {
// Only delete if the shared item has no references // Only delete if the shared item has no references
item.deleteUnusedSharedDependencies(store: self) item.deleteUnusedSharedDependencies(store: self)
let collection: SyncedCollection<T> = try self.syncedCollection() let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteUnusedShared(instance: item) collection.deleteUnusedGranted(instance: item)
} }
} }
} catch { } catch {

@ -227,7 +227,7 @@ public class StoreCenter {
} }
} }
fileprivate func _store(id: String?) -> Store? { fileprivate func _store(id: String?) -> Store {
if let storeId = id, let store = self._stores[storeId] { if let storeId = id, let store = self._stores[storeId] {
return store return store
} else { } else {
@ -782,7 +782,7 @@ public class StoreCenter {
@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)
} }
@ -794,23 +794,34 @@ public class StoreCenter {
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 self._instanceShared(id: id, type: type) { if let instance = self._instance(id: id, type: type, storeId: storeId) {
let count = self.mainStore.referenceCount(type: type, id: id) if instance.sharing != nil && !self.isReferenced(instance: instance) {
if count == 0 { try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
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)
// }
// }
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} }
/// Returns whether an instance has been shared with the user fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> T? {
fileprivate func _instanceShared<T: SyncedStorable>(id: String, type: T.Type) -> Bool {
let realId: T.ID = T.buildRealId(id: id) let realId: T.ID = T.buildRealId(id: id)
let instance: T? = self.mainStore.findById(realId) return self._store(id: storeId).findById(realId)
return instance?.sharing != nil
} }
/// Returns whether an instance has been shared with the user
// fileprivate func _instanceShared<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> Bool {
//// let realId: T.ID = T.buildRealId(id: id)
// let instance: T? = self._instance(id: id, type: type, storeId: storeId)
// return instance?.sharing != nil
// }
/// Deletes a data log by data id /// Deletes a data log by data id
fileprivate func _cleanupDataLog(dataId: String) { fileprivate func _cleanupDataLog(dataId: String) {
@ -826,6 +837,28 @@ public class StoreCenter {
self._deleteLogs.addOrUpdate(instance: dataLog) self._deleteLogs.addOrUpdate(instance: dataLog)
} }
func relationshipStore<T: SyncedStorable>(instance: T, relationship: Relationship) -> Store? {
switch relationship.storeLookup {
case .main: return Store.main
case .child: return self._stores[instance.stringId]
case .same: return instance.store
}
}
func isReferenced<T: SyncedStorable>(instance: T) -> Bool {
let relationships = T.relationships()
for relationship in relationships {
if let store = self.relationshipStore(instance: instance, relationship: relationship) {
if store.isReferenced(collectionType: relationship.type, type: T.self, id: instance.stringId) {
return true
}
} else {
Logger.w("missing store for instance \(instance)")
}
}
return false
}
// MARK: - Sync data conversion // MARK: - Sync data conversion
func decodeObjectIdentifierDictionary(_ dictionary: [String: Any]) throws -> [ObjectIdentifierArray] { func decodeObjectIdentifierDictionary(_ dictionary: [String: Any]) throws -> [ObjectIdentifierArray] {

@ -17,7 +17,7 @@ public protocol SomeCollection<Item>: Identifiable {
var type: any Storable.Type { get } var type: any Storable.Type { get }
func reset() func reset()
func referenceCount<S: Storable>(type: S.Type, id: String) -> Int func isReferenced<S: Storable>(type: S.Type, id: String) -> Bool
var items: [Item] { get } var items: [Item] { get }
@ -528,16 +528,25 @@ public class StoredCollection<T: Storable>: SomeCollection {
// MARK: - Reference count // MARK: - Reference count
/// Counts the references to an object - given its type and id - inside the collection /// Counts the references to an object - given its type and id - inside the collection
public func referenceCount<S: Storable>(type: S.Type, id: String) -> Int { public func isReferenced<S: Storable>(type: S.Type, id: String) -> Bool {
let relationships = T.relationships().filter { $0.type == type } let relationships = T.relationships().filter { $0.type == type }
guard relationships.count > 0 else { return 0 } guard relationships.count > 0 else { return false }
return self.items.reduce(0) { count, item in for item in self.items {
count for relationship in relationships {
+ relationships.filter { relationship in if item[keyPath: relationship.keyPath] as? String == id {
(item[keyPath: relationship.keyPath] as? String) == id return true
}.count }
}
} }
return false
// return self.items.reduce(0) { count, item in
// count
// + relationships.filter { relationship in
// (item[keyPath: relationship.keyPath] as? String) == id
// }.count
// }
} }
// MARK: - for Synced Collection // MARK: - for Synced Collection

@ -229,7 +229,7 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
for relationship in T.relationships() { for relationship in T.relationships() {
if let syncedType = relationship.type as? (any SyncedStorable.Type) { if let syncedType = relationship.type as? (any SyncedStorable.Type) {
do { do {
try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType) try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType, originStoreId: self.storeId)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -237,24 +237,31 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
} }
} }
fileprivate func _deleteUnusedSharedInstances<S: SyncedStorable>(relationship: Relationship, type: S.Type) throws { fileprivate func _deleteUnusedSharedInstances<S: SyncedStorable>(relationship: Relationship, type: S.Type, originStoreId: String?) throws {
let store: Store let store: Store
if relationship.mainStoreLookup { switch relationship.storeLookup {
store = self.store.storeCenter.mainStore case .main: store = self.store.storeCenter.mainStore
} else { case .same: store = self.store
store = self.store case .child:
throw StoreError.invalidStoreLookup(from: type, to: relationship.type)
} }
// if relationship.storeLookup {
// store = self.store.storeCenter.mainStore
// } else {
// store = self.store
// }
let collection: SyncedCollection<S> = try store.syncedCollection() let collection: SyncedCollection<S> = try store.syncedCollection()
collection._deleteUnusedSharedInstances() collection._deleteUnusedGrantedInstances(originStoreId: originStoreId)
} }
fileprivate func _deleteUnusedSharedInstances() { fileprivate func _deleteUnusedGrantedInstances(originStoreId: String?) {
let sharedItems = self.collection.items.filter { $0.sharing != nil } let sharedItems = self.collection.items.filter { $0.sharing == .granted }
for sharedItem in sharedItems { for sharedItem in sharedItems {
self.store.deleteUnusedSharedIfNecessary(sharedItem) self.store.deleteUnusedGrantedIfNecessary(sharedItem, originStoreId: originStoreId
)
} }
} }
@ -329,7 +336,7 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
self.collection.delete(contentOfs: sequence) self.collection.delete(contentOfs: sequence)
} }
func deleteUnusedShared(instance: T) { func deleteUnusedGranted(instance: T) {
guard instance.sharing != nil else { return } guard instance.sharing != nil else { return }
@ -451,8 +458,8 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
public var type: any Storable.Type { return T.self } public var type: any Storable.Type { return T.self }
public func referenceCount<S>(type: S.Type, id: String) -> Int where S : Storable { public func isReferenced<S>(type: S.Type, id: String) -> Bool where S : Storable {
return self.collection.referenceCount(type: type, id: id) return self.collection.isReferenced(type: type, id: id)
} }
public func reset() { public func reset() {

Loading…
Cancel
Save