migration for mono store

multistore
Laurent 1 year ago
parent ae5c292795
commit f82591c1b5
  1. 19
      LeStorage/ApiCallCollection.swift
  2. 2
      LeStorage/ModelObject.swift
  3. 2
      LeStorage/Storable.swift
  4. 47
      LeStorage/Store.swift
  5. 19
      LeStorage/StoreCenter.swift
  6. 28
      LeStorage/StoredCollection.swift
  7. 5
      LeStorage/Utils/FileManager+Extensions.swift

@ -8,12 +8,13 @@
import Foundation
protocol ACCollection {
protocol SomeCallCollection {
func findCallById(_ id: String) async -> (any SomeCall)?
func deleteById(_ id: String) async
func hasPendingAPICalls() async -> Bool
func contentOfApiCallFile() async -> String?
func hasPendingCalls() async -> Bool
func contentOfFile() async -> String?
func reset() async
@ -22,11 +23,8 @@ protocol ACCollection {
/// ApiCallCollection is an object communicating with a server to synchronize data managed locally
/// The Api calls are serialized and stored in a JSON file
/// Failing Api calls are stored forever and will be executed again later
actor ApiCallCollection<T: Storable>: ACCollection {
actor ApiCallCollection<T: Storable>: SomeCallCollection {
/// The reference to the Store
// fileprivate var _store: Store
/// The list of api calls
fileprivate(set) var items: [ApiCall<T>] = []
@ -46,9 +44,6 @@ actor ApiCallCollection<T: Storable>: ACCollection {
}
}
}
// init() {
// }
/// Starts the JSON file decoding synchronously or asynchronously
/// Reschedule Api calls if not empty
@ -282,7 +277,7 @@ actor ApiCallCollection<T: Storable>: ACCollection {
}
/// Returns the content of the API call file as a String
func contentOfApiCallFile() -> String? {
func contentOfFile() -> String? {
guard let fileURL = try? self._urlForJSONFile() else { return nil }
if FileManager.default.fileExists(atPath: fileURL.path()) {
return try? FileUtils.readFile(fileURL: fileURL)
@ -291,7 +286,7 @@ actor ApiCallCollection<T: Storable>: ACCollection {
}
/// Returns if the API call collection is not empty
func hasPendingAPICalls() -> Bool {
func hasPendingCalls() -> Bool {
return self.items.isNotEmpty
}

@ -22,4 +22,6 @@ open class ModelObject {
return false
}
static var relationshipNames: [String] = []
}

@ -33,6 +33,8 @@ public protocol Storable: Codable, Identifiable where ID : StringProtocol {
func copyFromServerInstance(_ instance: any Storable) -> Bool
static var relationshipNames: [String] { get }
}
extension Storable {

@ -44,6 +44,8 @@ open class Store {
fileprivate(set) var identifier: StoreIdentifier? = nil
fileprivate var _created: Bool = false
public init() {
self._createDirectory(directory: Store.storageDirectory)
}
@ -55,7 +57,7 @@ open class Store {
}
fileprivate func _createDirectory(directory: String) {
FileManager.default.createDirectoryInDocuments(directoryName: directory)
self._created = FileManager.default.createDirectoryInDocuments(directoryName: directory)
}
/// A method to provide ids corresponding to the django storage
@ -71,6 +73,10 @@ open class Store {
let collection = StoredCollection<T>(synchronized: synchronized, store: self, indexed: indexed, inMemory: inMemory, sendsUpdate: sendsUpdate)
self._collections[T.resourceName()] = collection
if self._created, let identifier {
self._migrate(collection, identifier: identifier, type: T.self)
}
return collection
}
@ -186,4 +192,43 @@ open class Store {
try await StoreCenter.main.sendDeletion(instance)
}
fileprivate var _validIds: [String] = []
fileprivate func _migrate<T : Storable>(_ collection: StoredCollection<T>, identifier: StoreIdentifier, type: T.Type) {
self._validIds.append(identifier.value)
let oldCollection: StoredCollection<T> = StoredCollection<T>(synchronized: false, store: Store.main, asynchronousIO: false)
let filtered: [T] = oldCollection.items.filter { item in
var propertyValue: String? = item.stringForPropertyName(identifier.parameterName)
if propertyValue == nil {
let values = T.relationshipNames.map { item.stringForPropertyName($0) }
propertyValue = values.compactMap { $0 }.first
}
return self._validIds.first(where: { $0 == propertyValue }) != nil
}
self._validIds.append(contentsOf: filtered.map { $0.stringId })
try? collection.addOrUpdate(contentOfs: filtered)
Logger.log("Migrated \(filtered.count) \(T.resourceName())")
}
}
extension Storable {
func stringForPropertyName(_ propertyName: String) -> String? {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
// Logger.log("child.label = \(child.label)")
if let label = child.label, label == "_\(propertyName)" {
return child.value as? String
}
}
Logger.log("returns nil")
return nil
}
}

@ -32,7 +32,7 @@ public class StoreCenter {
fileprivate var _services: Services?
/// The dictionary of registered StoredCollections
fileprivate var _apiCallCollections: [String : any ACCollection] = [:]
fileprivate var _apiCallCollections: [String : any SomeCallCollection] = [:]
fileprivate var _failedAPICallsCollection: StoredCollection<FailedAPICall>? = nil
@ -234,7 +234,7 @@ public class StoreCenter {
/// Returns whether any collection has pending API calls
public func hasPendingAPICalls() async -> Bool {
for collection in self._apiCallCollections.values {
if await collection.hasPendingAPICalls() {
if await collection.hasPendingCalls() {
return true
}
}
@ -243,7 +243,7 @@ public class StoreCenter {
/// Returns the content of the api call file
public func apiCallsFileContent(resourceName: String) async -> String {
return await self._apiCallCollections[resourceName]?.contentOfApiCallFile() ?? ""
return await self._apiCallCollections[resourceName]?.contentOfFile() ?? ""
}
/// This method triggers the framework to save and send failed api calls
@ -314,19 +314,6 @@ public class StoreCenter {
return !self.blackListedUserName.contains(where: { $0 == userName } )
}
// fileprivate func _registerStore(identifier: String, parameter: String) -> Store {
// let store = Store(identifier: identifier, parameter: parameter)
// self._stores[identifier] = store
// return store
// }
//
// public func store(identifier: String, parameter: String) -> Store {
// if let store = self._stores[identifier] {
// return store
// }
// return self._registerStore(identifier: identifier, parameter: parameter)
// }
public func destroyStore(identifier: String) {
let directory = "\(Store.storageDirectory)/\(identifier)"
FileManager.default.deleteDirectoryInDocuments(directoryName: directory)

@ -171,14 +171,22 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
for var item in decoded {
item.store = self._store
}
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)
if self.asynchronousIO {
DispatchQueue.main.async {
self._setItems(decoded)
}
} else {
self._setItems(decoded)
}
}
}
// 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 {
// Task {
// do {
@ -191,6 +199,12 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
}
fileprivate func _setItems(_ items: [T]) {
self.items = items
self._updateIndexIfNecessary()
NotificationCenter.default.post(name: NSNotification.Name.CollectionDidLoad, object: self)
}
/// Updates the whole index with the items array
fileprivate func _updateIndexIfNecessary() {
if let _ = self._indexes {

@ -9,7 +9,7 @@ import Foundation
extension FileManager {
func createDirectoryInDocuments(directoryName: String) {
func createDirectoryInDocuments(directoryName: String) -> Bool {
let documentsDirectory = self.urls(for: .documentDirectory, in: .userDomainMask).first!
let directoryURL = documentsDirectory.appendingPathComponent(directoryName)
@ -19,6 +19,9 @@ extension FileManager {
} catch {
Logger.error(error)
}
return true
} else {
return false
}
}

Loading…
Cancel
Save