|
|
|
|
@ -9,17 +9,21 @@ import Foundation |
|
|
|
|
|
|
|
|
|
protocol SomeCollection : Identifiable { |
|
|
|
|
func allItems() -> [any Storable] |
|
|
|
|
func deleteById(_ id: String) |
|
|
|
|
func deleteById(_ id: String) throws |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollection, ObservableObject { |
|
|
|
|
|
|
|
|
|
/// If true, will synchronize the data with the provided server located at the Store's synchronizationApiURL |
|
|
|
|
let synchronized: Bool |
|
|
|
|
|
|
|
|
|
/// The list of stored items |
|
|
|
|
@Published public fileprivate(set) var items: [T] = [] |
|
|
|
|
|
|
|
|
|
/// The reference to the Store |
|
|
|
|
fileprivate var _store: Store |
|
|
|
|
|
|
|
|
|
/// Indicates whether the collection has changed, thus requiring a write operation |
|
|
|
|
fileprivate var _hasChanged: Bool = false { |
|
|
|
|
didSet { |
|
|
|
|
if self._hasChanged == true { |
|
|
|
|
@ -35,10 +39,13 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
self._load() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the default filename for the collection |
|
|
|
|
fileprivate var _fileName: String { |
|
|
|
|
return T.resourceName() + ".json" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 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) { |
|
|
|
|
defer { |
|
|
|
|
self._hasChanged = true |
|
|
|
|
@ -54,37 +61,54 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func delete(instance: T) { |
|
|
|
|
defer { |
|
|
|
|
/// Deletes the instance in the collection by id |
|
|
|
|
public func delete(instance: T) throws { |
|
|
|
|
defer { |
|
|
|
|
self._hasChanged = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try instance.deleteDependencies() |
|
|
|
|
self.items.removeAll { $0.id == instance.id } |
|
|
|
|
self._sendDeletionIfNecessary(instance) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the instance corresponding to the provided [id] |
|
|
|
|
public func findById(_ id: String) -> T? { |
|
|
|
|
return self.items.first(where: { $0.id == id }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func deleteById(_ id: String) { |
|
|
|
|
/// Deletes the instance corresponding to the provided [id] |
|
|
|
|
public func deleteById(_ id: String) throws { |
|
|
|
|
if let instance = self.findById(id) { |
|
|
|
|
self.delete(instance: instance) |
|
|
|
|
try self.delete(instance: instance) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Proceeds to "hard" delete the items without synchronizing them |
|
|
|
|
public func deleteDependencies(_ items: any Sequence<T>) { |
|
|
|
|
defer { |
|
|
|
|
self._hasChanged = true |
|
|
|
|
} |
|
|
|
|
for item in items { |
|
|
|
|
self.items.removeAll(where: { $0.id == item.id }) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - SomeCall |
|
|
|
|
|
|
|
|
|
/// Returns the collection items as [any Storable] |
|
|
|
|
func allItems() -> [any Storable] { |
|
|
|
|
return self.items |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - File access |
|
|
|
|
|
|
|
|
|
/// Schedules a write operation |
|
|
|
|
fileprivate func _scheduleWrite() { |
|
|
|
|
self._write() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Writes all the items as a json array inside a file |
|
|
|
|
fileprivate func _write() { |
|
|
|
|
DispatchQueue(label: "lestorage.queue.write", qos: .background).async { |
|
|
|
|
do { |
|
|
|
|
@ -96,8 +120,8 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Launches a load operation if the file exists |
|
|
|
|
fileprivate func _load() { |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
let url = try FileUtils.directoryURLForFileName(self._fileName) |
|
|
|
|
if FileManager.default.fileExists(atPath: url.path()) { |
|
|
|
|
@ -109,6 +133,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Loads asynchronously into memory the objects contained inside the collection file |
|
|
|
|
fileprivate func _loadAsync() { |
|
|
|
|
DispatchQueue(label: "lestorage.queue.read", qos: .background).async { |
|
|
|
|
|
|
|
|
|
@ -130,6 +155,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
|
|
|
|
|
// MARK: - Synchronization |
|
|
|
|
|
|
|
|
|
/// Sends an insert api call for the provided [instance] |
|
|
|
|
fileprivate func _sendInsertionIfNecessary(_ instance: T) { |
|
|
|
|
Logger.log("_sendInsertionIfNecessary...") |
|
|
|
|
guard self.synchronized else { |
|
|
|
|
@ -146,6 +172,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Sends an update api call for the provided [instance] |
|
|
|
|
fileprivate func _sendUpdateIfNecessary(_ instance: T) { |
|
|
|
|
guard self.synchronized else { |
|
|
|
|
return |
|
|
|
|
@ -161,6 +188,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollec |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Sends an delete api call for the provided [instance] |
|
|
|
|
fileprivate func _sendDeletionIfNecessary(_ instance: T) { |
|
|
|
|
guard self.synchronized else { |
|
|
|
|
return |
|
|
|
|
|