diff --git a/LeStorage/Codables/Settings.swift b/LeStorage/Codables/Settings.swift index 34d04aa..3895506 100644 --- a/LeStorage/Codables/Settings.swift +++ b/LeStorage/Codables/Settings.swift @@ -9,14 +9,10 @@ import Foundation class Settings: MicroStorable { - static var fileName: String { "settings.json" } - required init() { } -// var id: String = Store.randomId() - var userId: String? = nil var username: String? = nil } diff --git a/LeStorage/MicroStorage.swift b/LeStorage/MicroStorage.swift index f479de5..23b8798 100644 --- a/LeStorage/MicroStorage.swift +++ b/LeStorage/MicroStorage.swift @@ -9,19 +9,22 @@ import Foundation public protocol MicroStorable : Codable { init() - static var fileName: String { get } +// static var fileName: String { get } } public class MicroStorage { public fileprivate(set) var item: T - public init() { + fileprivate var _fileName: String + + public init(fileName: String) { + self._fileName = fileName var instance: T? = nil do { - let url = try FileUtils.pathForFileInDocumentDirectory(T.fileName) + let url = try FileUtils.pathForFileInDocumentDirectory(fileName) if FileManager.default.fileExists(atPath: url.path()) { - let jsonString = try FileUtils.readDocumentFile(fileName: T.fileName) + let jsonString = try FileUtils.readDocumentFile(fileName: fileName) if let decoded: T = try jsonString.decode() { instance = decoded } @@ -41,7 +44,7 @@ public class MicroStorage { public func write() { do { let jsonString: String = try self.item.jsonString() - let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: T.fileName) + let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: self._fileName) } catch { Logger.error(error) } @@ -57,10 +60,10 @@ public class OptionalStorage { } } - fileprivate var fileName: String + fileprivate var _fileName: String public init(fileName: String) { - self.fileName = fileName + self._fileName = fileName do { let url = try FileUtils.pathForFileInDocumentDirectory(fileName) if FileManager.default.fileExists(atPath: url.path) { @@ -87,7 +90,7 @@ public class OptionalStorage { } do { - let _ = try FileUtils.writeToDocumentDirectory(content: content, fileName: self.fileName) + let _ = try FileUtils.writeToDocumentDirectory(content: content, fileName: self._fileName) } catch { Logger.error(error) } diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index ed48709..89854eb 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -44,8 +44,8 @@ fileprivate enum ServiceConf: String { return false case .getUser, .changePassword: return true - default: - return nil +// default: +// return nil } } diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 61ee424..f54b3b5 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -57,7 +57,7 @@ public class Store { fileprivate var _collections: [String : any SomeCollection] = [:] /// A store for the Settings object - fileprivate var _settingsStorage: MicroStorage = MicroStorage() + fileprivate var _settingsStorage: MicroStorage = MicroStorage(fileName: "settings.json") /// The name of the directory to store the json files static let storageDirectory = "storage" @@ -233,6 +233,8 @@ public class Store { return try await self._executeApiCall(apiCall) } + // MARK: - + /// Retrieves all the items on the server func getItems() async throws -> [T] { return try await self.service().get() @@ -250,4 +252,8 @@ public class Store { } } + public func hasPendingAPICalls() -> Bool { + return self._collections.values.allSatisfy { $0.hasPendingAPICalls() } + } + } diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index cce280a..239edfe 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -20,6 +20,7 @@ protocol SomeCollection: Identifiable { func reset() func loadDataFromServerIfAllowed() throws var synchronized: Bool { get } + func hasPendingAPICalls() -> Bool } @@ -49,7 +50,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti fileprivate var loadCompletion: ((StoredCollection) -> ())? = nil /// Provides fast access for instances if the collection has been instanced with [indexed] = true - fileprivate var _index: [String : T]? = nil + fileprivate var _indexes: [String : T]? = nil fileprivate var apiCallsCollection: StoredCollection>? = nil @@ -58,9 +59,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti didSet { if self._hasChanged == true { - if !self._inMemory { - self._scheduleWrite() - } + self._scheduleWrite() DispatchQueue.main.async { NotificationCenter.default.post(name: NSNotification.Name.CollectionDidChange, object: self) } @@ -76,7 +75,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti self.synchronized = synchronized self.asynchronousIO = asynchronousIO if indexed { - self._index = [:] + self._indexes = [:] } self._inMemory = inMemory self._sendsUpdate = sendsUpdate @@ -129,40 +128,45 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti fileprivate func _loadFromFile() throws { let url: URL = try self._urlForJSONFile() - if FileManager.default.fileExists(atPath: url.path()) { - if self.asynchronousIO { - Task(priority: .high) { -// try await Store.main.performMigrationIfNecessary(self) - try self._decodeJSONFile() - } - } else { + if self.asynchronousIO { + Task(priority: .high) { try self._decodeJSONFile() } - + } else { + try self._decodeJSONFile() } + } /// Decodes the json file into the items array fileprivate func _decodeJSONFile() throws { let fileURL = try self._urlForJSONFile() - let jsonString: String = try FileUtils.readFile(fileURL: fileURL) - if let decoded: [T] = try jsonString.decodeArray() { + + if FileManager.default.fileExists(atPath: fileURL.path()) { + let jsonString: String = try FileUtils.readFile(fileURL: fileURL) + let decoded: [T] = try jsonString.decodeArray() ?? [] DispatchQueue.main.async { Logger.log("loaded \(T.fileName()) with \(decoded.count) items") self.items = decoded self._updateIndexIfNecessary() self.loadCompletion?(self) - NotificationCenter.default.post(name: NSNotification.Name.CollectionDidLoad, object: self) } + } else { + DispatchQueue.main.async { + Logger.log("collection \(T.fileName()) has no file yet") + self.loadCompletion?(self) + NotificationCenter.default.post(name: NSNotification.Name.CollectionDidLoad, object: self) + } } + } /// Updates the whole index with the items array fileprivate func _updateIndexIfNecessary() { - if let _ = self._index { - self._index = self.items.dictionary(handler: { $0.stringId }) + if let _ = self._indexes { + self._indexes = self.items.dictionary { $0.stringId } } } @@ -198,10 +202,10 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti try self._sendUpdateIfNecessary(instance) } else { // insert self.items.append(instance) - self._index?[instance.stringId] = instance try self._sendInsertionIfNecessary(instance) } - + self._indexes?[instance.stringId] = instance + } func setSingletonNoSync(instance: T) { @@ -221,7 +225,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti try instance.deleteDependencies() self.items.removeAll { $0.id == instance.id } - self._index?.removeValue(forKey: instance.stringId) + self._indexes?.removeValue(forKey: instance.stringId) try self._sendDeletionIfNecessary(instance) } @@ -236,7 +240,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti for instance in sequence { try instance.deleteDependencies() self.items.removeAll { $0.id == instance.id } - self._index?.removeValue(forKey: instance.stringId) + self._indexes?.removeValue(forKey: instance.stringId) try self._sendDeletionIfNecessary(instance) } } @@ -259,18 +263,18 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } } else { // insert self.items.append(instance) - self._index?[instance.stringId] = instance if shouldSync { try self._sendInsertionIfNecessary(instance) } } + self._indexes?[instance.stringId] = instance } } /// Returns the instance corresponding to the provided [id] public func findById(_ id: String) -> T? { - if let index = self._index, let instance = index[id] { + if let index = self._indexes, let instance = index[id] { return instance } return self.items.first(where: { $0.id == id }) @@ -304,6 +308,9 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Schedules a write operation fileprivate func _scheduleWrite() { + + guard !self._inMemory else { return } + if self.asynchronousIO { DispatchQueue(label: "lestorage.queue.write", qos: .utility).asyncAndWait { // sync to make sure we don't have writes performed at the same time self._write() @@ -330,7 +337,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti self.items.removeAll() } - func reset() { + public func reset() { self.items.removeAll() do { @@ -372,10 +379,15 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti fileprivate func _createCall(_ instance: T, method: HTTPMethod) throws -> ApiCall { let baseURL = try _store.service().baseURL let jsonString = try instance.jsonString() - var url = baseURL + T.resourceName() + "/" - if method == .put || method == .delete { - url += (instance.stringId + "/") + + let url: String + switch method { + case .get, .post: + url = baseURL + T.path() + case .put, .delete: + url = baseURL + T.path(id: instance.stringId) } + return ApiCall(url: url, method: method.rawValue, dataId: String(instance.id), body: jsonString) } @@ -508,6 +520,11 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti try apiCallsCollection.deleteById(id) } + func hasPendingAPICalls() -> Bool { + guard let apiCallsCollection else { return false } + return apiCallsCollection.isNotEmpty + } + // MARK: - RandomAccessCollection public var startIndex: Int { return self.items.startIndex } diff --git a/LeStorage/StoredSingleton.swift b/LeStorage/StoredSingleton.swift index a3c5223..e656704 100644 --- a/LeStorage/StoredSingleton.swift +++ b/LeStorage/StoredSingleton.swift @@ -9,7 +9,7 @@ import Foundation public class StoredSingleton: StoredCollection { - public func setItemNoSync(_ instance: T) throws { + public func setItemNoSync(_ instance: T) { self.setSingletonNoSync(instance: instance) } diff --git a/LeStorage/Utils/Collection+Extension.swift b/LeStorage/Utils/Collection+Extension.swift index b4cc8f6..b9c4277 100644 --- a/LeStorage/Utils/Collection+Extension.swift +++ b/LeStorage/Utils/Collection+Extension.swift @@ -19,7 +19,7 @@ extension Array { } } - func dictionary(handler: (Element) -> (T)) -> [T : Element] { + func dictionary(_ handler: (Element) -> (T)) -> [T : Element] { return self.reduce(into: [T : Element]()) { r, e in r[handler(e)] = e }