| 1 | import type { HostPowerSnapshot } from "@t3tools/contracts"; |
| 2 | import * as Context from "effect/Context"; |
| 3 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Option from "effect/Option"; |
| 7 | import * as PubSub from "effect/PubSub"; |
| 8 | import * as Ref from "effect/Ref"; |
| 9 | import * as Stream from "effect/Stream"; |
| 10 | |
| 11 | import * as DesktopTelemetryReceiver from "../resourceTelemetry/DesktopTelemetryReceiver.ts"; |
| 12 | |
| 13 | export class HostPowerMonitor extends Context.Service< |
| 14 | HostPowerMonitor, |
| 15 | { |
| 16 | readonly snapshot: Effect.Effect<HostPowerSnapshot>; |
| 17 | readonly report: (snapshot: HostPowerSnapshot) => Effect.Effect<void>; |
| 18 | readonly streamChanges: Stream.Stream<HostPowerSnapshot>; |
| 19 | } |
| 20 | >()("t3/background/HostPowerMonitor") {} |
| 21 | |
| 22 | const makeUnknownSnapshot = ( |
| 23 | source: HostPowerSnapshot["source"], |
| 24 | updatedAt: HostPowerSnapshot["updatedAt"], |
| 25 | ): HostPowerSnapshot => ({ |
| 26 | source, |
| 27 | idle: "unknown", |
| 28 | idleSeconds: null, |
| 29 | locked: "unknown", |
| 30 | suspended: false, |
| 31 | onBattery: "unknown", |
| 32 | lowPowerMode: "unknown", |
| 33 | thermalState: "unknown", |
| 34 | stale: true, |
| 35 | updatedAt, |
| 36 | }); |
| 37 | |
| 38 | function samePowerState(left: HostPowerSnapshot, right: HostPowerSnapshot): boolean { |
| 39 | return ( |
| 40 | left.source === right.source && |
| 41 | left.idle === right.idle && |
| 42 | left.locked === right.locked && |
| 43 | left.suspended === right.suspended && |
| 44 | left.onBattery === right.onBattery && |
| 45 | left.lowPowerMode === right.lowPowerMode && |
| 46 | left.thermalState === right.thermalState && |
| 47 | left.stale === right.stale |
| 48 | ); |
| 49 | } |
| 50 | |
| 51 | export const make = Effect.fn("background.hostPower.make")(function* ( |
| 52 | initialSnapshot?: HostPowerSnapshot, |
| 53 | ) { |
| 54 | const initial = initialSnapshot ?? makeUnknownSnapshot("unknown", yield* DateTime.now); |
| 55 | const latestRef = yield* Ref.make(initial); |
| 56 | const changes = yield* PubSub.sliding<HostPowerSnapshot>(1); |
| 57 | |
| 58 | const report: HostPowerMonitor["Service"]["report"] = (snapshot) => |
| 59 | Ref.modify(latestRef, (current) => { |
| 60 | if (DateTime.isLessThan(snapshot.updatedAt, current.updatedAt)) { |
| 61 | return [Option.none<HostPowerSnapshot>(), current] as const; |
| 62 | } |
| 63 | return [ |
| 64 | samePowerState(current, snapshot) ? Option.none() : Option.some(snapshot), |
| 65 | snapshot, |
| 66 | ] as const; |
| 67 | }).pipe( |
| 68 | Effect.flatMap( |
| 69 | Option.match({ |
| 70 | onNone: () => Effect.void, |
| 71 | onSome: (next) => PubSub.publish(changes, next), |
| 72 | }), |
| 73 | ), |
| 74 | Effect.asVoid, |
| 75 | ); |
| 76 | |
| 77 | return HostPowerMonitor.of({ |
| 78 | snapshot: Ref.get(latestRef), |
| 79 | report, |
| 80 | streamChanges: Stream.fromPubSub(changes), |
| 81 | }); |
| 82 | }); |
| 83 | |
| 84 | export const layer = Layer.effect( |
| 85 | HostPowerMonitor, |
| 86 | Effect.gen(function* () { |
| 87 | const desktopTelemetry = yield* DesktopTelemetryReceiver.DesktopTelemetryReceiver; |
| 88 | const desktopSubscription = yield* desktopTelemetry.subscribe; |
| 89 | const initial = desktopSubscription.latest; |
| 90 | const monitor = yield* Option.match(initial, { |
| 91 | onNone: () => make(), |
| 92 | onSome: (snapshot) => make(snapshot.power), |
| 93 | }); |
| 94 | yield* desktopSubscription.changes.pipe( |
| 95 | Stream.map((snapshot) => snapshot.power), |
| 96 | Stream.runForEach(monitor.report), |
| 97 | Effect.forkScoped, |
| 98 | ); |
| 99 | return monitor; |
| 100 | }), |
| 101 | ); |