active_call/media/
cache.rs

1use anyhow::{Result, anyhow};
2use bytes::BytesMut;
3use once_cell::sync::Lazy;
4use sha2::{Digest, Sha256};
5use std::sync::RwLock;
6use std::{io::IoSlice, path::PathBuf};
7use tokio::io::AsyncReadExt;
8use tokio::{fs::create_dir_all, io::AsyncWriteExt};
9use tracing::{debug, info};
10
11// Default cache directory
12static DEFAULT_CACHE_DIR: &str = "/tmp/mediacache";
13
14// Global cache configuration
15static CACHE_CONFIG: Lazy<RwLock<CacheConfig>> = Lazy::new(|| {
16    RwLock::new(CacheConfig {
17        cache_dir: PathBuf::from(DEFAULT_CACHE_DIR),
18    })
19});
20
21#[derive(Debug, Clone)]
22pub struct CacheConfig {
23    pub cache_dir: PathBuf,
24}
25
26/// Set the cache directory for the media cache
27pub fn set_cache_dir(path: &str) -> Result<()> {
28    let path = PathBuf::from(path);
29    let mut config = CACHE_CONFIG
30        .write()
31        .map_err(|_| anyhow!("Failed to acquire write lock"))?;
32    config.cache_dir = path;
33    Ok(())
34}
35
36/// Get the current cache directory
37pub fn get_cache_dir() -> Result<PathBuf> {
38    let config = CACHE_CONFIG
39        .read()
40        .map_err(|_| anyhow!("Failed to acquire read lock"))?;
41    Ok(config.cache_dir.clone())
42}
43
44/// Ensure the cache directory exists
45pub async fn ensure_cache_dir() -> Result<()> {
46    let cache_dir = get_cache_dir()?;
47
48    if !cache_dir.exists() {
49        debug!("Creating cache directory: {:?}", cache_dir);
50        create_dir_all(&cache_dir).await?;
51    }
52
53    Ok(())
54}
55
56/// Generate a cache key from text or URL
57pub fn generate_cache_key(
58    input: &str,
59    sample_rate: u32,
60    speaker: Option<&String>,
61    speed: Option<f32>,
62) -> String {
63    let mut hasher = Sha256::new();
64    hasher.update(input.as_bytes());
65    let result = hasher.finalize();
66    match speaker {
67        Some(speaker) => format!(
68            "{}_{}_{}_{}",
69            hex::encode(result),
70            sample_rate,
71            speaker,
72            speed.unwrap_or(1.0)
73        ),
74        None => format!(
75            "{}_{}_{}",
76            hex::encode(result),
77            sample_rate,
78            speed.unwrap_or(1.0)
79        ),
80    }
81}
82
83/// Get the full path for a cached file
84pub fn get_cache_path(key: &str) -> Result<PathBuf> {
85    let cache_dir = get_cache_dir()?;
86    Ok(cache_dir.join(key).with_extension("pcm"))
87}
88
89/// Check if a file exists in the cache
90pub async fn is_cached(key: &str) -> Result<bool> {
91    let path = get_cache_path(key)?;
92    Ok(tokio::fs::try_exists(&path).await?)
93}
94
95/// Store data in the cache
96pub async fn store_in_cache(key: &str, data: &Vec<u8>) -> Result<()> {
97    ensure_cache_dir().await?;
98    let path = get_cache_path(key)?;
99    tokio::fs::write(&path.with_extension(".tmp"), data).await?;
100    tokio::fs::rename(&path.with_extension(".tmp"), &path).await?;
101    info!("cache: Stored {} -> {} bytes", key, data.len());
102    Ok(())
103}
104
105// Store datas in the cache
106pub async fn store_in_cache_vectored(key: &str, data: &[impl AsRef<[u8]>]) -> Result<()> {
107    ensure_cache_dir().await?;
108    let path = get_cache_path(key)?;
109    let tmp_path = path.with_extension(".tmp");
110    let mut file = tokio::fs::File::create(tmp_path.clone()).await?;
111    let io_slices = data
112        .iter()
113        .map(|d| IoSlice::new(d.as_ref()))
114        .collect::<Vec<_>>();
115    let n = file.write_vectored(&io_slices).await?;
116    tokio::fs::rename(&tmp_path, &path).await?;
117    info!("cache: Stored {} -> {} bytes", key, n);
118    Ok(())
119}
120
121/// Retrieve data from the cache
122pub async fn retrieve_from_cache(key: &str) -> Result<Vec<u8>> {
123    let path = get_cache_path(key)?;
124
125    if !tokio::fs::try_exists(&path).await? {
126        return Err(anyhow!("Cache file not found for key: {}", key));
127    }
128
129    let data = tokio::fs::read(&path).await?;
130    debug!(key, size = data.len(), "retrieved file from cache");
131    Ok(data)
132}
133
134// Retrieve data from the cache with a buffer
135pub async fn retrieve_from_cache_with_buffer(key: &str, buffer: &mut BytesMut) -> Result<()> {
136    let path = get_cache_path(key)?;
137    let mut file = tokio::fs::File::open(path).await?;
138    let metadata = file.metadata().await?;
139    let file_size = metadata.len() as usize;
140    buffer.reserve(file_size);
141
142    while file.read_buf(buffer).await? > 0 {}
143    Ok(())
144}
145
146/// Delete a specific file from the cache
147pub async fn delete_from_cache(key: &str) -> Result<()> {
148    let path = get_cache_path(key)?;
149
150    if tokio::fs::try_exists(&path).await? {
151        tokio::fs::remove_file(path).await?;
152        debug!("Deleted file from cache with key: {}", key);
153    }
154
155    Ok(())
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    #[tokio::test]
162    async fn test_cache_operations() -> Result<()> {
163        ensure_cache_dir().await?;
164
165        // Generate a cache key
166        let key = generate_cache_key("test_data", 8000, None, None);
167
168        // Test storing data in cache
169        let test_data = b"TEST DATA".to_vec();
170        store_in_cache(&key, &test_data).await?;
171
172        // Test if data is cached
173        assert!(is_cached(&key).await?);
174
175        // Test retrieving data from cache
176        let retrieved_data = retrieve_from_cache(&key).await?;
177        assert_eq!(retrieved_data, test_data);
178
179        // Test deleting data from cache
180        delete_from_cache(&key).await?;
181        assert!(!is_cached(&key).await?);
182
183        // Test clean cache
184        let key2 = generate_cache_key("test_data2", 16000, None, None);
185        store_in_cache(&key2, &test_data).await?;
186        Ok(())
187    }
188
189    #[test]
190    fn test_generate_cache_key() {
191        let key1 = generate_cache_key("hello", 16000, None, None);
192        let key2 = generate_cache_key("hello", 8000, None, None);
193        let key3 = generate_cache_key("world", 16000, None, None);
194
195        // Same input with different sample rates should produce different keys
196        assert_ne!(key1, key2);
197
198        // Different inputs with same sample rate should produce different keys
199        assert_ne!(key1, key3);
200    }
201
202    #[tokio::test]
203    async fn test_store_in_cache_v() -> Result<()> {
204        // Test data as multiple slices
205        let data1 = b"Hello, ";
206        let data2 = b"world!";
207        let data3 = b" This is a test.";
208        let data_slices = [data1.as_slice(), data2.as_slice(), data3.as_slice()];
209
210        let key = generate_cache_key("test_vectored_store", 16000, None, None);
211
212        // Ensure the key doesn't exist initially
213        delete_from_cache(&key).await.ok();
214        assert!(!is_cached(&key).await?);
215
216        // Store using vectored write
217        store_in_cache_vectored(&key, &data_slices).await?;
218
219        // Verify it was stored
220        assert!(is_cached(&key).await?);
221
222        // Retrieve and verify content
223        let retrieved = retrieve_from_cache(&key).await?;
224        let expected = [data1.as_slice(), data2.as_slice(), data3.as_slice()].concat();
225        assert_eq!(retrieved, expected);
226
227        // Clean up
228        delete_from_cache(&key).await?;
229        Ok(())
230    }
231
232    #[tokio::test]
233    async fn test_store_in_cache_v_empty_slices() -> Result<()> {
234        let empty_data: &[&[u8]] = &[];
235        let key = generate_cache_key("test_empty_vectored", 16000, None, None);
236
237        // Clean up first
238        delete_from_cache(&key).await.ok();
239
240        // Store empty data
241        store_in_cache_vectored(&key, empty_data).await?;
242
243        // Verify it was stored as empty file
244        assert!(is_cached(&key).await?);
245        let retrieved = retrieve_from_cache(&key).await?;
246        assert_eq!(retrieved.len(), 0);
247
248        // Clean up
249        delete_from_cache(&key).await?;
250        Ok(())
251    }
252
253    #[tokio::test]
254    async fn test_retrieve_from_cache_with_buffer() -> Result<()> {
255        // Test data
256        let test_data = b"This is test data for buffer retrieval testing.";
257        let key = generate_cache_key("test_buffer_retrieve", 16000, None, None);
258
259        // Clean up first
260        delete_from_cache(&key).await.ok();
261
262        // Store test data using regular store
263        store_in_cache(&key, &test_data.to_vec()).await?;
264
265        // Retrieve using buffer method
266        let mut buffer = BytesMut::new();
267        retrieve_from_cache_with_buffer(&key, &mut buffer).await?;
268
269        // Verify content
270        assert_eq!(buffer.as_ref(), test_data);
271        assert_eq!(buffer.len(), test_data.len());
272
273        // Clean up
274        delete_from_cache(&key).await?;
275        Ok(())
276    }
277
278    #[tokio::test]
279    async fn test_retrieve_from_cache_with_buffer_large_file() -> Result<()> {
280        // Create larger test data (1MB)
281        let large_data: Vec<u8> = vec![7; 1024 * 1024];
282        let key = generate_cache_key("test_large_buffer", 16000, None, None);
283
284        // Clean up first
285        delete_from_cache(&key).await.ok();
286
287        // Store large data
288        store_in_cache(&key, &large_data).await?;
289
290        // Retrieve using buffer method
291        let mut buffer = BytesMut::new();
292        retrieve_from_cache_with_buffer(&key, &mut buffer).await?;
293
294        // Verify content
295        assert_eq!(buffer.len(), large_data.len());
296        assert_eq!(buffer.as_ref(), large_data.as_slice());
297
298        // Clean up
299        delete_from_cache(&key).await?;
300        Ok(())
301    }
302
303    #[tokio::test]
304    async fn test_retrieve_from_cache_with_buffer_nonexistent() -> Result<()> {
305        let nonexistent_key = generate_cache_key("nonexistent_file", 16000, None, None);
306        let mut buffer = BytesMut::new();
307
308        // Should fail for nonexistent file
309        let result = retrieve_from_cache_with_buffer(&nonexistent_key, &mut buffer).await;
310        assert!(result.is_err());
311
312        Ok(())
313    }
314
315    #[tokio::test]
316    async fn test_store_v_and_retrieve_buffer_integration() -> Result<()> {
317        // Test integration between vectored store and buffer retrieve
318        let data_parts = [
319            b"Part 1: Hello".as_slice(),
320            b", Part 2: World".as_slice(),
321            b", Part 3: Integration Test!".as_slice(),
322        ];
323        let key = generate_cache_key("test_integration", 16000, None, None);
324
325        // Clean up first
326        delete_from_cache(&key).await.ok();
327
328        // Store using vectored write
329        store_in_cache_vectored(&key, &data_parts).await?;
330
331        // Retrieve using buffer method
332        let mut buffer = BytesMut::new();
333        retrieve_from_cache_with_buffer(&key, &mut buffer).await?;
334
335        // Verify the data was correctly concatenated
336        let expected = data_parts.concat();
337        assert_eq!(buffer.as_ref(), expected.as_slice());
338        assert_eq!(buffer.len(), expected.len());
339
340        // Also verify with regular retrieve for double-check
341        let regular_retrieve = retrieve_from_cache(&key).await?;
342        assert_eq!(regular_retrieve, expected);
343        assert_eq!(buffer.as_ref(), regular_retrieve.as_slice());
344
345        // Clean up
346        delete_from_cache(&key).await?;
347        Ok(())
348    }
349}