Fix lots of crap

sync_v2
Laurent 7 months ago
parent 27a403c99b
commit 76e5491cda
  1. 24
      LeStorage/ApiCallCollection.swift
  2. 19
      LeStorage/BaseCollection.swift
  3. 12
      LeStorage/Codables/ApiCall.swift
  4. 14
      LeStorage/Store.swift
  5. 35
      LeStorage/StoreCenter.swift
  6. 27
      LeStorage/SyncedCollection.swift

@ -143,6 +143,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
self._isExecutingCalls = false self._isExecutingCalls = false
self._schedulingTask?.cancel() self._schedulingTask?.cancel()
self.items.removeAll() self.items.removeAll()
self._hasChanged = true
do { do {
let url: URL = try self._urlForJSONFile() let url: URL = try self._urlForJSONFile()
@ -184,7 +185,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _waitAndExecuteApiCalls() async { fileprivate func _waitAndExecuteApiCalls() async {
// Logger.log("\(T.resourceName()) > RESCHED") // Logger.log("\(T.resourceName()) > RESCHED")
guard !self._isExecutingCalls, StoreCenter.main.collectionsCanSynchronize else { return } guard !self._isExecutingCalls, StoreCenter.main.forceNoSynchronization == false else { return }
guard self.items.isNotEmpty else { return } guard self.items.isNotEmpty else { return }
self._isExecutingCalls = true self._isExecutingCalls = true
@ -226,7 +227,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
let _: Empty = try await StoreCenter.main.executeGet(apiCall: apiCall) let _: Empty = try await StoreCenter.main.executeGet(apiCall: apiCall)
} else { } else {
let results: [T] = try await StoreCenter.main.executeGet(apiCall: apiCall) let results: [T] = try await StoreCenter.main.executeGet(apiCall: apiCall)
await StoreCenter.main.itemsRetrieved(results, storeId: apiCall.storeId) await StoreCenter.main.itemsRetrieved(results, storeId: apiCall.storeId, clear: apiCall.option != .additive)
} }
} }
@ -284,21 +285,22 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
/// we want to avoid sending the same GET twice /// we want to avoid sending the same GET twice
fileprivate func _createGetCallIfNonExistent(_ parameters: [String : String]?) -> ApiCall<T>? { fileprivate func _createGetCallIfNonExistent(_ parameters: [String : String]?, clear: Bool) -> ApiCall<T>? {
if let _ = self.items.first(where: { $0.method == .get && $0.urlParameters == parameters }) { if let _ = self.items.first(where: { $0.method == .get && $0.urlParameters == parameters }) {
return nil return nil
} }
let call = self._createCall(.get, instance: nil) let option: CallOption? = !clear ? .additive : nil
let call = self._createCall(.get, instance: nil, option: option)
call.urlParameters = parameters call.urlParameters = parameters
return call return call
} }
/// Creates an API call for the Storable [instance] and an HTTP [method] /// Creates an API call for the Storable [instance] and an HTTP [method]
fileprivate func _createCall(_ method: HTTPMethod, instance: T?, transactionId: String? = nil) -> ApiCall<T> { fileprivate func _createCall(_ method: HTTPMethod, instance: T?, transactionId: String? = nil, option: CallOption? = nil) -> ApiCall<T> {
if let instance { if let instance {
return ApiCall(method: method, data: instance, transactionId: transactionId) return ApiCall(method: method, data: instance, transactionId: transactionId, option: option)
} else { } else {
return ApiCall(method: .get, data: nil) return ApiCall(method: .get, data: nil, option: option)
} }
} }
@ -316,18 +318,18 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
/// Sends a GET request with an optional [storeId] /// Sends a GET request with an optional [storeId]
func sendGetRequest(storeId: String?) async throws { func sendGetRequest(storeId: String?, clear: Bool) async throws {
var parameters: [String : String]? = nil var parameters: [String : String]? = nil
if let storeId { if let storeId {
parameters = [Services.storeIdURLParameter : storeId] parameters = [Services.storeIdURLParameter : storeId]
} }
try await self._sendGetRequest(parameters: parameters) try await self._sendGetRequest(parameters: parameters, clear: clear)
} }
/// Sends an insert api call for the provided [instance] /// Sends an insert api call for the provided [instance]
fileprivate func _sendGetRequest(parameters: [String : String]?) async throws { fileprivate func _sendGetRequest(parameters: [String : String]?, clear: Bool = true) async throws {
if let getCall = self._createGetCallIfNonExistent(parameters) { if let getCall = self._createGetCallIfNonExistent(parameters, clear: clear) {
do { do {
try await self._prepareAndSendGetCall(getCall) try await self._prepareAndSendGetCall(getCall)
} catch { } catch {

@ -26,11 +26,6 @@ public protocol SomeCollection: CollectionHolder, Identifiable {
func findById(_ id: Item.ID) -> Item? func findById(_ id: Item.ID) -> Item?
} }
protocol SomeSyncedCollection: SomeCollection {
func loadDataFromServerIfAllowed() async throws
func loadCollectionsFromServerIfNoFile() async throws
}
public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
/// Doesn't write the collection in a file /// Doesn't write the collection in a file
@ -131,7 +126,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
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() ?? []
self._setItems(decoded) self.setItems(decoded)
} }
await MainActor.run { await MainActor.run {
self.setAsLoaded() self.setAsLoaded()
@ -167,7 +162,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
} }
/// Sets a collection of items and indexes them /// Sets a collection of items and indexes them
fileprivate func _setItems(_ items: [T]) { func setItems(_ items: [T]) {
for item in items { for item in items {
item.store = self.store item.store = self.store
} }
@ -182,15 +177,6 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
} }
} }
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
@ -407,7 +393,6 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
self.items.removeAll() self.items.removeAll()
self.store.removeFile(type: T.self) self.store.removeFile(type: T.self)
setChanged() setChanged()
self.hasLoaded = false
} }
public var type: any Storable.Type { return T.self } public var type: any Storable.Type { return T.self }

@ -17,6 +17,10 @@ public protocol SomeCall: Identifiable, Storable {
var dataContent: String? { get } var dataContent: String? { get }
} }
public enum CallOption: String, Codable {
case additive // keeps the content of the current collection
}
public class ApiCall<T: Storable>: ModelObject, Storable, SomeCall { public class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
public static func resourceName() -> String { return "apicalls_" + T.resourceName() } public static func resourceName() -> String { return "apicalls_" + T.resourceName() }
@ -24,7 +28,7 @@ public class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
public var id: String = Store.randomId() public var id: String = Store.randomId()
/// The transactionId to group calls together /// The transactionId serves to group calls together
var transactionId: String = Store.randomId() var transactionId: String = Store.randomId()
/// Creation date of the call /// Creation date of the call
@ -45,12 +49,16 @@ public class ApiCall<T: Storable>: ModelObject, Storable, SomeCall {
/// The parameters to add in the URL to obtain : "?p1=v1&p2=v2" /// The parameters to add in the URL to obtain : "?p1=v1&p2=v2"
var urlParameters: [String : String]? = nil var urlParameters: [String : String]? = nil
init(method: HTTPMethod, data: T?, transactionId: String? = nil) { /// The option for the call
var option: CallOption? = nil
init(method: HTTPMethod, data: T?, transactionId: String? = nil, option: CallOption? = nil) {
self.method = method self.method = method
self.data = data self.data = data
if let transactionId { if let transactionId {
self.transactionId = transactionId self.transactionId = transactionId
} }
self.option = option
} }
public func copy(from other: any Storable) { public func copy(from other: any Storable) {

@ -175,10 +175,14 @@ final public class Store {
} }
/// Loads all collection with the data from the server /// Loads all collection with the data from the server
public func loadCollectionsFromServer() { public func loadCollectionsFromServer(clear: Bool) {
for collection in self._syncedCollections() { for collection in self._syncedCollections() {
Task { Task {
try? await collection.loadDataFromServerIfAllowed() do {
try await collection.loadDataFromServerIfAllowed(clear: clear)
} catch {
Logger.error(error)
}
} }
} }
} }
@ -300,10 +304,10 @@ final public class Store {
} }
} }
func loadCollectionItems<T: SyncedStorable>(_ items: [T]) async { func loadCollectionItems<T: SyncedStorable>(_ items: [T], clear: Bool) async {
do { do {
let collection: BaseCollection<T> = try self.collection() let collection: SyncedCollection<T> = try self.syncedCollection()
await collection.clearAndLoadItems(items) await collection.loadItems(items, clear: clear)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }

@ -19,9 +19,6 @@ public class StoreCenter {
/// A KeychainStore object used to store the user's token /// A KeychainStore object used to store the user's token
var keychainStore: KeychainStore? = nil var keychainStore: KeychainStore? = nil
/// Indicates to Stored Collection if they can synchronize
public var collectionsCanSynchronize: Bool = true
/// Force the absence of synchronization /// Force the absence of synchronization
public var forceNoSynchronization: Bool = false public var forceNoSynchronization: Bool = false
@ -287,7 +284,7 @@ public class StoreCenter {
do { do {
try await apiCallCollection.loadFromFile() try await apiCallCollection.loadFromFile()
let count = await apiCallCollection.items.count let count = await apiCallCollection.items.count
Logger.log("collection \(T.resourceName()) loaded with \(count)") // Logger.log("collection \(T.resourceName()) loaded with \(count)")
await apiCallCollection.rescheduleApiCallsIfNecessary() await apiCallCollection.rescheduleApiCallsIfNecessary()
} catch { } catch {
Logger.error(error) Logger.error(error)
@ -378,10 +375,10 @@ public class StoreCenter {
/// Retry API calls immediately /// Retry API calls immediately
fileprivate func _resumeApiCalls() { fileprivate func _resumeApiCalls() {
guard self.collectionsCanSynchronize else { guard self.forceNoSynchronization == false else {
return return
} }
Logger.log("_resumeApiCalls") // Logger.log("_resumeApiCalls")
Task { Task {
for collection in self._apiCallCollections.values { for collection in self._apiCallCollections.values {
await collection.resumeApiCalls() await collection.resumeApiCalls()
@ -391,7 +388,7 @@ public class StoreCenter {
/// Reschedule an ApiCall by id /// Reschedule an ApiCall by id
func rescheduleApiCalls<T: SyncedStorable>(type: T.Type) async throws { func rescheduleApiCalls<T: SyncedStorable>(type: T.Type) async throws {
guard self.collectionsCanSynchronize else { guard self.forceNoSynchronization == false else {
return return
} }
let collection: ApiCallCollection<T> = try self.apiCallCollection() let collection: ApiCallCollection<T> = try self.apiCallCollection()
@ -419,7 +416,8 @@ public class StoreCenter {
/// Returns whether the collection can synchronize /// Returns whether the collection can synchronize
fileprivate func _canSynchronise() -> Bool { fileprivate func _canSynchronise() -> Bool {
return !self.forceNoSynchronization && self.collectionsCanSynchronize return !self.forceNoSynchronization
&& self.isAuthenticated
&& self.userIsAllowed() && self.userIsAllowed()
} }
@ -465,8 +463,8 @@ 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 { func itemsRetrieved<T: SyncedStorable>(_ results: [T], storeId: String?, clear: Bool) async {
await self._store(id: storeId).loadCollectionItems(results) await self._store(id: storeId).loadCollectionItems(results, clear: clear)
} }
/// Returns the names of all collections /// Returns the names of all collections
@ -488,8 +486,8 @@ public class StoreCenter {
} }
/// Loads all the data from the server for the users /// Loads all the data from the server for the users
public func initialSynchronization() { public func initialSynchronization(clear: Bool) {
Store.main.loadCollectionsFromServer() Store.main.loadCollectionsFromServer(clear: clear)
// request data that has been shared with the user // request data that has been shared with the user
Task { Task {
@ -522,11 +520,13 @@ public class StoreCenter {
} }
func sendGetRequest<T: SyncedStorable>(_ type: T.Type, storeId: String?) async throws { func sendGetRequest<T: SyncedStorable>(_ type: T.Type, storeId: String?, clear: Bool) async throws {
if self.canPerformGet(T.self) { guard self._canSynchronise(), self.canPerformGet(T.self) else {
let apiCallCollection: ApiCallCollection<T> = try self.apiCallCollection() return
try await apiCallCollection.sendGetRequest(storeId: storeId)
} }
let apiCallCollection: ApiCallCollection<T> = try self.apiCallCollection()
try await apiCallCollection.sendGetRequest(storeId: storeId, clear: clear)
} }
func canPerformGet<T: SyncedStorable>(_ type: T.Type) -> Bool { func canPerformGet<T: SyncedStorable>(_ type: T.Type) -> Bool {
@ -540,7 +540,8 @@ public class StoreCenter {
let json = try JSONSerialization.jsonObject(with: data, options: []) let json = try JSONSerialization.jsonObject(with: data, options: [])
as? [String: Any] as? [String: Any]
else { else {
Logger.w("data unrecognized") let string = String(data: data, encoding: .utf8) ?? "--"
Logger.w("data unrecognized: \(string)")
return return
} }
try await self._parseSyncUpdates(json, shared: true) try await self._parseSyncUpdates(json, shared: true)

@ -7,6 +7,11 @@
import Foundation import Foundation
protocol SomeSyncedCollection: SomeCollection {
func loadDataFromServerIfAllowed(clear: Bool) async throws
func loadCollectionsFromServerIfNoFile() async throws
}
public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSyncedCollection { public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSyncedCollection {
/// Returns a dummy SyncedCollection instance /// Returns a dummy SyncedCollection instance
@ -35,9 +40,9 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
} }
} }
func loadDataFromServerIfAllowed() async throws { // func loadDataFromServerIfAllowed() async throws {
try await self.loadDataFromServerIfAllowed(clear: false) // try await self.loadDataFromServerIfAllowed(clear: false)
} // }
/// Retrieves the data from the server and loads it into the items array /// Retrieves the data from the server and loads it into the items array
public func loadDataFromServerIfAllowed(clear: Bool = false) async throws { public func loadDataFromServerIfAllowed(clear: Bool = false) async throws {
@ -45,7 +50,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
throw StoreError.cannotSyncCollection(name: self.resourceName) throw StoreError.cannotSyncCollection(name: self.resourceName)
} }
do { do {
try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId) try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId, clear: clear)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -68,6 +73,18 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
} }
} }
@MainActor
func loadItems(_ items: [T], clear: Bool = false) {
if clear {
self.setItems(items)
} else {
self.addOrUpdateNoSync(contentOfs: items)
}
self.setAsLoaded()
self.setChanged()
}
// MARK: - Basic operations with sync // MARK: - Basic operations with sync
/// Adds or update an instance and writes /// Adds or update an instance and writes
@ -285,7 +302,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
} }
// MARK: - Migrations // MARK: - Others
/// Sends a POST request for the instance, and changes the collection to perform a write /// Sends a POST request for the instance, and changes the collection to perform a write
public func writeChangeAndInsertOnServer(instance: T) { public func writeChangeAndInsertOnServer(instance: T) {

Loading…
Cancel
Save