|
|
|
|
@ -17,12 +17,15 @@ enum StoreError: Error { |
|
|
|
|
|
|
|
|
|
public class Store { |
|
|
|
|
|
|
|
|
|
/// The Store singleton |
|
|
|
|
public static let main = Store() |
|
|
|
|
|
|
|
|
|
/// A method to provide ids corresponding to the django storage |
|
|
|
|
public static func randomId() -> String { |
|
|
|
|
return UUID().uuidString.lowercased() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// The URL of the django API |
|
|
|
|
public var synchronizationApiURL: String? { |
|
|
|
|
didSet { |
|
|
|
|
if let url = synchronizationApiURL { |
|
|
|
|
@ -30,18 +33,27 @@ public class Store { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// The services performing the API calls |
|
|
|
|
fileprivate var _services: Services? |
|
|
|
|
|
|
|
|
|
/// The dictionary of registered StoredCollections |
|
|
|
|
fileprivate var _collections: [String : any SomeCollection] = [:] |
|
|
|
|
|
|
|
|
|
/// The dictionary of ApiCall StoredCollections corresponding to the synchronized registered collections |
|
|
|
|
fileprivate var _apiCallsCollections: [String : any SomeCollection] = [:] |
|
|
|
|
|
|
|
|
|
/// The list of migrations to apply |
|
|
|
|
fileprivate var _migrations: [SomeMigration] = [] |
|
|
|
|
|
|
|
|
|
/// The collection of performed migration on the store |
|
|
|
|
fileprivate lazy var _migrationCollection: StoredCollection<MigrationHistory> = { StoredCollection(synchronized: false, store: Store.main, asynchronousIO: false) |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
public init() { } |
|
|
|
|
|
|
|
|
|
/// Registers a collection |
|
|
|
|
/// [synchronize] denotes a collection which modification will be sent to the django server |
|
|
|
|
public func registerCollection<T : Storable>(synchronized: Bool) -> StoredCollection<T> { |
|
|
|
|
|
|
|
|
|
// register collection |
|
|
|
|
@ -58,10 +70,12 @@ public class Store { |
|
|
|
|
return collection |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// The service instance |
|
|
|
|
var service: Services? { |
|
|
|
|
return self._services |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Looks for an instance by id |
|
|
|
|
public func findById<T : Storable>(_ id: String) -> T? { |
|
|
|
|
guard let collection = self._collections[T.resourceName()] as? StoredCollection<T> else { |
|
|
|
|
Logger.w("Collection \(T.resourceName()) not registered") |
|
|
|
|
@ -70,6 +84,7 @@ public class Store { |
|
|
|
|
return collection.findById(id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Filters a collection with a [isIncluded] predicate |
|
|
|
|
public func filter<T : Storable>(isIncluded: (T) throws -> (Bool)) rethrows -> [T] { |
|
|
|
|
do { |
|
|
|
|
return try self.collection().filter(isIncluded) |
|
|
|
|
@ -78,6 +93,7 @@ public class Store { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a collection by type |
|
|
|
|
func collection<T : Storable>() throws -> StoredCollection<T> { |
|
|
|
|
if let collection = self._collections[T.resourceName()] as? StoredCollection<T> { |
|
|
|
|
return collection |
|
|
|
|
@ -85,16 +101,19 @@ public class Store { |
|
|
|
|
throw StoreError.collectionNotRegistered(type: T.resourceName()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes the dependencies of a collection |
|
|
|
|
public func deleteDependencies<T : Storable>(items: any Sequence<T>) throws { |
|
|
|
|
try self.collection().deleteDependencies(items) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Migration |
|
|
|
|
|
|
|
|
|
/// [beta] Adds a migration to perform |
|
|
|
|
public func addMigration(_ migration: SomeMigration) { |
|
|
|
|
self._migrations.append(migration) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// [beta] Performs the migration if necessary |
|
|
|
|
func performMigrationIfNecessary<T : Storable>(_ collection: StoredCollection<T>) async throws { |
|
|
|
|
|
|
|
|
|
// Check for migrations |
|
|
|
|
@ -129,12 +148,14 @@ public class Store { |
|
|
|
|
|
|
|
|
|
// MARK: - Api call rescheduling |
|
|
|
|
|
|
|
|
|
/// Schedules all stored api calls from all collections |
|
|
|
|
fileprivate func _rescheduleCalls<T : Storable>(collection: StoredCollection<ApiCall<T>>) { |
|
|
|
|
for apiCall in collection { |
|
|
|
|
self.startCallsRescheduling(apiCall: apiCall) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns an API call collection corresponding to a type T |
|
|
|
|
func apiCallCollection<T : Storable>() throws -> StoredCollection<ApiCall<T>> { |
|
|
|
|
if let apiCallCollection = self._apiCallsCollections[T.resourceName()] as? StoredCollection<ApiCall<T>> { |
|
|
|
|
return apiCallCollection |
|
|
|
|
@ -142,6 +163,7 @@ public class Store { |
|
|
|
|
throw StoreError.apiCallCollectionNotRegistered(type: T.resourceName()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Registers an api call into its collection |
|
|
|
|
func registerApiCall<T : Storable>(_ apiCall: ApiCall<T>) throws { |
|
|
|
|
let collection: StoredCollection<ApiCall<T>> = try self.apiCallCollection() |
|
|
|
|
|
|
|
|
|
@ -161,6 +183,7 @@ public class Store { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an ApiCall by [id] and [collectionName] |
|
|
|
|
func deleteApiCallById(_ id: String, collectionName: String) throws { |
|
|
|
|
|
|
|
|
|
if let collection = self._apiCallsCollections[collectionName] { |
|
|
|
|
@ -170,6 +193,7 @@ public class Store { |
|
|
|
|
throw StoreError.collectionNotRegistered(type: collectionName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Schedule an ApiCall for its execution in the future |
|
|
|
|
func startCallsRescheduling<T : Storable>(apiCall: ApiCall<T>) { |
|
|
|
|
let delay = pow(2, 0 + apiCall.attemptsCount) |
|
|
|
|
let seconds = NSDecimalNumber(decimal: delay).intValue |
|
|
|
|
@ -189,6 +213,7 @@ public class Store { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Reschedule an ApiCall by id |
|
|
|
|
func startCallsRescheduling<T : Storable>(apiCallId: String, type: T.Type) throws { |
|
|
|
|
let apiCallCollection: StoredCollection<ApiCall<T>> = try self.apiCallCollection() |
|
|
|
|
if let apiCall = apiCallCollection.findById(apiCallId) { |
|
|
|
|
@ -196,6 +221,7 @@ public class Store { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Executes an ApiCall |
|
|
|
|
fileprivate func _executeApiCall<T : Storable>(_ apiCall: ApiCall<T>) async throws -> T { |
|
|
|
|
guard let service else { |
|
|
|
|
throw StoreError.missingService |
|
|
|
|
@ -203,10 +229,12 @@ public class Store { |
|
|
|
|
return try await service.runApiCall(apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Executes an ApiCall |
|
|
|
|
func execute<T>(apiCall: ApiCall<T>) async throws { |
|
|
|
|
_ = try await self._executeApiCall(apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Retrieves all the items on the server |
|
|
|
|
func getItems<T : Storable>() async throws -> [T] { |
|
|
|
|
guard let service else { |
|
|
|
|
throw StoreError.missingService |
|
|
|
|
|