loopers_engine/
session.rs1use 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 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}