photonic_interface_restore/
lib.rs1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::pin::pin;
4use std::sync::Arc;
5use std::time::Duration;
6
7use anyhow::{anyhow, Context, Result};
8use photonic::attr::Range;
9use serde::{Deserialize, Serialize};
10use tokio_stream::{StreamExt, StreamMap};
11
12use photonic::color::palette::rgb::Rgb;
13use photonic::input::{AnyInputValue, InputSink};
14use photonic::interface::{Interface, Introspection};
15
16#[derive(Serialize, Deserialize)]
17#[serde(untagged)]
18enum InputValue {
19 Boolean(bool),
20 Integer(i64),
21 Decimal(f32),
22 Color(Rgb),
23 IntegerRange(i64, i64),
24 DecimalRange(f32, f32),
25 ColorRange(Rgb, Rgb),
26}
27
28pub struct Restore {
29 pub path: PathBuf,
30
31 pub write_threshold: usize,
32 pub write_timeout: Duration,
33}
34
35impl Restore {}
36
37impl Interface for Restore {
38 async fn listen(self, introspection: Arc<Introspection>) -> Result<()> {
39 let mut data = if let Ok(data) = tokio::fs::read(&self.path).await {
41 serde_json::from_slice(&data).with_context(|| format!("Invalid restore data: {}", self.path.display()))?
42 } else {
43 HashMap::new()
44 };
45
46 for (name, value) in data.iter() {
48 let input = if let Some(input) = introspection.inputs.get(name) {
49 input
50 } else {
51 continue;
52 };
53 let result = match (&input.sink(), value) {
54 (InputSink::Boolean(sink), InputValue::Boolean(value)) => sink.send(*value).await,
55 (InputSink::Integer(sink), InputValue::Integer(value)) => sink.send(*value).await,
56 (InputSink::Decimal(sink), InputValue::Decimal(value)) => sink.send(*value).await,
57 (InputSink::Color(sink), InputValue::Color(value)) => sink.send(*value).await,
58 (InputSink::IntegerRange(sink), InputValue::IntegerRange(a, b)) => sink.send(Range::new(*a, *b)).await,
59 (InputSink::DecimalRange(sink), InputValue::DecimalRange(a, b)) => sink.send(Range::new(*a, *b)).await,
60 (InputSink::ColorRange(sink), InputValue::ColorRange(a, b)) => sink.send(Range::new(*a, *b)).await,
61 (_, _) => Err(anyhow!("Restore data type mismatch: {} - ignoring", name)),
62 };
63
64 if let Err(err) = result {
65 eprintln!("Failed to restore input value: {err}");
66 }
67 }
68
69 let inputs = introspection
71 .inputs
72 .iter()
73 .map(|(name, input)| (name.clone(), input.subscribe()))
74 .collect::<StreamMap<_, _>>();
75
76 let mut inputs = pin!(inputs.chunks_timeout(self.write_threshold, self.write_timeout));
78
79 loop {
80 let values = inputs.next().await.expect("Inputs never close");
81
82 for (name, value) in values {
84 let value = match value {
85 AnyInputValue::Trigger => continue, AnyInputValue::Boolean(value) => InputValue::Boolean(value),
87 AnyInputValue::Integer(value) => InputValue::Integer(value),
88 AnyInputValue::Decimal(value) => InputValue::Decimal(value),
89 AnyInputValue::Color(value) => InputValue::Color(value),
90 AnyInputValue::IntegerRange(value) => InputValue::IntegerRange(value.0, value.1),
91 AnyInputValue::DecimalRange(value) => InputValue::DecimalRange(value.0, value.1),
92 AnyInputValue::ColorRange(value) => InputValue::ColorRange(value.0, value.1),
93 };
94
95 data.insert(name, value);
96 }
97
98 let data = serde_json::to_vec(&data).context("Failed to serialize restore data")?;
99
100 tokio::fs::write(&self.path, data)
101 .await
102 .with_context(|| format!("Failed to persist restore data: {}", self.path.display()))?;
103 }
104 }
105}