cleanup + doc

multistore
Laurent 1 year ago
parent 2191733d97
commit 399e43d03a
  1. 39
      LeStorage/ApiCallCollection.swift
  2. 2
      LeStorage/Services.swift
  3. 9
      LeStorage/Store.swift
  4. 95
      LeStorage/StoredCollection.swift

@ -7,22 +7,31 @@
import Foundation
/// ApiCallCollection is an object communicating with a server to synchronize data managed locally
/// The Api calls are serialized and stored in a JSON file
/// Failing Api calls are stored forever and will be executed again later
actor ApiCallCollection<T: Storable> {
/// The reference to the Store
fileprivate var _store: Store
/// The list of api calls
fileprivate(set) var items: [ApiCall<T>] = []
/// number of time an execution loop has been called
/// The number of time an execution loop has been called
fileprivate var _attemptLoops: Int = 0
/// Indicates if the collection is currently retrying ApiCalls
fileprivate var _isRetryingCalls: Bool = false
/// Indicates whether the collection content has changed
/// Initiates a write when true
fileprivate var _hasChanged: Bool = false {
didSet {
self._write()
if self._hasChanged {
self._write()
self._hasChanged = false
}
}
}
@ -31,10 +40,13 @@ actor ApiCallCollection<T: Storable> {
}
/// Starts the JSON file decoding synchronously or asynchronously
/// Reschedule Api calls if not empty
func loadFromFile() throws {
try self._decodeJSONFile()
self.rescheduleApiCallsIfNecessary()
}
/// Returns the file URL of the collection
fileprivate func _urlForJSONFile() throws -> URL {
return try ApiCall<T>.urlForJSONFile()
}
@ -48,13 +60,10 @@ actor ApiCallCollection<T: Storable> {
let decoded: [ApiCall<T>] = try jsonString.decodeArray() ?? []
Logger.log("loaded \(T.fileName()) with \(decoded.count) items")
self.items = decoded
self.rescheduleApiCallsIfNecessary()
}
}
/// Writes the content of the data
fileprivate func _write() {
let fileName = ApiCall<T>.fileName()
DispatchQueue(label: "lestorage.queue.write", qos: .utility).asyncAndWait {
@ -69,6 +78,7 @@ actor ApiCallCollection<T: Storable> {
}
}
/// Adds or update an API call instance
func addOrUpdate(_ instance: ApiCall<T>) {
if let index = self.items.firstIndex(where: { $0.id == instance.id }) {
self.items[index] = instance
@ -84,17 +94,20 @@ actor ApiCallCollection<T: Storable> {
self._hasChanged = true
}
func deleteByDataId(_ id: String) {
if let apiCallIndex = self.items.firstIndex(where: { $0.dataId == id }) {
/// Deletes a call by a data id
func deleteByDataId(_ dataId: String) {
if let apiCallIndex = self.items.firstIndex(where: { $0.dataId == dataId }) {
self.items.remove(at: apiCallIndex)
self._hasChanged = true
}
}
/// Returns the Api call associated with the provided id
func findById(_ id: String) -> ApiCall<T>? {
return self.items.first(where: { $0.id == id })
}
/// Removes all objects in memory and deletes the JSON file
func reset() {
self.items.removeAll()
@ -108,6 +121,7 @@ actor ApiCallCollection<T: Storable> {
}
}
/// Reschedule the execution of API calls
fileprivate func _rescheduleApiCalls() {
guard self.items.isNotEmpty else {
@ -131,12 +145,13 @@ actor ApiCallCollection<T: Storable> {
do {
try await self._executeApiCall(apiCall)
// let _ = try await Store.main.execute(apiCall: apiCall)
} catch {
Logger.error(error)
}
}
self._hasChanged = true
if self.items.isEmpty {
self._isRetryingCalls = false
} else {
@ -229,6 +244,7 @@ actor ApiCallCollection<T: Storable> {
}
/// Initiates the process of sending the data with the server
fileprivate func _synchronize(_ instance: T, method: HTTPMethod) async throws {
if let apiCall = try self._callForInstance(instance, method: method) {
try self._prepareCall(apiCall: apiCall)
@ -236,6 +252,8 @@ actor ApiCallCollection<T: Storable> {
}
}
/// Executes an API call
/// For POST requests, potentially copies additional data coming from the server during the insert
fileprivate func _executeApiCall(_ apiCall: ApiCall<T>) async throws {
let result = try await self._store.execute(apiCall: apiCall)
switch apiCall.method {
@ -249,6 +267,7 @@ actor ApiCallCollection<T: Storable> {
Logger.log("")
}
/// Returns the content of the API call file as a String
func contentOfApiCallFile() -> String? {
guard let fileURL = try? self._urlForJSONFile() else { return nil }
if FileManager.default.fileExists(atPath: fileURL.path()) {

@ -116,7 +116,7 @@ public class Services {
}
if let apiCallId, let type = (T.self as? any Storable.Type) {
try Store.main.rescheduleApiCall(id: apiCallId, type: type)
try Store.main.rescheduleApiCalls(id: apiCallId, type: type)
Store.main.logFailedAPICall(apiCallId, request: request, collectionName: type.resourceName(), error: errorString)
} else {
Store.main.logFailedAPICall(request: request, error: errorString)

@ -73,7 +73,6 @@ public class Store {
public init() {
FileManager.default.createDirectoryInDocuments(directoryName: Store.storageDirectory)
// self._failedAPICallsCollection = registerCollection(synchronized: true)
}
/// Registers a collection
@ -191,11 +190,6 @@ public class Store {
throw StoreError.collectionNotRegistered(type: T.resourceName())
}
/// Deletes the dependencies of a collection
// public func deleteDependencies<T: Storable>(items: any Sequence<T>) throws {
// try self.collection().deleteDependencies(items)
// }
// MARK: - Api call rescheduling
/// Deletes an ApiCall by [id] and [collectionName]
@ -208,7 +202,7 @@ public class Store {
}
/// Reschedule an ApiCall by id
func rescheduleApiCall<T: Storable>(id: String, type: T.Type) throws {
func rescheduleApiCalls<T: Storable>(id: String, type: T.Type) throws {
guard self.collectionsCanSynchronize else {
return
}
@ -312,6 +306,7 @@ public class Store {
}
/// Logs a failed Api call with its request and error message
func logFailedAPICall(request: URLRequest, error: String) {
guard let failedAPICallsCollection = self._failedAPICallsCollection,

@ -116,10 +116,6 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
}
}
// self.apiCallsCollection = StoredCollection<ApiCall<T>>(synchronized: false, store: store, loadCompletion: { apiCallCollection in
// self._rescheduleApiCalls()
// })
}
self._load()
@ -131,21 +127,21 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
// MARK: - Paths
fileprivate func _storageDirectoryPath() throws -> URL {
return try FileUtils.pathForDirectoryInDocuments(directory: Store.storageDirectory)
}
fileprivate 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)
}
fileprivate func _urlForJSONFile() throws -> URL {
var storageDirectory = try self._storageDirectoryPath()
storageDirectory.append(component: T.fileName())
return storageDirectory
}
// fileprivate func _storageDirectoryPath() throws -> URL {
// return try FileUtils.pathForDirectoryInDocuments(directory: Store.storageDirectory)
// }
//
// fileprivate 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)
// }
//
// fileprivate func _urlForJSONFile() throws -> URL {
// var storageDirectory = try self._storageDirectoryPath()
// storageDirectory.append(component: T.fileName())
// return storageDirectory
// }
// MARK: - Loading
@ -181,7 +177,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
/// Decodes the json file into the items array
fileprivate func _decodeJSONFile() throws {
let fileURL = try self._urlForJSONFile()
let fileURL = try T.urlForJSONFile()
if FileManager.default.fileExists(atPath: fileURL.path()) {
let jsonString: String = try FileUtils.readFile(fileURL: fileURL)
@ -396,8 +392,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
Logger.log("Start write to \(T.fileName())...")
do {
let jsonString: String = try self.items.jsonString()
try self._writeToStorageDirectory(content: jsonString, fileName: T.fileName())
// let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: T.fileName())
try T.writeToStorageDirectory(content: jsonString, fileName: T.fileName())
} catch {
Logger.error(error) // TODO how to notify the main project
}
@ -414,7 +409,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
self.items.removeAll()
do {
let url: URL = try self._urlForJSONFile()
let url: URL = try T.urlForJSONFile()
if FileManager.default.fileExists(atPath: url.path()) {
try FileManager.default.removeItem(at: url)
}
@ -466,66 +461,16 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
}
}
/// Reschedule the api calls if possible
func rescheduleApiCallsIfNecessary() {
Task {
await self.apiCallsCollection?.rescheduleApiCallsIfNecessary()
}
}
//
// /// number of time an execution loop has been called
// fileprivate var _attemptLoops: Int = 0
//
// /// Indicates if the collection is currently retrying ApiCalls
// fileprivate var _isRetryingCalls: Bool = false
//
// /// Reschedule API calls
// fileprivate func _rescheduleApiCalls() {
//
// guard let apiCallsCollection, apiCallsCollection.isNotEmpty else {
// return
// }
//
// self._isRetryingCalls = true
// self._attemptLoops += 1
//
// Task {
//
// let delay = pow(2, self._attemptLoops)
// let seconds = NSDecimalNumber(decimal: delay).intValue
// Logger.log("wait for \(seconds) sec")
// try await Task.sleep(until: .now + .seconds(seconds))
//
// let apiCallsCopy = apiCallsCollection.items
// for apiCall in apiCallsCopy {
// apiCall.attemptsCount += 1
// apiCall.lastAttemptDate = Date()
//
// do {
// try await self._executeApiCall(apiCall)
//// let _ = try await Store.main.execute(apiCall: apiCall)
// } catch {
// Logger.error(error)
// }
// }
//
// if apiCallsCollection.isEmpty {
// self._isRetryingCalls = false
// } else {
// self._rescheduleApiCalls()
// }
//
// }
//
// }
/// Returns the content of the API call file as a String
func contentOfApiCallFile() async -> String? {
return await self.apiCallsCollection?.contentOfApiCallFile()
// guard let fileURL = try? self.apiCallsCollection?._urlForJSONFile() else { return nil }
// if FileManager.default.fileExists(atPath: fileURL.path()) {
// return try? FileUtils.readFile(fileURL: fileURL)
// }
// return nil
}
/// Returns if the API call collection is not empty

Loading…
Cancel
Save