diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index ccc3664..e9b74fb 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -33,9 +33,6 @@ actor ApiCallCollection: SomeCallCollection { /// Indicates if the collection is currently retrying ApiCalls fileprivate var _isRescheduling: Bool = false - /// The task of waiting and executing ApiCalls - fileprivate var _reschedulingTask: Task? = nil - /// Indicates whether the collection content has changed /// Initiates a write when true fileprivate var _hasChanged: Bool = false { @@ -123,7 +120,7 @@ actor ApiCallCollection: SomeCallCollection { /// Removes all objects in memory and deletes the JSON file func reset() { - self._reschedulingTask?.cancel() + self._isRescheduling = false self.items.removeAll() do { @@ -136,6 +133,7 @@ actor ApiCallCollection: SomeCallCollection { } } + /// Wait for an exponentionnaly long time depending on the number of attemps fileprivate func _wait() async { let delay = pow(2, self._attemptLoops) @@ -159,7 +157,7 @@ actor ApiCallCollection: SomeCallCollection { /// Reschedule the execution of API calls fileprivate func _rescheduleApiCalls() async { - Logger.log("\(T.resourceName()) > RESCHED") +// Logger.log("\(T.resourceName()) > RESCHED") guard !self._isRescheduling else { return } guard self.items.isNotEmpty else { return } @@ -179,7 +177,7 @@ actor ApiCallCollection: SomeCallCollection { case .post: let result: T = try await self._executeApiCall(apiCall) StoreCenter.main.updateFromServerInstance(result) - Logger.log("\(T.resourceName()) > SUCCESS!") +// Logger.log("\(T.resourceName()) > SUCCESS!") case .put: let _: T = try await self._executeApiCall(apiCall) case .delete: @@ -188,19 +186,19 @@ actor ApiCallCollection: SomeCallCollection { let _: [T] = try await self._executeApiCall(apiCall) } } catch { - Logger.log("\(T.resourceName()) > API CALL RETRY ERROR:") +// Logger.log("\(T.resourceName()) > API CALL RETRY ERROR:") Logger.error(error) } } - Logger.log("\(T.resourceName()) > STOP RESCHED") +// Logger.log("\(T.resourceName()) > STOP RESCHED") self._isRescheduling = false if self.items.isNotEmpty { await self._rescheduleApiCalls() } - Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)") +// Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)") } // MARK: - Synchronization diff --git a/LeStorage/ModelObject.swift b/LeStorage/ModelObject.swift index a4baf46..d9a0bd7 100644 --- a/LeStorage/ModelObject.swift +++ b/LeStorage/ModelObject.swift @@ -25,4 +25,8 @@ open class ModelObject { static var relationshipNames: [String] = [] + open func hasBeenDeleted() { + + } + } diff --git a/LeStorage/Storable.swift b/LeStorage/Storable.swift index 9309ff2..dbabd52 100644 --- a/LeStorage/Storable.swift +++ b/LeStorage/Storable.swift @@ -38,6 +38,8 @@ public protocol Storable: Codable, Identifiable { static var relationshipNames: [String] { get } + /// A method called after the instance has been deleted from its StoredCollection + func hasBeenDeleted() } extension Storable { @@ -69,16 +71,21 @@ extension Storable { return path } + /// Returns the local URL of the storage directory public static func storageDirectoryPath() throws -> URL { return try FileUtils.pathForDirectoryInDocuments(directory: Store.storageDirectory) } + /// Writes some content to a file inside the storage directory + /// - content: the string to write inside the file + /// - fileName: the name of the file inside the storage directory static func writeToStorageDirectory(content: String, fileName: String) throws { var fileURL = try self.storageDirectoryPath() fileURL.append(component: fileName) try content.write(to: fileURL, atomically: false, encoding: .utf8) } + /// Returns the URL of the Storable json file static func urlForJSONFile() throws -> URL { var storageDirectory = try self.storageDirectoryPath() storageDirectory.append(component: self.fileName()) diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index 19610a2..40e6106 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -40,6 +40,7 @@ public class StoreCenter { /// A collection storing FailedAPICall objects fileprivate var _failedAPICallsCollection: StoredCollection? = nil + /// A collection of Log objects fileprivate var _logs: StoredCollection? = nil /// A list of username that cannot synchronize with the server @@ -216,21 +217,11 @@ public class StoreCenter { await collection.rescheduleApiCallsIfNecessary() } - /// Executes an ApiCall -// fileprivate func _executeApiCall(_ apiCall: ApiCall) async throws -> T { -// return try await self.service().runApiCall(apiCall) -// } - /// Executes an ApiCall fileprivate func _executeApiCall(_ apiCall: ApiCall) async throws -> V { return try await self.service().runApiCall(apiCall) } - /// Executes an API call -// func execute(apiCall: ApiCall) async throws -> T { -// return try await self._executeApiCall(apiCall) -// } - /// Executes an API call func execute(apiCall: ApiCall) async throws -> V { return try await self._executeApiCall(apiCall) @@ -412,6 +403,7 @@ public class StoreCenter { try await self.apiCallCollection().sendDeletion(instance) } + /// Updates a local object with a server instance func updateFromServerInstance(_ result: T) { if let storedCollection: StoredCollection = self.collectionOfInstance(result) { if storedCollection.findById(result.id) != nil { @@ -420,6 +412,7 @@ public class StoreCenter { } } + /// Returns the collection hosting an instance func collectionOfInstance(_ instance: T) -> StoredCollection? { do { let storedCollection: StoredCollection = try Store.main.collection() @@ -433,6 +426,7 @@ public class StoreCenter { } } + /// Search inside the additional stores to find the collection hosting the instance func collectionOfInstanceInSubStores(_ instance: T) -> StoredCollection? { for store in self._stores.values { let storedCollection: StoredCollection? = try? store.collection() diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 8701e41..982bab5 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -250,21 +250,15 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti self.items.append(instance) } - /// Deletes the instance in the collection by id + /// Deletes the instance in the collection by id and sets the collection as changed to trigger a write public func delete(instance: T) throws { - defer { self._hasChanged = true } - - try instance.deleteDependencies() - self.items.removeAll { $0.id == instance.id } - self._indexes?.removeValue(forKey: instance.id) - - self._sendDeletionIfNecessary(instance) + try self._delete(instance) } - /// Deletes all items of the sequence by id + /// Deletes all items of the sequence by id and sets the collection as changed to trigger a write public func delete(contentOfs sequence: any Sequence) throws { defer { @@ -272,13 +266,22 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } for instance in sequence { - try instance.deleteDependencies() - self.items.removeAll { $0.id == instance.id } - self._indexes?.removeValue(forKey: instance.id) - self._sendDeletionIfNecessary(instance) + try self._delete(instance) } } + /// Deletes an instance in the collection. Also: + /// - Removes its reference from the index + /// - Notifies the server of the deletion + /// - Calls `hasBeenDeleted` on the deleted instance + fileprivate func _delete(_ instance: T) throws { + try instance.deleteDependencies() + self.items.removeAll { $0.id == instance.id } + self._indexes?.removeValue(forKey: instance.id) + self._sendDeletionIfNecessary(instance) + instance.hasBeenDeleted() + } + /// Adds or update a sequence of elements public func addOrUpdate(contentOfs sequence: any Sequence) throws { self._addOrUpdate(contentOfs: sequence) @@ -339,6 +342,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti if let index = self.items.firstIndex(where: { $0.id == item.id }) { self.items.remove(at: index) } + item.hasBeenDeleted() Task { do { @@ -437,6 +441,9 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } } + /// Updates a local item from a server instance. This method is typically used when the server makes update + /// to an object when it's inserted. The StoredCollection possibly needs to update its own copy with new values. + /// - serverInstance: the instance of the object on the server func updateFromServerInstance(_ serverInstance: T) { DispatchQueue.main.async { if let localInstance = self.findById(serverInstance.id) {