diff --git a/LeStorage/ApiCallCollection.swift b/LeStorage/ApiCallCollection.swift index f4aa3ab..68f5d0d 100644 --- a/LeStorage/ApiCallCollection.swift +++ b/LeStorage/ApiCallCollection.swift @@ -44,7 +44,7 @@ actor ApiCallCollection: SomeCallCollection { } } } - + /// Starts the JSON file decoding synchronously or asynchronously /// Reschedule Api calls if not empty func loadFromFile() throws { diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index eff8eb1..df339a6 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -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 } diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index d5cbe4b..94f9b20 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -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(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(_ collection: StoredCollection, identifier: StoreIdentifier, type: T.Type) { @@ -236,7 +253,6 @@ open class Store { 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) } @@ -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())") + } } } diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index e8886bb..e70fede 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -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() -> ApiCallCollection { - if let apiCallCollection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection { - return apiCallCollection - } + func loadApiCallCollection(type: T.Type) { let apiCallCollection = ApiCallCollection() 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() -> ApiCallCollection { +// if let apiCallCollection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection { +// return apiCallCollection +// } +// let apiCallCollection = ApiCallCollection() +// self._apiCallCollections[T.resourceName()] = apiCallCollection +// return apiCallCollection +// } + /// Returns the ApiCall collection using the resource name of the provided T type func apiCallCollection() throws -> ApiCallCollection { if let collection = self._apiCallCollections[T.resourceName()] as? ApiCallCollection { @@ -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 diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index 32d4d57..bed6b54 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -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: RandomAccessCollection, SomeCollecti self._inMemory = inMemory self._sendsUpdate = sendsUpdate self._store = store -// self.loadCompletion = loadCompletion - -// if synchronized { -// let apiCallCollection = ApiCallCollection() -// self.apiCallsCollection = apiCallCollection -// Task { -// do { -// try await apiCallCollection.loadFromFile() -// } catch { -// Logger.error(error) -// } -// } -// -// } self._load() } @@ -178,16 +157,8 @@ public class StoredCollection: 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: RandomAccessCollection, SomeCollecti // } // } // } - + } fileprivate func _setItems(_ items: [T]) { @@ -215,7 +186,7 @@ public class StoredCollection: 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) 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: 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 diff --git a/LeStorage/Utils/KeychainStore.swift b/LeStorage/Utils/KeychainStore.swift index cef6348..c0f4d11 100644 --- a/LeStorage/Utils/KeychainStore.swift +++ b/LeStorage/Utils/KeychainStore.swift @@ -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],