|
|
|
|
@ -16,6 +16,8 @@ public class StoreCenter { |
|
|
|
|
/// A dictionary of Stores associated to their id |
|
|
|
|
fileprivate var _stores: [String: Store] = [:] |
|
|
|
|
|
|
|
|
|
lazy var mainStore: Store = { Store(storeCenter: self) }() |
|
|
|
|
|
|
|
|
|
/// A KeychainStore object used to store the user's token |
|
|
|
|
var keychainStore: KeychainStore? = nil |
|
|
|
|
|
|
|
|
|
@ -36,7 +38,7 @@ public class StoreCenter { |
|
|
|
|
fileprivate var _apiCallCollections: [String: any SomeCallCollection] = [:] |
|
|
|
|
|
|
|
|
|
/// A collection of DataLog objects, used for the synchronization |
|
|
|
|
fileprivate var _dataLogs: StoredCollection<DataLog> |
|
|
|
|
lazy fileprivate var _deleteLogs: StoredCollection<DataLog> = { self.mainStore.registerCollection() }() |
|
|
|
|
|
|
|
|
|
/// A synchronized collection of DataAccess |
|
|
|
|
fileprivate var _dataAccess: SyncedCollection<DataAccess>? = nil |
|
|
|
|
@ -53,11 +55,11 @@ public class StoreCenter { |
|
|
|
|
/// The URL manager |
|
|
|
|
fileprivate var _urlManager: URLManager? = nil |
|
|
|
|
|
|
|
|
|
/// Memory only alternate device id for testing purpose |
|
|
|
|
var alternateDeviceId: String? = nil |
|
|
|
|
|
|
|
|
|
init() { |
|
|
|
|
|
|
|
|
|
// self._syncGetRequests = ApiCallCollection() |
|
|
|
|
self._dataLogs = Store.main.registerCollection() |
|
|
|
|
|
|
|
|
|
self._setupNotifications() |
|
|
|
|
|
|
|
|
|
self.loadApiCallCollection(type: GetSyncData.self) |
|
|
|
|
@ -72,10 +74,10 @@ public class StoreCenter { |
|
|
|
|
public func configureURLs(secureScheme: Bool, domain: String) { |
|
|
|
|
let urlManager: URLManager = URLManager(secureScheme: secureScheme, domain: domain) |
|
|
|
|
self._urlManager = urlManager |
|
|
|
|
self._services = Services(url: urlManager.api) |
|
|
|
|
self._services = Services(storeCenter: self, url: urlManager.api) |
|
|
|
|
self.keychainStore = KeychainStore(serverId: urlManager.api) |
|
|
|
|
|
|
|
|
|
self._dataAccess = Store.main.registerSynchronizedCollection() |
|
|
|
|
self._dataAccess = self.mainStore.registerSynchronizedCollection() |
|
|
|
|
|
|
|
|
|
Logger.log("Sync URL: \(urlManager.api)") |
|
|
|
|
|
|
|
|
|
@ -98,7 +100,7 @@ public class StoreCenter { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
let url = urlManager.websocket(userId: userId) |
|
|
|
|
self._webSocketManager = WebSocketManager(urlString: url) |
|
|
|
|
self._webSocketManager = WebSocketManager(storeCenter: self, urlString: url) |
|
|
|
|
Logger.log("websocket configured: \(url)") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -173,7 +175,7 @@ public class StoreCenter { |
|
|
|
|
if let store = self._stores[identifier] { |
|
|
|
|
return store |
|
|
|
|
} else { |
|
|
|
|
let store = Store(identifier: identifier) |
|
|
|
|
let store = Store(storeCenter: self, identifier: identifier) |
|
|
|
|
self._registerStore(store: store) |
|
|
|
|
return store |
|
|
|
|
} |
|
|
|
|
@ -220,7 +222,7 @@ public class StoreCenter { |
|
|
|
|
|
|
|
|
|
self._stores.removeAll() |
|
|
|
|
self._dataAccess?.reset() |
|
|
|
|
self._dataLogs.reset() |
|
|
|
|
self._deleteLogs.reset() |
|
|
|
|
|
|
|
|
|
self._settingsStorage.update { settings in |
|
|
|
|
settings.username = nil |
|
|
|
|
@ -258,6 +260,10 @@ public class StoreCenter { |
|
|
|
|
/// If created, stores it inside the keychain to get a consistent value even if the app is deleted |
|
|
|
|
/// as UIDevice.current.identifierForVendor value changes when the app is deleted and installed again |
|
|
|
|
func deviceId() -> String { |
|
|
|
|
if let alternateDeviceId { |
|
|
|
|
return alternateDeviceId |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let keychainStore = KeychainStore(serverId: "lestorage.main") |
|
|
|
|
do { |
|
|
|
|
return try keychainStore.getValue() |
|
|
|
|
@ -278,7 +284,7 @@ public class StoreCenter { |
|
|
|
|
/// Instantiates and loads an ApiCallCollection with the provided type |
|
|
|
|
public func loadApiCallCollection<T: SyncedStorable>(type: T.Type) { |
|
|
|
|
if self._apiCallCollections[T.resourceName()] == nil { |
|
|
|
|
let apiCallCollection = ApiCallCollection<T>() |
|
|
|
|
let apiCallCollection = ApiCallCollection<T>(storeCenter: self) |
|
|
|
|
self._apiCallCollections[T.resourceName()] = apiCallCollection |
|
|
|
|
Task { |
|
|
|
|
do { |
|
|
|
|
@ -491,7 +497,7 @@ public class StoreCenter { |
|
|
|
|
|
|
|
|
|
/// Loads all the data from the server for the users |
|
|
|
|
public func initialSynchronization(clear: Bool) { |
|
|
|
|
Store.main.loadCollectionsFromServer(clear: clear) |
|
|
|
|
self.mainStore.loadCollectionsFromServer(clear: clear) |
|
|
|
|
|
|
|
|
|
// request data that has been shared with the user |
|
|
|
|
Task { |
|
|
|
|
@ -607,7 +613,7 @@ public class StoreCenter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} catch { |
|
|
|
|
StoreCenter.main.log(message: error.localizedDescription) |
|
|
|
|
self.log(message: error.localizedDescription) |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -640,7 +646,7 @@ public class StoreCenter { |
|
|
|
|
// Logger.log(">>> \(decodedObject.lastUpdate.timeIntervalSince1970) : \(decodedObject.id)") |
|
|
|
|
|
|
|
|
|
let storeId: String? = decodedObject.getStoreId() |
|
|
|
|
StoreCenter.main.synchronizationAddOrUpdate(decodedObject, storeId: storeId, shared: shared) |
|
|
|
|
self.synchronizationAddOrUpdate(decodedObject, storeId: storeId, shared: shared) |
|
|
|
|
} catch { |
|
|
|
|
Logger.w("Issue with json decoding: \(updateItem)") |
|
|
|
|
Logger.error(error) |
|
|
|
|
@ -663,7 +669,7 @@ public class StoreCenter { |
|
|
|
|
let data = try JSONSerialization.data(withJSONObject: deleted, options: []) |
|
|
|
|
let deletedObject = try JSON.decoder.decode(ObjectIdentifier.self, from: data) |
|
|
|
|
|
|
|
|
|
StoreCenter.main.synchronizationDelete(id: deletedObject.modelId, model: className, storeId: deletedObject.storeId) |
|
|
|
|
self.synchronizationDelete(id: deletedObject.modelId, model: className, storeId: deletedObject.storeId) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
@ -683,7 +689,7 @@ public class StoreCenter { |
|
|
|
|
do { |
|
|
|
|
let data = try JSONSerialization.data(withJSONObject: revoked, options: []) |
|
|
|
|
let revokedObject = try JSON.decoder.decode(ObjectIdentifier.self, from: data) |
|
|
|
|
StoreCenter.main.synchronizationDelete(id: revokedObject.modelId, model: className, storeId: revokedObject.storeId) |
|
|
|
|
self.synchronizationDelete(id: revokedObject.modelId, model: className, storeId: revokedObject.storeId) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
@ -707,7 +713,7 @@ public class StoreCenter { |
|
|
|
|
do { |
|
|
|
|
let data = try JSONSerialization.data(withJSONObject: parentItem, options: []) |
|
|
|
|
let revokedObject = try JSON.decoder.decode(ObjectIdentifier.self, from: data) |
|
|
|
|
StoreCenter.main.synchronizationRevoke(id: revokedObject.modelId, model: className, storeId: revokedObject.storeId) |
|
|
|
|
self.synchronizationRevoke(id: revokedObject.modelId, model: className, storeId: revokedObject.storeId) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
@ -736,18 +742,18 @@ public class StoreCenter { |
|
|
|
|
if let store = self._stores[storeId] { |
|
|
|
|
return store |
|
|
|
|
} else { |
|
|
|
|
let store = Store(identifier: storeId) |
|
|
|
|
let store = Store(storeCenter: self, identifier: storeId) |
|
|
|
|
self._registerStore(store: store) |
|
|
|
|
return store |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
return Store.main |
|
|
|
|
return self.mainStore |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns whether a data has already been deleted by, to avoid inserting it again |
|
|
|
|
fileprivate func _hasAlreadyBeenDeleted<T: Storable>(_ instance: T) -> Bool { |
|
|
|
|
return self._dataLogs.contains(where: { |
|
|
|
|
return self._deleteLogs.contains(where: { |
|
|
|
|
$0.dataId == instance.stringId && $0.operation == .delete |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
@ -783,7 +789,7 @@ public class StoreCenter { |
|
|
|
|
do { |
|
|
|
|
let type = try StoreCenter.classFromName(model) |
|
|
|
|
if self._instanceShared(id: id, type: type) { |
|
|
|
|
let count = Store.main.referenceCount(type: type, id: id) |
|
|
|
|
let count = self.mainStore.referenceCount(type: type, id: id) |
|
|
|
|
if count == 0 { |
|
|
|
|
try self._store(id: storeId).deleteNoSync(type: type, id: id) |
|
|
|
|
} |
|
|
|
|
@ -797,14 +803,14 @@ public class StoreCenter { |
|
|
|
|
/// Returns whether an instance has been shared with the user |
|
|
|
|
fileprivate func _instanceShared<T: SyncedStorable>(id: String, type: T.Type) -> Bool { |
|
|
|
|
let realId: T.ID = T.buildRealId(id: id) |
|
|
|
|
let instance: T? = Store.main.findById(realId) |
|
|
|
|
let instance: T? = self.mainStore.findById(realId) |
|
|
|
|
return instance?.shared == true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes a data log by data id |
|
|
|
|
fileprivate func _cleanupDataLog(dataId: String) { |
|
|
|
|
let logs = self._dataLogs.filter { $0.dataId == dataId } |
|
|
|
|
self._dataLogs.delete(contentOfs: logs) |
|
|
|
|
let logs = self._deleteLogs.filter { $0.dataId == dataId } |
|
|
|
|
self._deleteLogs.delete(contentOfs: logs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Creates a delete log for an instance |
|
|
|
|
@ -816,7 +822,7 @@ public class StoreCenter { |
|
|
|
|
fileprivate func _addDataLog<T: Storable>(_ instance: T, method: HTTPMethod) { |
|
|
|
|
let dataLog = DataLog( |
|
|
|
|
dataId: instance.stringId, modelName: String(describing: T.self), operation: method) |
|
|
|
|
self._dataLogs.addOrUpdate(instance: dataLog) |
|
|
|
|
self._deleteLogs.addOrUpdate(instance: dataLog) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Miscellanous |
|
|
|
|
@ -863,7 +869,7 @@ public class StoreCenter { |
|
|
|
|
|
|
|
|
|
/// This method triggers the framework to save and send failed api calls |
|
|
|
|
public func logsFailedAPICalls() { |
|
|
|
|
self._failedAPICallsCollection = Store.main.registerSynchronizedCollection(limit: 50) |
|
|
|
|
self._failedAPICallsCollection = self.mainStore.registerSynchronizedCollection(limit: 50) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// If configured for, logs and send to the server a failed API call |
|
|
|
|
@ -963,7 +969,7 @@ public class StoreCenter { |
|
|
|
|
/// Returns the collection hosting an instance |
|
|
|
|
func collectionOfInstance<T: Storable>(_ instance: T) -> BaseCollection<T>? { |
|
|
|
|
do { |
|
|
|
|
let collection: BaseCollection<T> = try Store.main.collection() |
|
|
|
|
let collection: BaseCollection<T> = try self.mainStore.collection() |
|
|
|
|
if collection.findById(instance.id) != nil { |
|
|
|
|
return collection |
|
|
|
|
} else { |
|
|
|
|
@ -1028,7 +1034,7 @@ public class StoreCenter { |
|
|
|
|
if let logs = self._logs { |
|
|
|
|
return logs |
|
|
|
|
} else { |
|
|
|
|
let logsCollection: SyncedCollection<Log> = Store.main.registerSynchronizedCollection(limit: 50) |
|
|
|
|
let logsCollection: SyncedCollection<Log> = self.mainStore.registerSynchronizedCollection(limit: 50) |
|
|
|
|
self._logs = logsCollection |
|
|
|
|
return logsCollection |
|
|
|
|
} |
|
|
|
|
|