|
|
|
@ -18,9 +18,10 @@ protocol SomeCollection: Identifiable { |
|
|
|
func deleteById(_ id: String) throws |
|
|
|
func deleteById(_ id: String) throws |
|
|
|
func deleteApiCallById(_ id: String) throws |
|
|
|
func deleteApiCallById(_ id: String) throws |
|
|
|
func reset() |
|
|
|
func reset() |
|
|
|
func loadDataFromServer() throws |
|
|
|
func loadDataFromServerIfAllowed() throws |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extension Notification.Name { |
|
|
|
extension Notification.Name { |
|
|
|
public static let CollectionDidLoad: Notification.Name = Notification.Name.init("notification.collectionDidLoad") |
|
|
|
public static let CollectionDidLoad: Notification.Name = Notification.Name.init("notification.collectionDidLoad") |
|
|
|
public static let CollectionDidChange: Notification.Name = Notification.Name.init("notification.collectionDidChange") |
|
|
|
public static let CollectionDidChange: Notification.Name = Notification.Name.init("notification.collectionDidChange") |
|
|
|
@ -102,9 +103,9 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
try content.write(to: fileURL, atomically: false, encoding: .utf8) |
|
|
|
try content.write(to: fileURL, atomically: false, encoding: .utf8) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _pathForFile(_ fileName: String) throws -> URL { |
|
|
|
fileprivate func _urlForJSONFile() throws -> URL { |
|
|
|
var storageDirectory = try self._storageDirectoryPath() |
|
|
|
var storageDirectory = try self._storageDirectoryPath() |
|
|
|
storageDirectory.append(component: fileName) |
|
|
|
storageDirectory.append(component: T.fileName()) |
|
|
|
return storageDirectory |
|
|
|
return storageDirectory |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -115,7 +116,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
do { |
|
|
|
do { |
|
|
|
if self._inMemory { |
|
|
|
if self._inMemory { |
|
|
|
try self.loadDataFromServer() |
|
|
|
try self.loadDataFromServerIfAllowed() |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
try self._loadFromFile() |
|
|
|
try self._loadFromFile() |
|
|
|
} |
|
|
|
} |
|
|
|
@ -126,12 +127,12 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _loadFromFile() throws { |
|
|
|
fileprivate func _loadFromFile() throws { |
|
|
|
let url: URL = try self._pathForFile(T.fileName()) |
|
|
|
let url: URL = try self._urlForJSONFile() |
|
|
|
if FileManager.default.fileExists(atPath: url.path()) { |
|
|
|
if FileManager.default.fileExists(atPath: url.path()) { |
|
|
|
|
|
|
|
|
|
|
|
if self.asynchronousIO { |
|
|
|
if self.asynchronousIO { |
|
|
|
Task(priority: .high) { |
|
|
|
Task(priority: .high) { |
|
|
|
try await Store.main.performMigrationIfNecessary(self) |
|
|
|
// try await Store.main.performMigrationIfNecessary(self) |
|
|
|
try self._decodeJSONFile() |
|
|
|
try self._decodeJSONFile() |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
@ -143,7 +144,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
/// Decodes the json file into the items array |
|
|
|
/// Decodes the json file into the items array |
|
|
|
fileprivate func _decodeJSONFile() throws { |
|
|
|
fileprivate func _decodeJSONFile() throws { |
|
|
|
let fileURL = try self._pathForFile(T.fileName()) |
|
|
|
let fileURL = try self._urlForJSONFile() |
|
|
|
let jsonString: String = try FileUtils.readFile(fileURL: fileURL) |
|
|
|
let jsonString: String = try FileUtils.readFile(fileURL: fileURL) |
|
|
|
if let decoded: [T] = try jsonString.decodeArray() { |
|
|
|
if let decoded: [T] = try jsonString.decodeArray() { |
|
|
|
DispatchQueue.main.async { |
|
|
|
DispatchQueue.main.async { |
|
|
|
@ -165,13 +166,14 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Retrieves the data from the server and loads it into the items array |
|
|
|
/// Retrieves the data from the server and loads it into the items array |
|
|
|
public func loadDataFromServer() throws { |
|
|
|
public func loadDataFromServerIfAllowed() throws { |
|
|
|
guard self.synchronized else { |
|
|
|
guard self.synchronized, !(self is StoredSingleton<T>) else { |
|
|
|
throw StoreError.unSynchronizedCollection |
|
|
|
throw StoreError.unSynchronizedCollection |
|
|
|
} |
|
|
|
} |
|
|
|
Task { |
|
|
|
Task { |
|
|
|
do { |
|
|
|
do { |
|
|
|
self.items = try await self._store.getItems() |
|
|
|
let items: [T] = try await self._store.getItems() |
|
|
|
|
|
|
|
try self._addOrUpdate(contentOfs: items, shouldSync: false) |
|
|
|
self._hasChanged = true |
|
|
|
self._hasChanged = true |
|
|
|
} catch { |
|
|
|
} catch { |
|
|
|
Logger.error(error) |
|
|
|
Logger.error(error) |
|
|
|
@ -201,6 +203,14 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func setSingletonNoSync(instance: T) { |
|
|
|
|
|
|
|
defer { |
|
|
|
|
|
|
|
self._hasChanged = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
self.items.removeAll() |
|
|
|
|
|
|
|
self.items.append(instance) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Deletes the instance in the collection by id |
|
|
|
/// Deletes the instance in the collection by id |
|
|
|
public func delete(instance: T) throws { |
|
|
|
public func delete(instance: T) throws { |
|
|
|
|
|
|
|
|
|
|
|
@ -230,8 +240,12 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Inserts or updates all items in the sequence |
|
|
|
|
|
|
|
public func addOrUpdate(contentOfs sequence: any Sequence<T>) throws { |
|
|
|
public func addOrUpdate(contentOfs sequence: any Sequence<T>) throws { |
|
|
|
|
|
|
|
try self._addOrUpdate(contentOfs: sequence) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Inserts or updates all items in the sequence |
|
|
|
|
|
|
|
fileprivate func _addOrUpdate(contentOfs sequence: any Sequence<T>, shouldSync: Bool = true) throws { |
|
|
|
defer { |
|
|
|
defer { |
|
|
|
self._hasChanged = true |
|
|
|
self._hasChanged = true |
|
|
|
} |
|
|
|
} |
|
|
|
@ -239,13 +253,17 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
for instance in sequence { |
|
|
|
for instance in sequence { |
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) { |
|
|
|
self.items[index] = instance |
|
|
|
self.items[index] = instance |
|
|
|
|
|
|
|
if shouldSync { |
|
|
|
try self._sendUpdateIfNecessary(instance) |
|
|
|
try self._sendUpdateIfNecessary(instance) |
|
|
|
|
|
|
|
} |
|
|
|
} else { // insert |
|
|
|
} else { // insert |
|
|
|
self.items.append(instance) |
|
|
|
self.items.append(instance) |
|
|
|
self._index?[instance.stringId] = instance |
|
|
|
self._index?[instance.stringId] = instance |
|
|
|
|
|
|
|
if shouldSync { |
|
|
|
try self._sendInsertionIfNecessary(instance) |
|
|
|
try self._sendInsertionIfNecessary(instance) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -307,9 +325,20 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
Logger.log("End write") |
|
|
|
Logger.log("End write") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func clear() { |
|
|
|
|
|
|
|
self.items.removeAll() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func reset() { |
|
|
|
func reset() { |
|
|
|
self.items.removeAll() |
|
|
|
self.items.removeAll() |
|
|
|
try? FileUtils.removeFileFromDocumentDirectory(fileName: T.fileName()) |
|
|
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
let url: URL = try self._urlForJSONFile() |
|
|
|
|
|
|
|
try FileManager.default.removeItem(at: url) |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Logger.error(error) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if let apiCallsCollection = self.apiCallsCollection { |
|
|
|
if let apiCallsCollection = self.apiCallsCollection { |
|
|
|
apiCallsCollection.reset() |
|
|
|
apiCallsCollection.reset() |
|
|
|
} |
|
|
|
} |
|
|
|
@ -357,7 +386,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
/// Sends an insert api call for the provided [instance] |
|
|
|
/// Sends an insert api call for the provided [instance] |
|
|
|
fileprivate func _sendInsertionIfNecessary(_ instance: T) throws { |
|
|
|
fileprivate func _sendInsertionIfNecessary(_ instance: T) throws { |
|
|
|
guard self.synchronized else { |
|
|
|
guard self.synchronized, Store.main.collectionsCanSynchronize else { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -379,7 +408,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
/// Sends an update api call for the provided [instance] |
|
|
|
/// Sends an update api call for the provided [instance] |
|
|
|
fileprivate func _sendUpdateIfNecessary(_ instance: T) throws { |
|
|
|
fileprivate func _sendUpdateIfNecessary(_ instance: T) throws { |
|
|
|
guard self.synchronized, self._sendsUpdate else { |
|
|
|
guard self.synchronized, self._sendsUpdate, Store.main.collectionsCanSynchronize else { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -399,7 +428,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti |
|
|
|
|
|
|
|
|
|
|
|
/// Sends an delete api call for the provided [instance] |
|
|
|
/// Sends an delete api call for the provided [instance] |
|
|
|
fileprivate func _sendDeletionIfNecessary(_ instance: T) throws { |
|
|
|
fileprivate func _sendDeletionIfNecessary(_ instance: T) throws { |
|
|
|
guard self.synchronized else { |
|
|
|
guard self.synchronized, Store.main.collectionsCanSynchronize else { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|