Adds a hasBeenDeleted method on Storable + doc + cleanup

sync2
Laurent 1 year ago
parent 67f07cfb6f
commit cfd2ccb9fc
  1. 16
      LeStorage/ApiCallCollection.swift
  2. 4
      LeStorage/ModelObject.swift
  3. 7
      LeStorage/Storable.swift
  4. 14
      LeStorage/StoreCenter.swift
  5. 33
      LeStorage/StoredCollection.swift

@ -33,9 +33,6 @@ actor ApiCallCollection<T: Storable>: 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<Void, any Error>? = nil
/// Indicates whether the collection content has changed
/// Initiates a write when true
fileprivate var _hasChanged: Bool = false {
@ -123,7 +120,7 @@ actor ApiCallCollection<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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

@ -25,4 +25,8 @@ open class ModelObject {
static var relationshipNames: [String] = []
open func hasBeenDeleted() {
}
}

@ -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())

@ -40,6 +40,7 @@ public class StoreCenter {
/// A collection storing FailedAPICall objects
fileprivate var _failedAPICallsCollection: StoredCollection<FailedAPICall>? = nil
/// A collection of Log objects
fileprivate var _logs: StoredCollection<Log>? = 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<T: Storable>(_ apiCall: ApiCall<T>) async throws -> T {
// return try await self.service().runApiCall(apiCall)
// }
/// Executes an ApiCall
fileprivate func _executeApiCall<T: Storable, V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
return try await self.service().runApiCall(apiCall)
}
/// Executes an API call
// func execute<T>(apiCall: ApiCall<T>) async throws -> T {
// return try await self._executeApiCall(apiCall)
// }
/// Executes an API call
func execute<T, V: Decodable>(apiCall: ApiCall<T>) 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<T: Storable>(_ result: T) {
if let storedCollection: StoredCollection<T> = self.collectionOfInstance(result) {
if storedCollection.findById(result.id) != nil {
@ -420,6 +412,7 @@ public class StoreCenter {
}
}
/// Returns the collection hosting an instance
func collectionOfInstance<T: Storable>(_ instance: T) -> StoredCollection<T>? {
do {
let storedCollection: StoredCollection<T> = 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<T: Storable>(_ instance: T) -> StoredCollection<T>? {
for store in self._stores.values {
let storedCollection: StoredCollection<T>? = try? store.collection()

@ -250,21 +250,15 @@ public class StoredCollection<T: Storable>: 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<T>) throws {
defer {
@ -272,13 +266,22 @@ public class StoredCollection<T: Storable>: 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<T>) throws {
self._addOrUpdate(contentOfs: sequence)
@ -339,6 +342,7 @@ public class StoredCollection<T: Storable>: 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<T: Storable>: 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) {

Loading…
Cancel
Save