plato_hook_helper/
lib.rs

1//! # plato-hook-helper
2//! `plato-hook-helper` is a set of utility functions to assist with writing fetch hooks for the
3//! [Plato](https://github.com/baskerville/plato) e-reader document system.
4
5use std::io::{BufRead, BufReader, Read, Stdin, Stdout, Write};
6
7use serde::{Deserialize, Serialize};
8
9/// The status of the e-reader's Wi-Fi.
10pub enum WifiStatus {
11    /// The Wi-Fi is turned on, allowing network connections to be made.
12    Enabled,
13    /// The Wi-Fi is turned off, no network connections can be made.
14    Disabled,
15}
16
17impl From<WifiStatus> for bool {
18    fn from(f: WifiStatus) -> bool {
19        match f {
20            WifiStatus::Enabled => true,
21            WifiStatus::Disabled => false,
22        }
23    }
24}
25
26/// The structure of a notification event. Used to display a message on the device.
27#[derive(Serialize, Deserialize)]
28struct NotificationEvent {
29    r#type: String,
30    message: String,
31}
32
33/// The structure of a Wi-Fi event. Used to enable or disable the device's Wi-Fi.
34#[derive(Serialize, Deserialize)]
35struct WifiEvent {
36    r#type: String,
37    enable: bool,
38}
39
40/// The structure of a network event. Used to signal when the device's network status changes.
41#[derive(Serialize, Deserialize, PartialEq, Debug)]
42pub struct NetworkEvent {
43    r#type: String,
44    status: String,
45}
46
47/// A helper struct for interacting with the Plato e-reader software. Holds a writer to output JSON
48/// to, by default `stdout`.
49pub struct PlatoHelper<W: Write, R: Read> {
50    writer: W,
51    reader: R,
52}
53
54impl Default for PlatoHelper<Stdout, Stdin> {
55    fn default() -> Self {
56        PlatoHelper {
57            writer: std::io::stdout(),
58            reader: std::io::stdin(),
59        }
60    }
61}
62
63impl<W: Write, R: Read> PlatoHelper<W, R> {
64    pub fn new(writer: W, reader: R) -> Self {
65        PlatoHelper { writer, reader }
66    }
67
68    /// Take's a serializable struct and writes it to the internal writer as a JSON string.
69    fn write_json<T: Serialize>(&mut self, value: &T) -> std::io::Result<()> {
70        let json = serde_json::to_string(value)
71            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
72        self.writer.write_all(json.as_bytes())?;
73        Ok(())
74    }
75
76    /// Reads JSON from the internal reader, blocking until a valid JSON string is received matching
77    /// the given type `J`.
78    fn read_json_blocking<J>(&mut self) -> Result<J, std::io::Error>
79    where
80        J: for<'de> Deserialize<'de>,
81    {
82        let mut reader = BufReader::new(&mut self.reader);
83        let mut input = String::new();
84        loop {
85            input.clear();
86            reader.read_line(&mut input)?;
87            if let Ok(json) = serde_json::from_str(&input) {
88                return Ok(json);
89            }
90        }
91    }
92
93    /// Displays a notification on the device with the given `message`.
94    pub fn display_notification(&mut self, message: &str) -> std::io::Result<()> {
95        let event = NotificationEvent {
96            r#type: "notify".to_string(),
97            message: message.to_string(),
98        };
99        self.write_json(&event)
100    }
101
102    /// Sets the device's Wi-Fi state to `status`.
103    pub fn set_wifi(&mut self, status: WifiStatus) -> std::io::Result<()> {
104        let event = WifiEvent {
105            r#type: "setWifi".to_string(),
106            enable: status.into(),
107        };
108        self.write_json(&event)
109    }
110
111    /// Waits until a network event is received from the internal reader.
112    /// This function will block indefinitely until a valid event is received or an IO error occurs.
113    pub fn wait_for_network_blocking(&mut self) -> Result<NetworkEvent, std::io::Error> {
114        self.read_json_blocking()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use std::io::{BufWriter, Cursor};
121
122    use super::*;
123
124    #[test]
125    fn notification_formatting() {
126        let mut buffer = Vec::new();
127        let writer = BufWriter::new(&mut buffer);
128        {
129            let mut plato = PlatoHelper::new(writer, std::io::stdin());
130            plato.display_notification("Hello, World!").unwrap();
131        }
132
133        let notification = NotificationEvent {
134            r#type: "notify".to_string(),
135            message: "Hello, World!".to_string(),
136        };
137
138        let event = serde_json::to_string(&notification).unwrap();
139        let output = String::from_utf8(buffer).unwrap();
140
141        assert_eq!(event, output);
142    }
143
144    #[test]
145    fn wait_for_network_blocking_deserializes_correctly() {
146        let json = r#"{"type": "network", "status": "up"}"#;
147        let reader = Cursor::new(json);
148        let mut plato = PlatoHelper::new(Vec::new(), reader);
149        let result: Result<NetworkEvent, std::io::Error> = plato.wait_for_network_blocking();
150
151        assert_eq!(
152            result.unwrap(),
153            NetworkEvent {
154                r#type: "network".to_string(),
155                status: "up".to_string()
156            }
157        );
158    }
159}