From d2257caab7b8e5911f7bbee1b3bb5aa25e56c281 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 14 Jan 2026 16:13:02 -0600 Subject: [PATCH] Update Services, Utilities Summary: - Sources: Services, Utilities - Added symbols: func resolveDirectoryURL, enum Logger - Removed symbols: func resolveDirectoryURL Stats: - 3 files changed, 82 insertions(+), 68 deletions(-) --- .../Services/FileStorageHelper.swift | 111 +++++++----------- .../LocalData/Services/StorageRouter.swift | 13 +- Sources/LocalData/Utilities/Logger.swift | 26 ++++ 3 files changed, 82 insertions(+), 68 deletions(-) create mode 100644 Sources/LocalData/Utilities/Logger.swift diff --git a/Sources/LocalData/Services/FileStorageHelper.swift b/Sources/LocalData/Services/FileStorageHelper.swift index 3f89efe..7fdaaf8 100644 --- a/Sources/LocalData/Services/FileStorageHelper.swift +++ b/Sources/LocalData/Services/FileStorageHelper.swift @@ -30,6 +30,7 @@ actor FileStorageHelper { let baseURL = try appGroupContainerURL(identifier: appGroupIdentifier) let directoryURL = try resolveDirectoryURL(baseURL: baseURL, directory: directory) let url = directoryURL.appendingPathComponent(fileName) + Logger.debug("Writing to App Group file: \(url.path)") try ensureDirectoryExists(at: url.deletingLastPathComponent()) try write(data, to: url, useCompleteFileProtection: useCompleteFileProtection) } @@ -47,22 +48,15 @@ actor FileStorageHelper { fileName: String, useCompleteFileProtection: Bool = false ) throws { - let url = directory.url().appendingPathComponent(fileName) + let directoryURL = try resolveDirectoryURL(directory: directory) + let url = directoryURL.appendingPathComponent(fileName) + Logger.debug("Writing file: \(url.path)") // Ensure directory exists try ensureDirectoryExists(at: url.deletingLastPathComponent()) // Write with appropriate options - var options: Data.WritingOptions = [.atomic] - if useCompleteFileProtection { - options.insert(.completeFileProtection) - } - - do { - try data.write(to: url, options: options) - } catch { - throw StorageError.fileError(error) - } + try write(data, to: url, useCompleteFileProtection: useCompleteFileProtection) } /// Reads data from a file. @@ -75,17 +69,10 @@ actor FileStorageHelper { from directory: FileDirectory, fileName: String ) throws -> Data? { - let url = directory.url().appendingPathComponent(fileName) - - guard FileManager.default.fileExists(atPath: url.path) else { - return nil - } - - do { - return try Data(contentsOf: url) - } catch { - throw StorageError.fileError(error) - } + let directoryURL = try resolveDirectoryURL(directory: directory) + let url = directoryURL.appendingPathComponent(fileName) + Logger.debug("Reading file: \(url.path)") + return try read(from: url) } /// Reads data from an App Group container. @@ -97,6 +84,7 @@ actor FileStorageHelper { let baseURL = try appGroupContainerURL(identifier: appGroupIdentifier) let directoryURL = try resolveDirectoryURL(baseURL: baseURL, directory: directory) let url = directoryURL.appendingPathComponent(fileName) + Logger.debug("Reading App Group file: \(url.path)") return try read(from: url) } @@ -109,17 +97,9 @@ actor FileStorageHelper { from directory: FileDirectory, fileName: String ) throws { - let url = directory.url().appendingPathComponent(fileName) - - guard FileManager.default.fileExists(atPath: url.path) else { - return // File doesn't exist, nothing to delete - } - - do { - try FileManager.default.removeItem(at: url) - } catch { - throw StorageError.fileError(error) - } + let directoryURL = try resolveDirectoryURL(directory: directory) + let url = directoryURL.appendingPathComponent(fileName) + try delete(file: url) } /// Deletes a file from an App Group container. @@ -143,8 +123,13 @@ actor FileStorageHelper { in directory: FileDirectory, fileName: String ) -> Bool { - let url = directory.url().appendingPathComponent(fileName) - return FileManager.default.fileExists(atPath: url.path) + do { + let directoryURL = try resolveDirectoryURL(directory: directory) + let url = directoryURL.appendingPathComponent(fileName) + return FileManager.default.fileExists(atPath: url.path) + } catch { + return false + } } /// Checks if a file exists in an App Group container. @@ -168,17 +153,8 @@ actor FileStorageHelper { /// - Returns: An array of file names. /// - Throws: `StorageError.fileError` if listing fails. public func list(in directory: FileDirectory) throws -> [String] { - let url = directory.url() - - guard FileManager.default.fileExists(atPath: url.path) else { - return [] - } - - do { - return try FileManager.default.contentsOfDirectory(atPath: url.path) - } catch { - throw StorageError.fileError(error) - } + let directoryURL = try resolveDirectoryURL(directory: directory) + return try list(in: directoryURL) } /// Lists all files in an App Group container directory. @@ -198,18 +174,9 @@ actor FileStorageHelper { of directory: FileDirectory, fileName: String ) throws -> Int64? { - let url = directory.url().appendingPathComponent(fileName) - - guard FileManager.default.fileExists(atPath: url.path) else { - return nil - } - - do { - let attributes = try FileManager.default.attributesOfItem(atPath: url.path) - return attributes[.size] as? Int64 - } catch { - throw StorageError.fileError(error) - } + let directoryURL = try resolveDirectoryURL(directory: directory) + let url = directoryURL.appendingPathComponent(fileName) + return try size(of: url) } /// Gets the size of a file in an App Group container. @@ -232,12 +199,14 @@ actor FileStorageHelper { } do { + Logger.debug("Ensuring directory exists: \(url.path)") try FileManager.default.createDirectory( at: url, withIntermediateDirectories: true, attributes: nil ) } catch { + Logger.error("Failed to create directory", error: error) throw StorageError.fileError(error) } } @@ -250,7 +219,9 @@ actor FileStorageHelper { do { try data.write(to: url, options: options) + Logger.debug("Successfully wrote \(data.count) bytes to \(url.lastPathComponent)") } catch { + Logger.error("Failed to write to \(url.path)", error: error) throw StorageError.fileError(error) } } @@ -308,21 +279,27 @@ actor FileStorageHelper { guard let url = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: identifier ) else { + Logger.error("Invalid App Group identifier: \(identifier)") throw StorageError.invalidAppGroupIdentifier(identifier) } + Logger.debug("Resolved App Group container: \(url.path)") return url } - private func resolveDirectoryURL(baseURL: URL, directory: FileDirectory) throws -> URL { + private func resolveDirectoryURL(baseURL: URL? = nil, directory: FileDirectory) throws -> URL { let base: URL - switch directory { - case .documents: - base = baseURL.appending(path: "Documents") - case .caches: - base = baseURL.appending(path: "Library/Caches") - case .custom(let url): - let relativePath = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) - return baseURL.appending(path: relativePath) + if let baseURL = baseURL { + switch directory { + case .documents: + base = baseURL.appending(path: "Documents") + case .caches: + base = baseURL.appending(path: "Library/Caches") + case .custom(let url): + let relativePath = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + return baseURL.appending(path: relativePath) + } + } else { + base = directory.url() } if let subDirectory = configuration.subDirectory { diff --git a/Sources/LocalData/Services/StorageRouter.swift b/Sources/LocalData/Services/StorageRouter.swift index 798fb00..bf55389 100644 --- a/Sources/LocalData/Services/StorageRouter.swift +++ b/Sources/LocalData/Services/StorageRouter.swift @@ -1,4 +1,5 @@ import Foundation + #if os(iOS) || os(watchOS) import WatchConnectivity #endif @@ -66,6 +67,7 @@ public actor StorageRouter: StorageProviding { /// - key: The storage key defining where and how to store. /// - Throws: Various errors depending on the storage domain and security policy. public func set(_ value: Key.Value, for key: Key) async throws { + Logger.debug(">>> [STORAGE] SET: \(key.name) [Domain: \(key.domain)]") try validateCatalogRegistration(for: key) try validatePlatformAvailability(for: key) @@ -74,6 +76,7 @@ public actor StorageRouter: StorageProviding { try await store(securedData, for: key) try await handleSync(key, data: securedData) + Logger.debug("<<< [STORAGE] SET SUCCESS: \(key.name)") } /// Retrieves a value for the given key. @@ -81,15 +84,19 @@ public actor StorageRouter: StorageProviding { /// - Returns: The stored value. /// - Throws: `StorageError.notFound` if no value exists, plus domain-specific errors. public func get(_ key: Key) async throws -> Key.Value { + Logger.debug(">>> [STORAGE] GET: \(key.name)") try validateCatalogRegistration(for: key) try validatePlatformAvailability(for: key) guard let securedData = try await retrieve(for: key) else { + Logger.debug("<<< [STORAGE] GET NOT FOUND: \(key.name)") throw StorageError.notFound } let data = try await applySecurity(securedData, for: key, isEncrypt: false) - return try deserialize(data, with: key.serializer) + let result = try deserialize(data, with: key.serializer) + Logger.debug("<<< [STORAGE] GET SUCCESS: \(key.name)") + return result } /// Removes the value for the given key. @@ -352,15 +359,19 @@ public actor StorageRouter: StorageProviding { private func resolveService(_ service: String?) throws -> String { guard let resolved = service ?? storageConfiguration.defaultKeychainService else { + Logger.error("No keychain service provided and no default configured") throw StorageError.keychainError(errSecBadReq) // Or a more specific error } + Logger.debug("Resolved Keychain Service: \(resolved)") return resolved } private func resolveIdentifier(_ identifier: String?) throws -> String { guard let resolved = identifier ?? storageConfiguration.defaultAppGroupIdentifier else { + Logger.error("No App Group identifier provided and no default configured") throw StorageError.invalidAppGroupIdentifier("none") } + Logger.debug("Resolved App Group ID: \(resolved)") return resolved } } diff --git a/Sources/LocalData/Utilities/Logger.swift b/Sources/LocalData/Utilities/Logger.swift new file mode 100644 index 0000000..a235a00 --- /dev/null +++ b/Sources/LocalData/Utilities/Logger.swift @@ -0,0 +1,26 @@ +import Foundation + +/// Internal logging utility for the LocalData package. +enum Logger { + static var isLoggingEnabled = true + + static func debug(_ message: String) { + #if DEBUG + if isLoggingEnabled { + print(" {LOCAL_DATA} ℹ️ \(message)") + } + #endif + } + + static func error(_ message: String, error: Error? = nil) { + #if DEBUG + var logMessage = " {LOCAL_DATA} ❌ \(message)" + if let error = error { + logMessage += " | Error: \(error.localizedDescription)" + } + if isLoggingEnabled { + print(logMessage) + } + #endif + } +}