From a58c5129fcc31e02cb42fbb025652f470f39401a Mon Sep 17 00:00:00 2001 From: Brendan Szymanski Date: Fri, 5 Jun 2026 01:12:48 -0400 Subject: [PATCH] Add ImageService with memory + disk cache --- Sources/LuminateCore/ImageService.swift | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Sources/LuminateCore/ImageService.swift diff --git a/Sources/LuminateCore/ImageService.swift b/Sources/LuminateCore/ImageService.swift new file mode 100644 index 0000000..827b191 --- /dev/null +++ b/Sources/LuminateCore/ImageService.swift @@ -0,0 +1,50 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public actor ImageService { + private let cacheDir: URL + private let memoryCache = NSCache() + + public init(cacheDir: URL? = nil) { + let defaultCache = FileManager.default.urls( + for: .cachesDirectory, in: .userDomainMask + ).first!.appendingPathComponent("luminate/images") + self.cacheDir = cacheDir ?? defaultCache + try? FileManager.default.createDirectory(at: self.cacheDir, withIntermediateDirectories: true) + } + + public func loadImage(url: URL) async throws -> Data { + let key = url.absoluteString as NSString + if let cached = memoryCache.object(forKey: key) { + return cached as Data + } + let diskKey = url.absoluteString.data(using: .utf8)!.base64EncodedString() + .replacingOccurrences(of: "/", with: "_") + let diskURL = cacheDir.appendingPathComponent(diskKey) + if let data = try? Data(contentsOf: diskURL) { + memoryCache.setObject(data as NSData, forKey: key) + return data + } + let (data, _) = try await URLSession.shared.data(from: url) + let nsData = data as NSData + memoryCache.setObject(nsData, forKey: key) + try? data.write(to: diskURL) + return data + } + + public func prefetch(urls: [URL]) async { + await withTaskGroup(of: Void.self) { group in + for url in urls { + group.addTask { _ = try? await self.loadImage(url: url) } + } + } + } + + public func clearCache() { + memoryCache.removeAllObjects() + try? FileManager.default.removeItem(at: cacheDir) + try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true) + } +}