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._schedulingTask?.cancel()
self.items.removeAll()
self._hasChanged = true
do {
let url: URL = try self._urlForJSONFile()
@ -184,7 +185,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _waitAndExecuteApiCalls() async {
// 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 }
self._isExecutingCalls = true
@ -226,7 +227,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
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)
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
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 }) {
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
return call
}
/// 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 {
return ApiCall(method: method, data: instance, transactionId: transactionId)
return ApiCall(method: method, data: instance, transactionId: transactionId, option: option)
} 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]
func sendGetRequest(storeId: String?) async throws {
func sendGetRequest(storeId: String?, clear: Bool) async throws {
var parameters: [String : String]? = nil
if let 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]
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 {
try await self._prepareAndSendGetCall(getCall)
} catch {

@ -26,11 +26,6 @@ public protocol SomeCollection: CollectionHolder, Identifiable {
func findById(_ id: Item.ID) -> Item?
}
protocol SomeSyncedCollection: SomeCollection {
func loadDataFromServerIfAllowed() async throws
func loadCollectionsFromServerIfNoFile() async throws
}
public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
/// 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()) {
let jsonString: String = try FileUtils.readFile(fileURL: fileURL)
let decoded: [T] = try jsonString.decodeArray() ?? []
self._setItems(decoded)
self.setItems(decoded)
}
await MainActor.run {
self.setAsLoaded()
@ -167,7 +162,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
}
/// Sets a collection of items and indexes them
fileprivate func _setItems(_ items: [T]) {
func setItems(_ items: [T]) {
for item in items {
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
/// Adds or updates the provided instance inside the collection
@ -407,7 +393,6 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
self.items.removeAll()
self.store.removeFile(type: T.self)
setChanged()
self.hasLoaded = false
}
public var type: any Storable.Type { return T.self }

@ -17,6 +17,10 @@ public protocol SomeCall: Identifiable, Storable {
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 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()
/// The transactionId to group calls together
/// The transactionId serves to group calls together
var transactionId: String = Store.randomId()
/// Creation date of the call
@ -44,13 +48,17 @@ public 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
/// The option for the call
var option: CallOption? = nil
init(method: HTTPMethod, data: T?, transactionId: String? = nil) {
init(method: HTTPMethod, data: T?, transactionId: String? = nil, option: CallOption? = nil) {
self.method = method
self.data = data
if let transactionId {
self.transactionId = transactionId
}
self.option = option
}
public func copy(from other: any Storable) {

@ -175,10 +175,14 @@ final public class Store {
}
/// Loads all collection with the data from the server
public func loadCollectionsFromServer() {
public func loadCollectionsFromServer(clear: Bool) {
for collection in self._syncedCollections() {
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 {
let collection: BaseCollection<T> = try self.collection()
await collection.clearAndLoadItems(items)
let collection: SyncedCollection<T> = try self.syncedCollection()
await collection.loadItems(items, clear: clear)
} catch {
Logger.error(error)
}

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

@ -7,6 +7,11 @@
import Foundation
protocol SomeSyncedCollection: SomeCollection {
func loadDataFromServerIfAllowed(clear: Bool) async throws
func loadCollectionsFromServerIfNoFile() async throws
}
public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSyncedCollection {
/// Returns a dummy SyncedCollection instance
@ -35,9 +40,9 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
}
}
func loadDataFromServerIfAllowed() async throws {
try await self.loadDataFromServerIfAllowed(clear: false)
}
// func loadDataFromServerIfAllowed() async throws {
// try await self.loadDataFromServerIfAllowed(clear: false)
// }
/// Retrieves the data from the server and loads it into the items array
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)
}
do {
try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId)
try await StoreCenter.main.sendGetRequest(T.self, storeId: self.storeId, clear: clear)
} catch {
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
/// 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
public func writeChangeAndInsertOnServer(instance: T) {

Loading…
Cancel
Save