diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index ecf6b5f..f4aa3ab 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.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: ACCollection { +actor ApiCallCollection: SomeCallCollection { - /// The reference to the Store -// fileprivate var _store: Store - /// The list of api calls fileprivate(set) var items: [ApiCall] = [] @@ -46,9 +44,6 @@ actor ApiCallCollection: ACCollection { } } } - -// init() { -// } /// Starts the JSON file decoding synchronously or asynchronously /// Reschedule Api calls if not empty @@ -282,7 +277,7 @@ actor ApiCallCollection: 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: ACCollection { } /// Returns if the API call collection is not empty - func hasPendingAPICalls() -> Bool { + func hasPendingCalls() -> Bool { return self.items.isNotEmpty } diff --git a/LeStorage/ModelObject.swift b/LeStorage/ModelObject.swift index e0ee0e0..7958a52 100644 --- a/LeStorage/ModelObject.swift +++ b/LeStorage/ModelObject.swift @@ -22,4 +22,6 @@ open class ModelObject { return false } + static var relationshipNames: [String] = [] + } diff --git a/LeStorage/Storable.swift b/LeStorage/Storable.swift index 73babe0..5d1ffc1 100644 --- a/LeStorage/Storable.swift +++ b/LeStorage/Storable.swift @@ -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 { diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 7db2737..b47b021 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -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(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(_ collection: StoredCollection, identifier: StoreIdentifier, type: T.Type) { + + self._validIds.append(identifier.value) + + let oldCollection: StoredCollection = StoredCollection(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 + } + } diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index d4dde07..17653b8 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -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? = 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) diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 9366c79..1d7d10c 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -171,14 +171,22 @@ public class StoredCollection: 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: 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 { diff --git a/LeStorage/Utils/FileManager+Extensions.swift b/LeStorage/Utils/FileManager+Extensions.swift index 44579fc..010cd89 100644 --- a/LeStorage/Utils/FileManager+Extensions.swift +++ b/LeStorage/Utils/FileManager+Extensions.swift @@ -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 } }