diff --git a/LeStorage/Relationship.swift b/LeStorage/Relationship.swift index 7b0fe17..2955660 100644 --- a/LeStorage/Relationship.swift +++ b/LeStorage/Relationship.swift @@ -5,12 +5,18 @@ // Created by Laurent Morvillier on 27/11/2024. // +public enum StoreLookup { + case same + case main + case child +} + 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.keyPath = keyPath - self.mainStoreLookup = mainStoreLookup + self.storeLookup = storeLookup } /// The type of the relationship @@ -20,6 +26,6 @@ public struct Relationship { var keyPath: AnyKeyPath /// Indicates whether the linked object is on the main Store - var mainStoreLookup: Bool + var storeLookup: StoreLookup } diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 1a5a3b8..747aa34 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -19,6 +19,7 @@ public enum StoreError: Error, LocalizedError { case synchronizationInactive case storeNotRegistered(id: String) case castIssue(type: String) + case invalidStoreLookup(from: any Storable.Type, to: any Storable.Type) public var errorDescription: String? { switch self { @@ -42,6 +43,8 @@ public enum StoreError: Error, LocalizedError { return "The store with identifier \(id) is not registered" case .castIssue(let 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 - func referenceCount(type: T.Type, id: String) -> Int { - var count: Int = 0 - for collection in self._collections.values { - count += collection.referenceCount(type: type, id: id) +// func referenceCount(type: T.Type, id: String) -> Int { +// var count: Int = 0 +// for collection in self._collections.values { +// count += collection.referenceCount(type: type, id: id) +// } +// return count +// } + + func isReferenced(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(_ instance: T) { - if self.referenceCount(type: T.self, id: instance.stringId) == 0 { + public func deleteUnusedGrantedIfNecessary(_ instance: T, originStoreId: String?) { + if !self.storeCenter.isReferenced(instance: instance) { do { let collection: SyncedCollection = try self.syncedCollection() - collection.deleteUnusedShared(instance: instance) + collection.deleteUnusedGranted(instance: instance) } catch { Logger.error(error) } @@ -320,11 +331,11 @@ final public class Store { do { for item in items { 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 item.deleteUnusedSharedDependencies(store: self) let collection: SyncedCollection = try self.syncedCollection() - collection.deleteUnusedShared(instance: item) + collection.deleteUnusedGranted(instance: item) } } } catch { diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index 9ae0c6c..612e3b3 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -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] { return store } else { @@ -782,7 +782,7 @@ public class StoreCenter { @MainActor func synchronizationDelete(id: String, type: T.Type, storeId: String?) { do { - try self._store(id: storeId)?.deleteNoSyncNoCascade(type: type, id: id) + try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id) } catch { Logger.error(error) } @@ -794,23 +794,34 @@ public class StoreCenter { func synchronizationRevoke(id: String, type: T.Type, storeId: String?) { do { - if self._instanceShared(id: id, type: type) { - let count = self.mainStore.referenceCount(type: type, id: id) - if count == 0 { - try self._store(id: storeId)?.deleteNoSyncNoCascade(type: type, id: id) + 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 { +// try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id) +// } +// } } catch { Logger.error(error) } } - /// Returns whether an instance has been shared with the user - fileprivate func _instanceShared(id: String, type: T.Type) -> Bool { + fileprivate func _instance(id: String, type: T.Type, storeId: String?) -> T? { let realId: T.ID = T.buildRealId(id: id) - let instance: T? = self.mainStore.findById(realId) - return instance?.sharing != nil + return self._store(id: storeId).findById(realId) } + + /// Returns whether an instance has been shared with the user +// fileprivate func _instanceShared(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 fileprivate func _cleanupDataLog(dataId: String) { @@ -826,6 +837,28 @@ public class StoreCenter { self._deleteLogs.addOrUpdate(instance: dataLog) } + func relationshipStore(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(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 func decodeObjectIdentifierDictionary(_ dictionary: [String: Any]) throws -> [ObjectIdentifierArray] { diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 2cb8dd7..11c48ea 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -17,7 +17,7 @@ public protocol SomeCollection: Identifiable { var type: any Storable.Type { get } func reset() - func referenceCount(type: S.Type, id: String) -> Int + func isReferenced(type: S.Type, id: String) -> Bool var items: [Item] { get } @@ -528,16 +528,25 @@ public class StoredCollection: SomeCollection { // MARK: - Reference count /// Counts the references to an object - given its type and id - inside the collection - public func referenceCount(type: S.Type, id: String) -> Int { + public func isReferenced(type: S.Type, id: String) -> Bool { 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 - count - + relationships.filter { relationship in - (item[keyPath: relationship.keyPath] as? String) == id - }.count + for item in self.items { + for relationship in relationships { + if item[keyPath: relationship.keyPath] as? String == id { + return true + } + } } + 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 diff --git a/LeStorage/SyncedCollection.swift b/LeStorage/SyncedCollection.swift index 961a876..eeb4178 100644 --- a/LeStorage/SyncedCollection.swift +++ b/LeStorage/SyncedCollection.swift @@ -229,7 +229,7 @@ public class SyncedCollection: SomeSyncedCollection, Collect for relationship in T.relationships() { if let syncedType = relationship.type as? (any SyncedStorable.Type) { do { - try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType) + try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType, originStoreId: self.storeId) } catch { Logger.error(error) } @@ -237,24 +237,31 @@ public class SyncedCollection: SomeSyncedCollection, Collect } } - fileprivate func _deleteUnusedSharedInstances(relationship: Relationship, type: S.Type) throws { + fileprivate func _deleteUnusedSharedInstances(relationship: Relationship, type: S.Type, originStoreId: String?) throws { let store: Store - if relationship.mainStoreLookup { - store = self.store.storeCenter.mainStore - } else { - store = self.store + switch relationship.storeLookup { + case .main: store = self.store.storeCenter.mainStore + case .same: 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 = 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 { - self.store.deleteUnusedSharedIfNecessary(sharedItem) + self.store.deleteUnusedGrantedIfNecessary(sharedItem, originStoreId: originStoreId + ) } } @@ -329,7 +336,7 @@ public class SyncedCollection: SomeSyncedCollection, Collect self.collection.delete(contentOfs: sequence) } - func deleteUnusedShared(instance: T) { + func deleteUnusedGranted(instance: T) { guard instance.sharing != nil else { return } @@ -451,8 +458,8 @@ public class SyncedCollection: SomeSyncedCollection, Collect public var type: any Storable.Type { return T.self } - public func referenceCount(type: S.Type, id: String) -> Int where S : Storable { - return self.collection.referenceCount(type: type, id: id) + public func isReferenced(type: S.Type, id: String) -> Bool where S : Storable { + return self.collection.isReferenced(type: type, id: id) } public func reset() {