Improve loadDataFromServer to provision the GET request

sync2
Laurent 8 months ago
parent 4c4cc246b9
commit cca9812b86
  1. 47
      LeStorage/ApiCallCollection.swift
  2. 2
      LeStorage/Codables/ApiCall.swift
  3. 2
      LeStorage/Codables/GetSyncData.swift
  4. 18
      LeStorage/Services.swift
  5. 28
      LeStorage/Store.swift
  6. 9
      LeStorage/StoreCenter.swift
  7. 24
      LeStorage/StoredCollection+Sync.swift
  8. 15
      LeStorage/StoredCollection.swift

@ -190,7 +190,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
for batch in batches.values { for batch in batches.values {
do { do {
if batch.count == 1, let apiCall = batch.first, apiCall.method == .get { 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 { } else {
let results = try await self._executeApiCalls(batch) let results = try await self._executeApiCalls(batch)
if T.copyServerResponse { if T.copyServerResponse {
@ -211,6 +211,15 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
// Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)") // Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)")
} }
fileprivate func _executeGetCall(apiCall: ApiCall<T>) 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 /// Wait for an exponentionnaly long time depending on the number of attemps
fileprivate func _wait() async { fileprivate func _wait() async {
@ -282,11 +291,16 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
/// Sends an insert api call for the provided [instance] /// 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 { do {
let apiCall = ApiCall<T>(method: .get, data: nil) let apiCall = ApiCall<T>(method: .get, data: nil)
apiCall.urlParameters = instance.queryParameters() if let parameteredInstance = instance as? URLParameterConvertible {
let _: Empty? = try await self._prepareAndSendCall(apiCall) apiCall.urlParameters = parameteredInstance.queryParameters()
}
if let storeId {
apiCall.urlParameters = [Services.storeIdURLParameter : storeId]
}
try await self._prepareAndSendGetCall(apiCall)
} catch { } catch {
self.rescheduleApiCallsIfNecessary() self.rescheduleApiCallsIfNecessary()
Logger.error(error) Logger.error(error)
@ -312,31 +326,16 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
return try await self._executeApiCalls(apiCalls) return try await self._executeApiCalls(apiCalls)
} }
// /// Initiates the process of sending the data with the server fileprivate func _prepareAndSendGetCall(_ apiCall: ApiCall<T>) async throws {
//<<<<<<< HEAD
// fileprivate func _sendServerRequest<V: Decodable>(_ 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<V: Decodable>(_ 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<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V? {
self._prepareCall(apiCall: apiCall) self._prepareCall(apiCall: apiCall)
return try await self._executeGetCall(apiCall) try await self._executeGetCall(apiCall: apiCall)
} }
/// Executes an API call /// Executes an API call
/// For POST requests, potentially copies additional data coming from the server during the insert /// For POST requests, potentially copies additional data coming from the server during the insert
fileprivate func _executeGetCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V { // fileprivate func _executeGetCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
return try await StoreCenter.main.executeGet(apiCall: apiCall) // return try await StoreCenter.main.executeGet(apiCall: apiCall)
} // }
/// Executes an API call /// Executes an API call
/// For POST requests, potentially copies additional data coming from the server during the insert /// For POST requests, potentially copies additional data coming from the server during the insert

@ -87,6 +87,8 @@ public class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
return nil return nil
} }
var storeId: String? { return self.urlParameters?[Services.storeIdURLParameter] }
public static func relationships() -> [Relationship] { return [] } public static func relationships() -> [Relationship] { return [] }
} }

@ -14,7 +14,7 @@ class GetSyncData: SyncedModelObject, SyncedStorable, URLParameterConvertible {
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static func resourceName() -> String { static func resourceName() -> String {
return "sync-requests" return "sync-data"
} }
func copy(from other: any Storable) { func copy(from other: any Storable) {

@ -33,7 +33,7 @@ let changePasswordCall: ServiceCall = ServiceCall(
let postDeviceTokenCall: ServiceCall = ServiceCall( let postDeviceTokenCall: ServiceCall = ServiceCall(
path: "device-token/", method: .post, requiresToken: true) path: "device-token/", method: .post, requiresToken: true)
let getUserDataAccessCall: ServiceCall = ServiceCall( let getUserDataAccessCall: ServiceCall = ServiceCall(
path: "user-data-access/", method: .get, requiresToken: true) path: "data-access/", method: .get, requiresToken: true)
let userNamesCall: ServiceCall = ServiceCall( let userNamesCall: ServiceCall = ServiceCall(
path: "user-names/", method: .get, requiresToken: true) path: "user-names/", method: .get, requiresToken: true)
@ -52,6 +52,8 @@ public class Services {
Logger.log("create keystore with id: \(url)") Logger.log("create keystore with id: \(url)")
} }
static let storeIdURLParameter = "store_id"
// MARK: - Base // MARK: - Base
/// Runs a request using a configuration object /// Runs a request using a configuration object
@ -80,7 +82,7 @@ public class Services {
/// - Parameters: /// - Parameters:
/// - request: the URLRequest to run /// - 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 /// - 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<T: SyncedStorable, V: Decodable>( fileprivate func _runGetApiCallRequest<T: SyncedStorable, V: Decodable>(
_ request: URLRequest, apiCall: ApiCall<T> _ request: URLRequest, apiCall: ApiCall<T>
) async throws -> V { ) async throws -> V {
let debugURL = request.url?.absoluteString ?? "" 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 /// - identifier: an optional StoreIdentifier that allows to filter GET requests with the StoreIdentifier values
fileprivate func _baseRequest( fileprivate func _baseRequest(
servicePath: String, method: HTTPMethod, requiresToken: Bool? = nil, servicePath: String, method: HTTPMethod, requiresToken: Bool? = nil,
identifier: String? = nil, getArguments: [String: String]? = nil identifier: String? = nil, getArguments: [String : String]? = nil
) throws -> URLRequest { ) throws -> URLRequest {
var urlString = baseURL + servicePath var urlString = baseURL + servicePath
var arguments: [String:String] = getArguments ?? [:] var arguments: [String : String] = getArguments ?? [:]
if let identifier { if let identifier {
arguments["store_id"] = identifier arguments[Services.storeIdURLParameter] = identifier
// let component = "?store_id=\(identifier)"
// urlString.append(component)
} }
urlString.append(arguments.toQueryString()) urlString.append(arguments.toQueryString())
@ -369,7 +369,7 @@ public class Services {
/// - apiCall: An ApiCall instance to configure the returned request /// - apiCall: An ApiCall instance to configure the returned request
fileprivate func _syncGetRequest<T: SyncedStorable>(from apiCall: ApiCall<T>) throws -> URLRequest { fileprivate func _syncGetRequest<T: SyncedStorable>(from apiCall: ApiCall<T>) throws -> URLRequest {
var urlString = baseURL + "data/" var urlString = "\(baseURL)\(T.resourceName())/" // baseURL + T.resourceName() // "data/"
if let urlParameters = apiCall.formattedURLParameters() { if let urlParameters = apiCall.formattedURLParameters() {
urlString.append(urlParameters) urlString.append(urlParameters)
} }
@ -547,7 +547,7 @@ public class Services {
/// Executes an ApiCall /// Executes an ApiCall
func runGetApiCall<T: SyncedStorable, V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V { func runGetApiCall<T: SyncedStorable, V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
let request = try self._syncGetRequest(from: apiCall) 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 /// Executes an ApiCall

@ -281,26 +281,14 @@ final public class Store {
} }
} }
/// Requests an insertion to the StoreCenter func loadCollectionItems<T: SyncedStorable>(_ items: [T]) async {
/// - Parameters: do {
/// - instance: an object to insert let collection: StoredCollection<T> = try self.collection()
// func sendInsertion<T: SyncedStorable>(_ instance: T) async throws -> T? { await collection.clearAndLoadItems(items)
// return try await StoreCenter.main.sendInsertion(instance) } catch {
// } Logger.error(error)
// }
// /// Requests an update to the StoreCenter }
// /// - Parameters:
// /// - instance: an object to update
// @discardableResult func sendUpdate<T: SyncedStorable>(_ 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<T: SyncedStorable>(_ instance: T) async throws {
// return try await StoreCenter.main.sendDeletion(instance)
// }
/// Returns whether all collections have loaded locally /// Returns whether all collections have loaded locally
public func fileCollectionsAllLoaded() -> Bool { public func fileCollectionsAllLoaded() -> Bool {

@ -442,6 +442,10 @@ public class StoreCenter {
return try await self.service().get(identifier: identifier) return try await self.service().get(identifier: identifier)
} }
func itemsRetrieved<T: SyncedStorable>(_ results: [T], storeId: String?) async {
await self._store(id: storeId).loadCollectionItems(results)
}
// MARK: - Synchronization // MARK: - Synchronization
/// Creates the ApiCallCollection to manage the calls to the API /// Creates the ApiCallCollection to manage the calls to the API
@ -485,6 +489,11 @@ public class StoreCenter {
} }
func sendGetRequest<T: SyncedStorable>(_ type: T.Type, storeId: String?) async throws {
let apiCallCollection: ApiCallCollection<T> = try self.apiCallCollection()
try await apiCallCollection.sendGetRequest(storeId: storeId)
}
/// Processes Data Access data /// Processes Data Access data
func userDataAccessRetrieved(_ data: Data) async { func userDataAccessRetrieved(_ data: Data) async {
do { do {

@ -40,16 +40,20 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
throw StoreError.cannotSyncCollection(name: self.resourceName) throw StoreError.cannotSyncCollection(name: self.resourceName)
} }
do { do {
let items: [T] = try await self.store.getItems() try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId)
if items.count > 0 {
DispatchQueue.main.async {
if clear {
self.clear() // let items: [T] = try await self.store.getItems()
} // if items.count > 0 {
self.addOrUpdateNoSync(contentOfs: items) // DispatchQueue.main.async {
} // if clear {
} // self.clear()
self.setAsLoaded() // }
// self.addOrUpdateNoSync(contentOfs: items)
// }
// }
// self.setAsLoaded()
} catch { } catch {
Logger.error(error) Logger.error(error)
} }

@ -128,9 +128,6 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
if FileManager.default.fileExists(atPath: fileURL.path()) { if FileManager.default.fileExists(atPath: fileURL.path()) {
let jsonString: String = try FileUtils.readFile(fileURL: fileURL) let jsonString: String = try FileUtils.readFile(fileURL: fileURL)
let decoded: [T] = try jsonString.decodeArray() ?? [] let decoded: [T] = try jsonString.decodeArray() ?? []
for item in decoded {
item.store = self.store
}
self._setItems(decoded) self._setItems(decoded)
} }
self.setAsLoaded() self.setAsLoaded()
@ -148,6 +145,9 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
/// Sets a collection of items and indexes them /// Sets a collection of items and indexes them
fileprivate func _setItems(_ items: [T]) { fileprivate func _setItems(_ items: [T]) {
for item in items {
item.store = self.store
}
self.items = items self.items = items
self._updateIndexIfNecessary() self._updateIndexIfNecessary()
} }
@ -159,6 +159,15 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
} }
} }
func clearAndLoadItems(_ items: [T]) async {
await MainActor.run {
self.clear()
self._setItems(items)
self.setAsLoaded()
self.setChanged()
}
}
// MARK: - Basic operations // MARK: - Basic operations
/// Adds or updates the provided instance inside the collection /// Adds or updates the provided instance inside the collection

Loading…
Cancel
Save