Refactoring to pass a reference of StoreCenter in the various classes

sync_v2
Laurent 7 months ago
parent b32b0f2a74
commit c8f204462a
  1. 20
      LeStorage/ApiCallCollection.swift
  2. 10
      LeStorage/BaseCollection.swift
  3. 4
      LeStorage/Codables/GetSyncData.swift
  4. 59
      LeStorage/Services.swift
  5. 20
      LeStorage/Store.swift
  6. 60
      LeStorage/StoreCenter.swift
  7. 26
      LeStorage/SyncedCollection.swift
  8. 2
      LeStorage/SyncedStorable.swift
  9. 9
      LeStorage/WebSocketManager.swift
  10. 10
      LeStorageTests/ApiCallTests.swift
  11. 7
      LeStorageTests/CollectionsTests.swift
  12. 8
      LeStorageTests/IdentifiableTests.swift
  13. 4
      LeStorageTests/StoredCollectionTests.swift

@ -39,6 +39,8 @@ enum ApiCallError: Error, LocalizedError {
/// Failing Api calls are stored forever and will be executed again later
actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate var storeCenter: StoreCenter
/// The list of api calls
fileprivate(set) var items: [ApiCall<T>] = []
@ -60,6 +62,10 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
}
}
}
init(storeCenter: StoreCenter) {
self.storeCenter = storeCenter
}
/// Starts the JSON file decoding synchronously or asynchronously
/// Reschedule Api calls if not empty
@ -185,7 +191,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _waitAndExecuteApiCalls() async {
// Logger.log("\(T.resourceName()) > RESCHED")
guard !self._isExecutingCalls, StoreCenter.main.forceNoSynchronization == false else { return }
guard !self._isExecutingCalls, self.storeCenter.forceNoSynchronization == false else { return }
guard self.items.isNotEmpty else { return }
self._isExecutingCalls = true
@ -235,7 +241,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
let results = try await self._executeApiCalls(batch)
if T.copyServerResponse {
let instances = results.compactMap { $0.data }
StoreCenter.main.updateLocalInstances(instances)
self.storeCenter.updateLocalInstances(instances)
}
}
} catch {
@ -246,10 +252,10 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _executeGetCall(apiCall: ApiCall<T>) async throws {
if T.self == GetSyncData.self {
let _: Empty = try await StoreCenter.main.executeGet(apiCall: apiCall)
let _: Empty = try await self.storeCenter.executeGet(apiCall: apiCall)
} else {
let results: [T] = try await StoreCenter.main.executeGet(apiCall: apiCall)
await StoreCenter.main.itemsRetrieved(results, storeId: apiCall.storeId, clear: apiCall.option != .additive)
let results: [T] = try await self.storeCenter.executeGet(apiCall: apiCall)
await self.storeCenter.itemsRetrieved(results, storeId: apiCall.storeId, clear: apiCall.option != .additive)
}
}
@ -333,7 +339,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
/// Sends a GET request with an URLParameterConvertible [instance]
func sendGetRequest(instance: URLParameterConvertible) async throws {
let parameters = instance.queryParameters()
let parameters = instance.queryParameters(storeCenter: self.storeCenter)
try await self._sendGetRequest(parameters: parameters)
}
@ -394,7 +400,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
/// Executes an API call
/// For POST requests, potentially copies additional data coming from the server during the insert
fileprivate func _executeApiCalls(_ apiCalls: [ApiCall<T>]) async throws -> [OperationResult<T>] {
let results = try await StoreCenter.main.execute(apiCalls: apiCalls)
let results = try await self.storeCenter.execute(apiCalls: apiCalls)
for result in results {
switch result.status {
case 200..<300:

@ -78,10 +78,12 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
}
init() {
self.store = Store.main
init(store: Store) {
self.store = store
}
var storeCenter: StoreCenter { return self.store.storeCenter }
/// Returns the name of the managed resource
public var resourceName: String {
return T.resourceName()
@ -400,7 +402,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder {
try self.store.write(content: jsonString, fileName: T.fileName())
} catch {
Logger.error(error)
StoreCenter.main.log(
self.storeCenter.log(
message: "write failed for \(T.resourceName()): \(error.localizedDescription)")
}
}
@ -440,7 +442,7 @@ public class StoredCollection<T: Storable>: BaseCollection<T>, RandomAccessColle
/// Returns a dummy StoredCollection instance
public static func placeholder() -> StoredCollection<T> {
return StoredCollection<T>()
return StoredCollection<T>(store: Store(storeCenter: StoreCenter.main))
}
// MARK: - RandomAccessCollection

@ -30,9 +30,9 @@ class GetSyncData: SyncedModelObject, SyncedStorable, URLParameterConvertible {
self.date = getSyncData.date
}
func queryParameters() -> [String : String] {
func queryParameters(storeCenter: StoreCenter) -> [String : String] {
return ["last_update" : self._formattedLastUpdate,
"device_id" : StoreCenter.main.deviceId()]
"device_id" : storeCenter.deviceId()]
}
fileprivate var _formattedLastUpdate: String {

@ -41,10 +41,13 @@ let userNamesCall: ServiceCall = ServiceCall(
/// A class used to send HTTP request to the django server
public class Services {
fileprivate let storeCenter: StoreCenter
/// The base API URL to send requests
fileprivate(set) var baseURL: String
public init(url: String) {
public init(storeCenter: StoreCenter, url: String) {
self.storeCenter = storeCenter
self.baseURL = url
}
@ -91,10 +94,10 @@ public class Services {
print("\(debugURL) ended, status code = \(statusCode)")
switch statusCode {
case 200..<300: // success
try await StoreCenter.main.deleteApiCallById(type: T.self, id: apiCall.id)
try await self.storeCenter.deleteApiCallById(type: T.self, id: apiCall.id)
if T.self == GetSyncData.self {
await StoreCenter.main.synchronizeContent(task.0)
await self.storeCenter.synchronizeContent(task.0)
}
default: // error
@ -107,8 +110,8 @@ public class Services {
errorMessage = message
}
try await StoreCenter.main.rescheduleApiCalls(type: T.self)
StoreCenter.main.logFailedAPICall(
try await self.storeCenter.rescheduleApiCalls(type: T.self)
self.storeCenter.logFailedAPICall(
apiCall.id, request: request, collectionName: T.resourceName(),
error: errorMessage.message)
@ -116,7 +119,7 @@ public class Services {
}
} else {
let message: String = "Unexpected and unmanaged URL Response \(task.1)"
StoreCenter.main.log(message: message)
self.storeCenter.log(message: message)
Logger.w(message)
}
@ -160,7 +163,7 @@ public class Services {
}
} else {
let message: String = "Unexpected and unmanaged URL Response \(task.1)"
StoreCenter.main.log(message: message)
self.storeCenter.log(message: message)
Logger.w(message)
}
return try self._decode(data: task.0)
@ -258,7 +261,7 @@ public class Services {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addAppVersion()
if !(requiresToken == false) {
let token = try StoreCenter.main.token()
let token = try self.storeCenter.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
return request
@ -313,9 +316,9 @@ public class Services {
if let message = self.errorMessageFromResponse(data: task.0) {
errorMessage = message
}
try await StoreCenter.main.rescheduleApiCalls(type: T.self)
try await self.storeCenter.rescheduleApiCalls(type: T.self)
// StoreCenter.main.logFailedAPICall(
// self.storeCenter.logFailedAPICall(
// apiCall.id, request: request, collectionName: T.resourceName(),
// error: errorMessage.message)
@ -323,12 +326,12 @@ public class Services {
}
} else {
let message: String = "Unexpected and unmanaged URL Response \(task.1)"
StoreCenter.main.log(message: message)
self.storeCenter.log(message: message)
Logger.w(message)
}
if rescheduleApiCalls {
try? await StoreCenter.main.rescheduleApiCalls(type: T.self)
try? await self.storeCenter.rescheduleApiCalls(type: T.self)
}
return results
@ -353,8 +356,8 @@ public class Services {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addAppVersion()
if self._isTokenRequired(type: T.self, method: apiCall.method), StoreCenter.main.isAuthenticated {
let token = try StoreCenter.main.token()
if self._isTokenRequired(type: T.self, method: apiCall.method), self.storeCenter.isAuthenticated {
let token = try self.storeCenter.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
@ -396,7 +399,7 @@ public class Services {
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let token = try StoreCenter.main.token()
let token = try self.storeCenter.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
request.addAppVersion()
@ -411,7 +414,7 @@ public class Services {
}
let payload = SyncPayload(operations: operations,
deviceId: StoreCenter.main.deviceId())
deviceId: self.storeCenter.deviceId())
request.httpBody = try JSON.encoder.encode(payload)
return request
@ -423,7 +426,7 @@ public class Services {
func synchronizeLastUpdates(since: Date?) async throws {
let request = try self._getSyncLogRequest(since: since)
if let data = try await self._runRequest(request) {
await StoreCenter.main.synchronizeContent(data)
await self.storeCenter.synchronizeContent(data)
}
}
@ -447,7 +450,7 @@ public class Services {
request.httpMethod = HTTPMethod.get.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let token = try StoreCenter.main.token()
let token = try self.storeCenter.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
return request
@ -482,7 +485,7 @@ public class Services {
}
} else {
let message: String = "Unexpected and unmanaged URL Response \(task.1)"
StoreCenter.main.log(message: message)
self.storeCenter.log(message: message)
Logger.w(message)
}
return nil
@ -540,7 +543,7 @@ public class Services {
request.addAppVersion()
if self._isTokenRequired(type: T.self, method: apiCall.method) {
do {
let token = try StoreCenter.main.token()
let token = try self.storeCenter.token()
request.setValue("Token \(token)", forHTTPHeaderField: "Authorization")
} catch {
Logger.log("missing token")
@ -585,14 +588,14 @@ public class Services {
/// - password: the account's password
public func requestToken(username: String, password: String) async throws -> String {
var postRequest = try self._baseRequest(call: requestTokenCall)
let deviceId = StoreCenter.main.deviceId()
let deviceId = self.storeCenter.deviceId()
let deviceModel = await UIDevice.current.deviceModel()
let credentials = Credentials(username: username, password: password, deviceId: deviceId, deviceModel: deviceModel)
postRequest.httpBody = try JSON.encoder.encode(credentials)
let response: AuthResponse = try await self._runRequest(postRequest)
try StoreCenter.main.storeToken(username: username, token: response.token)
try self.storeCenter.storeToken(username: username, token: response.token)
return response.token
}
@ -606,7 +609,7 @@ public class Services {
let postRequest = try self._baseRequest(call: getUserCall)
let loggingDate = Date() // ideally we want the date of the latest retrieved object when loading collection objects
let user: U = try await self._runRequest(postRequest)
StoreCenter.main.userDidLogIn(user: user, at: loggingDate)
self.storeCenter.userDidLogIn(user: user, at: loggingDate)
return user
}
@ -615,7 +618,7 @@ public class Services {
/// - username: the account's username
/// - password: the account's password
public func logout() async throws {
let deviceId: String = StoreCenter.main.deviceId()
let deviceId: String = self.storeCenter.deviceId()
let _: Empty = try await self._runRequest(
serviceCall: logoutCall, payload: Logout(deviceId: deviceId))
}
@ -635,7 +638,7 @@ public class Services {
func getUserDataAccess() async throws {
let request = try self._baseRequest(call: getUserDataAccessCall)
if let data = try await self._runRequest(request) {
await StoreCenter.main.userDataAccessRetrieved(data)
await self.storeCenter.userDataAccessRetrieved(data)
}
}
@ -648,7 +651,7 @@ public class Services {
async throws
{
guard let username = StoreCenter.main.userName else {
guard let username = self.storeCenter.userName else {
throw ServiceError.missingUserName
}
@ -663,7 +666,7 @@ public class Services {
let response: Token = try await self._runRequest(
serviceCall: changePasswordCall, payload: params)
try StoreCenter.main.storeToken(username: username, token: response.token)
try self.storeCenter.storeToken(username: username, token: response.token)
}
/// The method send a request to reset the user's password
@ -681,7 +684,7 @@ public class Services {
/// - username: the account's username
/// - password: the account's password
public func deleteAccount() async throws {
guard let userId = StoreCenter.main.userId else {
guard let userId = self.storeCenter.userId else {
throw StoreError.missingUserId
}
let path = "users/\(userId)/"

@ -43,8 +43,10 @@ public enum StoreError: Error, LocalizedError {
final public class Store {
fileprivate(set) var storeCenter: StoreCenter
/// The Store singleton
public static let main = Store()
// public static let main = Store()
/// The dictionary of registered collections
fileprivate var _collections: [String : any SomeCollection] = [:]
@ -55,16 +57,20 @@ final public class Store {
/// The store identifier, used to name the store directory, and to perform filtering requests to the server
public fileprivate(set) var identifier: String? = nil
public init() {
public init(storeCenter: StoreCenter) {
self.storeCenter = storeCenter
self._createDirectory(directory: Store.storageDirectory)
}
public required init(identifier: String) {
public required init(storeCenter: StoreCenter, identifier: String) {
self.storeCenter = storeCenter
self.identifier = identifier
let directory = "\(Store.storageDirectory)/\(identifier)"
self._createDirectory(directory: directory)
}
public static var main: Store { return StoreCenter.main.mainStore }
/// Creates the store directory
/// - Parameters:
/// - directory: the name of the directory
@ -105,7 +111,7 @@ final public class Store {
let collection = SyncedCollection<T>(store: self, indexed: indexed, inMemory: inMemory, limit: limit)
self._collections[T.resourceName()] = collection
StoreCenter.main.loadApiCallCollection(type: T.self)
self.storeCenter.loadApiCallCollection(type: T.self)
return collection
}
@ -120,7 +126,7 @@ final public class Store {
self._collections[T.resourceName()] = storedObject
if synchronized {
StoreCenter.main.loadApiCallCollection(type: T.self)
self.storeCenter.loadApiCallCollection(type: T.self)
}
return storedObject
@ -298,9 +304,9 @@ final public class Store {
/// Retrieves all the items on the server
public func getItems<T: SyncedStorable>() async throws -> [T] {
if let identifier = self.identifier {
return try await StoreCenter.main.getItems(identifier: identifier)
return try await self.storeCenter.getItems(identifier: identifier)
} else {
return try await StoreCenter.main.getItems()
return try await self.storeCenter.getItems()
}
}

@ -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
}

@ -16,7 +16,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
/// Returns a dummy SyncedCollection instance
public static func placeholder() -> SyncedCollection<T> {
return SyncedCollection<T>()
return SyncedCollection<T>(store: Store(storeCenter: StoreCenter.main))
}
/// Migrates if necessary and asynchronously decodes the json file
@ -50,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, clear: clear)
try await self.storeCenter.sendGetRequest(T.self, storeId: self.storeId, clear: clear)
} catch {
Logger.error(error)
}
@ -211,7 +211,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
if self.deleteItem(instance, shouldBeSynchronized: true) {
deleted.append(instance)
}
StoreCenter.main.createDeleteLog(instance)
self.storeCenter.createDeleteLog(instance)
}
let batch = OperationBatch<T>()
@ -254,7 +254,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
/// Deletes an instance without writing, logs the operation and sends an API call
fileprivate func _deleteNoWrite(instance: T) {
self.deleteItem(instance, shouldBeSynchronized: true)
StoreCenter.main.createDeleteLog(instance)
self.storeCenter.createDeleteLog(instance)
// await self._sendDeletion(instance)
}
@ -324,7 +324,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
fileprivate func _sendOperationBatch(_ batch: OperationBatch<T>) async {
do {
try await StoreCenter.main.sendOperationBatch(batch)
try await self.storeCenter.sendOperationBatch(batch)
} catch {
Logger.error(error)
}
@ -332,7 +332,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
fileprivate func _executeBatchOnce(_ batch: OperationBatch<T>) async {
do {
try await StoreCenter.main.singleBatchExecution(batch)
try await self.storeCenter.singleBatchExecution(batch)
} catch {
Logger.error(error)
}
@ -341,13 +341,13 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
// MARK: Single calls
public func addsIfPostSucceeds(_ instance: T) async throws {
if let result = try await StoreCenter.main.service().post(instance) {
if let result = try await self.storeCenter.service().post(instance) {
self.addOrUpdateNoSync(result)
}
}
public func updateIfPutSucceeds(_ instance: T) async throws {
if let result = try await StoreCenter.main.service().put(instance) {
if let result = try await self.storeCenter.service().put(instance) {
self.addOrUpdateNoSync(result)
}
}
@ -422,11 +422,13 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced
// MARK: - Others
/// Sends a POST request for the instance, and changes the collection to perform a write
public func writeChangeAndInsertOnServer(instance: T) async {
defer {
self.setChanged()
public func writeChangeAndInsertOnServer(instance: T) {
Task {
await self._sendInsertion(instance)
await MainActor.run {
self.setChanged()
}
}
await self._sendInsertion(instance)
}
}

@ -23,7 +23,7 @@ public protocol SyncedStorable: Storable {
}
protocol URLParameterConvertible {
func queryParameters() -> [String : String]
func queryParameters(storeCenter: StoreCenter) -> [String : String]
}
public protocol SideStorable {

@ -10,6 +10,8 @@ import SwiftUI
import Combine
class WebSocketManager: ObservableObject {
fileprivate(set) var storeCenter: StoreCenter
fileprivate var _webSocketTask: URLSessionWebSocketTask?
fileprivate var _timer: Timer?
@ -19,7 +21,8 @@ class WebSocketManager: ObservableObject {
fileprivate var _failure = false
fileprivate var _pingOk = false
init(urlString: String) {
init(storeCenter: StoreCenter, urlString: String) {
self.storeCenter = storeCenter
self._url = urlString
_setupWebSocket()
}
@ -63,13 +66,13 @@ class WebSocketManager: ObservableObject {
switch message {
case .string(let deviceId):
// print("device id = \(StoreCenter.main.deviceId()), origin id: \(deviceId)")
guard StoreCenter.main.deviceId() != deviceId else {
guard self.storeCenter.deviceId() != deviceId else {
break
}
Task {
do {
try await StoreCenter.main.synchronizeLastUpdates()
try await self.storeCenter.synchronizeLastUpdates()
} catch {
Logger.error(error)
}

@ -34,7 +34,7 @@ class Thing: SyncedModelObject, SyncedStorable, URLParameterConvertible {
static func relationships() -> [LeStorage.Relationship] { return [] }
func queryParameters() -> [String : String] {
func queryParameters(storeCenter: StoreCenter) -> [String : String] {
return ["yeah?" : "god!"]
}
@ -43,7 +43,7 @@ class Thing: SyncedModelObject, SyncedStorable, URLParameterConvertible {
struct ApiCallTests {
@Test func testApiCallProvisioning1() async throws {
let collection = ApiCallCollection<Thing>()
let collection = ApiCallCollection<Thing>(storeCenter: StoreCenter.main)
let thing = Thing(name: "yeah")
@ -69,7 +69,7 @@ struct ApiCallTests {
}
@Test func testApiCallProvisioning2() async throws {
let collection = ApiCallCollection<Thing>()
let collection = ApiCallCollection<Thing>(storeCenter: StoreCenter.main)
let thing = Thing(name: "yeah")
@ -94,7 +94,7 @@ struct ApiCallTests {
}
@Test func testApiCallProvisioning3() async throws {
let collection = ApiCallCollection<Thing>()
let collection = ApiCallCollection<Thing>(storeCenter: StoreCenter.main)
let thing = Thing(name: "yeah")
@ -107,7 +107,7 @@ struct ApiCallTests {
}
@Test func testGetProvisioning() async throws {
let collection = ApiCallCollection<Thing>()
let collection = ApiCallCollection<Thing>(storeCenter: StoreCenter.main)
try await collection.sendGetRequest(storeId: "1")
await #expect(collection.items.count == 1)

@ -6,7 +6,7 @@
//
import Testing
import LeStorage
@testable import LeStorage
class Car: ModelObject, Storable {
@ -47,8 +47,9 @@ struct CollectionsTests {
var boats: SyncedCollection<Boat>
init() {
cars = Store.main.registerCollection(inMemory: true)
boats = Store.main.registerSynchronizedCollection(inMemory: true)
let storeCenter = StoreCenter.main
cars = storeCenter.mainStore.registerCollection(inMemory: true)
boats = storeCenter.mainStore.registerSynchronizedCollection(inMemory: true)
}
func ensureCollectionLoaded(_ collection: any SomeCollection) async throws {

@ -6,7 +6,7 @@
//
import Testing
import LeStorage
@testable import LeStorage
class IntObject: ModelObject, Storable {
@ -32,7 +32,6 @@ class IntObject: ModelObject, Storable {
class StringObject: ModelObject, Storable {
static func resourceName() -> String { "string" }
static func tokenExemptedMethods() -> [LeStorage.HTTPMethod] { [] }
static var relationshipNames: [String] = []
var id: String
var name: String
@ -56,8 +55,9 @@ struct IdentifiableTests {
let stringObjects: StoredCollection<StringObject>
init() {
intObjects = Store.main.registerCollection()
stringObjects = Store.main.registerCollection()
let storeCenter = StoreCenter.main
intObjects = storeCenter.mainStore.registerCollection()
stringObjects = storeCenter.mainStore.registerCollection()
}
func ensureCollectionLoaded(_ collection: any SomeCollection) async throws {

@ -6,7 +6,7 @@
//
import Testing
import LeStorage
@testable import LeStorage
struct Error: Swift.Error, CustomStringConvertible {
let description: String
@ -21,7 +21,7 @@ struct StoredCollectionTests {
var collection: StoredCollection<MockStorable>
init() {
collection = Store.main.registerCollection()
collection = StoreCenter.main.mainStore.registerCollection()
}
func ensureCollectionLoaded() async throws {

Loading…
Cancel
Save