From cca9812b868c3a9ff1ae0f85270bbff5f82e5b86 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 14 Mar 2025 11:12:49 +0100 Subject: [PATCH] Improve loadDataFromServer to provision the GET request --- LeStorage/ApiCallCollection.swift | 47 +++++++++++++-------------- LeStorage/Codables/ApiCall.swift | 2 ++ LeStorage/Codables/GetSyncData.swift | 2 +- LeStorage/Services.swift | 18 +++++----- LeStorage/Store.swift | 28 +++++----------- LeStorage/StoreCenter.swift | 9 +++++ LeStorage/StoredCollection+Sync.swift | 24 ++++++++------ LeStorage/StoredCollection.swift | 15 +++++++-- 8 files changed, 78 insertions(+), 67 deletions(-) diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index 98bedae..3190b6a 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -190,7 +190,7 @@ actor ApiCallCollection: SomeCallCollection { for batch in batches.values { do { if batch.count == 1, let apiCall = batch.first, apiCall.method == .get { - let _: Empty = try await self._executeGetCall(apiCall) + try await self._executeGetCall(apiCall: apiCall) } else { let results = try await self._executeApiCalls(batch) if T.copyServerResponse { @@ -211,6 +211,15 @@ actor ApiCallCollection: SomeCallCollection { // Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)") } + fileprivate func _executeGetCall(apiCall: ApiCall) async throws { + if T.self == GetSyncData.self { + let _: Empty = try await StoreCenter.main.executeGet(apiCall: apiCall) + } else { + let results: [T] = try await StoreCenter.main.executeGet(apiCall: apiCall) + await StoreCenter.main.itemsRetrieved(results, storeId: apiCall.storeId) + } + } + /// Wait for an exponentionnaly long time depending on the number of attemps fileprivate func _wait() async { @@ -282,11 +291,16 @@ actor ApiCallCollection: SomeCallCollection { } /// Sends an insert api call for the provided [instance] - func sendGetRequest(instance: T) async throws where T : URLParameterConvertible { + func sendGetRequest(instance: T? = nil, storeId: String? = nil) async throws { do { let apiCall = ApiCall(method: .get, data: nil) - apiCall.urlParameters = instance.queryParameters() - let _: Empty? = try await self._prepareAndSendCall(apiCall) + if let parameteredInstance = instance as? URLParameterConvertible { + apiCall.urlParameters = parameteredInstance.queryParameters() + } + if let storeId { + apiCall.urlParameters = [Services.storeIdURLParameter : storeId] + } + try await self._prepareAndSendGetCall(apiCall) } catch { self.rescheduleApiCallsIfNecessary() Logger.error(error) @@ -312,31 +326,16 @@ actor ApiCallCollection: SomeCallCollection { return try await self._executeApiCalls(apiCalls) } -// /// Initiates the process of sending the data with the server -//<<<<<<< HEAD -// fileprivate func _sendServerRequest(_ method: HTTPMethod, instance: T? = nil) async throws -> V? { -// if let apiCall = try await self._call(method: method, instance: instance) { -// return try await self._prepareAndSendCall(apiCall) -//======= -// fileprivate func _synchronize(_ instance: T, method: HTTPMethod) async throws -> V? { -// if let apiCall = try await self._callForInstance(instance, method: method) { -// return try await self._executeApiCall(apiCall) -//>>>>>>> main -// } else { -// return nil -// } -// } - - fileprivate func _prepareAndSendCall(_ apiCall: ApiCall) async throws -> V? { + fileprivate func _prepareAndSendGetCall(_ apiCall: ApiCall) async throws { self._prepareCall(apiCall: apiCall) - return try await self._executeGetCall(apiCall) + try await self._executeGetCall(apiCall: apiCall) } /// Executes an API call /// For POST requests, potentially copies additional data coming from the server during the insert - fileprivate func _executeGetCall(_ apiCall: ApiCall) async throws -> V { - return try await StoreCenter.main.executeGet(apiCall: apiCall) - } +// fileprivate func _executeGetCall(_ apiCall: ApiCall) async throws -> V { +// return try await StoreCenter.main.executeGet(apiCall: apiCall) +// } /// Executes an API call /// For POST requests, potentially copies additional data coming from the server during the insert diff --git a/LeStorage/Codables/ApiCall.swift b/LeStorage/Codables/ApiCall.swift index e155658..5b20fbf 100644 --- a/LeStorage/Codables/ApiCall.swift +++ b/LeStorage/Codables/ApiCall.swift @@ -87,6 +87,8 @@ public class ApiCall: ModelObject, Storable, SomeCall { return nil } + var storeId: String? { return self.urlParameters?[Services.storeIdURLParameter] } + public static func relationships() -> [Relationship] { return [] } } diff --git a/LeStorage/Codables/GetSyncData.swift b/LeStorage/Codables/GetSyncData.swift index c447a87..e705c53 100644 --- a/LeStorage/Codables/GetSyncData.swift +++ b/LeStorage/Codables/GetSyncData.swift @@ -14,7 +14,7 @@ class GetSyncData: SyncedModelObject, SyncedStorable, URLParameterConvertible { static func tokenExemptedMethods() -> [HTTPMethod] { return [] } static func resourceName() -> String { - return "sync-requests" + return "sync-data" } func copy(from other: any Storable) { diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index 009788d..23f18c8 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -33,7 +33,7 @@ let changePasswordCall: ServiceCall = ServiceCall( let postDeviceTokenCall: ServiceCall = ServiceCall( path: "device-token/", method: .post, requiresToken: true) let getUserDataAccessCall: ServiceCall = ServiceCall( - path: "user-data-access/", method: .get, requiresToken: true) + path: "data-access/", method: .get, requiresToken: true) let userNamesCall: ServiceCall = ServiceCall( path: "user-names/", method: .get, requiresToken: true) @@ -52,6 +52,8 @@ public class Services { Logger.log("create keystore with id: \(url)") } + static let storeIdURLParameter = "store_id" + // MARK: - Base /// Runs a request using a configuration object @@ -80,7 +82,7 @@ public class Services { /// - Parameters: /// - request: the URLRequest to run /// - apiCallId: the id of the ApiCall to delete in case of success, or to schedule for a rerun in case of failure - fileprivate func _runRequest( + fileprivate func _runGetApiCallRequest( _ request: URLRequest, apiCall: ApiCall ) async throws -> V { let debugURL = request.url?.absoluteString ?? "" @@ -242,14 +244,12 @@ public class Services { /// - identifier: an optional StoreIdentifier that allows to filter GET requests with the StoreIdentifier values fileprivate func _baseRequest( servicePath: String, method: HTTPMethod, requiresToken: Bool? = nil, - identifier: String? = nil, getArguments: [String: String]? = nil + identifier: String? = nil, getArguments: [String : String]? = nil ) throws -> URLRequest { var urlString = baseURL + servicePath - var arguments: [String:String] = getArguments ?? [:] + var arguments: [String : String] = getArguments ?? [:] if let identifier { - arguments["store_id"] = identifier -// let component = "?store_id=\(identifier)" -// urlString.append(component) + arguments[Services.storeIdURLParameter] = identifier } urlString.append(arguments.toQueryString()) @@ -369,7 +369,7 @@ public class Services { /// - apiCall: An ApiCall instance to configure the returned request fileprivate func _syncGetRequest(from apiCall: ApiCall) throws -> URLRequest { - var urlString = baseURL + "data/" + var urlString = "\(baseURL)\(T.resourceName())/" // baseURL + T.resourceName() // "data/" if let urlParameters = apiCall.formattedURLParameters() { urlString.append(urlParameters) } @@ -547,7 +547,7 @@ public class Services { /// Executes an ApiCall func runGetApiCall(_ apiCall: ApiCall) async throws -> V { let request = try self._syncGetRequest(from: apiCall) - return try await self._runRequest(request, apiCall: apiCall) + return try await self._runGetApiCallRequest(request, apiCall: apiCall) } /// Executes an ApiCall diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 9e70366..0a46a48 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -281,26 +281,14 @@ final public class Store { } } - /// Requests an insertion to the StoreCenter - /// - Parameters: - /// - instance: an object to insert -// func sendInsertion(_ instance: T) async throws -> T? { -// return try await StoreCenter.main.sendInsertion(instance) -// } -// -// /// Requests an update to the StoreCenter -// /// - Parameters: -// /// - instance: an object to update -// @discardableResult func sendUpdate(_ instance: T) async throws -> T? { -// return try await StoreCenter.main.sendUpdate(instance) -// } -// -// /// Requests a deletion to the StoreCenter -// /// - Parameters: -// /// - instance: an object to delete -// func sendDeletion(_ instance: T) async throws { -// return try await StoreCenter.main.sendDeletion(instance) -// } + func loadCollectionItems(_ items: [T]) async { + do { + let collection: StoredCollection = try self.collection() + await collection.clearAndLoadItems(items) + } catch { + Logger.error(error) + } + } /// Returns whether all collections have loaded locally public func fileCollectionsAllLoaded() -> Bool { diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index 3cd0bba..95d1b2e 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -442,6 +442,10 @@ public class StoreCenter { return try await self.service().get(identifier: identifier) } + func itemsRetrieved(_ results: [T], storeId: String?) async { + await self._store(id: storeId).loadCollectionItems(results) + } + // MARK: - Synchronization /// Creates the ApiCallCollection to manage the calls to the API @@ -485,6 +489,11 @@ public class StoreCenter { } + func sendGetRequest(_ type: T.Type, storeId: String?) async throws { + let apiCallCollection: ApiCallCollection = try self.apiCallCollection() + try await apiCallCollection.sendGetRequest(storeId: storeId) + } + /// Processes Data Access data func userDataAccessRetrieved(_ data: Data) async { do { diff --git a/LeStorage/StoredCollection+Sync.swift b/LeStorage/StoredCollection+Sync.swift index fdf2fc4..1527835 100644 --- a/LeStorage/StoredCollection+Sync.swift +++ b/LeStorage/StoredCollection+Sync.swift @@ -40,16 +40,20 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable { throw StoreError.cannotSyncCollection(name: self.resourceName) } do { - let items: [T] = try await self.store.getItems() - if items.count > 0 { - DispatchQueue.main.async { - if clear { - self.clear() - } - self.addOrUpdateNoSync(contentOfs: items) - } - } - self.setAsLoaded() + try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId) + + + +// let items: [T] = try await self.store.getItems() +// if items.count > 0 { +// DispatchQueue.main.async { +// if clear { +// self.clear() +// } +// self.addOrUpdateNoSync(contentOfs: items) +// } +// } +// self.setAsLoaded() } catch { Logger.error(error) } diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 65354fa..dbe9a7f 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -128,9 +128,6 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti if FileManager.default.fileExists(atPath: fileURL.path()) { let jsonString: String = try FileUtils.readFile(fileURL: fileURL) let decoded: [T] = try jsonString.decodeArray() ?? [] - for item in decoded { - item.store = self.store - } self._setItems(decoded) } self.setAsLoaded() @@ -148,6 +145,9 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Sets a collection of items and indexes them fileprivate func _setItems(_ items: [T]) { + for item in items { + item.store = self.store + } self.items = items self._updateIndexIfNecessary() } @@ -159,6 +159,15 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } } + func clearAndLoadItems(_ items: [T]) async { + await MainActor.run { + self.clear() + self._setItems(items) + self.setAsLoaded() + self.setChanged() + } + } + // MARK: - Basic operations /// Adds or updates the provided instance inside the collection