loopers_engine/
session.rs

1use crate::looper;
2use crate::looper::Looper;
3use crate::{last_session_path, MetricStructure};
4use chrono::Local;
5use crossbeam_channel::{bounded, Sender, TrySendError};
6use std::collections::HashMap;
7use std::fs::{create_dir_all, File};
8use std::io::Write;
9use std::path::PathBuf;
10use std::thread;
11use std::time::{Duration, Instant};
12
13use crate::error::SaveLoadError;
14use loopers_common::api::{QuantizationMode, SavedSession};
15use loopers_common::gui_channel::{GuiSender, LogMessage};
16use std::sync::Arc;
17
18const LOOPER_SAVE_TIMEOUT: Duration = Duration::from_secs(10);
19
20pub struct SaveSessionData {
21    pub metric_structure: MetricStructure,
22    pub metronome_volume: u8,
23    pub sync_mode: QuantizationMode,
24    pub path: Arc<PathBuf>,
25    pub sample_rate: usize,
26}
27
28pub enum SessionCommand {
29    SaveSession(SaveSessionData),
30    AddLooper(u32, Sender<looper::ControlMessage>),
31    RemoveLooper(u32),
32}
33
34#[derive(Clone)]
35pub struct SessionSaver {
36    channel: Sender<SessionCommand>,
37}
38
39impl SessionSaver {
40    pub fn new(mut gui_channel: GuiSender) -> SessionSaver {
41        let (tx, rx) = bounded(10);
42
43        thread::spawn(move || {
44            let mut loopers: HashMap<u32, Sender<looper::ControlMessage>> = HashMap::new();
45
46            loop {
47                match rx.recv() {
48                    Ok(SessionCommand::SaveSession(sd)) => {
49                        if let Err(e) = Self::execute_save_session(sd, &loopers, &mut gui_channel) {
50                            let mut log = LogMessage::error();
51                            if let Err(e) = write!(log, "Failed to save session: {:?}", e) {
52                                error!("Failed to write error message: {}", e);
53                            } else {
54                                gui_channel.send_log(log);
55                            }
56                        }
57                    }
58                    Ok(SessionCommand::AddLooper(id, tx)) => {
59                        loopers.insert(id, tx);
60                    }
61                    Ok(SessionCommand::RemoveLooper(id)) => {
62                        loopers.remove(&id);
63                    }
64                    Err(_) => {
65                        debug!("channel closed, stopping");
66                        break;
67                    }
68                }
69            }
70        });
71
72        SessionSaver { channel: tx }
73    }
74
75    fn execute_save_session(
76        sd: SaveSessionData,
77        loopers: &HashMap<u32, Sender<looper::ControlMessage>>,
78        gui_channel: &mut GuiSender,
79    ) -> Result<(), SaveLoadError> {
80        let now = Local::now();
81        let mut path = (&*sd.path).clone();
82        path.push(now.format("%Y-%m-%d_%H:%M:%S").to_string());
83
84        create_dir_all(&path)?;
85
86        let mut session = SavedSession {
87            save_time: now.timestamp_millis(),
88            metric_structure: sd.metric_structure.to_saved(),
89            metronome_volume: sd.metronome_volume,
90            sync_mode: sd.sync_mode,
91            sample_rate: sd.sample_rate,
92            loopers: Vec::with_capacity(loopers.len()),
93        };
94
95        let mut channels = vec![];
96        for (id, l) in loopers.iter() {
97            let (tx, rx) = bounded(1);
98
99            l.send(looper::ControlMessage::Serialize(path.clone(), tx))
100                .map_err(|_f| SaveLoadError::LooperSaveError(*id))?;
101
102            channels.push((*id, rx));
103        }
104
105        let start = Instant::now();
106        let mut timeout = LOOPER_SAVE_TIMEOUT;
107        for (id, c) in channels {
108            session.loopers.push(
109                c.recv_timeout(timeout)
110                    .map_err(|_| SaveLoadError::LooperSaveError(id))??,
111            );
112
113            timeout = timeout
114                .checked_sub(start.elapsed())
115                .ok_or(SaveLoadError::LooperTimeoutError)?;
116        }
117
118        path.push("project.loopers");
119        let mut file = File::create(&path)?;
120
121        match serde_json::to_string_pretty(&session) {
122            Ok(v) => {
123                writeln!(file, "{}", v)?;
124
125                // save our last session
126                let config_path = last_session_path()?;
127                let mut last_session = File::create(config_path)?;
128                write!(last_session, "{}", path.to_string_lossy())?;
129            }
130            Err(e) => {
131                return Err(SaveLoadError::OtherError(format!(
132                    "Failed to serialize session: {:?}",
133                    e
134                )));
135            }
136        }
137
138        if let Err(_) = write!(gui_channel, "Session saved to {}", path.to_string_lossy())
139            .and_then(|_| gui_channel.flush())
140        {
141            warn!("failed to write gui message");
142        }
143
144        Ok(())
145    }
146
147    pub fn add_looper(&mut self, looper: &Looper) {
148        self.channel
149            .send(SessionCommand::AddLooper(looper.id, looper.channel()))
150            .expect("channel closed!");
151    }
152
153    pub fn remove_looper(&mut self, id: u32) {
154        self.channel
155            .send(SessionCommand::RemoveLooper(id))
156            .expect("channel closed");
157    }
158
159    pub fn save_session(&mut self, data: SaveSessionData) -> Result<(), SaveLoadError> {
160        self.channel
161            .try_send(SessionCommand::SaveSession(data))
162            .map_err(|err| match err {
163                TrySendError::Full(_) => SaveLoadError::ChannelFull,
164                TrySendError::Disconnected(_) => SaveLoadError::ChannelClosed,
165            })
166    }
167}