sync2
Laurent 12 months ago
commit 23a34838e5
  1. 24
      LeStorage/ApiCallCollection.swift
  2. 2
      LeStorage/Services.swift
  3. 21
      LeStorage/Store.swift
  4. 11
      LeStorage/StoredCollection.swift
  5. 28
      LeStorage/Utils/Errors.swift
  6. 9
      LeStorage/Utils/FileUtils.swift
  7. 11
      LeStorage/Utils/KeychainStore.swift

@ -20,6 +20,17 @@ protocol SomeCallCollection {
} }
enum ApiCallError: Error, LocalizedError {
case encodingError(id: String, type: String)
var errorDescription: String? {
switch self {
case .encodingError(let id, let type):
return "Can't encode instance \(type) with id: \(id)"
}
}
}
/// ApiCallCollection is an object communicating with a server to synchronize data managed locally /// ApiCallCollection is an object communicating with a server to synchronize data managed locally
/// The Api calls are serialized and stored in a JSON file /// The Api calls are serialized and stored in a JSON file
/// Failing Api calls are stored forever and will be executed again later /// Failing Api calls are stored forever and will be executed again later
@ -267,7 +278,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
/// Prepares a call for execution by updating its properties and adding it to its collection for storage /// 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.lastAttemptDate = Date()
apiCall.attemptsCount += 1 apiCall.attemptsCount += 1
self.addOrUpdate(apiCall) self.addOrUpdate(apiCall)
@ -291,6 +302,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
return try await self._sendServerRequest(HTTPMethod.post, instance: instance) return try await self._sendServerRequest(HTTPMethod.post, instance: instance)
} catch { } catch {
self.rescheduleApiCallsIfNecessary() self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "POST failed for \(instance): \(error.localizedDescription)")
Logger.error(error) Logger.error(error)
} }
return nil return nil
@ -303,6 +315,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
return try await self._sendServerRequest(HTTPMethod.put, instance: instance) return try await self._sendServerRequest(HTTPMethod.put, instance: instance)
} catch { } catch {
self.rescheduleApiCallsIfNecessary() self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "PUT failed for \(instance): \(error.localizedDescription)")
Logger.error(error) Logger.error(error)
} }
return nil return nil
@ -314,6 +327,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
let _: Empty? = try await self._sendServerRequest(HTTPMethod.delete, instance: instance) let _: Empty? = try await self._sendServerRequest(HTTPMethod.delete, instance: instance)
} catch { } catch {
self.rescheduleApiCallsIfNecessary() self.rescheduleApiCallsIfNecessary()
StoreCenter.main.log(message: "DELETE failed for \(instance): \(error.localizedDescription)")
Logger.error(error) Logger.error(error)
} }
return return
@ -331,16 +345,10 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
fileprivate func _prepareAndSendCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V? { fileprivate func _prepareAndSendCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V? {
try self._prepareCall(apiCall: apiCall) self._prepareCall(apiCall: apiCall)
return try await self._executeApiCall(apiCall) return try await self._executeApiCall(apiCall)
} }
/// 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 -> T {
// return try await StoreCenter.main.execute(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 _executeApiCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V { fileprivate func _executeApiCall<V: Decodable>(_ apiCall: ApiCall<T>) async throws -> V {

@ -599,7 +599,7 @@ public class Services {
/// - password: the account's password /// - password: the account's password
public func deleteAccount() async throws { public func deleteAccount() async throws {
guard let userId = StoreCenter.main.userId else { guard let userId = StoreCenter.main.userId else {
throw ServiceError.missingUserId throw StoreError.missingUserId
} }
let path = "users/\(userId)/" let path = "users/\(userId)/"
let deleteAccount = ServiceCall(path: path, method: .delete, requiresToken: true) let deleteAccount = ServiceCall(path: path, method: .delete, requiresToken: true)

@ -8,13 +8,28 @@
import Foundation import Foundation
import UIKit import UIKit
public enum StoreError: Error { public enum StoreError: Error, LocalizedError {
case missingService case missingService
case missingUserId case missingUserId
case unexpectedCollectionType(name: String)
case apiCallCollectionNotRegistered(type: String)
case collectionNotRegistered(type: String) case collectionNotRegistered(type: String)
case cannotSyncCollection(name: String) case cannotSyncCollection(name: String)
case apiCallCollectionNotRegistered(type: String)
public var errorDescription: String? {
switch self {
case .missingService:
return "Services instance is nil"
case .missingUserId:
return "The user id is missing"
case .collectionNotRegistered(let type):
return "The collection \(type) is not registered"
case .cannotSyncCollection(let name):
return "Tries to load the collection \(name) from the server while it's not authorized"
case .apiCallCollectionNotRegistered(let type):
return "The api call collection has not been registered for \(type)"
}
}
} }
//public struct StoreIdentifier { //public struct StoreIdentifier {

@ -7,12 +7,6 @@
import Foundation import Foundation
enum StoredCollectionError: Error {
case unmanagedHTTPMethod(method: String)
case missingApiCallCollection
case missingInstance
}
protocol CollectionHolder { protocol CollectionHolder {
associatedtype Item associatedtype Item
@ -334,14 +328,13 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
/// Writes all the items as a json array inside a file /// Writes all the items as a json array inside a file
fileprivate func _write() { fileprivate func _write() {
// Logger.log("Start write to \(T.fileName())...")
do { do {
let jsonString: String = try self.items.jsonString() let jsonString: String = try self.items.jsonString()
try self.store.write(content: jsonString, fileName: T.fileName()) try self.store.write(content: jsonString, fileName: T.fileName())
} catch { } catch {
Logger.error(error) // TODO how to notify the main project Logger.error(error)
StoreCenter.main.log(message: "write failed for \(T.resourceName()): \(error.localizedDescription)")
} }
// Logger.log("End write")
} }
/// Simply clears the items of the collection /// Simply clears the items of the collection

@ -20,17 +20,39 @@ public class ErrorUtils {
} }
public enum ServiceError: Error { public enum ServiceError: Error, LocalizedError {
case urlCreationError(url: String) case urlCreationError(url: String)
case cantConvertToUUID(id: String) case cantConvertToUUID(id: String)
case missingUserName case missingUserName
case missingUserId
case responseError(response: String) case responseError(response: String)
case cantDecodeData(resource: String, method: String, content: String?) case cantDecodeData(resource: String, method: String, content: String?)
public var errorDescription: String? {
switch self {
case .urlCreationError(let url):
return "Can't create URL from \(url)"
case .cantConvertToUUID(let id):
return "Cant convert \(id) to UUID"
case .missingUserName:
return "There is no userName defined in the Settings"
case .responseError(let response):
return "The server returned an error: \(response)"
case .cantDecodeData(let resource, let method, let content):
return "cannot decode data from \(resource), method: \(method): \(content ?? "")"
}
}
} }
public enum UUIDError: Error { public enum UUIDError: Error, LocalizedError {
case cantConvertString(string: String) case cantConvertString(string: String)
public var errorDescription: String? {
switch self {
case .cantConvertString(let string):
return "cant convert string to UUID: \(string)"
}
}
} }
public enum LeStorageError: Error { public enum LeStorageError: Error {

@ -7,8 +7,15 @@
import Foundation import Foundation
enum FileError : Error { enum FileError: Error, LocalizedError {
case documentDirectoryNotFound case documentDirectoryNotFound
var errorDescription: String? {
switch self {
case .documentDirectoryNotFound:
return "The document directory has not been found"
}
}
} }
class FileUtils { class FileUtils {

@ -11,6 +11,17 @@ enum KeychainError: Error {
case keychainItemNotFound(serverId: String) case keychainItemNotFound(serverId: String)
case unexpectedPasswordData case unexpectedPasswordData
case unhandledError(status: OSStatus) case unhandledError(status: OSStatus)
var errorDescription: String? {
switch self {
case .keychainItemNotFound(let serverId):
return "The keychainItem was not found: \(serverId)"
case .unexpectedPasswordData:
return "Keychain error: The data could not be converted to string"
case .unhandledError(let status):
return "Keychain error: Unmanaged status: \(status)"
}
}
} }
class KeychainStore { class KeychainStore {

Loading…
Cancel
Save