libsurfer/
state_file_io.rs

1use std::path::PathBuf;
2
3#[cfg(not(target_arch = "wasm32"))]
4use camino::Utf8PathBuf;
5use eyre::Context;
6use rfd::FileHandle;
7
8#[cfg(not(target_arch = "wasm32"))]
9use crate::async_util::perform_async_work;
10
11use crate::{
12    async_util::AsyncJob, message::Message, wave_source::STATE_FILE_EXTENSION, SystemState,
13};
14
15impl SystemState {
16    #[cfg(target_arch = "wasm32")]
17    pub fn load_state_file(&mut self, path: Option<PathBuf>) {
18        if path.is_some() {
19            return;
20        }
21        let message = move |bytes: Vec<u8>| match ron::de::from_bytes(&bytes)
22            .context("Failed loading state file")
23        {
24            Ok(s) => vec![Message::LoadState(s, path)],
25            Err(e) => {
26                log::error!("Failed to load state: {e:#?}");
27                vec![]
28            }
29        };
30        self.file_dialog_open(
31            "Load state",
32            (
33                format!("Surfer state files (*.{STATE_FILE_EXTENSION})"),
34                vec![STATE_FILE_EXTENSION.to_string()],
35            ),
36            message,
37        );
38    }
39
40    #[cfg(not(target_arch = "wasm32"))]
41    pub fn load_state_file(&mut self, path: Option<PathBuf>) {
42        let messages = move |path: PathBuf| {
43            let source = Utf8PathBuf::from_path_buf(path.clone()).unwrap();
44            if let Ok(bytes) = std::fs::read(source.clone()) {
45                match ron::de::from_bytes(&bytes)
46                    .context(format!("Failed loading {}", source.file_name().unwrap()))
47                {
48                    Ok(s) => vec![Message::LoadState(s, Some(path))],
49                    Err(e) => {
50                        log::error!("Failed to load state: {e:#?}");
51                        vec![]
52                    }
53                }
54            } else {
55                log::error!("Failed to load state file: {path:#?}");
56                vec![]
57            }
58        };
59        if let Some(path) = path {
60            let sender = self.channels.msg_sender.clone();
61            for message in messages(path) {
62                sender.send(message).unwrap();
63            }
64        } else {
65            self.file_dialog_open(
66                "Load state",
67                (
68                    format!("Surfer state files (*.{STATE_FILE_EXTENSION})"),
69                    vec![STATE_FILE_EXTENSION.to_string()],
70                ),
71                messages,
72            );
73        }
74    }
75
76    #[cfg(not(target_arch = "wasm32"))]
77    pub fn save_state_file(&mut self, path: Option<PathBuf>) {
78        let Some(encoded) = self.encode_state() else {
79            return;
80        };
81
82        let messages = async move |destination: FileHandle| {
83            destination
84                .write(encoded.as_bytes())
85                .await
86                .map_err(|e| log::error!("Failed to write state to {destination:#?} {e:#?}"))
87                .ok();
88            vec![
89                Message::SetStateFile(destination.path().into()),
90                Message::AsyncDone(AsyncJob::SaveState),
91            ]
92        };
93        if let Some(path) = path {
94            let sender = self.channels.msg_sender.clone();
95            perform_async_work(async move {
96                for message in messages(path.into()).await {
97                    sender.send(message).unwrap();
98                }
99            });
100        } else {
101            self.file_dialog_save(
102                "Save state",
103                (
104                    format!("Surfer state files (*.{STATE_FILE_EXTENSION})"),
105                    ([STATE_FILE_EXTENSION.to_string()]).to_vec(),
106                ),
107                messages,
108            );
109        }
110    }
111
112    #[cfg(target_arch = "wasm32")]
113    pub fn save_state_file(&mut self, path: Option<PathBuf>) {
114        if path.is_some() {
115            return;
116        }
117        let Some(encoded) = self.encode_state() else {
118            return;
119        };
120        let messages = async move |destination: FileHandle| {
121            destination
122                .write(encoded.as_bytes())
123                .await
124                .map_err(|e| log::error!("Failed to write state to {destination:#?} {e:#?}"))
125                .ok();
126            vec![Message::AsyncDone(AsyncJob::SaveState)]
127        };
128        self.file_dialog_save(
129            "Save state",
130            (
131                format!("Surfer state files (*.{STATE_FILE_EXTENSION})"),
132                ([STATE_FILE_EXTENSION.to_string()]).to_vec(),
133            ),
134            messages,
135        );
136    }
137
138    fn encode_state(&self) -> Option<String> {
139        let opt = ron::Options::default();
140
141        opt.to_string_pretty(&self.user, ron::ser::PrettyConfig::default())
142            .context("Failed to encode state")
143            .map_err(|e| log::error!("Failed to encode state. {e:#?}"))
144            .ok()
145    }
146}