From 0c255cc2565ebafabb30fb86be5806df76ea0932 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 6 May 2024 14:19:43 +0200 Subject: [PATCH] Cleanup unused stuff + Renamed StoredObject and convenience methods --- LeStorage.xcodeproj/project.pbxproj | 8 +- LeStorage/MicroStorage.swift | 4 +- LeStorage/Services.swift | 4 +- LeStorage/Store.swift | 100 ++++-------- LeStorage/StoredCollection.swift | 63 ++++++-- ...oredObject.swift => StoredSingleton.swift} | 7 +- LeStorage/Wip/Migration.swift | 152 +++++++++--------- 7 files changed, 165 insertions(+), 173 deletions(-) rename LeStorage/{StoredObject.swift => StoredSingleton.swift} (65%) diff --git a/LeStorage.xcodeproj/project.pbxproj b/LeStorage.xcodeproj/project.pbxproj index 3647d37..c69dcd5 100644 --- a/LeStorage.xcodeproj/project.pbxproj +++ b/LeStorage.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ C425D4442B6D24E1002A7B48 /* LeStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4432B6D24E1002A7B48 /* LeStorageTests.swift */; }; C425D4452B6D24E1002A7B48 /* LeStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = C425D4372B6D24E1002A7B48 /* LeStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C425D4582B6D2519002A7B48 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4572B6D2519002A7B48 /* Store.swift */; }; - C456EFE22BE52379007388E2 /* StoredObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C456EFE12BE52379007388E2 /* StoredObject.swift */; }; + C456EFE22BE52379007388E2 /* StoredSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C456EFE12BE52379007388E2 /* StoredSingleton.swift */; }; C49EF0242BD6BDC50077B5AA /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */; }; C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */; }; C4A47D512B6D2C4E00ADC637 /* Codable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D502B6D2C4E00ADC637 /* Codable+Extensions.swift */; }; @@ -48,7 +48,7 @@ C425D43E2B6D24E1002A7B48 /* LeStorageTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LeStorageTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C425D4432B6D24E1002A7B48 /* LeStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeStorageTests.swift; sourceTree = ""; }; C425D4572B6D2519002A7B48 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; - C456EFE12BE52379007388E2 /* StoredObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredObject.swift; sourceTree = ""; }; + C456EFE12BE52379007388E2 /* StoredSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredSingleton.swift; sourceTree = ""; }; C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = ""; }; C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCollection.swift; sourceTree = ""; }; C4A47D502B6D2C4E00ADC637 /* Codable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Codable+Extensions.swift"; sourceTree = ""; }; @@ -116,7 +116,7 @@ C425D4572B6D2519002A7B48 /* Store.swift */, C4A47D642B6E92FE00ADC637 /* Storable.swift */, C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */, - C456EFE12BE52379007388E2 /* StoredObject.swift */, + C456EFE12BE52379007388E2 /* StoredSingleton.swift */, C4A47D932B7CF7C500ADC637 /* MicroStorage.swift */, C4A47D822B7665BC00ADC637 /* Wip */, C4A47D582B6D352900ADC637 /* Utils */, @@ -280,7 +280,7 @@ C4A47DAF2B85FD3800ADC637 /* Errors.swift in Sources */, C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */, C4A47D552B6D2DBF00ADC637 /* FileUtils.swift in Sources */, - C456EFE22BE52379007388E2 /* StoredObject.swift in Sources */, + C456EFE22BE52379007388E2 /* StoredSingleton.swift in Sources */, C4A47D652B6E92FE00ADC637 /* Storable.swift in Sources */, C4A47D6D2B71364600ADC637 /* ModelObject.swift in Sources */, C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */, diff --git a/LeStorage/MicroStorage.swift b/LeStorage/MicroStorage.swift index b6b9958..3111e6b 100644 --- a/LeStorage/MicroStorage.swift +++ b/LeStorage/MicroStorage.swift @@ -35,10 +35,10 @@ public class MicroStorage { public func update(handler: (T) -> ()) { handler(self.item) - self._write() + self.write() } - fileprivate func _write() { + func write() { do { let jsonString: String = try self.item.jsonString() let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: T.fileName) diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index 5ffbbf2..7a1cd65 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -146,7 +146,7 @@ public class Services { request.httpMethod = method.rawValue request.setValue("application/json", forHTTPHeaderField: "Content-Type") if !(requiresToken == false), let token = try? self.keychainStore.getToken() { - Logger.log("current token = \(token)") +// Logger.log("current token = \(token)") request.addValue("Token \(token)", forHTTPHeaderField: "Authorization") } @@ -182,7 +182,7 @@ public class Services { do { let token = try self.keychainStore.getToken() - Logger.log("current token = \(token)") +// Logger.log("current token = \(token)") request.setValue("Token \(token)", forHTTPHeaderField: "Authorization") } catch { Logger.log("missing token") diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 41f5bdd..1ff5bf5 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -51,19 +51,18 @@ public class Store { /// The dictionary of registered StoredCollections fileprivate var _collections: [String : any SomeCollection] = [:] -// /// The dictionary of ApiCall StoredCollections corresponding to the synchronized registered collections -// fileprivate var _apiCallsCollections: [String : any SomeCollection] = [:] - - /// The list of migrations to apply - fileprivate var _migrations: [SomeMigration] = [] - - /// The collection of performed migration on the store - fileprivate lazy var _migrationCollection: StoredCollection = { StoredCollection(synchronized: false, store: Store.main, asynchronousIO: false) - }() - - fileprivate var settingsStorage: MicroStorage = MicroStorage() + /// A store for the Settings object + fileprivate var _settingsStorage: MicroStorage = MicroStorage() + /// The name of the directory to store the json files static let storageDirectory = "storage" + + /// Indicates to Stored Collection if they can synchronize + public var collectionsCanSynchronize: Bool = true { + didSet { + Logger.log(">>> collectionsCanSynchronize = \(self.collectionsCanSynchronize)") + } + } public init() { FileManager.default.createDirectoryInDocuments(directoryName: Store.storageDirectory) @@ -87,10 +86,10 @@ public class Store { return collection } - public func registerObject(synchronized: Bool, inMemory: Bool = false, sendsUpdate: Bool = true) -> StoredObject { + public func registerObject(synchronized: Bool, inMemory: Bool = false, sendsUpdate: Bool = true) -> StoredSingleton { // register collection - let storedObject = StoredObject(synchronized: synchronized, store: Store.main, inMemory: inMemory, sendsUpdate: sendsUpdate, loadCompletion: nil) + let storedObject = StoredSingleton(synchronized: synchronized, store: Store.main, inMemory: inMemory, sendsUpdate: sendsUpdate, loadCompletion: nil) self._collections[T.resourceName()] = storedObject return storedObject @@ -99,18 +98,25 @@ public class Store { // MARK: - Settings func setUserUUID(uuidString: String) { - self.settingsStorage.update { settings in + self._settingsStorage.update { settings in settings.userId = uuidString } } - public func currentUserUUID() -> UUID { - if let uuidString = self.settingsStorage.item.userId, - let uuid = UUID(uuidString: uuidString) { + public var currentUserUUID: UUID? { + if let uuidString = self._settingsStorage.item.userId, + let uuid = UUID(uuidString: uuidString) { + return uuid + } + return nil + } + + public func mandatoryUserUUID() -> UUID { + if let uuid = self.currentUserUUID { return uuid } else { let uuid = UIDevice.current.identifierForVendor ?? UUID() - self.settingsStorage.update { settings in + self._settingsStorage.update { settings in settings.userId = uuid.uuidString } return uuid @@ -118,18 +124,18 @@ public class Store { } func userName() -> String? { - return self.settingsStorage.item.username + return self._settingsStorage.item.username } func setUserName(_ username: String) { - self.settingsStorage.update { settings in + self._settingsStorage.update { settings in settings.username = username } } public func disconnect(resetAll: Bool = false) { try? self.service().disconnect() - self.settingsStorage.update { settings in + self._settingsStorage.update { settings in settings.username = nil settings.userId = nil } @@ -182,47 +188,7 @@ public class Store { public func deleteDependencies(items: any Sequence) throws { try self.collection().deleteDependencies(items) } - - // MARK: - Migration - - /// [beta] Adds a migration to perform - public func addMigration(_ migration: SomeMigration) { - self._migrations.append(migration) - } - - /// [beta] Performs the migration if necessary - func performMigrationIfNecessary(_ collection: StoredCollection) async throws { - // Check for migrations - let migrations = self._migrations.filter { $0.resourceName == T.resourceName() } - if migrations.isEmpty { return } - - // Check for applied migrations - var version: Int = -1 - var performedMigration: MigrationHistory? = self._migrationCollection.first(where: { $0.resourceName == T.resourceName() }) - if let performedMigration { - version = Int(performedMigration.version) - } - - // Apply necessary migrations - let applicableMigrations = migrations.filter { $0.version > version } - .sorted(keyPath: \.version) - for migration in applicableMigrations { - - Logger.log("Start migration for \(migration.resourceName), version: \(migration.version)") - - try migration.migrate(synchronized: collection.synchronized) - - if let performedMigration { - performedMigration.version = migration.version - } else { - performedMigration = MigrationHistory(version: migration.version, resourceName: migration.resourceName) - } - - try self._migrationCollection.addOrUpdate(instance: performedMigration!) - } - } - // MARK: - Api call rescheduling /// Deletes an ApiCall by [id] and [collectionName] @@ -236,6 +202,9 @@ public class Store { /// Reschedule an ApiCall by id func rescheduleApiCall(id: String, type: T.Type) throws { + guard self.collectionsCanSynchronize else { + return + } let collection: StoredCollection = try self.collection() collection.rescheduleApiCallsIfNecessary() } @@ -245,11 +214,6 @@ public class Store { return try await self.service().runApiCall(apiCall) } - /// Executes an ApiCall -// func execute(apiCall: ApiCall) async throws { -// _ = try await self._executeApiCall(apiCall) -// } - func execute(apiCall: ApiCall) async throws -> T { return try await self._executeApiCall(apiCall) } @@ -265,9 +229,9 @@ public class Store { } } - public func loadCollections() { + public func loadCollectionFromServer() { for collection in self._collections.values { - try? collection.loadDataFromServer() + try? collection.loadDataFromServerIfAllowed() } } diff --git a/LeStorage/StoredCollection.swift b/LeStorage/StoredCollection.swift index c1c8726..185efc9 100644 --- a/LeStorage/StoredCollection.swift +++ b/LeStorage/StoredCollection.swift @@ -18,9 +18,10 @@ protocol SomeCollection: Identifiable { func deleteById(_ id: String) throws func deleteApiCallById(_ id: String) throws func reset() - func loadDataFromServer() throws + func loadDataFromServerIfAllowed() throws } + extension Notification.Name { public static let CollectionDidLoad: Notification.Name = Notification.Name.init("notification.collectionDidLoad") public static let CollectionDidChange: Notification.Name = Notification.Name.init("notification.collectionDidChange") @@ -102,9 +103,9 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti try content.write(to: fileURL, atomically: false, encoding: .utf8) } - fileprivate func _pathForFile(_ fileName: String) throws -> URL { + fileprivate func _urlForJSONFile() throws -> URL { var storageDirectory = try self._storageDirectoryPath() - storageDirectory.append(component: fileName) + storageDirectory.append(component: T.fileName()) return storageDirectory } @@ -115,7 +116,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti do { if self._inMemory { - try self.loadDataFromServer() + try self.loadDataFromServerIfAllowed() } else { try self._loadFromFile() } @@ -126,12 +127,12 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } fileprivate func _loadFromFile() throws { - let url: URL = try self._pathForFile(T.fileName()) + 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 await Store.main.performMigrationIfNecessary(self) try self._decodeJSONFile() } } else { @@ -143,7 +144,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Decodes the json file into the items array fileprivate func _decodeJSONFile() throws { - let fileURL = try self._pathForFile(T.fileName()) + let fileURL = try self._urlForJSONFile() let jsonString: String = try FileUtils.readFile(fileURL: fileURL) if let decoded: [T] = try jsonString.decodeArray() { DispatchQueue.main.async { @@ -165,13 +166,14 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } /// Retrieves the data from the server and loads it into the items array - public func loadDataFromServer() throws { - guard self.synchronized else { + public func loadDataFromServerIfAllowed() throws { + guard self.synchronized, !(self is StoredSingleton) else { throw StoreError.unSynchronizedCollection } Task { do { - self.items = try await self._store.getItems() + let items: [T] = try await self._store.getItems() + try self._addOrUpdate(contentOfs: items, shouldSync: false) self._hasChanged = true } catch { Logger.error(error) @@ -201,6 +203,14 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } + func setSingletonNoSync(instance: T) { + defer { + self._hasChanged = true + } + self.items.removeAll() + self.items.append(instance) + } + /// Deletes the instance in the collection by id public func delete(instance: T) throws { @@ -230,8 +240,12 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti } } - /// Inserts or updates all items in the sequence public func addOrUpdate(contentOfs sequence: any Sequence) throws { + try self._addOrUpdate(contentOfs: sequence) + } + + /// Inserts or updates all items in the sequence + fileprivate func _addOrUpdate(contentOfs sequence: any Sequence, shouldSync: Bool = true) throws { defer { self._hasChanged = true } @@ -239,11 +253,15 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti for instance in sequence { if let index = self.items.firstIndex(where: { $0.id == instance.id }) { self.items[index] = instance - try self._sendUpdateIfNecessary(instance) + if shouldSync { + try self._sendUpdateIfNecessary(instance) + } } else { // insert self.items.append(instance) self._index?[instance.stringId] = instance - try self._sendInsertionIfNecessary(instance) + if shouldSync { + try self._sendInsertionIfNecessary(instance) + } } } @@ -307,9 +325,20 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti Logger.log("End write") } + func clear() { + self.items.removeAll() + } + func reset() { self.items.removeAll() - try? FileUtils.removeFileFromDocumentDirectory(fileName: T.fileName()) + + do { + let url: URL = try self._urlForJSONFile() + try FileManager.default.removeItem(at: url) + } catch { + Logger.error(error) + } + if let apiCallsCollection = self.apiCallsCollection { apiCallsCollection.reset() } @@ -357,7 +386,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Sends an insert api call for the provided [instance] fileprivate func _sendInsertionIfNecessary(_ instance: T) throws { - guard self.synchronized else { + guard self.synchronized, Store.main.collectionsCanSynchronize else { return } @@ -379,7 +408,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Sends an update api call for the provided [instance] fileprivate func _sendUpdateIfNecessary(_ instance: T) throws { - guard self.synchronized, self._sendsUpdate else { + guard self.synchronized, self._sendsUpdate, Store.main.collectionsCanSynchronize else { return } @@ -399,7 +428,7 @@ public class StoredCollection: RandomAccessCollection, SomeCollecti /// Sends an delete api call for the provided [instance] fileprivate func _sendDeletionIfNecessary(_ instance: T) throws { - guard self.synchronized else { + guard self.synchronized, Store.main.collectionsCanSynchronize else { return } diff --git a/LeStorage/StoredObject.swift b/LeStorage/StoredSingleton.swift similarity index 65% rename from LeStorage/StoredObject.swift rename to LeStorage/StoredSingleton.swift index 1882583..2da2579 100644 --- a/LeStorage/StoredObject.swift +++ b/LeStorage/StoredSingleton.swift @@ -7,11 +7,10 @@ import Foundation -public class StoredObject: StoredCollection { +public class StoredSingleton: StoredCollection { - public func setItem(_ instance: T) throws { - self.reset() - try self.addOrUpdate(instance: instance) + public func setItemNoSync(_ instance: T) throws { + self.setSingletonNoSync(instance: instance) } public func update() throws { diff --git a/LeStorage/Wip/Migration.swift b/LeStorage/Wip/Migration.swift index 67bcc5c..9baa215 100644 --- a/LeStorage/Wip/Migration.swift +++ b/LeStorage/Wip/Migration.swift @@ -7,79 +7,79 @@ import Foundation -public protocol MigrationSource : Storable { - associatedtype Destination : Storable - func migrate() -> Destination -} - -public protocol SomeMigration { - func migrate(synchronized: Bool) throws - var version: UInt { get } - var resourceName: String { get } -} - -public class Migration : SomeMigration where S.Destination == D { - - public var version: UInt - - public var resourceName: String { - return S.resourceName() - } - - public init(version: UInt) { - self.version = version - } - - public func migrate(synchronized: Bool) throws { - try self._migrateMainCollection() - if synchronized { - try self._migrateApiCallsCollection() - } - } - - fileprivate func _migrateMainCollection() throws { - let jsonString = try FileUtils.readDocumentFile(fileName: S.fileName()) - if let decoded: [S] = try jsonString.decodeArray() { - - let migratedObjects: [D] = decoded.map { $0.migrate() } - - let jsonString: String = try migratedObjects.jsonString() - let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: D.fileName()) - } - } - - fileprivate func _migrateApiCallsCollection() throws { - let jsonString = try FileUtils.readDocumentFile(fileName: ApiCall.fileName()) - if let apiCalls: [ApiCall] = try jsonString.decodeArray() { - - let migratedCalls = try apiCalls.map { apiCall in - if let source: S = try apiCall.body.decode() { - let migrated: D = source.migrate() - apiCall.body = try migrated.jsonString() - } - return apiCall - } - - let jsonString = try migratedCalls.jsonString() - let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: ApiCall.fileName()) - - Logger.log("Ended _migrateApiCallsCollection: \(jsonString)") - } - } - -} - -class MigrationHistory: ModelObject, Storable { - - public static func resourceName() -> String { "migration_history" } - - public var id: String = Store.randomId() - public var version: UInt - public var resourceName: String - - init(version: UInt, resourceName: String) { - self.version = version - self.resourceName = resourceName - } - -} +//public protocol MigrationSource : Storable { +// associatedtype Destination : Storable +// func migrate() -> Destination +//} +// +//public protocol SomeMigration { +// func migrate(synchronized: Bool) throws +// var version: UInt { get } +// var resourceName: String { get } +//} +// +//public class Migration : SomeMigration where S.Destination == D { +// +// public var version: UInt +// +// public var resourceName: String { +// return S.resourceName() +// } +// +// public init(version: UInt) { +// self.version = version +// } +// +// public func migrate(synchronized: Bool) throws { +// try self._migrateMainCollection() +// if synchronized { +// try self._migrateApiCallsCollection() +// } +// } +// +// fileprivate func _migrateMainCollection() throws { +// let jsonString = try FileUtils.readDocumentFile(fileName: S.fileName()) +// if let decoded: [S] = try jsonString.decodeArray() { +// +// let migratedObjects: [D] = decoded.map { $0.migrate() } +// +// let jsonString: String = try migratedObjects.jsonString() +// let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: D.fileName()) +// } +// } +// +// fileprivate func _migrateApiCallsCollection() throws { +// let jsonString = try FileUtils.readDocumentFile(fileName: ApiCall.fileName()) +// if let apiCalls: [ApiCall] = try jsonString.decodeArray() { +// +// let migratedCalls = try apiCalls.map { apiCall in +// if let source: S = try apiCall.body.decode() { +// let migrated: D = source.migrate() +// apiCall.body = try migrated.jsonString() +// } +// return apiCall +// } +// +// let jsonString = try migratedCalls.jsonString() +// let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: ApiCall.fileName()) +// +// Logger.log("Ended _migrateApiCallsCollection: \(jsonString)") +// } +// } +// +//} +// +//class MigrationHistory: ModelObject, Storable { +// +// public static func resourceName() -> String { "migration_history" } +// +// public var id: String = Store.randomId() +// public var version: UInt +// public var resourceName: String +// +// init(version: UInt, resourceName: String) { +// self.version = version +// self.resourceName = resourceName +// } +// +//}