|
|
|
|
@ -12,7 +12,30 @@ protocol SomeSyncedCollection: SomeCollection { |
|
|
|
|
func loadCollectionsFromServerIfNoFile() async throws |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSyncedCollection { |
|
|
|
|
public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, CollectionDelegate { |
|
|
|
|
|
|
|
|
|
public typealias Item = T |
|
|
|
|
|
|
|
|
|
let store: Store |
|
|
|
|
let collection: StoredCollection<T> |
|
|
|
|
|
|
|
|
|
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false) { |
|
|
|
|
|
|
|
|
|
self.store = store |
|
|
|
|
self.collection = StoredCollection<T>(store: store, indexed: indexed, limit: limit, synchronousLoading: synchronousLoading) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
init(store: Store, inMemory: Bool) async { |
|
|
|
|
self.store = store |
|
|
|
|
self.collection = await StoredCollection(store: store, inMemory: inMemory) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var storeCenter: StoreCenter { return self.store.storeCenter } |
|
|
|
|
|
|
|
|
|
public var storeId: String? { |
|
|
|
|
return self.store.identifier |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a dummy SyncedCollection instance |
|
|
|
|
public static func placeholder() -> SyncedCollection<T> { |
|
|
|
|
@ -20,17 +43,17 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 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 |
|
|
|
|
func loadCollectionsFromServerIfNoFile() async throws { |
|
|
|
|
@ -64,90 +87,56 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
|
await _updateLocalInstance(serverInstance) |
|
|
|
|
await self.collection.updateLocalInstance(serverInstance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DispatchQueue.main.async { |
|
|
|
|
// if let localInstance = self.findById(serverInstance.id) { |
|
|
|
|
// localInstance.copy(from: serverInstance) |
|
|
|
|
// self.setChanged() |
|
|
|
|
// } |
|
|
|
|
// } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@MainActor |
|
|
|
|
fileprivate func _updateLocalInstance(_ serverInstance: T) { |
|
|
|
|
if let localInstance = self.findById(serverInstance.id) { |
|
|
|
|
localInstance.copy(from: serverInstance) |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@MainActor |
|
|
|
|
func loadItems(_ items: [T], clear: Bool = false) { |
|
|
|
|
if clear { |
|
|
|
|
self.setItems(items) |
|
|
|
|
self.setAsLoaded() |
|
|
|
|
} else { |
|
|
|
|
self.setAsLoaded() |
|
|
|
|
self.addOrUpdateNoSync(contentOfs: items) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.triggerWrite() |
|
|
|
|
self.collection.loadAndWrite(items, clear: clear) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Basic operations with sync |
|
|
|
|
|
|
|
|
|
/// Adds or update an instance synchronously, dispatching network operations to background tasks |
|
|
|
|
public override func addOrUpdate(instance: T) { |
|
|
|
|
if let result = _addOrUpdateCore(instance: instance) { |
|
|
|
|
if result.isNewItem { |
|
|
|
|
Task { await self._sendInsertion(result.item) } |
|
|
|
|
} else { |
|
|
|
|
Task { await self._sendUpdate(result.item) } |
|
|
|
|
} |
|
|
|
|
public func addOrUpdate(instance: T) { |
|
|
|
|
let result = _addOrUpdateCore(instance: instance) |
|
|
|
|
if result.method == .insert { |
|
|
|
|
Task { await self._sendInsertion(instance) } |
|
|
|
|
} else { |
|
|
|
|
Task { await self._sendUpdate(instance) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Private helper function that contains the shared logic |
|
|
|
|
private func _addOrUpdateCore(instance: T) -> (item: T, isNewItem: Bool)? { |
|
|
|
|
private func _addOrUpdateCore(instance: T) -> ActionResult<T> { |
|
|
|
|
instance.lastUpdate = Date() |
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
if self.updateItem(instance, index: index, shouldBeSynchronized: true) { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
if instance.shared == true { |
|
|
|
|
self._cleanUpSharedDependencies() |
|
|
|
|
} |
|
|
|
|
return (instance, false) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if self.addItem(instance: instance, shouldBeSynchronized: true) { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
return (instance, true) |
|
|
|
|
|
|
|
|
|
let result = self.collection.addOrUpdate(instance: instance) |
|
|
|
|
if result.method == .update { |
|
|
|
|
if instance.shared == true { |
|
|
|
|
self._cleanUpSharedDependencies() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
|
|
|
|
|
return result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _addOrUpdateCore(contentOfs sequence: any Sequence<T>) -> OperationBatch<T> { |
|
|
|
|
|
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let date = Date() |
|
|
|
|
let batch = OperationBatch<T>() |
|
|
|
|
|
|
|
|
|
for instance in sequence { |
|
|
|
|
|
|
|
|
|
instance.lastUpdate = date |
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
if self.updateItem(instance, index: index, shouldBeSynchronized: true) { |
|
|
|
|
batch.addUpdate(instance) |
|
|
|
|
} |
|
|
|
|
} else { // insert |
|
|
|
|
if self.addItem(instance: instance, shouldBeSynchronized: true) { |
|
|
|
|
batch.addInsert(instance) |
|
|
|
|
} |
|
|
|
|
let result = self.collection.addOrUpdate(instance: instance) |
|
|
|
|
|
|
|
|
|
if result.method == .insert { |
|
|
|
|
batch.addInsert(instance) |
|
|
|
|
} else { |
|
|
|
|
batch.addUpdate(instance) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -158,25 +147,42 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Adds or update a sequence and writes |
|
|
|
|
override public func addOrUpdate(contentOfs sequence: any Sequence<T>) { |
|
|
|
|
public func addOrUpdate(contentOfs sequence: any Sequence<T>) { |
|
|
|
|
let batch = self._addOrUpdateCore(contentOfs: sequence) |
|
|
|
|
Task { await self._sendOperationBatch(batch) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance and writes |
|
|
|
|
public func delete(instance: T) { |
|
|
|
|
|
|
|
|
|
self.collection.delete(instance: instance, actionOption: .syncedCascade) |
|
|
|
|
self.storeCenter.createDeleteLog(instance) |
|
|
|
|
Task { await self._sendDeletion(instance) } |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes all items of the sequence by id and sets the collection as changed to trigger a write |
|
|
|
|
fileprivate func _deleteCore(contentOfs sequence: any RandomAccessCollection<T>) -> OperationBatch<T> { |
|
|
|
|
public func delete(contentOfs sequence: any RandomAccessCollection<T>) { |
|
|
|
|
self.delete(contentOfs: sequence, actionOption: .syncedCascade) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
func delete(contentOfs sequence: any RandomAccessCollection<T>, actionOption: ActionOption) { |
|
|
|
|
guard sequence.isNotEmpty else { return } |
|
|
|
|
let batch = self._deleteCore(contentOfs: sequence, actionOption: actionOption) |
|
|
|
|
if actionOption.synchronize { |
|
|
|
|
Task { await self._sendOperationBatch(batch) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var deleted: [T] = [] |
|
|
|
|
/// Deletes all items of the sequence by id and sets the collection as changed to trigger a write |
|
|
|
|
fileprivate func _deleteCore(contentOfs sequence: any RandomAccessCollection<T>, actionOption: ActionOption) -> OperationBatch<T> { |
|
|
|
|
|
|
|
|
|
for instance in sequence { |
|
|
|
|
if self.deleteItem(instance, shouldBeSynchronized: true) { |
|
|
|
|
deleted.append(instance) |
|
|
|
|
var deleted: [T] = [] |
|
|
|
|
self.collection.delete(contentOfs: sequence, actionOption: actionOption) { result in |
|
|
|
|
self.storeCenter.createDeleteLog(result.instance) |
|
|
|
|
if !result.pending { |
|
|
|
|
deleted.append(result.instance) |
|
|
|
|
} |
|
|
|
|
self.storeCenter.createDeleteLog(instance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let batch = OperationBatch<T>() |
|
|
|
|
@ -184,24 +190,6 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
return batch |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes all items of the sequence by id and sets the collection as changed to trigger a write |
|
|
|
|
public override func delete(contentOfs sequence: any RandomAccessCollection<T>) { |
|
|
|
|
guard sequence.isNotEmpty else { return } |
|
|
|
|
let batch = self._deleteCore(contentOfs: sequence) |
|
|
|
|
Task { await self._sendOperationBatch(batch) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance and writes |
|
|
|
|
override public func delete(instance: T) { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
self.deleteItem(instance, shouldBeSynchronized: true) |
|
|
|
|
self.storeCenter.createDeleteLog(instance) |
|
|
|
|
|
|
|
|
|
Task { await self._sendDeletion(instance) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance without writing, logs the operation and sends an API call |
|
|
|
|
// fileprivate func _deleteNoWrite(instance: T) { |
|
|
|
|
// self.deleteItem(instance, shouldBeSynchronized: true) |
|
|
|
|
@ -209,30 +197,33 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
//// await self._sendDeletion(instance) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
public func deleteDependencies(_ items: any RandomAccessCollection<T>, shouldBeSynchronized: Bool) { |
|
|
|
|
guard items.isNotEmpty else { return } |
|
|
|
|
if shouldBeSynchronized { |
|
|
|
|
self.delete(contentOfs: items) |
|
|
|
|
} else { |
|
|
|
|
self.deleteNoSync(contentOfs: items) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override func deleteDependencies(_ items: any Sequence<T>) { |
|
|
|
|
super.deleteDependencies(items) |
|
|
|
|
|
|
|
|
|
let batch = OperationBatch<T>() |
|
|
|
|
batch.deletes = Array(items) |
|
|
|
|
Task { await self._sendOperationBatch(batch) } |
|
|
|
|
} |
|
|
|
|
// 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 deleteDependenciesAsync(_ items: any Sequence<T>) async { |
|
|
|
|
super.deleteDependencies(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) } |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
let batch = OperationBatch<T>() |
|
|
|
|
batch.deletes = Array(items) |
|
|
|
|
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() { |
|
|
|
|
for relationship in T.relationships() { |
|
|
|
|
@ -261,22 +252,38 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
|
|
|
|
|
fileprivate func _deleteUnusedSharedInstances() { |
|
|
|
|
|
|
|
|
|
let sharedItems = self.items.filter { $0.shared == true } |
|
|
|
|
let sharedItems = self.collection.items.filter { $0.shared == true } |
|
|
|
|
for sharedItem in sharedItems { |
|
|
|
|
self.store.deleteUnusedSharedIfNecessary(sharedItem) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func deleteAllItemsAndDependencies(actionOption: ActionOption) { |
|
|
|
|
if actionOption.synchronize { |
|
|
|
|
self.delete(contentOfs: self.items, actionOption: actionOption) |
|
|
|
|
} else { |
|
|
|
|
self.collection.deleteAllItemsAndDependencies(actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func deleteDependencies(actionOption: ActionOption, _ isIncluded: (T) -> Bool) { |
|
|
|
|
let items = self.items.filter(isIncluded) |
|
|
|
|
if actionOption.synchronize { |
|
|
|
|
self.delete(contentOfs: items, actionOption: actionOption) |
|
|
|
|
} else { |
|
|
|
|
self.collection.delete(contentOfs: items) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Asynchronous operations |
|
|
|
|
|
|
|
|
|
/// Adds or update an instance asynchronously and waits for network operations |
|
|
|
|
func addOrUpdateAsync(instance: T) async throws { |
|
|
|
|
if let result = _addOrUpdateCore(instance: instance) { |
|
|
|
|
if result.isNewItem { |
|
|
|
|
try await self._executeBatchOnce(OperationBatch(insert: result.item)) |
|
|
|
|
} else { |
|
|
|
|
try await self._executeBatchOnce(OperationBatch(update: result.item)) |
|
|
|
|
} |
|
|
|
|
let result = _addOrUpdateCore(instance: instance) |
|
|
|
|
if result.method == .insert { |
|
|
|
|
try await self._executeBatchOnce(OperationBatch(insert: instance)) |
|
|
|
|
} else { |
|
|
|
|
try await self._executeBatchOnce(OperationBatch(update: instance)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -288,16 +295,13 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
/// Deletes all items of the sequence by id and sets the collection as changed to trigger a write |
|
|
|
|
public func deleteAsync(contentOfs sequence: any RandomAccessCollection<T>) async throws { |
|
|
|
|
guard sequence.isNotEmpty else { return } |
|
|
|
|
let batch = self._deleteCore(contentOfs: sequence) |
|
|
|
|
let batch = self._deleteCore(contentOfs: sequence, actionOption: .syncedCascade) |
|
|
|
|
try await self._executeBatchOnce(batch) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance and writes |
|
|
|
|
func deleteAsync(instance: T) async throws { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
self.deleteItem(instance, shouldBeSynchronized: true) |
|
|
|
|
self.collection.delete(instance: instance, actionOption: .syncedCascade) |
|
|
|
|
self.storeCenter.createDeleteLog(instance) |
|
|
|
|
try await self._executeBatchOnce(OperationBatch(delete: instance)) |
|
|
|
|
} |
|
|
|
|
@ -306,51 +310,63 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
|
|
|
|
|
/// Adds or update an instance without synchronizing it |
|
|
|
|
func addOrUpdateNoSync(_ instance: T) { |
|
|
|
|
self.addOrUpdateItem(instance: instance) |
|
|
|
|
self.collection.addOrUpdate(instance: instance) |
|
|
|
|
// self.addOrUpdateItem(instance: instance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Adds or update a sequence of elements without synchronizing it |
|
|
|
|
func addOrUpdateNoSync(contentOfs sequence: any Sequence<T>) { |
|
|
|
|
self.addSequence(sequence) |
|
|
|
|
self.collection.addOrUpdate(contentOfs: sequence) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes the instance in the collection without synchronization |
|
|
|
|
func deleteNoSync(contentOfs sequence: any Sequence<T>) { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
for item in sequence { |
|
|
|
|
self.deleteItem(item, shouldBeSynchronized: false) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// func deleteNoSync(instance: T) { |
|
|
|
|
// self.collection.delete(instance: instance) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
/// Deletes the instance in the collection without synchronization |
|
|
|
|
func deleteNoSync(instance: T) { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
self.deleteItem(instance, shouldBeSynchronized: false) |
|
|
|
|
func deleteNoSync(contentOfs sequence: any RandomAccessCollection<T>) { |
|
|
|
|
self.collection.delete(contentOfs: sequence) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteUnusedShared(instance: T) { |
|
|
|
|
|
|
|
|
|
guard instance.shared == true else { return } |
|
|
|
|
|
|
|
|
|
// Delete the instance and its non-used shared dependencies |
|
|
|
|
self.deleteUnusedShared(instance, shouldBeSynchronized: false) |
|
|
|
|
|
|
|
|
|
self.triggerWrite() |
|
|
|
|
self.delete(instance: instance) |
|
|
|
|
instance.deleteUnusedSharedDependencies(store: self.store) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes the instance in the collection without synchronization |
|
|
|
|
func deleteByStringIdNoSync(_ id: String) { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
func deleteNoSyncNoCascade(id: String) { |
|
|
|
|
self.collection.deleteByStringId(id, actionOption: .standard) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Collection Delegate |
|
|
|
|
|
|
|
|
|
func loadingForMemoryCollection() async { |
|
|
|
|
do { |
|
|
|
|
try await self.loadDataFromServerIfAllowed() |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
let realId = T.buildRealId(id: id) |
|
|
|
|
if let instance = self.findById(realId) { |
|
|
|
|
self.deleteItem(instance, shouldBeSynchronized: false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func itemMerged(_ pendingOperation: PendingOperation<T>) { |
|
|
|
|
|
|
|
|
|
let batch = OperationBatch<T>() |
|
|
|
|
switch pendingOperation.method { |
|
|
|
|
case .add: |
|
|
|
|
batch.inserts.append(pendingOperation.data) |
|
|
|
|
case .update: |
|
|
|
|
batch.updates.append(pendingOperation.data) |
|
|
|
|
case .delete: |
|
|
|
|
batch.deletes.append(pendingOperation.data) |
|
|
|
|
case .deleteUnusedShared: |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Task { await self._sendOperationBatch(batch) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Send requests |
|
|
|
|
@ -397,14 +413,15 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
|
|
|
|
|
/// Adds or update an instance if it is newer than the local instance |
|
|
|
|
func addOrUpdateIfNewer(_ instance: T, shared: Bool) { |
|
|
|
|
defer { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
let localInstance = self.items[index] |
|
|
|
|
// defer { |
|
|
|
|
// self.triggerWrite() |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
if let index = self.collection.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
let localInstance = self.collection.items[index] |
|
|
|
|
if instance.lastUpdate > localInstance.lastUpdate { |
|
|
|
|
self.updateItem(instance, index: index) |
|
|
|
|
self.collection.update(instance, index: index, actionOption: .standard) |
|
|
|
|
} else { |
|
|
|
|
print("do not update \(T.resourceName()): \(instance.lastUpdate.timeIntervalSince1970) / local: \(localInstance.lastUpdate.timeIntervalSince1970)") |
|
|
|
|
} |
|
|
|
|
@ -412,7 +429,7 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
if shared { |
|
|
|
|
instance.shared = true |
|
|
|
|
} |
|
|
|
|
self.addItem(instance: instance, shouldBeSynchronized: false) |
|
|
|
|
self.collection.add(instance: instance, actionOption: .standard) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
@ -421,14 +438,37 @@ public class SyncedCollection<T : SyncedStorable>: BaseCollection<T>, SomeSynced |
|
|
|
|
|
|
|
|
|
/// Sends a POST request for the instance, and changes the collection to perform a write |
|
|
|
|
public func writeChangeAndInsertOnServer(instance: T) { |
|
|
|
|
|
|
|
|
|
self.collection.addOrUpdate(instance: instance) |
|
|
|
|
Task { |
|
|
|
|
await self._sendInsertion(instance) |
|
|
|
|
await MainActor.run { |
|
|
|
|
self.triggerWrite() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - SomeCollection |
|
|
|
|
|
|
|
|
|
public var hasLoaded: Bool { return self.collection.hasLoaded} |
|
|
|
|
|
|
|
|
|
public var inMemory: Bool { return self.collection.inMemory } |
|
|
|
|
|
|
|
|
|
public var type: any Storable.Type { return T.self } |
|
|
|
|
|
|
|
|
|
public func referenceCount<S>(type: S.Type, id: String) -> Int where S : Storable { |
|
|
|
|
return self.collection.referenceCount(type: type, id: id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func reset() { |
|
|
|
|
self.collection.reset() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func findById(_ id: T.ID) -> T? { |
|
|
|
|
return self.collection.findById(id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public var items: [T] { |
|
|
|
|
return self.collection.items |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class OperationBatch<T> { |
|
|
|
|
@ -459,3 +499,25 @@ class OperationBatch<T> { |
|
|
|
|
self.deletes.append(instance) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
extension SyncedCollection: RandomAccessCollection { |
|
|
|
|
|
|
|
|
|
public var startIndex: Int { return self.collection.items.startIndex } |
|
|
|
|
|
|
|
|
|
public var endIndex: Int { return self.collection.items.endIndex } |
|
|
|
|
|
|
|
|
|
public func index(after i: Int) -> Int { |
|
|
|
|
return self.collection.items.index(after: i) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public subscript(index: Int) -> T { |
|
|
|
|
get { |
|
|
|
|
return self.collection.items[index] |
|
|
|
|
} |
|
|
|
|
set(newValue) { |
|
|
|
|
self.collection.update(newValue, index: index, actionOption: .standard) |
|
|
|
|
// self.collection.items[index] = newValue |
|
|
|
|
// self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|