Fixes and improvements

multistore
Laurent 1 year ago
parent 67516d6df6
commit 136f8b99f1
  1. 2
      LeStorage/ApiCallCollection.swift
  2. 11
      LeStorage/Services.swift
  3. 28
      LeStorage/Store.swift
  4. 51
      LeStorage/StoreCenter.swift
  5. 46
      LeStorage/StoredCollection.swift
  6. 4
      LeStorage/Utils/KeychainStore.swift

@ -44,7 +44,7 @@ actor ApiCallCollection<T: Storable>: SomeCallCollection {
}
}
}
/// Starts the JSON file decoding synchronously or asynchronously
/// Reschedule Api calls if not empty
func loadFromFile() throws {

@ -193,12 +193,12 @@ public class Services {
/// - requiresToken: An optional boolean to indicate if the token is required
/// - identifier: an optional StoreIdentifier that allows to filter GET requests with the StoreIdentifier values
fileprivate func _baseRequest(servicePath: String, method: HTTPMethod, requiresToken: Bool? = nil, identifier: StoreIdentifier? = nil) throws -> URLRequest {
let urlString = baseURL + servicePath
guard var url = URL(string: urlString) else {
throw ServiceError.urlCreationError(url: urlString)
}
var urlString = baseURL + servicePath
if let identifier {
url.append(path: identifier.urlComponent)
urlString.append(identifier.urlComponent)
}
guard let url = URL(string: urlString) else {
throw ServiceError.urlCreationError(url: urlString)
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
@ -207,7 +207,6 @@ public class Services {
let token = try self.keychainStore.getToken()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
return request
}

@ -19,7 +19,7 @@ public enum StoreError: Error {
case unexpectedCollectionType(name: String)
case apiCallCollectionNotRegistered(type: String)
case collectionNotRegistered(type: String)
case unSynchronizedCollection
case cannotSyncCollection(name: String)
}
public struct StoreIdentifier {
@ -82,6 +82,10 @@ open class Store {
let collection = StoredCollection<T>(synchronized: synchronized, store: self, indexed: indexed, inMemory: inMemory, sendsUpdate: sendsUpdate)
self._collections[T.resourceName()] = collection
if synchronized {
StoreCenter.main.loadApiCallCollection(type: T.self)
}
if self._created, let identifier {
self._migrate(collection, identifier: identifier, type: T.self)
}
@ -227,6 +231,19 @@ open class Store {
try await StoreCenter.main.sendDeletion(instance)
}
public func loadCollectionsFromServerIfNoFile() {
for (name, collection) in self._collections {
// Logger.log("Load \(name)")
Task {
do {
try await collection.loadCollectionsFromServerIfNoFile()
} catch {
Logger.error(error)
}
}
}
}
fileprivate var _validIds: [String] = []
fileprivate func _migrate<T : Storable>(_ collection: StoredCollection<T>, identifier: StoreIdentifier, type: T.Type) {
@ -236,7 +253,6 @@ open class Store {
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) }
@ -244,10 +260,12 @@ open class Store {
}
return self._validIds.first(where: { $0 == propertyValue }) != nil
}
self._validIds.append(contentsOf: filtered.map { $0.stringId })
try? collection.addOrUpdateNoSync(contentOfs: filtered)
Logger.log("Migrated \(filtered.count) \(T.resourceName())")
if filtered.count > 0 {
self._validIds.append(contentsOf: filtered.map { $0.stringId })
try? collection.addOrUpdateNoSync(contentOfs: filtered)
Logger.log("Migrated \(filtered.count) \(T.resourceName())")
}
}
}

@ -41,7 +41,7 @@ public class StoreCenter {
fileprivate var _blackListedUserName: [String] = []
init() {
self._loadExistingApiCollections()
// self._loadExistingApiCollections()
}
/// Returns the service instance
@ -77,12 +77,6 @@ public class StoreCenter {
}
}
fileprivate func _loadExistingApiCollections() {
let string = "clubs"
}
// MARK: - Settings
/// Stores the user UUID
@ -117,26 +111,13 @@ public class StoreCenter {
/// Disconnect the user from the storage and resets collection
public func disconnect() {
try? self.service().deleteToken()
self.resetApiCalls()
self._settingsStorage.update { settings in
settings.username = nil
settings.userId = nil
}
// switch resetOption {
// case .all:
// for collection in self._collections.values {
// collection.reset()
// }
// case .synchronizedOnly:
// for collection in self._collections.values {
// if collection.synchronized {
// collection.reset()
// }
// }
// default:
// break
// }
}
/// Returns whether the system has a user token
@ -149,16 +130,28 @@ public class StoreCenter {
}
}
/// Returns or create the ApiCall collection matching the provided T type
func getOrCreateApiCallCollection<T: Storable>() -> ApiCallCollection<T> {
if let apiCallCollection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection<T> {
return apiCallCollection
}
func loadApiCallCollection<T: Storable>(type: T.Type) {
let apiCallCollection = ApiCallCollection<T>()
self._apiCallCollections[T.resourceName()] = apiCallCollection
return apiCallCollection
Task {
do {
try await apiCallCollection.loadFromFile()
} catch {
Logger.error(error)
}
}
}
/// Returns or create the ApiCall collection matching the provided T type
// func getOrCreateApiCallCollection<T: Storable>() -> ApiCallCollection<T> {
// if let apiCallCollection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection<T> {
// return apiCallCollection
// }
// let apiCallCollection = ApiCallCollection<T>()
// self._apiCallCollections[T.resourceName()] = apiCallCollection
// return apiCallCollection
// }
/// Returns the ApiCall collection using the resource name of the provided T type
func apiCallCollection<T: Storable>() throws -> ApiCallCollection<T> {
if let collection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection<T> {
@ -350,6 +343,8 @@ public class StoreCenter {
public func destroyStore(identifier: String) {
let directory = "\(Store.storageDirectory)/\(identifier)"
FileManager.default.deleteDirectoryInDocuments(directoryName: directory)
self._stores.removeValue(forKey: identifier)
}
/// Returns whether the collection can synchronize

@ -27,14 +27,7 @@ protocol SomeCollection: CollectionHolder, Identifiable {
func allItems() -> [any Storable]
func loadDataFromServerIfAllowed() async throws
// func resetApiCalls()
// func deleteApiCallById(_ id: String) async throws
// func apiCallById(_ id: String) async -> (any SomeCall)?
// func hasPendingAPICalls() async -> Bool
// func contentOfApiCallFile() async -> String?
func loadCollectionsFromServerIfNoFile() async throws
}
@ -98,20 +91,6 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
self._inMemory = inMemory
self._sendsUpdate = sendsUpdate
self._store = store
// self.loadCompletion = loadCompletion
// if synchronized {
// let apiCallCollection = ApiCallCollection<T>()
// self.apiCallsCollection = apiCallCollection
// Task {
// do {
// try await apiCallCollection.loadFromFile()
// } catch {
// Logger.error(error)
// }
// }
//
// }
self._load()
}
@ -178,16 +157,8 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
} 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 {
}
// else if self.synchronized {
// Task {
// do {
// try await self.loadDataFromServerIfAllowed()
@ -196,7 +167,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
// }
// }
// }
}
fileprivate func _setItems(_ items: [T]) {
@ -215,7 +186,7 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
/// Retrieves the data from the server and loads it into the items array
public func loadDataFromServerIfAllowed() async throws {
guard self.synchronized, !(self is StoredSingleton<T>) else {
throw StoreError.unSynchronizedCollection
throw StoreError.cannotSyncCollection(name: self.resourceName)
}
do {
let items: [T] = try await self._store.getItems()
@ -229,6 +200,13 @@ public class StoredCollection<T: Storable>: RandomAccessCollection, SomeCollecti
}
}
func loadCollectionsFromServerIfNoFile() async throws {
let fileURL: URL = try self._store.fileURL(type: T.self)
if !FileManager.default.fileExists(atPath: fileURL.path()) {
try await self.loadDataFromServerIfAllowed()
}
}
// MARK: - Basic operations
/// Adds or updates the provided instance inside the collection

@ -8,7 +8,7 @@
import Foundation
enum KeychainError: Error {
case noPassword
case keychainItemNotFound(serverId: String)
case unexpectedPasswordData
case unhandledError(status: OSStatus)
}
@ -42,7 +42,7 @@ class KeychainStore {
var item: CFTypeRef?
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
guard status != errSecItemNotFound else { throw KeychainError.keychainItemNotFound(serverId: self.serverId) }
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
guard let existingItem = item as? [String : Any],

Loading…
Cancel
Save