Compare commits

..

No commits in common. 'main' and 'sync3' have entirely different histories.
main ... sync3

  1. 11
      LeStorage/Codables/Log.swift
  2. 2
      LeStorage/Storable.swift
  3. 55
      LeStorage/Store.swift
  4. 37
      LeStorage/StoreCenter.swift
  5. 103
      LeStorage/StoredCollection.swift
  6. 91
      LeStorage/SyncedCollection.swift
  7. 16
      LeStorage/WebSocketManager.swift

@ -23,13 +23,13 @@ class Log: SyncedModelObject, SyncedStorable {
} }
var id: String = Store.randomId() var id: String = Store.randomId()
var date: Date = Date() var date: Date = Date()
var user: String? = nil
var message: String = "" var message: String = ""
init(message: String, user: String?) { init(message: String) {
self.message = message self.message = message
self.user = user
super.init() super.init()
} }
@ -38,7 +38,6 @@ class Log: SyncedModelObject, SyncedStorable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id case id
case date case date
case user
case message case message
} }
@ -46,7 +45,6 @@ class Log: SyncedModelObject, SyncedStorable {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id) id = try container.decode(String.self, forKey: .id)
date = try container.decode(Date.self, forKey: .date) date = try container.decode(Date.self, forKey: .date)
user = try container.decodeIfPresent(String.self, forKey: .user)
message = try container.decode(String.self, forKey: .message) message = try container.decode(String.self, forKey: .message)
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -55,17 +53,16 @@ class Log: SyncedModelObject, SyncedStorable {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(date, forKey: .date) try container.encode(date, forKey: .date)
try container.encodeIfPresent(user, forKey: .user)
try container.encode(message, forKey: .message) try container.encode(message, forKey: .message)
try super.encode(to: encoder) try super.encode(to: encoder)
} }
func copy(from other: any Storable) { func copy(from other: any Storable) {
guard let log = other as? Log else { return } guard let log = other as? Log else { return }
self.date = log.date self.date = log.date
self.message = log.message self.message = log.message
} }
public func copyForUpdate(from other: any Storable) { public func copyForUpdate(from other: any Storable) {
fatalError("should not happen") fatalError("should not happen")
} }

@ -37,8 +37,6 @@ public protocol Storable: Codable, Identifiable, NSObjectProtocol {
static func parentRelationships() -> [Relationship] static func parentRelationships() -> [Relationship]
static func childrenRelationships() -> [Relationship] static func childrenRelationships() -> [Relationship]
/// Denotes a data that own its own store
/// Effectively used to trigger directory creation when adding an item to the collection
static func storeParent() -> Bool static func storeParent() -> Bool
} }

@ -142,13 +142,13 @@ final public class Store {
/// - Parameters: /// - Parameters:
/// - indexed: Creates an index to quickly access the data /// - indexed: Creates an index to quickly access the data
/// - inMemory: Indicates if the collection should only live in memory, and not write into a file /// - inMemory: Indicates if the collection should only live in memory, and not write into a file
public func registerSynchronizedCollection<T : SyncedStorable>(indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) -> SyncedCollection<T> { public func registerSynchronizedCollection<T : SyncedStorable>(indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false) -> SyncedCollection<T> {
if let collection: SyncedCollection<T> = try? self.syncedCollection() { if let collection: SyncedCollection<T> = try? self.syncedCollection() {
return collection return collection
} }
let collection = SyncedCollection<T>(store: self, indexed: indexed, inMemory: inMemory, limit: limit, noLoad: noLoad) let collection = SyncedCollection<T>(store: self, indexed: indexed, inMemory: inMemory, limit: limit, synchronousLoading: synchronousLoading, noLoad: noLoad)
self._collections[T.resourceName()] = collection self._collections[T.resourceName()] = collection
self._baseCollections[T.resourceName()] = collection.collection self._baseCollections[T.resourceName()] = collection.collection
@ -225,6 +225,14 @@ final public class Store {
throw StoreError.collectionNotRegistered(type: T.resourceName()) throw StoreError.collectionNotRegistered(type: T.resourceName())
} }
/// Returns a collection by type
// func collection<T: Storable>() throws -> BaseCollection<T> {
// if let collection = self._collections[T.resourceName()] as? BaseCollection<T> {
// return collection
// }
// throw StoreError.collectionNotRegistered(type: T.resourceName())
// }
func registerOrGetSyncedCollection<T: SyncedStorable>(_ type: T.Type) -> SyncedCollection<T> { func registerOrGetSyncedCollection<T: SyncedStorable>(_ type: T.Type) -> SyncedCollection<T> {
do { do {
return try self.syncedCollection() return try self.syncedCollection()
@ -274,7 +282,7 @@ final public class Store {
// MARK: - Synchronization // MARK: - Synchronization
fileprivate func _requestWrite<T: SyncedStorable>(type: T.Type) { fileprivate func _requestWrite<T: SyncedStorable>(type: T.Type) {
self._baseCollections[T.resourceName()]?.requestWriteIfNecessary() self._baseCollections[T.resourceName()]?.requestWrite()
} }
@MainActor @MainActor
@ -323,6 +331,16 @@ final public class Store {
} }
self._requestWrite(type: T.self) self._requestWrite(type: T.self)
// for identifier in identifiers {
// do {
// try self.deleteNoSyncNoCascade(type: type, id: identifier.modelId)
// } catch {
// Logger.error(error)
// }
// self.storeCenter.cleanupDataLog(dataId: identifier.modelId)
// }
// self._requestWrite(type: T.self)
} }
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type) -> T? { fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type) -> T? {
@ -330,12 +348,31 @@ final public class Store {
return self.findById(realId) return self.findById(realId)
} }
/// Calls deleteById from the collection corresponding to the instance
// func deleteNoSync<T: Storable>(instance: T) {
// do {
// let collection: BaseCollection<T> = try self.collection()
// collection.delete(instance: instance)
// } catch {
// Logger.error(error)
// }
// }
/// Calls deleteById from the collection corresponding to the instance /// Calls deleteById from the collection corresponding to the instance
func deleteNoSyncNoCascadeNoWrite<T: SyncedStorable>(type: T.Type, id: String) throws { func deleteNoSyncNoCascadeNoWrite<T: SyncedStorable>(type: T.Type, id: String) throws {
let collection: SyncedCollection<T> = try self.syncedCollection() let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteByStringId(id, actionOption: .noCascadeNoWrite) collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
} }
/// Calls deleteById from the collection corresponding to the instance
// func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int {
// var count: Int = 0
// for collection in self._collections.values {
// count += collection.referenceCount(type: type, id: id)
// }
// return count
// }
func isReferenced<T: Storable, S: Storable>(collectionType: S.Type, type: T.Type, id: String) -> Bool { func isReferenced<T: Storable, S: Storable>(collectionType: S.Type, type: T.Type, id: String) -> Bool {
if let collection = self._baseCollections[S.resourceName()] { if let collection = self._baseCollections[S.resourceName()] {
return collection.hasParentReferences(type: type, id: id) return collection.hasParentReferences(type: type, id: id)
@ -425,6 +462,18 @@ final public class Store {
} }
} }
// public func deleteDependencies<T: Storable>(type: T.Type, actionOption: ActionOption, _ isIncluded: (T) -> Bool) {
//
// do {
// let collection: any SomeCollection = try self.someCollection(type: type)
// if let syncCollection = collection as? SyncedCollection<T> {
// collection.deleteDependencies(actionOption: actionOption, isIncluded)
// } catch {
// Logger.error(error)
// }
//
// }
// MARK: - Write // MARK: - Write
/// Returns the directory URL of the store /// Returns the directory URL of the store

@ -44,7 +44,7 @@ public class StoreCenter {
fileprivate var _apiCallCollections: [String: any SomeCallCollection] = [:] fileprivate var _apiCallCollections: [String: any SomeCallCollection] = [:]
/// A collection of DataLog objects, used for the synchronization /// A collection of DataLog objects, used for the synchronization
fileprivate var _deleteLogs: StoredCollection<DataLog>? = nil lazy fileprivate var _deleteLogs: StoredCollection<DataLog> = { self.mainStore.registerCollection() }()
/// A synchronized collection of DataAccess /// A synchronized collection of DataAccess
fileprivate(set) var dataAccessCollection: SyncedCollection<DataAccess>? = nil fileprivate(set) var dataAccessCollection: SyncedCollection<DataAccess>? = nil
@ -96,7 +96,6 @@ public class StoreCenter {
self.useWebsockets = webSockets self.useWebsockets = webSockets
self.useSynchronization = useSynchronization self.useSynchronization = useSynchronization
self._deleteLogs = self.mainStore.registerCollection()
let urlManager: URLManager = URLManager(secureScheme: secureScheme, domain: domain) let urlManager: URLManager = URLManager(secureScheme: secureScheme, domain: domain)
self._urlManager = urlManager self._urlManager = urlManager
@ -146,12 +145,6 @@ public class StoreCenter {
public var websocketFailure: Bool { public var websocketFailure: Bool {
return self._webSocketManager?.failure ?? true return self._webSocketManager?.failure ?? true
} }
public var websocketError: Error? {
return self._webSocketManager?.error
}
public var websocketReconnectAttempts: Int {
return self._webSocketManager?.reconnectAttempts ?? 0
}
public var apiURL: String? { public var apiURL: String? {
return self._urlManager?.api return self._urlManager?.api
@ -266,7 +259,7 @@ public class StoreCenter {
self._storeLibrary.reset() self._storeLibrary.reset()
self.dataAccessCollection?.reset() self.dataAccessCollection?.reset()
self._deleteLogs?.reset() self._deleteLogs.reset()
self._settingsStorage.update { settings in self._settingsStorage.update { settings in
settings.username = nil settings.username = nil
@ -815,10 +808,7 @@ public class StoreCenter {
/// Returns whether a data has already been deleted by, to avoid inserting it again /// Returns whether a data has already been deleted by, to avoid inserting it again
func hasAlreadyBeenDeleted<T: Storable>(_ instance: T) -> Bool { func hasAlreadyBeenDeleted<T: Storable>(_ instance: T) -> Bool {
guard let deleteLogs = self._deleteLogs else { return self._deleteLogs.contains(where: {
fatalError("missing delete logs collection")
}
return deleteLogs.contains(where: {
$0.dataId == instance.stringId && $0.operation == .delete $0.dataId == instance.stringId && $0.operation == .delete
}) })
} }
@ -871,11 +861,8 @@ public class StoreCenter {
/// Deletes a data log by data id /// Deletes a data log by data id
func cleanupDataLog(dataId: String) { func cleanupDataLog(dataId: String) {
guard let deleteLogs = self._deleteLogs else { let logs = self._deleteLogs.filter { $0.dataId == dataId }
return self._deleteLogs.delete(contentOfs: logs)
}
let logs = deleteLogs.filter { $0.dataId == dataId }
deleteLogs.delete(contentOfs: logs)
} }
/// Creates a delete log for an instance /// Creates a delete log for an instance
@ -883,7 +870,7 @@ public class StoreCenter {
let dataLog = DataLog(dataId: instance.stringId, let dataLog = DataLog(dataId: instance.stringId,
modelName: String(describing: T.self), modelName: String(describing: T.self),
operation: .delete) operation: .delete)
self._deleteLogs?.addOrUpdate(instance: dataLog) self._deleteLogs.addOrUpdate(instance: dataLog)
} }
/// Returns the appropriate store for a relationship /// Returns the appropriate store for a relationship
@ -1197,11 +1184,21 @@ public class StoreCenter {
/// Logs a message in the logs collection /// Logs a message in the logs collection
public func log(message: String) { public func log(message: String) {
DispatchQueue.main.async { DispatchQueue.main.async {
let log = Log(message: message, user: self.userId) let log = Log(message: message)
self._logsCollection().addOrUpdate(instance: log) self._logsCollection().addOrUpdate(instance: log)
} }
} }
// MARK: - Migration
/// Migrates the token from the provided service to the main Services instance
// public func migrateToken(_ services: Services) throws {
// guard let userName = self.userName() else {
// return
// }
// try self.service().migrateToken(services, userName: userName)
// }
deinit { deinit {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }

@ -25,7 +25,7 @@ public protocol SomeCollection<Item>: Identifiable {
func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool) func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool)
func findById(_ id: Item.ID) -> Item? func findById(_ id: Item.ID) -> Item?
func requestWriteIfNecessary() func requestWrite()
} }
protocol CollectionDelegate<Item> { protocol CollectionDelegate<Item> {
@ -81,16 +81,15 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Indicates whether the collection has changed, thus requiring a write operation /// Indicates whether the collection has changed, thus requiring a write operation
fileprivate var _triggerWrite: Bool = false { fileprivate var _triggerWrite: Bool = false {
didSet { didSet {
if self._triggerWrite == true { if self._triggerWrite == true && self.inMemory == false {
self._scheduleWrite() self._scheduleWrite()
DispatchQueue.main.async {
NotificationCenter.default.post(
name: NSNotification.Name.CollectionDidChange, object: self)
}
self._triggerWrite = false self._triggerWrite = false
} }
DispatchQueue.main.async {
NotificationCenter.default.post(
name: NSNotification.Name.CollectionDidChange, object: self)
}
} }
} }
@ -100,6 +99,8 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Sets a max number of items inside the collection /// Sets a max number of items inside the collection
fileprivate(set) var limit: Int? = nil fileprivate(set) var limit: Int? = nil
fileprivate var _delegate: (any CollectionDelegate<T>)? = nil
init(store: Store, inMemory: Bool = false) async { init(store: Store, inMemory: Bool = false) async {
self.store = store self.store = store
if self.inMemory == false { if self.inMemory == false {
@ -107,19 +108,24 @@ public class StoredCollection<T: Storable>: SomeCollection {
} }
} }
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) { init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false, delegate: (any CollectionDelegate<T>)? = nil) {
if indexed { if indexed {
self._indexes = [:] self._indexes = [:]
} }
self.inMemory = inMemory self.inMemory = inMemory
self.store = store self.store = store
self.limit = limit self.limit = limit
self._delegate = delegate
if noLoad { if noLoad {
self.hasLoaded = true self.hasLoaded = true
} else { } else {
Task { Task {
await self.load() if synchronousLoading {
await self.loadFromFile()
} else {
await self.load()
}
} }
} }
@ -143,10 +149,8 @@ public class StoredCollection<T: Storable>: SomeCollection {
// MARK: - Loading // MARK: - Loading
/// Sets the collection as changed to trigger a write /// Sets the collection as changed to trigger a write
public func requestWriteIfNecessary() { public func requestWrite() {
if self.inMemory == false { self._triggerWrite = true
self._triggerWrite = true
}
} }
/// Migrates if necessary and asynchronously decodes the json file /// Migrates if necessary and asynchronously decodes the json file
@ -154,31 +158,19 @@ public class StoredCollection<T: Storable>: SomeCollection {
if !self.inMemory { if !self.inMemory {
await self.loadFromFile() await self.loadFromFile()
} else { } else {
await self._delegate?.loadingForMemoryCollection()
await MainActor.run { await MainActor.run {
self.setAsLoaded() self.setAsLoaded()
} }
} }
} }
/// Starts the JSON file decoding asynchronously /// Starts the JSON file decoding synchronously or asynchronously
func loadFromFile() async { func loadFromFile() async {
do { do {
try await self._decodeJSONFile() try await self._decodeJSONFile()
} catch { } catch {
Logger.error(error) Logger.error(error)
await MainActor.run {
self.setAsLoaded()
}
do {
let fileURL = try self.store.fileURL(type: T.self)
let jsonString: String = try FileUtils.readFile(fileURL: fileURL)
if !jsonString.isEmpty {
StoreCenter.main.log(message: "Could not decode: \(jsonString)")
}
} catch {
}
} }
} }
@ -212,10 +204,11 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Sets a collection of items and indexes them /// Sets a collection of items and indexes them
func setItems(_ items: [T]) { func setItems(_ items: [T]) {
self.clear() self.items.removeAll()
for item in items { for item in items {
self._addItem(instance: item) self._addItem(instance: item)
} }
self.requestWrite()
} }
@MainActor @MainActor
@ -227,7 +220,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.setAsLoaded() self.setAsLoaded()
self.addOrUpdate(contentOfs: items) self.addOrUpdate(contentOfs: items)
} }
self.requestWriteIfNecessary()
} }
/// Updates the whole index with the items array /// Updates the whole index with the items array
@ -243,7 +236,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Adds it if its id is not found, and otherwise updates it /// Adds it if its id is not found, and otherwise updates it
@discardableResult public func addOrUpdate(instance: T) -> ActionResult<T> { @discardableResult public func addOrUpdate(instance: T) -> ActionResult<T> {
defer { defer {
self.requestWriteIfNecessary() self.requestWrite()
} }
return self._rawAddOrUpdate(instance: instance) return self._rawAddOrUpdate(instance: instance)
} }
@ -252,7 +245,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
public func addOrUpdate(contentOfs sequence: any Sequence<T>, _ handler: ((ActionResult<T>) -> ())? = nil) { public func addOrUpdate(contentOfs sequence: any Sequence<T>, _ handler: ((ActionResult<T>) -> ())? = nil) {
defer { defer {
self.requestWriteIfNecessary() self.requestWrite()
} }
for instance in sequence { for instance in sequence {
@ -275,9 +268,9 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// A method the treat the collection as a single instance holder /// A method the treat the collection as a single instance holder
func setSingletonNoSync(instance: T) { func setSingletonNoSync(instance: T) {
defer { defer {
self.requestWriteIfNecessary() self.requestWrite()
} }
self.clear() self.items.removeAll()
self._addItem(instance: instance) self._addItem(instance: instance)
} }
@ -333,7 +326,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.addPendingOperation(method: .add, instance: instance, actionOption: actionOption) self.addPendingOperation(method: .add, instance: instance, actionOption: actionOption)
return false return false
} }
self.invalidateCache()
self._affectStoreIdIfNecessary(instance: instance) self._affectStoreIdIfNecessary(instance: instance)
self.items.append(instance) self.items.append(instance)
@ -360,7 +352,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.addPendingOperation(method: .update, instance: instance, actionOption: actionOption) self.addPendingOperation(method: .update, instance: instance, actionOption: actionOption)
return false return false
} }
self.invalidateCache()
let item = self.items[index] let item = self.items[index]
if item !== instance { if item !== instance {
@ -410,7 +401,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
} }
func localDeleteOnly(instance: T) { func localDeleteOnly(instance: T) {
self.invalidateCache()
self.items.removeAll { $0.id == instance.id } self.items.removeAll { $0.id == instance.id }
self._indexes?.removeValue(forKey: instance.id) self._indexes?.removeValue(forKey: instance.id)
} }
@ -428,7 +418,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.deleteItem(instance, actionOption: actionOption) self.deleteItem(instance, actionOption: actionOption)
} }
if actionOption.write { if actionOption.write {
self.requestWriteIfNecessary() self.requestWrite()
} }
} }
@ -440,12 +430,12 @@ public class StoredCollection<T: Storable>: SomeCollection {
return self.items.first(where: { $0.id == id }) return self.items.first(where: { $0.id == id })
} }
/// Deletes a list of items /// Proceeds to "hard" delete the items without synchronizing them
/// Also removes related API calls
public func deleteDependencies(_ items: any Sequence<T>) { public func deleteDependencies(_ items: any Sequence<T>) {
defer { defer {
self.requestWriteIfNecessary() self.requestWrite()
} }
self.invalidateCache()
let itemsArray = Array(items) // fix error if items is self.items let itemsArray = Array(items) // fix error if items is self.items
for item in itemsArray { for item in itemsArray {
if let index = self.items.firstIndex(where: { $0.id == item.id }) { if let index = self.items.firstIndex(where: { $0.id == item.id }) {
@ -499,6 +489,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.deleteUnusedShared(data, actionOption: item.actionOption) self.deleteUnusedShared(data, actionOption: item.actionOption)
} }
self._delegate?.itemMerged(item)
} }
manager.reset() manager.reset()
@ -537,13 +528,12 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Simply clears the items of the collection /// Simply clears the items of the collection
public func clear() { public func clear() {
self.invalidateCache()
self.items.removeAll() self.items.removeAll()
} }
/// Removes the items of the collection and deletes the corresponding file /// Removes the items of the collection and deletes the corresponding file
public func reset() { public func reset() {
self.clear() self.items.removeAll()
self.store.removeFile(type: T.self) self.store.removeFile(type: T.self)
} }
@ -572,35 +562,10 @@ public class StoredCollection<T: Storable>: SomeCollection {
func updateLocalInstance(_ serverInstance: T) { func updateLocalInstance(_ serverInstance: T) {
if let localInstance = self.findById(serverInstance.id) { if let localInstance = self.findById(serverInstance.id) {
localInstance.copy(from: serverInstance) localInstance.copy(from: serverInstance)
self.requestWriteIfNecessary() self.requestWrite()
} }
} }
// MARK: - Cached queries
fileprivate var _cacheVersion = 0
fileprivate var _queryCache: [AnyHashable: (version: Int, result: Any)] = [:]
// Generic query method with caching
public func cached<Result>(
key: AnyHashable,
compute: ([T]) -> Result
) -> Result {
if let cached = self._queryCache[key],
cached.version == self._cacheVersion,
let result = cached.result as? Result {
return result
}
let result = compute(items)
self._queryCache[key] = (self._cacheVersion, result)
return result
}
private func invalidateCache() {
self._cacheVersion += 1
}
} }
extension StoredCollection: RandomAccessCollection { extension StoredCollection: RandomAccessCollection {

@ -19,10 +19,10 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
let store: Store let store: Store
let collection: StoredCollection<T> let collection: StoredCollection<T>
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) { init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false) {
self.store = store self.store = store
self.collection = StoredCollection<T>(store: store, indexed: indexed, inMemory: inMemory, limit: limit, noLoad: noLoad) self.collection = StoredCollection<T>(store: store, indexed: indexed, inMemory: inMemory, limit: limit, synchronousLoading: synchronousLoading, noLoad: noLoad)
} }
@ -42,6 +42,19 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return SyncedCollection<T>(store: Store(storeCenter: StoreCenter.main)) return SyncedCollection<T>(store: Store(storeCenter: StoreCenter.main))
} }
/// Migrates if necessary and asynchronously decodes the json file
// override func load() async {
// do {
// if self.inMemory {
// try await self.loadDataFromServerIfAllowed()
// } else {
// await self.loadFromFile()
// }
// } catch {
// Logger.error(error)
// }
// }
/// Loads the collection using the server data only if the collection file doesn't exists /// Loads the collection using the server data only if the collection file doesn't exists
func loadCollectionsFromServerIfNoFile() async throws { func loadCollectionsFromServerIfNoFile() async throws {
let fileURL: URL = try self.store.fileURL(type: T.self) let fileURL: URL = try self.store.fileURL(type: T.self)
@ -177,6 +190,41 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return batch return batch
} }
/// Deletes an instance without writing, logs the operation and sends an API call
// fileprivate func _deleteNoWrite(instance: T) {
// self.deleteItem(instance, shouldBeSynchronized: true)
// self.storeCenter.createDeleteLog(instance)
//// await self._sendDeletion(instance)
// }
// public func deleteDependencies(_ items: any RandomAccessCollection<T>, actionOption: ActionOption) {
// guard items.isNotEmpty else { return }
// if actionOption.synchronize {
// self.delete(contentOfs: items)
// } else {
// self.deleteNoSync(contentOfs: items)
// }
// }
// public func deleteDependencies(_ items: any Sequence<T>) {
//
// self.collection.deleteDependencies(items)
//
//// super.deleteDependencies(items)
//
// let batch = OperationBatch<T>()
// batch.deletes = Array(items)
// Task { await self._sendOperationBatch(batch) }
// }
// public func deleteDependenciesAsync(_ items: any Sequence<T>) async {
// super.deleteDependencies(items)
//
// let batch = OperationBatch<T>()
// batch.deletes = Array(items)
// await self._sendOperationBatch(batch)
// }
fileprivate func _cleanUpSharedDependencies() { fileprivate func _cleanUpSharedDependencies() {
for relationship in T.relationships() { for relationship in T.relationships() {
if let syncedType = relationship.type as? (any SyncedStorable.Type) { if let syncedType = relationship.type as? (any SyncedStorable.Type) {
@ -198,6 +246,11 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
case .child: case .child:
throw StoreError.invalidStoreLookup(from: type, to: relationship.type) throw StoreError.invalidStoreLookup(from: type, to: relationship.type)
} }
// if relationship.storeLookup {
// store = self.store.storeCenter.mainStore
// } else {
// store = self.store
// }
let collection: SyncedCollection<S> = try store.syncedCollection() let collection: SyncedCollection<S> = try store.syncedCollection()
collection._deleteUnusedGrantedInstances(originStoreId: originStoreId) collection._deleteUnusedGrantedInstances(originStoreId: originStoreId)
@ -273,6 +326,11 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
self.collection.addOrUpdate(contentOfs: sequence) self.collection.addOrUpdate(contentOfs: sequence)
} }
/// Deletes the instance in the collection without synchronization
// func deleteNoSync(instance: T) {
// self.collection.delete(instance: instance)
// }
public func deleteNoSync(contentOfs sequence: any RandomAccessCollection<T>) { public func deleteNoSync(contentOfs sequence: any RandomAccessCollection<T>) {
self.collection.delete(contentOfs: sequence) self.collection.delete(contentOfs: sequence)
} }
@ -288,6 +346,16 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
instance.deleteUnusedSharedDependencies(store: self.store) instance.deleteUnusedSharedDependencies(store: self.store)
} }
/// Deletes the instance in the collection without synchronization
// func deleteNoSyncNoCascade(id: String) {
// self.collection.deleteByStringId(id, actionOption: .standard)
// }
//
// /// Deletes the instance in the collection without synchronization
// func deleteNoSyncNoCascadeNoWrite(id: String) {
// self.collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
// }
func deleteByStringId(_ id: String, actionOption: ActionOption = .standard) { func deleteByStringId(_ id: String, actionOption: ActionOption = .standard) {
self.collection.deleteByStringId(id, actionOption: actionOption) self.collection.deleteByStringId(id, actionOption: actionOption)
} }
@ -364,6 +432,10 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
/// Adds or update an instance if it is newer than the local instance /// Adds or update an instance if it is newer than the local instance
func addOrUpdateIfNewer(_ instance: T, shared: SharingStatus?) { func addOrUpdateIfNewer(_ instance: T, shared: SharingStatus?) {
// defer {
// self.triggerWrite()
// }
if let index = self.collection.items.firstIndex(where: { $0.id == instance.id }) { if let index = self.collection.items.firstIndex(where: { $0.id == instance.id }) {
let localInstance = self.collection.items[index] let localInstance = self.collection.items[index]
if instance.lastUpdate > localInstance.lastUpdate { if instance.lastUpdate > localInstance.lastUpdate {
@ -413,17 +485,8 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return self.collection.items return self.collection.items
} }
public func requestWriteIfNecessary() { public func requestWrite() {
self.collection.requestWriteIfNecessary() self.collection.requestWrite()
}
// MARK: - Cached queries
public func cached<Result>(
key: AnyHashable,
compute: ([T]) -> Result
) -> Result {
return self.collection.cached(key: key, compute: compute)
} }
} }
@ -473,6 +536,8 @@ extension SyncedCollection: RandomAccessCollection {
} }
set(newValue) { set(newValue) {
self.collection.update(newValue, index: index, actionOption: .standard) self.collection.update(newValue, index: index, actionOption: .standard)
// self.collection.items[index] = newValue
// self._triggerWrite = true
} }
} }
} }

@ -19,7 +19,6 @@ class WebSocketManager: ObservableObject {
fileprivate var _reconnectAttempts = 0 fileprivate var _reconnectAttempts = 0
fileprivate var _failure = false fileprivate var _failure = false
fileprivate var _error: Error? = nil
fileprivate var _pingOk = false fileprivate var _pingOk = false
init(storeCenter: StoreCenter, urlString: String) { init(storeCenter: StoreCenter, urlString: String) {
@ -60,13 +59,10 @@ class WebSocketManager: ObservableObject {
switch result { switch result {
case .failure(let error): case .failure(let error):
self._failure = true self._failure = true
self._error = error
print("Error in receiving message: \(error)") print("Error in receiving message: \(error)")
self._handleWebSocketError(error) self._handleWebSocketError(error)
case .success(let message): case .success(let message):
self._failure = false self._failure = false
self._error = nil
self._reconnectAttempts = 0
switch message { switch message {
case .string(let deviceId): case .string(let deviceId):
// print("device id = \(StoreCenter.main.deviceId()), origin id: \(deviceId)") // print("device id = \(StoreCenter.main.deviceId()), origin id: \(deviceId)")
@ -94,7 +90,7 @@ class WebSocketManager: ObservableObject {
private func _handleWebSocketError(_ error: Error) { private func _handleWebSocketError(_ error: Error) {
// print("WebSocket error: \(error)") // print("WebSocket error: \(error)")
// up to 10 seconds of reconnection // Exponential backoff for reconnection
let delay = min(Double(self._reconnectAttempts), 10.0) let delay = min(Double(self._reconnectAttempts), 10.0)
self._reconnectAttempts += 1 self._reconnectAttempts += 1
@ -135,17 +131,7 @@ class WebSocketManager: ObservableObject {
var pingStatus: Bool { var pingStatus: Bool {
return self._pingOk return self._pingOk
} }
var failure: Bool { var failure: Bool {
return self._failure return self._failure
} }
var error: Error? {
return self._error
}
var reconnectAttempts: Int {
return self._reconnectAttempts
}
} }

Loading…
Cancel
Save