108 lines
3.3 KiB
Swift
108 lines
3.3 KiB
Swift
import Foundation
|
|
import PromiseKit
|
|
import SwiftUI
|
|
|
|
public protocol DiskCache {
|
|
func value<T: Codable>(for key: String) -> Promise<T>
|
|
func set<T: Codable>(_ value: T, for key: String) -> Promise<Void>
|
|
}
|
|
|
|
private struct DiskCacheKey: EnvironmentKey {
|
|
static let defaultValue = Current.diskCache
|
|
}
|
|
|
|
// also in AppEnvironment
|
|
public extension EnvironmentValues {
|
|
var diskCache: DiskCache {
|
|
get { self[DiskCacheKey.self] }
|
|
set { self[DiskCacheKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
public final class DiskCacheImpl: DiskCache {
|
|
public func value<T: Codable>(for key: String) -> Promise<T> {
|
|
let (promise, seal) = Promise<T>.pending()
|
|
DispatchQueue.global().async { [coordinator, container] in
|
|
var coordinatorError: NSError?
|
|
coordinator.coordinate(
|
|
readingItemAt: Self.URL(in: container, for: key),
|
|
error: &coordinatorError
|
|
) { url in
|
|
do {
|
|
let data = try Data(contentsOf: url)
|
|
let value = try JSONDecoder().decode(T.self, from: data)
|
|
seal.fulfill(value)
|
|
} catch {
|
|
seal.reject(error)
|
|
}
|
|
}
|
|
|
|
if let error = coordinatorError {
|
|
seal.reject(error)
|
|
}
|
|
}
|
|
return promise
|
|
}
|
|
|
|
public func set(_ value: some Codable, for key: String) -> Promise<Void> {
|
|
let data: Data
|
|
|
|
do {
|
|
// the contents of the value may be unsafe off the thread this is called on
|
|
// we can at least move the write operation itself off the thread
|
|
data = try JSONEncoder().encode(value)
|
|
} catch {
|
|
return .init(error: error)
|
|
}
|
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
DispatchQueue.global().async { [coordinator, container] in
|
|
var coordinatorError: NSError?
|
|
coordinator.coordinate(
|
|
writingItemAt: Self.URL(in: container, for: key),
|
|
error: &coordinatorError
|
|
) { url in
|
|
do {
|
|
try data.write(to: url)
|
|
seal.fulfill(())
|
|
} catch {
|
|
seal.reject(error)
|
|
}
|
|
}
|
|
if let error = coordinatorError {
|
|
seal.reject(error)
|
|
}
|
|
}
|
|
return promise
|
|
}
|
|
|
|
private class func URL(containerName: String) -> URL {
|
|
let fileManager = FileManager.default
|
|
let url = AppConstants.AppGroupContainer
|
|
.appendingPathComponent("DiskCache", isDirectory: true)
|
|
.appendingPathComponent(containerName, isDirectory: false)
|
|
|
|
try? fileManager.createDirectory(
|
|
at: url,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
|
|
return url
|
|
}
|
|
|
|
var container: URL
|
|
var coordinator: NSFileCoordinator
|
|
|
|
init(containerName: String = "Default") {
|
|
self.coordinator = NSFileCoordinator()
|
|
self.container = Self.URL(containerName: containerName)
|
|
}
|
|
|
|
static func URL(in container: URL, for key: String) -> URL {
|
|
let escapedKey = key.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? key
|
|
|
|
return container.appendingPathComponent("\(escapedKey).json", isDirectory: false)
|
|
}
|
|
}
|