iOS/Sources/Shared/Common/DiskCache.swift
Bruno Pantaleão Gonçalves 186aea4fa7
Create Watch Home from iOS (#2944)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

- Create watch home from iPhone
- Configure Assist for watch from iPhone
- Deprecate message for iOS Actions
- Use Scripts or Scenes directly
- Show/Hide Assist in Apple Watch
- Options to require confirmation before running watch item
(script/scene/action)

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->
![CleanShot 2024-08-27 at 15 52
39@2x](https://github.com/user-attachments/assets/5360c60d-9638-40b9-b799-d12668bd579f)


https://github.com/user-attachments/assets/1e4509f8-45e5-4b39-bfdc-62f9bce64617


## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->
2024-08-28 09:45:07 +02:00

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)
}
}