Batches api calls by transactionId

sync2
Laurent 10 months ago
parent 4423d3f52a
commit b8077f231c
  1. 160
      LeStorage/ApiCallCollection.swift
  2. 8
      LeStorage/Codables/ApiCall.swift
  3. 206
      LeStorage/Services.swift
  4. 34
      LeStorage/Store.swift
  5. 68
      LeStorage/StoreCenter.swift
  6. 159
      LeStorage/StoredCollection+Sync.swift

@ -183,33 +183,47 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
await self._wait()
let apiCallsCopy = self.items
for apiCall in apiCallsCopy {
apiCall.attemptsCount += 1
apiCall.lastAttemptDate = Date()
let batches = Dictionary(grouping: self.items, by: { $0.transactionId })
for batch in batches.values {
do {
switch apiCall.method {
case .post:
let result: T = try await self._executeApiCall(apiCall)
StoreCenter.main.updateFromServerInstance(result)
// Logger.log("\(T.resourceName()) > SUCCESS!")
case .put:
let _: T = try await self._executeApiCall(apiCall)
case .delete:
let _: Empty = try await self._executeApiCall(apiCall)
case .get:
if T.self == GetSyncData.self {
let _: Empty = try await self._executeApiCall(apiCall)
} else {
let _: [T] = try await self._executeApiCall(apiCall)
}
if batch.count == 1, let apiCall = batch.first, apiCall.method == .get {
let _: Empty = try await self._executeGetCall(apiCall)
} else {
try await self._executeApiCalls(batch)
}
} catch {
// Logger.log("\(T.resourceName()) > API CALL RETRY ERROR:")
// Logger.error(error)
Logger.error(error)
}
}
// let apiCallsCopy = self.items
// for apiCall in apiCallsCopy {
// apiCall.attemptsCount += 1
// apiCall.lastAttemptDate = Date()
//
// do {
// switch apiCall.method {
// case .post:
// let result: T = try await self._executeApiCall(apiCall)
// StoreCenter.main.updateFromServerInstance(result)
//// Logger.log("\(T.resourceName()) > SUCCESS!")
// case .put:
// let _: T = try await self._executeApiCall(apiCall)
// case .delete:
// let _: Empty = try await self._executeApiCall(apiCall)
// case .get:
// if T.self == GetSyncData.self {
// let _: Empty = try await self._executeApiCall(apiCall)
// } else {
// let _: [T] = try await self._executeApiCall(apiCall)
// }
// }
// } catch {
//// Logger.log("\(T.resourceName()) > API CALL RETRY ERROR:")
//// Logger.error(error)
// }
// }
self._isRescheduling = false
if self.items.isNotEmpty {
@ -271,10 +285,10 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
}
/// Creates an API call for the Storable [instance] and an HTTP [method]
fileprivate func _createCall(_ method: HTTPMethod, instance: T? = nil) throws -> ApiCall<T> {
fileprivate func _createCall(_ method: HTTPMethod, instance: T? = nil, transactionId: String? = nil) throws -> ApiCall<T> {
if let instance {
let jsonString = try instance.jsonString()
return ApiCall(method: method, dataId: instance.stringId, body: jsonString)
return ApiCall(method: method, dataId: instance.stringId, body: jsonString, transactionId: transactionId)
} else {
return ApiCall(method: .get)
}
@ -299,42 +313,64 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
}
}
/// Sends an insert api call for the provided [instance]
func sendInsertion(_ instance: T) async throws -> T? {
do {
return try await self._sendServerRequest(HTTPMethod.post, instance: instance)
} catch {
self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "POST failed for \(instance): \(error.localizedDescription)")
Logger.error(error)
func executeBatch(_ batch: BatchPreparation<T>) async throws {
var apiCalls: [ApiCall<T>] = []
let transactionId = Store.randomId()
for insert in batch.inserts {
let call = try self._createCall(.post, instance: insert, transactionId: transactionId)
self._prepareCall(apiCall: call)
apiCalls.append(call)
}
return nil
}
/// Sends an update api call for the provided [instance]
func sendUpdate(_ instance: T) async throws -> T? {
do {
return try await self._sendServerRequest(HTTPMethod.put, instance: instance)
} catch {
self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "PUT failed for \(instance): \(error.localizedDescription)")
Logger.error(error)
for update in batch.updates {
let call = try self._createCall(.put, instance: update, transactionId: transactionId)
self._prepareCall(apiCall: call)
apiCalls.append(call)
}
return nil
}
/// Sends an delete api call for the provided [instance]
func sendDeletion(_ instance: T) async throws {
do {
let _: Empty? = try await self._sendServerRequest(HTTPMethod.delete, instance: instance)
} catch {
self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "DELETE failed for \(instance): \(error.localizedDescription)")
Logger.error(error)
for delete in batch.deletes {
let call = try self._createCall(.delete, instance: delete, transactionId: transactionId)
self._prepareCall(apiCall: call)
apiCalls.append(call)
}
return
try await self._executeApiCalls(apiCalls)
}
/// Sends an insert api call for the provided [instance]
// func sendInsertion(_ instance: T) async throws -> T? {
// do {
// return try await self._sendServerRequest(HTTPMethod.post, instance: instance)
// } catch {
// self.rescheduleApiCallsIfNecessary()
// StoreCenter.main.log(message: "POST failed for \(instance): \(error.localizedDescription)")
// Logger.error(error)
// }
// return nil
//
// }
//
// /// Sends an update api call for the provided [instance]
// func sendUpdate(_ instance: T) async throws -> T? {
// do {
// return try await self._sendServerRequest(HTTPMethod.put, instance: instance)
// } catch {
// self.rescheduleApiCallsIfNecessary()
// StoreCenter.main.log(message: "PUT failed for \(instance): \(error.localizedDescription)")
// Logger.error(error)
// }
// return nil
// }
//
// /// Sends an delete api call for the provided [instance]
// func sendDeletion(_ instance: T) async throws {
// do {
// let _: Empty? = try await self._sendServerRequest(HTTPMethod.delete, instance: instance)
// } catch {
// self.rescheduleApiCallsIfNecessary()
// StoreCenter.main.log(message: "DELETE failed for \(instance): \(error.localizedDescription)")
// Logger.error(error)
// }
// return
// }
/// Initiates the process of sending the data with the server
fileprivate func _sendServerRequest<V: Decodable>(_ method: HTTPMethod, instance: T? = nil) async throws -> V? {
@ -349,13 +385,19 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _prepareAndSendCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V? {
self._prepareCall(apiCall: apiCall)
return try await self._executeApiCall(apiCall)
return try await self._executeGetCall(apiCall)
}
/// Executes an API call
/// 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 {
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
fileprivate func _executeApiCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
return try await StoreCenter.main.execute(apiCall: apiCall)
fileprivate func _executeApiCalls(_ apiCalls: [ApiCall<T>]) async throws {
try await StoreCenter.main.execute(apiCalls: apiCalls)
}
/// Returns the content of the API call file as a String

@ -19,6 +19,9 @@ class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
var id: String = Store.randomId()
/// The transactionId to group calls together
var transactionId: String = Store.randomId()
/// Creation date of the call
var creationDate: Date? = Date()
@ -41,10 +44,13 @@ class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
/// The parameters to add in the URL to obtain : "?p1=v1&p2=v2"
var urlParameters: [String : String]? = nil
init(method: HTTPMethod, dataId: String? = nil, body: String? = nil) {
init(method: HTTPMethod, dataId: String? = nil, body: String? = nil, transactionId: String? = nil) {
self.method = method
self.dataId = dataId
self.body = body
if let transactionId {
self.transactionId = transactionId
}
}
func copy(from other: any Storable) {

@ -79,6 +79,64 @@ public class Services {
return try await _runRequest(request)
}
/// Runs a request using a traditional URLRequest
/// - 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 _runSyncPostRequest<T: SyncedStorable>(
_ request: URLRequest, type: T.Type) async throws {
let debugURL = request.url?.absoluteString ?? ""
// print("Run \(request.httpMethod ?? "") \(debugURL)")
let task: (Data, URLResponse) = try await URLSession.shared.data(for: request)
print("sync POST \(String(describing: T.self)) => \(String(data: task.0, encoding: .utf8) ?? "")")
var rescheduleApiCalls: Bool = false
if let response = task.1 as? HTTPURLResponse {
let statusCode = response.statusCode
print("\(debugURL) ended, status code = \(statusCode)")
switch statusCode {
case 200..<300: // success
let decoded: BatchResponse<T> = try self._decode(data: task.0)
for result in decoded.results {
switch result.status {
case 200..<300:
try await StoreCenter.main.deleteApiCallById(type: T.self, id: result.apiCallId)
default:
rescheduleApiCalls = true
}
}
default: // error
Logger.log(
"Failed Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")")
let errorString: String = String(data: task.0, encoding: .utf8) ?? ""
var errorMessage = ErrorMessage(error: errorString, domain: "")
if let message = self.errorMessageFromResponse(data: task.0) {
errorMessage = message
}
try await StoreCenter.main.rescheduleApiCalls(type: T.self)
// StoreCenter.main.logFailedAPICall(
// apiCall.id, request: request, collectionName: T.resourceName(),
// error: errorMessage.message)
throw ServiceError.responseError(response: errorMessage.error)
}
} else {
let message: String = "Unexpected and unmanaged URL Response \(task.1)"
StoreCenter.main.log(message: message)
Logger.w(message)
}
if rescheduleApiCalls {
try await StoreCenter.main.rescheduleApiCalls(type: T.self)
}
}
/// Runs a request using a traditional URLRequest
/// - Parameters:
/// - request: the URLRequest to run
@ -112,7 +170,7 @@ public class Services {
errorMessage = message
}
try await StoreCenter.main.rescheduleApiCalls(id: apiCall.id, type: T.self)
try await StoreCenter.main.rescheduleApiCalls(type: T.self)
StoreCenter.main.logFailedAPICall(
apiCall.id, request: request, collectionName: T.resourceName(),
error: errorMessage.message)
@ -265,7 +323,7 @@ public class Services {
/// Returns the URLRequest for an ApiCall
/// - Parameters:
/// - apiCall: An ApiCall instance to configure the returned request
fileprivate func _syncRequest<T: SyncedStorable>(from apiCall: ApiCall<T>) throws -> URLRequest {
fileprivate func _syncGetRequest<T: SyncedStorable>(from apiCall: ApiCall<T>) throws -> URLRequest {
var urlString = baseURL + "data/"
if let urlParameters = apiCall.formattedURLParameters() {
@ -277,39 +335,57 @@ public class Services {
}
var request = URLRequest(url: url)
if apiCall.method == .get {
request.httpMethod = HTTPMethod.get.rawValue
} else {
request.httpMethod = HTTPMethod.post.rawValue
request.httpMethod = HTTPMethod.get.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if self._isTokenRequired(type: T.self, method: apiCall.method) {
let token = try self.keychainStore.getValue()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
return request
}
/// Returns the URLRequest for an ApiCall
/// - Parameters:
/// - apiCall: An ApiCall instance to configure the returned request
fileprivate func _syncPostRequest<T: SyncedStorable>(from apiCalls: [ApiCall<T>]) throws -> URLRequest {
let urlString = baseURL + "data/"
guard let url = URL(string: urlString) else {
throw ServiceError.urlCreationError(url: urlString)
}
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let token = try self.keychainStore.getValue()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
if let body = apiCall.body {
if let data = body.data(using: .utf8) {
let modelName = String(describing: T.self)
let operations = try apiCalls.map { apiCall in
if let body = apiCall.body, let data = body.data(using: .utf8) {
let object = try JSON.decoder.decode(T.self, from: data)
let modelName = String(describing: T.self)
let payload = SyncPayload(
operation: apiCall.method.rawValue,
modelName: modelName,
data: object,
storeId: object.getStoreId(),
deviceId: StoreCenter.main.deviceId())
request.httpBody = try JSON.encoder.encode(payload)
return Operation(apiCallId: apiCall.id,
operation: apiCall.method.rawValue,
modelName: modelName,
data: object,
storeId: object.getStoreId())
} else {
throw ServiceError.cantDecodeData(resource: T.resourceName(), method: apiCall.method.rawValue, content: apiCall.body)
}
}
if self._isTokenRequired(type: T.self, method: apiCall.method) {
let token = try self.keychainStore.getValue()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
let payload = SyncPayload(operations: operations,
deviceId: StoreCenter.main.deviceId())
request.httpBody = try JSON.encoder.encode(payload)
return request
}
/// Starts a request to retrieve the synchronization updates
/// - Parameters:
/// - since: The date from which updates are retrieved
@ -388,44 +464,41 @@ public class Services {
}
/// Executes a POST request
public func post<T: Storable>(_ instance: T) async throws -> T {
let method: HTTPMethod = .post
let payload = SyncPayload(
operation: method.rawValue,
modelName: String(describing: T.self),
data: instance,
deviceId: StoreCenter.main.deviceId())
let syncRequest = try self._baseSyncRequest(method: .post, payload: payload)
return try await self._runRequest(syncRequest)
// var postRequest = try self._postRequest(type: T.self)
// postRequest.httpBody = try jsonEncoder.encode(instance)
// return try await self._runRequest(postRequest)
}
/// Executes a PUT request
public func put<T: SyncedStorable>(_ instance: T) async throws -> T {
let method: HTTPMethod = .put
let payload = SyncPayload(
operation: method.rawValue,
modelName: String(describing: T.self),
data: instance,
deviceId: StoreCenter.main.deviceId())
let syncRequest = try self._baseSyncRequest(method: .post, payload: payload)
return try await self._runRequest(syncRequest)
// public func post<T: Storable>(_ instance: T) async throws -> T {
//
// let method: HTTPMethod = .post
// let payload = SyncPayload(
// operation: method.rawValue,
// modelName: String(describing: T.self),
// data: instance,
// deviceId: StoreCenter.main.deviceId())
// let syncRequest = try self._baseSyncRequest(method: .post, payload: payload)
// return try await self._runRequest(syncRequest)
// }
//
// /// Executes a PUT request
// public func put<T: SyncedStorable>(_ instance: T) async throws -> T {
//
// let method: HTTPMethod = .put
// let payload = SyncPayload(
// operation: method.rawValue,
// modelName: String(describing: T.self),
// data: instance,
// deviceId: StoreCenter.main.deviceId())
// let syncRequest = try self._baseSyncRequest(method: .post, payload: payload)
// return try await self._runRequest(syncRequest)
// }
// var postRequest = try self._putRequest(type: T.self, id: instance.stringId)
// postRequest.httpBody = try jsonEncoder.encode(instance)
// return try await self._runRequest(postRequest)
/// Executes an ApiCall
func runGetApiCall<T: SyncedStorable, V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
let request = try self._syncGetRequest(from: apiCall)
return try await self._runRequest(request, apiCall: apiCall)
}
/// Executes an ApiCall
func runApiCall<T: SyncedStorable, V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {
let request = try self._syncRequest(from: apiCall)
// print("HTTP \(request.httpMethod ?? "") : id = \(apiCall.dataId)")
return try await self._runRequest(request, apiCall: apiCall)
func runApiCalls<T: SyncedStorable>(_ apiCalls: [ApiCall<T>]) async throws {
let request = try self._syncPostRequest(from: apiCalls)
try await self._runSyncPostRequest(request, type: T.self)
}
/// Returns the URLRequest for an ApiCall
@ -649,11 +722,26 @@ public class Services {
}
struct SyncPayload<T: Encodable>: Encodable {
var operations: [Operation<T>]
var deviceId: String?
}
struct Operation<T: Encodable>: Encodable {
var apiCallId: String
var operation: String
var modelName: String
var data: T
var storeId: String?
var deviceId: String?
}
struct BatchResponse<T: Decodable>: Decodable {
var results: [OperationResult<T>]
}
struct OperationResult<T: Decodable>: Decodable {
var apiCallId: String
var status: Int
var data: T
}
struct ErrorMessage {

@ -287,23 +287,23 @@ final public class Store {
/// Requests an insertion to the StoreCenter
/// - Parameters:
/// - instance: an object to insert
func sendInsertion<T: SyncedStorable>(_ 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<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)
}
// func sendInsertion<T: SyncedStorable>(_ 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<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
public func fileCollectionsAllLoaded() -> Bool {

@ -344,7 +344,7 @@ public class StoreCenter {
}
/// Reschedule an ApiCall by id
func rescheduleApiCalls<T: SyncedStorable>(id: String, type: T.Type) async throws {
func rescheduleApiCalls<T: SyncedStorable>(type: T.Type) async throws {
guard self.collectionsCanSynchronize else {
return
}
@ -360,8 +360,13 @@ public class StoreCenter {
// }
/// Executes an API call
func execute<T: SyncedStorable, V: Decodable>(apiCall: ApiCall<T>) async throws -> V {
return try await self.service().runApiCall(apiCall)
func executeGet<T: SyncedStorable, V: Decodable>(apiCall: ApiCall<T>) async throws -> V {
return try await self.service().runGetApiCall(apiCall)
}
/// Executes an API call
func execute<T: SyncedStorable>(apiCalls: [ApiCall<T>]) async throws {
try await self.service().runApiCalls(apiCalls)
}
// MARK: - Api calls
@ -372,35 +377,44 @@ public class StoreCenter {
&& self.userIsAllowed()
}
/// Transmit the insertion request to the ApiCall collection
/// - Parameters:
/// - instance: an object to insert
func sendInsertion<T: SyncedStorable>(_ instance: T) async throws -> T? {
func prepareOperationBatch<T: SyncedStorable>(_ batch: BatchPreparation<T>) {
guard self._canSynchronise() else {
return nil
return
}
return try await self.apiCallCollection().sendInsertion(instance)
}
/// Transmit the update request to the ApiCall collection
/// - Parameters:
/// - instance: an object to update
func sendUpdate<T: SyncedStorable>(_ instance: T) async throws -> T? {
guard self._canSynchronise() else {
return nil
Task {
try await self.apiCallCollection().executeBatch(batch)
}
return try await self.apiCallCollection().sendUpdate(instance)
}
/// Transmit the deletion request to the ApiCall collection
/// Transmit the insertion request to the ApiCall collection
/// - Parameters:
/// - instance: an object to delete
func sendDeletion<T: SyncedStorable>(_ instance: T) async throws {
guard self._canSynchronise() else {
return
}
try await self.apiCallCollection().sendDeletion(instance)
}
/// - instance: an object to insert
// func sendInsertion<T: SyncedStorable>(_ instance: T) async throws -> T? {
// guard self._canSynchronise() else {
// return nil
// }
// return try await self.apiCallCollection().sendInsertion(instance)
// }
//
// /// Transmit the update request to the ApiCall collection
// /// - Parameters:
// /// - instance: an object to update
// func sendUpdate<T: SyncedStorable>(_ instance: T) async throws -> T? {
// guard self._canSynchronise() else {
// return nil
// }
// return try await self.apiCallCollection().sendUpdate(instance)
// }
//
// /// Transmit the deletion request to the ApiCall collection
// /// - Parameters:
// /// - instance: an object to delete
// func sendDeletion<T: SyncedStorable>(_ instance: T) async throws {
// guard self._canSynchronise() else {
// return
// }
// try await self.apiCallCollection().sendDeletion(instance)
// }
/// Retrieves all the items on the server
func getItems<T: SyncedStorable>(identifier: String? = nil) async throws -> [T] {

@ -102,10 +102,10 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
instance.lastUpdate = Date()
if let index = self.items.firstIndex(where: { $0.id == instance.id }) {
self.updateItem(instance, index: index)
self._sendUpdateIfNecessary(instance)
self._sendUpdate(instance)
} else {
self.addItem(instance: instance)
self._sendInsertionIfNecessary(instance)
self._sendInsertion(instance)
}
}
@ -115,16 +115,23 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
self.setChanged()
}
let date = Date()
let batch = BatchPreparation<T>()
for instance in sequence {
instance.lastUpdate = Date()
instance.lastUpdate = date
if let index = self.items.firstIndex(where: { $0.id == instance.id }) {
self.updateItem(instance, index: index)
self._sendUpdateIfNecessary(instance)
batch.addUpdate(instance)
// self._sendUpdateIfNecessary(instance)
} else { // insert
self.addItem(instance: instance)
self._sendInsertionIfNecessary(instance)
batch.addInsert(instance)
// self._sendInsertionIfNecessary(instance)
}
}
self._prepareBatch(batch)
}
@ -141,8 +148,13 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
}
for instance in sequence {
self._deleteNoWrite(instance: instance)
self.deleteItem(instance)
StoreCenter.main.createDeleteLog(instance)
}
let batch = BatchPreparation<T>()
batch.deletes = sequence
self._prepareBatch(batch)
}
/// Deletes an instance and writes
@ -157,54 +169,71 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
fileprivate func _deleteNoWrite(instance: T) {
self.deleteItem(instance)
StoreCenter.main.createDeleteLog(instance)
self._sendDeletionIfNecessary(instance)
self._sendDeletion(instance)
}
// MARK: - Reschedule calls
// MARK: - Send requests
/// Sends an insert api call for the provided
/// Calls copyFromServerInstance on the instance with the result of the HTTP call
/// - Parameters:
/// - instance: the object to POST
fileprivate func _sendInsertionIfNecessary(_ instance: T) {
Task {
do {
if let result = try await self.store.sendInsertion(instance) {
self.updateFromServerInstance(result)
}
} catch {
Logger.error(error)
}
}
fileprivate func _sendInsertion(_ instance: T) {
self._prepareBatch(BatchPreparation(insert: instance))
}
/// Sends an update api call for the provided [instance]
/// - Parameters:
/// - instance: the object to PUT
fileprivate func _sendUpdateIfNecessary(_ instance: T) {
Task {
do {
try await self.store.sendUpdate(instance)
} catch {
Logger.error(error)
}
}
fileprivate func _sendUpdate(_ instance: T) {
self._prepareBatch(BatchPreparation(update: instance))
}
/// Sends an delete api call for the provided [instance]
/// - Parameters:
/// - instance: the object to DELETE
fileprivate func _sendDeletionIfNecessary(_ instance: T) {
Task {
do {
try await self.store.sendDeletion(instance)
} catch {
Logger.error(error)
}
}
fileprivate func _sendDeletion(_ instance: T) {
self._prepareBatch(BatchPreparation(delete: instance))
}
fileprivate func _prepareBatch(_ batch: BatchPreparation<T>) {
StoreCenter.main.prepareOperationBatch(batch)
}
/// Sends an insert api call for the provided
/// Calls copyFromServerInstance on the instance with the result of the HTTP call
/// - Parameters:
/// - instance: the object to POST
// fileprivate func _sendInsertionIfNecessary(_ instance: T) {
//
// Task {
// do {
// if let result = try await self.store.sendInsertion(instance) {
// self.updateFromServerInstance(result)
// }
// } catch {
// Logger.error(error)
// }
// }
// }
//
// /// Sends an update api call for the provided [instance]
// /// - Parameters:
// /// - instance: the object to PUT
// fileprivate func _sendUpdateIfNecessary(_ instance: T) {
// Task {
// do {
// try await self.store.sendUpdate(instance)
// } catch {
// Logger.error(error)
// }
// }
// }
//
// /// Sends an delete api call for the provided [instance]
// /// - Parameters:
// /// - instance: the object to DELETE
// fileprivate func _sendDeletionIfNecessary(_ instance: T) {
// Task {
// do {
// try await self.store.sendDeletion(instance)
// } catch {
// Logger.error(error)
// }
// }
// }
// MARK: - Synchronization
/// Adds or update an instance if it is newer than the local instance
@ -228,25 +257,39 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
}
// MARK: - Migrations
/// Makes POST ApiCall for all items in the collection
public func insertAllIntoCurrentService() {
for item in self.items {
self._sendInsertionIfNecessary(item)
}
}
/// Makes POST ApiCall for the provided item
public func insertIntoCurrentService(item: T) {
self._sendInsertionIfNecessary(item)
}
/// Sends a POST request for the instance, and changes the collection to perform a write
public func writeChangeAndInsertOnServer(instance: T) {
defer {
self.setChanged()
}
self._sendInsertionIfNecessary(instance)
self._sendInsertion(instance)
}
}
class BatchPreparation<T> {
var inserts: [T] = []
var updates: [T] = []
var deletes: any Sequence<T> = []
init() {
}
init(insert: T) {
self.inserts = [insert]
}
init(update: T) {
self.updates = [update]
}
init(delete: T) {
self.deletes = [delete]
}
func addInsert(_ instance: T) {
self.inserts.append(instance)
}
func addUpdate(_ instance: T) {
self.updates.append(instance)
}
}

Loading…
Cancel
Save