diff --git a/LeStorage.xcodeproj/project.pbxproj b/LeStorage.xcodeproj/project.pbxproj index 7580f1e..64c61aa 100644 --- a/LeStorage.xcodeproj/project.pbxproj +++ b/LeStorage.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ C4A47D9C2B7CFFE000ADC637 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D9A2B7CFFC500ADC637 /* Settings.swift */; }; C4A47DAF2B85FD3800ADC637 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAE2B85FD3800ADC637 /* Errors.swift */; }; C4FC2E292C2B2EC30021F3BF /* StoreCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E282C2B2EC30021F3BF /* StoreCenter.swift */; }; + C4FC2E312C353E7B0021F3BF /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FC2E302C353E7B0021F3BF /* Log.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,6 +72,7 @@ C4A47D9A2B7CFFC500ADC637 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; C4A47DAE2B85FD3800ADC637 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4FC2E282C2B2EC30021F3BF /* StoreCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCenter.swift; sourceTree = ""; }; + C4FC2E302C353E7B0021F3BF /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -165,9 +167,10 @@ C4A47D9D2B7CFFF500ADC637 /* Codables */ = { isa = PBXGroup; children = ( - C4A47D9A2B7CFFC500ADC637 /* Settings.swift */, C4A47D992B7CFFC500ADC637 /* ApiCall.swift */, C45D35902C0A1DB5000F379F /* FailedAPICall.swift */, + C4FC2E302C353E7B0021F3BF /* Log.swift */, + C4A47D9A2B7CFFC500ADC637 /* Settings.swift */, ); path = Codables; sourceTree = ""; @@ -284,6 +287,7 @@ files = ( C4A47D532B6D2C5F00ADC637 /* Logger.swift in Sources */, C4A47D842B7B97F000ADC637 /* KeychainStore.swift in Sources */, + C4FC2E312C353E7B0021F3BF /* Log.swift in Sources */, C4A47D512B6D2C4E00ADC637 /* Codable+Extensions.swift in Sources */, C425D4392B6D24E1002A7B48 /* LeStorage.docc in Sources */, C4A47DAF2B85FD3800ADC637 /* Errors.swift in Sources */, diff --git a/LeStorage/Codables/Log.swift b/LeStorage/Codables/Log.swift new file mode 100644 index 0000000..943fb97 --- /dev/null +++ b/LeStorage/Codables/Log.swift @@ -0,0 +1,26 @@ +// +// Log.swift +// LeStorage +// +// Created by Laurent Morvillier on 03/07/2024. +// + +import Foundation + +class Log: ModelObject, Storable { + + static func resourceName() -> String { return "logs" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + static func filterByStoreIdentifier() -> Bool { return false } + + var id: String = Store.randomId() + + var date: Date = Date() + + var message: String + + init(message: String) { + self.message = message + } + +} diff --git a/LeStorage/Services.swift b/LeStorage/Services.swift index 4b29dd7..8f38be3 100644 --- a/LeStorage/Services.swift +++ b/LeStorage/Services.swift @@ -103,12 +103,15 @@ public class Services { let statusCode = response.statusCode Logger.log("request ended with status code = \(statusCode)") switch statusCode { - case 200..<300: - if let apiCallId, - let collectionName = (T.self as? any Storable.Type)?.resourceName() { - try await StoreCenter.main.deleteApiCallById(apiCallId, collectionName: collectionName) + case 200..<300: // success + if let apiCallId { + if let collectionName = (T.self as? any Storable.Type)?.resourceName() { + try await StoreCenter.main.deleteApiCallById(apiCallId, collectionName: collectionName) + } else { + StoreCenter.main.log(message: "collectionName not found for \(type(of: T.self)), could not delete ApiCall \(apiCallId)") + } } - default: + default: // error Logger.log("Failed Run \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")") let errorString: String = String(data: task.0, encoding: .utf8) ?? "" var errorMessage = ErrorMessage(error: errorString, domain: "") @@ -126,7 +129,12 @@ public class Services { throw ServiceError.responseError(response: errorMessage.error) } + } else { + let message: String = "Unexpected and unmanaged URL Response \(task.1)" + StoreCenter.main.log(message: message) + Logger.w(message) } + return try jsonDecoder.decode(T.self, from: task.0) } diff --git a/LeStorage/Store.swift b/LeStorage/Store.swift index 33aa576..981c8aa 100644 --- a/LeStorage/Store.swift +++ b/LeStorage/Store.swift @@ -238,6 +238,7 @@ open class Store { return try await StoreCenter.main.sendDeletion(instance) } + /// Loads all synchronized collection with server data if they don't already have a local file public func loadCollectionsFromServerIfNoFile() { for collection in self._collections.values { // Logger.log("Load \(name)") @@ -253,6 +254,7 @@ open class Store { } } + /// Returns whether all collections have loaded locally public func collectionsAllLoaded() -> Bool { return self._collections.values.allSatisfy { $0.hasLoadedLocally } } diff --git a/LeStorage/StoreCenter.swift b/LeStorage/StoreCenter.swift index c0c188e..34ca0fd 100644 --- a/LeStorage/StoreCenter.swift +++ b/LeStorage/StoreCenter.swift @@ -37,6 +37,8 @@ public class StoreCenter { /// A collection storing FailedAPICall objects fileprivate var _failedAPICallsCollection: StoredCollection? = nil + fileprivate var _logs: StoredCollection? = nil + /// A list of username that cannot synchronize with the server fileprivate var _blackListedUserName: [String] = [] @@ -382,4 +384,26 @@ public class StoreCenter { return try await self.apiCallCollection().sendDeletion(instance) } + // MARK: - Logs + + fileprivate func _logsCollection() -> StoredCollection { + if let logs = self._logs { + return logs + } else { + let logsCollection: StoredCollection = Store.main.registerCollection(synchronized: true) + self._logs = logsCollection + return logsCollection + } + } + + /// Logs a message in the logs collection + public func log(message: String) { + let log = Log(message: message) + do { + try self._logsCollection().addOrUpdate(instance: log) + } catch { + Logger.error(error) + } + } + } diff --git a/LeStorage/Utils/Logger.swift b/LeStorage/Utils/Logger.swift index fcc4fd7..4d79490 100644 --- a/LeStorage/Utils/Logger.swift +++ b/LeStorage/Utils/Logger.swift @@ -35,7 +35,7 @@ import Foundation @objc static public func w(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) { let filestr: NSString = NSString(string: file) - print("Warning: \(filestr.lastPathComponent).\(line).\(function): \(message)") + print("!!! Warning !!! \(filestr.lastPathComponent).\(line).\(function): \(message)") } @objc static public func crashLogging(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {