|
|
|
|
@ -1,5 +1,5 @@ |
|
|
|
|
// |
|
|
|
|
// BaseCollection.swift |
|
|
|
|
// StoredCollection.swift |
|
|
|
|
// LeStorage |
|
|
|
|
// |
|
|
|
|
// Created by Laurent Morvillier on 02/02/2024. |
|
|
|
|
@ -8,26 +8,57 @@ |
|
|
|
|
import Foundation |
|
|
|
|
import Combine |
|
|
|
|
|
|
|
|
|
public protocol CollectionHolder { |
|
|
|
|
associatedtype Item: Storable |
|
|
|
|
public protocol SomeCollection<Item>: Identifiable { |
|
|
|
|
|
|
|
|
|
var items: [Item] { get } |
|
|
|
|
func reset() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public protocol SomeCollection: CollectionHolder, Identifiable { |
|
|
|
|
associatedtype Item: Storable |
|
|
|
|
|
|
|
|
|
var resourceName: String { get } |
|
|
|
|
var hasLoaded: Bool { get } |
|
|
|
|
var inMemory: Bool { get } |
|
|
|
|
var type: any Storable.Type { get } |
|
|
|
|
|
|
|
|
|
func reset() |
|
|
|
|
func referenceCount<S: Storable>(type: S.Type, id: String) -> Int |
|
|
|
|
|
|
|
|
|
var items: [Item] { get } |
|
|
|
|
|
|
|
|
|
func deleteAllItemsAndDependencies(actionOption: ActionOption) |
|
|
|
|
func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool) |
|
|
|
|
|
|
|
|
|
func findById(_ id: Item.ID) -> Item? |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protocol CollectionDelegate<Item> { |
|
|
|
|
associatedtype Item: Storable |
|
|
|
|
func loadingForMemoryCollection() async |
|
|
|
|
func itemMerged(_ pendingOperation: PendingOperation<Item>) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum CollectionMethod { |
|
|
|
|
case insert |
|
|
|
|
case update |
|
|
|
|
case delete |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
public struct ActionResult<T> { |
|
|
|
|
var instance: T |
|
|
|
|
var method: CollectionMethod |
|
|
|
|
var pending: Bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public struct ActionOption: Codable { |
|
|
|
|
var synchronize: Bool |
|
|
|
|
var cascade: Bool |
|
|
|
|
|
|
|
|
|
static let standard: ActionOption = ActionOption(synchronize: false, cascade: false) |
|
|
|
|
static let cascade: ActionOption = ActionOption(synchronize: false, cascade: true) |
|
|
|
|
static let syncedCascade: ActionOption = ActionOption(synchronize: true, cascade: true) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class StoredCollection<T: Storable>: SomeCollection { |
|
|
|
|
|
|
|
|
|
public typealias Item = T |
|
|
|
|
|
|
|
|
|
/// Doesn't write the collection in a file |
|
|
|
|
fileprivate(set) public var inMemory: Bool = false |
|
|
|
|
@ -47,7 +78,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
/// Indicates whether the collection has changed, thus requiring a write operation |
|
|
|
|
fileprivate var _triggerWrite: Bool = false { |
|
|
|
|
didSet { |
|
|
|
|
if self._triggerWrite == true { |
|
|
|
|
if self._triggerWrite == true && self.inMemory == false { |
|
|
|
|
|
|
|
|
|
self._scheduleWrite() |
|
|
|
|
DispatchQueue.main.async { |
|
|
|
|
@ -65,6 +96,8 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
/// Sets a max number of items inside the collection |
|
|
|
|
fileprivate(set) var limit: Int? = nil |
|
|
|
|
|
|
|
|
|
fileprivate var _delegate: (any CollectionDelegate<T>)? = nil |
|
|
|
|
|
|
|
|
|
init(store: Store, inMemory: Bool = false) async { |
|
|
|
|
self.store = store |
|
|
|
|
if self.inMemory == false { |
|
|
|
|
@ -72,13 +105,14 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false) { |
|
|
|
|
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, delegate: (any CollectionDelegate<T>)? = nil) { |
|
|
|
|
if indexed { |
|
|
|
|
self._indexes = [:] |
|
|
|
|
} |
|
|
|
|
self.inMemory = inMemory |
|
|
|
|
self.store = store |
|
|
|
|
self.limit = limit |
|
|
|
|
self._delegate = delegate |
|
|
|
|
|
|
|
|
|
if synchronousLoading { |
|
|
|
|
Task { |
|
|
|
|
@ -109,7 +143,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
// MARK: - Loading |
|
|
|
|
|
|
|
|
|
/// Sets the collection as changed to trigger a write |
|
|
|
|
func triggerWrite() { |
|
|
|
|
fileprivate func requestWrite() { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -118,6 +152,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
if !self.inMemory { |
|
|
|
|
await self.loadFromFile() |
|
|
|
|
} else { |
|
|
|
|
await self._delegate?.loadingForMemoryCollection() |
|
|
|
|
await MainActor.run { |
|
|
|
|
self.setAsLoaded() |
|
|
|
|
} |
|
|
|
|
@ -169,6 +204,19 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
self._updateIndexIfNecessary() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@MainActor |
|
|
|
|
func loadAndWrite(_ items: [T], clear: Bool = false) { |
|
|
|
|
if clear { |
|
|
|
|
self.setItems(items) |
|
|
|
|
self.setAsLoaded() |
|
|
|
|
} else { |
|
|
|
|
self.setAsLoaded() |
|
|
|
|
self.addOrUpdate(contentOfs: items) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Updates the whole index with the items array |
|
|
|
|
fileprivate func _updateIndexIfNecessary() { |
|
|
|
|
if self._indexes != nil { |
|
|
|
|
@ -180,76 +228,71 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
|
|
|
|
|
/// Adds or updates the provided instance inside the collection |
|
|
|
|
/// Adds it if its id is not found, and otherwise updates it |
|
|
|
|
public func addOrUpdate(instance: T) { |
|
|
|
|
self.addOrUpdateItem(instance: instance) |
|
|
|
|
@discardableResult public func addOrUpdate(instance: T) -> ActionResult<T> { |
|
|
|
|
defer { |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
return self._rawAddOrUpdate(instance: instance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Adds or update an instance inside the collection and writes |
|
|
|
|
func addOrUpdateItem(instance: T) { |
|
|
|
|
/// Adds or update a sequence of elements |
|
|
|
|
public func addOrUpdate(contentOfs sequence: any Sequence<T>, _ handler: ((ActionResult<T>) -> ())? = nil) { |
|
|
|
|
|
|
|
|
|
defer { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for instance in sequence { |
|
|
|
|
let result = self._rawAddOrUpdate(instance: instance) |
|
|
|
|
handler?(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _rawAddOrUpdate(instance: T) -> ActionResult<T> { |
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
self.updateItem(instance, index: index) |
|
|
|
|
let updated = self.updateItem(instance, index: index, actionOption: .standard) |
|
|
|
|
return ActionResult(instance: instance, method: .update, pending: !updated) |
|
|
|
|
} else { |
|
|
|
|
self.addItem(instance: instance) |
|
|
|
|
let added = self.addItem(instance: instance) |
|
|
|
|
return ActionResult(instance: instance, method: .insert, pending: !added) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// A method the treat the collection as a single instance holder |
|
|
|
|
func setSingletonNoSync(instance: T) { |
|
|
|
|
defer { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
self.items.removeAll() |
|
|
|
|
self.addItem(instance: instance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes the instance in the collection and sets the collection as changed to trigger a write |
|
|
|
|
public func delete(instance: T) { |
|
|
|
|
public func delete(instance: T, actionOption: ActionOption) { |
|
|
|
|
defer { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
self.deleteItem(instance) |
|
|
|
|
self.deleteItem(instance, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes all items of the sequence by id and sets the collection as changed to trigger a write |
|
|
|
|
public func delete(contentOfs sequence: any RandomAccessCollection<T>) { |
|
|
|
|
|
|
|
|
|
defer { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
public func delete(contentOfs sequence: any RandomAccessCollection<T>, _ handler: ((ActionResult<T>) -> ())? = nil) { |
|
|
|
|
self.delete(contentOfs: sequence, actionOption: .cascade, handler: handler) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for instance in sequence { |
|
|
|
|
self.deleteItem(instance) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
func delete(contentOfs sequence: any RandomAccessCollection<T>, actionOption: ActionOption, handler: ((ActionResult<T>) -> ())? = nil) { |
|
|
|
|
|
|
|
|
|
/// Adds or update a sequence of elements |
|
|
|
|
public func addOrUpdate(contentOfs sequence: any Sequence<T>) { |
|
|
|
|
self.addSequence(sequence) |
|
|
|
|
// self._addOrUpdate(contentOfs: sequence) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Adds a sequence of objects inside the collection and performs a write |
|
|
|
|
func addSequence(_ sequence: any Sequence<T>) { |
|
|
|
|
defer { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for instance in sequence { |
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
|
self.updateItem(instance, index: index) |
|
|
|
|
} else { // insert |
|
|
|
|
self.addItem(instance: instance) |
|
|
|
|
let deleted = self.deleteItem(instance, actionOption: actionOption) |
|
|
|
|
handler?(ActionResult(instance: instance, method: .delete, pending: !deleted)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// This method sets the storeId for the given instance if the collection belongs to a store with an id |
|
|
|
|
fileprivate func _affectStoreIdIfNecessary(instance: T) { |
|
|
|
|
if let storeId = self.store.identifier { |
|
|
|
|
@ -261,11 +304,16 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func add(instance: T, actionOption: ActionOption) { |
|
|
|
|
self.addItem(instance: instance, actionOption: actionOption) |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Adds an instance to the collection |
|
|
|
|
@discardableResult func addItem(instance: T, shouldBeSynchronized: Bool = false) -> Bool { |
|
|
|
|
@discardableResult fileprivate func addItem(instance: T, actionOption: ActionOption = .standard) -> Bool { |
|
|
|
|
|
|
|
|
|
if !self.hasLoaded { |
|
|
|
|
self.addPendingOperation(method: .addOrUpdate, instance: instance, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
self.addPendingOperation(method: .add, instance: instance, actionOption: actionOption) |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -277,11 +325,16 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func update(_ instance: T, index: Int, actionOption: ActionOption) { |
|
|
|
|
self.updateItem(instance, index: index, actionOption: actionOption) |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Updates an instance to the collection by index |
|
|
|
|
@discardableResult func updateItem(_ instance: T, index: Int, shouldBeSynchronized: Bool = false) -> Bool { |
|
|
|
|
@discardableResult fileprivate func updateItem(_ instance: T, index: Int, actionOption: ActionOption) -> Bool { |
|
|
|
|
|
|
|
|
|
if !self.hasLoaded { |
|
|
|
|
self.addPendingOperation(method: .addOrUpdate, instance: instance, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
self.addPendingOperation(method: .update, instance: instance, actionOption: actionOption) |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -296,15 +349,15 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance from the collection |
|
|
|
|
@discardableResult func deleteItem(_ instance: T, shouldBeSynchronized: Bool = false) -> Bool { |
|
|
|
|
@discardableResult fileprivate func deleteItem(_ instance: T, actionOption: ActionOption = .cascade) -> Bool { |
|
|
|
|
|
|
|
|
|
if !self.hasLoaded { |
|
|
|
|
self.addPendingOperation(method: .delete, instance: instance, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
self.addPendingOperation(method: .delete, instance: instance, actionOption: actionOption) |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if shouldBeSynchronized { // when user initiated, we want to cascade delete, but when synchronized, we want the delete notifications to make the job and be sure everything works |
|
|
|
|
instance.deleteDependencies(store: self.store, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
if actionOption.cascade { // when user initiated, we want to cascade delete, but when synchronized, we want the delete notifications to make the job and be sure everything works |
|
|
|
|
instance.deleteDependencies(store: self.store, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.localDeleteOnly(instance: instance) |
|
|
|
|
@ -312,10 +365,10 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Deletes an instance from the collection |
|
|
|
|
@discardableResult func deleteUnusedShared(_ instance: T, shouldBeSynchronized: Bool = false) -> Bool { |
|
|
|
|
@discardableResult func deleteUnusedShared(_ instance: T, actionOption: ActionOption) -> Bool { |
|
|
|
|
|
|
|
|
|
if !self.hasLoaded { |
|
|
|
|
self.addPendingOperation(method: .deleteUnusedShared, instance: instance, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
self.addPendingOperation(method: .deleteUnusedShared, instance: instance, actionOption: actionOption) |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -339,6 +392,13 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteByStringId(_ id: String, actionOption: ActionOption = .cascade) { |
|
|
|
|
let realId = T.buildRealId(id: id) |
|
|
|
|
if let instance = self.findById(realId) { |
|
|
|
|
self.deleteItem(instance, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the instance corresponding to the provided [id] |
|
|
|
|
public func findById(_ id: T.ID) -> T? { |
|
|
|
|
if let index = self._indexes, let instance = index[id] { |
|
|
|
|
@ -361,17 +421,33 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func deleteAllItemsAndDependencies(actionOption: ActionOption) { |
|
|
|
|
self._delete(contentOfs: self.items, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func deleteDependencies(actionOption: ActionOption, _ isIncluded: (T) -> Bool) { |
|
|
|
|
let items = self.items.filter(isIncluded) |
|
|
|
|
self._delete(contentOfs: items, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _delete(contentOfs sequence: any RandomAccessCollection<T>, actionOption: ActionOption) { |
|
|
|
|
for instance in sequence { |
|
|
|
|
self.deleteItem(instance, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Pending operations |
|
|
|
|
|
|
|
|
|
func addPendingOperation(method: StorageMethod, instance: T, shouldBeSynchronized: Bool) { |
|
|
|
|
func addPendingOperation(method: StorageMethod, instance: T, actionOption: ActionOption) { |
|
|
|
|
if self.pendingOperationManager == nil { |
|
|
|
|
self.pendingOperationManager = PendingOperationManager<T>(store: self.store, inMemory: self.inMemory) |
|
|
|
|
} |
|
|
|
|
self._addPendingOperationIfPossible(method: method, instance: instance, shouldBeSynchronized: false) |
|
|
|
|
self._addPendingOperationIfPossible(method: method, instance: instance, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _addPendingOperationIfPossible(method: StorageMethod, instance: T, shouldBeSynchronized: Bool) { |
|
|
|
|
self.pendingOperationManager?.addPendingOperation(method: method, instance: instance, shouldBeSynchronized: shouldBeSynchronized) |
|
|
|
|
fileprivate func _addPendingOperationIfPossible(method: StorageMethod, instance: T, actionOption: ActionOption) { |
|
|
|
|
self.pendingOperationManager?.addPendingOperation(method: method, instance: instance, actionOption: actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _mergePendingOperations() { |
|
|
|
|
@ -381,18 +457,16 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
Logger.log(">>> Merge pending: \(manager.items.count)") |
|
|
|
|
for item in manager.items { |
|
|
|
|
let data = item.data |
|
|
|
|
switch (item.method, item.shouldBeSynchronized) { |
|
|
|
|
case (.addOrUpdate, true): |
|
|
|
|
switch item.method { |
|
|
|
|
case .add, .update: |
|
|
|
|
self.addOrUpdate(instance: data) |
|
|
|
|
case (.addOrUpdate, false): |
|
|
|
|
self.addOrUpdateItem(instance: data) |
|
|
|
|
case (.delete, true): |
|
|
|
|
self.delete(instance: data) |
|
|
|
|
case (.delete, false): |
|
|
|
|
self.deleteItem(data) |
|
|
|
|
case (.deleteUnusedShared, _): |
|
|
|
|
self.deleteUnusedShared(data) |
|
|
|
|
case .delete: |
|
|
|
|
self.deleteItem(data, actionOption: item.actionOption) |
|
|
|
|
case .deleteUnusedShared: |
|
|
|
|
self.deleteUnusedShared(data, actionOption: item.actionOption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self._delegate?.itemMerged(item) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.pendingOperationManager = nil |
|
|
|
|
@ -402,10 +476,7 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
|
|
|
|
|
/// Schedules a write operation |
|
|
|
|
fileprivate func _scheduleWrite() { |
|
|
|
|
|
|
|
|
|
guard !self.inMemory else { return } |
|
|
|
|
|
|
|
|
|
DispatchQueue(label: "lestorage.queue.write", qos: .utility).asyncAndWait { // sync to make sure we don't have writes performed at the same time |
|
|
|
|
DispatchQueue(label: "lestorage.queue.write", qos: .utility).asyncAndWait { |
|
|
|
|
self._write() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -431,7 +502,6 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
public func reset() { |
|
|
|
|
self.items.removeAll() |
|
|
|
|
self.store.removeFile(type: T.self) |
|
|
|
|
triggerWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public var type: any Storable.Type { return T.self } |
|
|
|
|
@ -451,38 +521,23 @@ public class BaseCollection<T: Storable>: SomeCollection, CollectionHolder { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class StoredCollection<T: Storable>: BaseCollection<T>, RandomAccessCollection { |
|
|
|
|
// MARK: - for Synced Collection |
|
|
|
|
|
|
|
|
|
/// Returns a dummy StoredCollection instance |
|
|
|
|
public static func placeholder() -> StoredCollection<T> { |
|
|
|
|
return StoredCollection<T>(store: Store(storeCenter: StoreCenter.main)) |
|
|
|
|
@MainActor |
|
|
|
|
func updateLocalInstance(_ serverInstance: T) { |
|
|
|
|
if let localInstance = self.findById(serverInstance.id) { |
|
|
|
|
localInstance.copy(from: serverInstance) |
|
|
|
|
self.requestWrite() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - RandomAccessCollection |
|
|
|
|
|
|
|
|
|
public var startIndex: Int { return self.items.startIndex } |
|
|
|
|
|
|
|
|
|
public var endIndex: Int { return self.items.endIndex } |
|
|
|
|
|
|
|
|
|
public func index(after i: Int) -> Int { |
|
|
|
|
return self.items.index(after: i) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
open subscript(index: Int) -> T { |
|
|
|
|
get { |
|
|
|
|
return self.items[index] |
|
|
|
|
} |
|
|
|
|
set(newValue) { |
|
|
|
|
self.items[index] = newValue |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
extension StoredCollection: RandomAccessCollection { |
|
|
|
|
|
|
|
|
|
extension SyncedCollection: RandomAccessCollection { |
|
|
|
|
public static func placeholder() -> StoredCollection<T> { |
|
|
|
|
return StoredCollection<T>(store: Store(storeCenter: StoreCenter.main)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public var startIndex: Int { return self.items.startIndex } |
|
|
|
|
|
|
|
|
|
@ -501,4 +556,5 @@ extension SyncedCollection: RandomAccessCollection { |
|
|
|
|
self._triggerWrite = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |