Skip to main content

figma_mcp/figma/
image_cache.rs

1use std::collections::HashMap;
2use std::sync::{Arc, RwLock};
3use std::time::SystemTime;
4
5use crate::{Error, Result};
6
7#[derive(Clone)]
8pub struct ImageCache {
9    entries: Arc<RwLock<HashMap<String, ImageEntry>>>,
10}
11
12#[derive(Clone, Debug)]
13pub struct ImageEntry {
14    pub file_key: String,
15    pub node_id: String,
16    pub format: String,
17    pub scale: f64,
18    pub figma_url: String,
19    pub cached_data: Option<Vec<u8>>,
20    pub export_time: SystemTime,
21}
22
23impl ImageCache {
24    pub fn new() -> Self {
25        Self {
26            entries: Arc::new(RwLock::new(HashMap::new())),
27        }
28    }
29
30    pub fn register_export(
31        &self,
32        file_key: String,
33        node_id: String,
34        format: String,
35        scale: f64,
36        figma_url: String,
37    ) -> Result<String> {
38        let uri = Self::generate_uri(&file_key, &node_id, &format, scale);
39        
40        let entry = ImageEntry {
41            file_key,
42            node_id,
43            format,
44            scale,
45            figma_url,
46            cached_data: None,
47            export_time: SystemTime::now(),
48        };
49
50        let mut entries = self.entries.write()
51            .map_err(|_| Error::Internal("Failed to acquire lock".to_string()))?;
52        entries.insert(uri.clone(), entry);
53
54        Ok(uri)
55    }
56
57    pub fn list_all(&self) -> Result<Vec<(String, ImageEntry)>> {
58        let entries = self.entries.read()
59            .map_err(|_| Error::Internal("Failed to acquire lock".to_string()))?;
60        
61        Ok(entries.iter()
62            .map(|(uri, entry)| (uri.clone(), entry.clone()))
63            .collect())
64    }
65
66    pub fn get_entry(&self, uri: &str) -> Result<Option<ImageEntry>> {
67        let entries = self.entries.read()
68            .map_err(|_| Error::Internal("Failed to acquire lock".to_string()))?;
69        
70        Ok(entries.get(uri).cloned())
71    }
72
73    pub fn update_cached_data(&self, uri: &str, data: Vec<u8>) -> Result<()> {
74        let mut entries = self.entries.write()
75            .map_err(|_| Error::Internal("Failed to acquire lock".to_string()))?;
76        
77        if let Some(entry) = entries.get_mut(uri) {
78            entry.cached_data = Some(data);
79            Ok(())
80        } else {
81            Err(Error::NotFound(format!("Resource not found: {}", uri)))
82        }
83    }
84
85    pub fn is_expired(&self, entry: &ImageEntry) -> bool {
86        if let Ok(elapsed) = entry.export_time.elapsed() {
87            // Figma URLs typically expire after 1 hour
88            elapsed.as_secs() > 3600
89        } else {
90            true
91        }
92    }
93
94    pub fn get_mime_type(format: &str) -> &'static str {
95        match format.to_lowercase().as_str() {
96            "png" => "image/png",
97            "jpg" | "jpeg" => "image/jpeg",
98            "svg" => "image/svg+xml",
99            "pdf" => "application/pdf",
100            _ => "application/octet-stream",
101        }
102    }
103
104    fn generate_uri(file_key: &str, node_id: &str, format: &str, scale: f64) -> String {
105        if scale != 1.0 {
106            format!("figma://file/{}/node/{}@{}x.{}", file_key, node_id, scale as u32, format)
107        } else {
108            format!("figma://file/{}/node/{}.{}", file_key, node_id, format)
109        }
110    }
111}
112
113impl Default for ImageCache {
114    fn default() -> Self {
115        Self::new()
116    }
117}