|
|
|
|
@ -19,6 +19,10 @@ protocol SomeCallCollection { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum ApiCallError: Error { |
|
|
|
|
case cantCreateCall |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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 |
|
|
|
|
@ -227,12 +231,17 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection { |
|
|
|
|
|
|
|
|
|
/// Creates an API call for the Storable [instance] and an HTTP [method] |
|
|
|
|
fileprivate func _createCall(_ instance: T, method: HTTPMethod) throws -> ApiCall<T> { |
|
|
|
|
let jsonString = try instance.jsonString() |
|
|
|
|
return ApiCall(method: method, dataId: instance.stringId, body: jsonString) |
|
|
|
|
do { |
|
|
|
|
let jsonString = try instance.jsonString() |
|
|
|
|
return ApiCall(method: method, dataId: instance.stringId, body: jsonString) |
|
|
|
|
} catch { |
|
|
|
|
StoreCenter.main.log(message: "call could not be created for \(T.resourceName()): \(error.localizedDescription)") |
|
|
|
|
throw ApiCallError.cantCreateCall |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Prepares a call for execution by updating its properties and adding it to its collection for storage |
|
|
|
|
fileprivate func _prepareCall(apiCall: ApiCall<T>) throws { |
|
|
|
|
fileprivate func _prepareCall(apiCall: ApiCall<T>) { |
|
|
|
|
apiCall.lastAttemptDate = Date() |
|
|
|
|
apiCall.attemptsCount += 1 |
|
|
|
|
self.addOrUpdate(apiCall) |
|
|
|
|
@ -244,6 +253,7 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection { |
|
|
|
|
return try await self._synchronize(instance, method: HTTPMethod.post) |
|
|
|
|
} catch { |
|
|
|
|
self.rescheduleApiCallsIfNecessary() |
|
|
|
|
StoreCenter.main.log(message: "POST failed for \(instance)") |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
@ -256,6 +266,7 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection { |
|
|
|
|
return try await self._synchronize(instance, method: HTTPMethod.put) |
|
|
|
|
} catch { |
|
|
|
|
self.rescheduleApiCallsIfNecessary() |
|
|
|
|
StoreCenter.main.log(message: "PUT failed for \(instance)") |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
@ -267,6 +278,7 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection { |
|
|
|
|
let _: Empty? = try await self._synchronize(instance, method: HTTPMethod.delete) |
|
|
|
|
} catch { |
|
|
|
|
self.rescheduleApiCallsIfNecessary() |
|
|
|
|
StoreCenter.main.log(message: "DELETE failed for \(instance)") |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
return |
|
|
|
|
@ -275,7 +287,7 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection { |
|
|
|
|
/// Initiates the process of sending the data with the server |
|
|
|
|
fileprivate func _synchronize<V: Decodable>(_ instance: T, method: HTTPMethod) async throws -> V? { |
|
|
|
|
if let apiCall = try self._callForInstance(instance, method: method) { |
|
|
|
|
try self._prepareCall(apiCall: apiCall) |
|
|
|
|
self._prepareCall(apiCall: apiCall) |
|
|
|
|
return try await self._executeApiCall(apiCall) |
|
|
|
|
} else { |
|
|
|
|
return nil |
|
|
|
|
|