mirror of
https://github.com/home-assistant/iOS.git
synced 2026-02-16 18:58:17 -06:00
115 lines
4.0 KiB
Swift
115 lines
4.0 KiB
Swift
import Foundation
|
|
import PromiseKit
|
|
|
|
public class StorageSensor: SensorProvider {
|
|
public enum StorageError: Error, Equatable {
|
|
case noData
|
|
case invalidData
|
|
case missingData(URLResourceKey)
|
|
}
|
|
|
|
public let request: SensorProviderRequest
|
|
public required init(request: SensorProviderRequest) {
|
|
self.request = request
|
|
}
|
|
|
|
#if os(watchOS)
|
|
public func sensors() -> Promise<[WebhookSensor]> {
|
|
.init(error: StorageError.noData)
|
|
}
|
|
#else
|
|
public func sensors() -> Promise<[WebhookSensor]> {
|
|
firstly {
|
|
Promise<Void>.value(())
|
|
}.map(on: .global(qos: .userInitiated)) {
|
|
if let volumes = Current.device.volumes(), volumes.isEmpty == false {
|
|
return volumes
|
|
} else {
|
|
throw StorageError.noData
|
|
}
|
|
}.map { (volumes: [URLResourceKey: Int64]) -> [WebhookSensor] in
|
|
try [Self.sensor(for: volumes)]
|
|
}
|
|
}
|
|
|
|
private static func sensor(for volumes: [URLResourceKey: Int64]) throws -> WebhookSensor {
|
|
let sensor = WebhookSensor(
|
|
name: "Storage",
|
|
uniqueID: WebhookSensorId.storage.rawValue,
|
|
icon: .databaseIcon,
|
|
state: "Unknown"
|
|
)
|
|
|
|
let values = try Values(volumes: volumes)
|
|
sensor.State = values.availablePercent()
|
|
sensor.UnitOfMeasurement = "% available"
|
|
|
|
sensor.Attributes = [
|
|
"Total": values.byteString(for: \.total),
|
|
"Available": values.byteString(for: \.availableOverall),
|
|
"Available (Important)": values.byteString(for: \.availableImportant),
|
|
"Available (Opportunistic)": values.byteString(for: \.availableOpportunistic),
|
|
]
|
|
|
|
return sensor
|
|
}
|
|
|
|
struct Values {
|
|
let availableOverall: Int64
|
|
let availableImportant: Int64
|
|
let availableOpportunistic: Int64
|
|
let total: Int64
|
|
|
|
private let formatter = with(ByteCountFormatter()) {
|
|
$0.allowedUnits = [.useGB, .useMB]
|
|
$0.countStyle = .file
|
|
$0.allowsNonnumericFormatting = false
|
|
$0.formattingContext = .standalone
|
|
$0.zeroPadsFractionDigits = true
|
|
}
|
|
|
|
// Custom formatter for consistent locale-independent formatting
|
|
private let numberFormatter = with(NumberFormatter()) {
|
|
$0.numberStyle = .decimal
|
|
$0.minimumFractionDigits = 2
|
|
$0.maximumFractionDigits = 2
|
|
$0.locale = Locale(identifier: "en_US_POSIX")
|
|
}
|
|
|
|
init(volumes: [URLResourceKey: Int64]) throws {
|
|
func value(of key: URLResourceKey) throws -> Int64 {
|
|
if let value = volumes[key] {
|
|
return value
|
|
} else {
|
|
throw StorageError.missingData(key)
|
|
}
|
|
}
|
|
|
|
self.availableOverall = try value(of: .volumeAvailableCapacityKey)
|
|
self.availableImportant = try value(of: .volumeAvailableCapacityForImportantUsageKey)
|
|
self.availableOpportunistic = try value(of: .volumeAvailableCapacityForOpportunisticUsageKey)
|
|
self.total = try value(of: .volumeTotalCapacityKey)
|
|
|
|
guard total > 0 else {
|
|
throw StorageError.invalidData
|
|
}
|
|
}
|
|
|
|
func availablePercent() -> String {
|
|
precondition(total > 0, "init should prevent this")
|
|
let percent = Decimal(availableOpportunistic) / Decimal(total) * Decimal(100.0)
|
|
return String(format: "%.02lf", Double(truncating: percent as NSNumber))
|
|
}
|
|
|
|
func byteString(for keyPath: KeyPath<Self, Int64>) -> String {
|
|
let bytes = self[keyPath: keyPath]
|
|
let gb = Double(bytes) / 1_000_000_000.0 // Using decimal GB (1000^3)
|
|
|
|
// Use custom number formatter for consistent locale-independent formatting
|
|
let formattedNumber = numberFormatter.string(from: NSNumber(value: gb)) ?? String(format: "%.2f", gb)
|
|
return "\(formattedNumber) GB"
|
|
}
|
|
}
|
|
#endif
|
|
}
|