minor improvements, changes MicroStorage creation with filename

multistore
Laurent 2 years ago
parent e30d53bad0
commit 40126c6075
  1. 4
      LeStorage/Codables/Settings.swift
  2. 19
      LeStorage/MicroStorage.swift
  3. 4
      LeStorage/Services.swift
  4. 8
      LeStorage/Store.swift
  5. 73
      LeStorage/StoredCollection.swift
  6. 2
      LeStorage/StoredSingleton.swift
  7. 2
      LeStorage/Utils/Collection+Extension.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
}

@ -9,19 +9,22 @@ import Foundation
public protocol MicroStorable : Codable {
init()
static var fileName: String { get }
// static var fileName: String { get }
}
public class MicroStorage<T : MicroStorable> {
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<T : MicroStorable> {
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<T : Codable> {
}
}
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<T : Codable> {
}
do {
let _ = try FileUtils.writeToDocumentDirectory(content: content, fileName: self.fileName)
let _ = try FileUtils.writeToDocumentDirectory(content: content, fileName: self._fileName)
} catch {
Logger.error(error)
}

@ -44,8 +44,8 @@ fileprivate enum ServiceConf: String {
return false
case .getUser, .changePassword:
return true
default:
return nil
// default:
// return nil
}
}

@ -57,7 +57,7 @@ public class Store {
fileprivate var _collections: [String : any SomeCollection] = [:]
/// A store for the Settings object
fileprivate var _settingsStorage: MicroStorage<Settings> = MicroStorage()
fileprivate var _settingsStorage: MicroStorage<Settings> = 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<T: Storable>() 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() }
}
}

@ -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<T: Storable>: RandomAccessCollection, SomeCollecti
fileprivate var loadCompletion: ((StoredCollection<T>) -> ())? = 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<ApiCall<T>>? = nil
@ -58,9 +59,7 @@ public class StoredCollection<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: 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<T: Storable>: RandomAccessCollection, SomeCollecti
self.items.removeAll()
}
func reset() {
public func reset() {
self.items.removeAll()
do {
@ -372,10 +379,15 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
fileprivate func _createCall(_ instance: T, method: HTTPMethod) throws -> ApiCall<T> {
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<T: Storable>: 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 }

@ -9,7 +9,7 @@ import Foundation
public class StoredSingleton<T: Storable>: StoredCollection<T> {
public func setItemNoSync(_ instance: T) throws {
public func setItemNoSync(_ instance: T) {
self.setSingletonNoSync(instance: instance)
}

@ -19,7 +19,7 @@ extension Array {
}
}
func dictionary<T : Hashable>(handler: (Element) -> (T)) -> [T : Element] {
func dictionary<T : Hashable>(_ handler: (Element) -> (T)) -> [T : Element] {
return self.reduce(into: [T : Element]()) { r, e in
r[handler(e)] = e
}

Loading…
Cancel
Save