|
|
|
|
@ -7,6 +7,13 @@ |
|
|
|
|
|
|
|
|
|
import Foundation |
|
|
|
|
|
|
|
|
|
enum StoreError: Error { |
|
|
|
|
case missingService |
|
|
|
|
case unexpectedCollectionType(name: String) |
|
|
|
|
case apiCallCollectionNotRegistered(type: String) |
|
|
|
|
case collectionNotRegistered(type: String) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class Store { |
|
|
|
|
|
|
|
|
|
public static let main = Store() |
|
|
|
|
@ -24,13 +31,26 @@ public class Store { |
|
|
|
|
} |
|
|
|
|
fileprivate var _services: Services? |
|
|
|
|
|
|
|
|
|
fileprivate var collections: [String : any SomeCollection] = [:] |
|
|
|
|
fileprivate var _collections: [String : any SomeCollection] = [:] |
|
|
|
|
fileprivate var _apiCallsCollections: [String : any SomeCollection] = [:] |
|
|
|
|
|
|
|
|
|
fileprivate var _apiCallsTimer: Timer? = nil |
|
|
|
|
|
|
|
|
|
fileprivate var _reschedulingCount: Int = 0 |
|
|
|
|
|
|
|
|
|
public init() { } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public func registerCollection<T : Storable>(synchronized: Bool) -> StoredCollection<T> { |
|
|
|
|
|
|
|
|
|
// register collection |
|
|
|
|
let collection = StoredCollection<T>(synchronized: synchronized, store: self) |
|
|
|
|
self.collections[T.resourceName] = collection |
|
|
|
|
self._collections[T.resourceName()] = collection |
|
|
|
|
|
|
|
|
|
if synchronized { // register additional collection for api calls |
|
|
|
|
let apiCallCollection = StoredCollection<ApiCall<T>>(synchronized: false, store: self) |
|
|
|
|
self._apiCallsCollections[T.resourceName()] = apiCallCollection |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return collection |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -39,11 +59,87 @@ public class Store { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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") |
|
|
|
|
guard let collection = self._collections[T.resourceName()] as? StoredCollection<T> else { |
|
|
|
|
Logger.w("Collection \(T.resourceName()) not registered") |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
return collection.findById(id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Api call rescheduling |
|
|
|
|
|
|
|
|
|
func apiCallCollection<T : Storable>() throws -> StoredCollection<ApiCall<T>> { |
|
|
|
|
if let apiCallCollection = self._apiCallsCollections[T.resourceName()] as? StoredCollection<ApiCall<T>> { |
|
|
|
|
return apiCallCollection |
|
|
|
|
} |
|
|
|
|
throw StoreError.apiCallCollectionNotRegistered(type: T.resourceName()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func registerApiCall<T : Storable>(_ apiCall: ApiCall<T>) throws { |
|
|
|
|
let collection: StoredCollection<ApiCall<T>> = try self.apiCallCollection() |
|
|
|
|
collection.addOrUpdate(instance: apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteApiCallById<T : Storable> (_ id: String, type: T.Type) throws { |
|
|
|
|
let collection: StoredCollection<ApiCall<T>> = try self.apiCallCollection() |
|
|
|
|
collection.deleteById(id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteApiCallById(_ id: String, collectionName: String) throws { |
|
|
|
|
if let collection = self._apiCallsCollections[collectionName] { |
|
|
|
|
collection.deleteById(id) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
throw StoreError.collectionNotRegistered(type: collectionName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteApiCall<T : Storable> (_ apiCall: ApiCall<T>) throws { |
|
|
|
|
let collection: StoredCollection<ApiCall<T>> = try self.apiCallCollection() |
|
|
|
|
collection.delete(instance: apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func startCallsRescheduling() { |
|
|
|
|
|
|
|
|
|
self._reschedulingCount += 1 |
|
|
|
|
|
|
|
|
|
let delay = pow(2, 1 + self._reschedulingCount) |
|
|
|
|
let seconds = NSDecimalNumber(decimal: delay).doubleValue |
|
|
|
|
self._apiCallsTimer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { timer in |
|
|
|
|
self._executeApiCalls() |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _executeApiCalls() { |
|
|
|
|
|
|
|
|
|
DispatchQueue(label: "lestorage.queue.network").async { |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
for collection in self._apiCallsCollections.values { |
|
|
|
|
if let apiCalls = collection.allItems() as? [any SomeCall] { |
|
|
|
|
for apiCall in apiCalls { |
|
|
|
|
try apiCall.execute() |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
Logger.w("_apiCallsCollections item not castable to [any SomeCall] ") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _executeApiCall<T : Storable>(_ apiCall: ApiCall<T>) async throws -> T { |
|
|
|
|
guard let service else { |
|
|
|
|
throw StoreError.missingService |
|
|
|
|
} |
|
|
|
|
return try await service.runApiCall(apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func execute<T>(apiCall: ApiCall<T>) async throws { |
|
|
|
|
_ = try await self._executeApiCall(apiCall) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|