photonic_interface_restore/
lib.rs

1use 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        // Read existing restore data, if possible
40        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        // Try to find and restore all inputs from data
47        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        // Merge all inputs into a stream of (name, value)
70        let inputs = introspection
71            .inputs
72            .iter()
73            .map(|(name, input)| (name.clone(), input.subscribe()))
74            .collect::<StreamMap<_, _>>();
75
76        // Form chunks by size and timeout
77        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            // Persist the values in the aggregated view
83            for (name, value) in values {
84                let value = match value {
85                    AnyInputValue::Trigger => continue, // Skip triggers
86                    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}