1use anyhow::{Context, anyhow};
2use async_tar::{Builder, Header};
3use gpui::{BackgroundExecutor, Task};
4
5use collections::HashMap;
6use parking_lot::Mutex;
7use rodio::Source;
8use smol::fs::File;
9use std::{io, path::PathBuf, sync::Arc, time::Duration};
10
11use crate::{REPLAY_DURATION, rodio_ext::Replay};
12
13#[derive(Default, Clone)]
14pub(crate) struct Replays(Arc<Mutex<HashMap<String, Replay>>>);
15
16impl Replays {
17 pub(crate) fn add_voip_stream(&self, stream_name: String, source: Replay) {
18 let mut map = self.0.lock();
19 map.retain(|_, replay| replay.source_is_active());
20 map.insert(stream_name, source);
21 }
22
23 pub(crate) fn replays_to_tar(
24 &self,
25 executor: BackgroundExecutor,
26 ) -> Task<anyhow::Result<(PathBuf, Duration)>> {
27 let map = Arc::clone(&self.0);
28 executor.spawn(async move {
29 let recordings: Vec<_> = map
30 .lock()
31 .iter_mut()
32 .map(|(name, replay)| {
33 let queued = REPLAY_DURATION.min(replay.duration_ready());
34 (name.clone(), replay.take_duration(queued).record())
35 })
36 .collect();
37 let longest = recordings
38 .iter()
39 .map(|(_, r)| {
40 r.total_duration()
41 .expect("SamplesBuffer always returns a total duration")
42 })
43 .max()
44 .ok_or(anyhow!("There is no audio to capture"))?;
45
46 let path = std::env::current_dir()
47 .context("Could not get current dir")?
48 .join("replays.tar");
49 let tar = File::create(&path)
50 .await
51 .context("Could not create file for tar")?;
52
53 let mut tar = Builder::new(tar);
54
55 for (name, recording) in recordings {
56 let mut writer = io::Cursor::new(Vec::new());
57 rodio::wav_to_writer(recording, &mut writer).context("failed to encode wav")?;
58 let wav_data = writer.into_inner();
59 let path = name.replace(' ', "_") + ".wav";
60 let mut header = Header::new_gnu();
61 // rw permissions for everyone
62 header.set_mode(0o666);
63 header.set_size(wav_data.len() as u64);
64 tar.append_data(&mut header, path, wav_data.as_slice())
65 .await
66 .context("failed to apped wav to tar")?;
67 }
68 tar.into_inner()
69 .await
70 .context("Could not finish writing tar")?
71 .sync_all()
72 .await
73 .context("Could not flush tar file to disk")?;
74 Ok((path, longest))
75 })
76 }
77}