ai_agent/utils/plugins/
plugin_flagging.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::sync::Mutex;
7
8use once_cell::sync::Lazy;
9use serde::{Deserialize, Serialize};
10
11use super::plugin_directories::get_plugins_directory;
12
13const FLAGGED_PLUGINS_FILENAME: &str = "flagged-plugins.json";
14const SEEN_EXPIRY_MS: u64 = 48 * 60 * 60 * 1000; #[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct FlaggedPlugin {
19 #[serde(rename = "flaggedAt")]
20 pub flagged_at: String,
21 #[serde(rename = "seenAt", skip_serializing_if = "Option::is_none")]
22 pub seen_at: Option<String>,
23}
24
25static CACHE: Lazy<Mutex<Option<HashMap<String, FlaggedPlugin>>>> = Lazy::new(|| Mutex::new(None));
26
27fn get_flagged_plugins_path() -> PathBuf {
28 PathBuf::from(get_plugins_directory()).join(FLAGGED_PLUGINS_FILENAME)
29}
30
31pub async fn load_flagged_plugins() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
33 let all = read_from_disk().await.unwrap_or_default();
34 let now = std::time::SystemTime::now()
35 .duration_since(std::time::UNIX_EPOCH)
36 .unwrap_or_default()
37 .as_millis() as u64;
38
39 let mut changed = false;
40 let mut filtered = HashMap::new();
41
42 for (id, entry) in all {
43 if let Some(ref seen_at) = entry.seen_at {
44 let seen_time = chrono::DateTime::parse_from_rfc3339(seen_at)
45 .map(|dt| dt.timestamp_millis() as u64)
46 .unwrap_or(0);
47
48 if now.saturating_sub(seen_time) >= SEEN_EXPIRY_MS {
49 changed = true;
50 continue;
51 }
52 }
53 filtered.insert(id, entry);
54 }
55
56 if changed {
57 write_to_disk(&filtered).await?;
58 }
59
60 {
61 let mut cache = CACHE.lock().unwrap();
62 *cache = Some(filtered);
63 }
64
65 Ok(())
66}
67
68async fn read_from_disk()
69-> Result<HashMap<String, FlaggedPlugin>, Box<dyn std::error::Error + Send + Sync>> {
70 let path = get_flagged_plugins_path();
71 let content = tokio::fs::read_to_string(&path).await?;
72 let data: serde_json::Value = serde_json::from_str(&content)?;
73
74 if let Some(plugins) = data.get("plugins").and_then(|p| p.as_object()) {
75 let mut result = HashMap::new();
76 for (id, entry) in plugins {
77 if let Some(flagged_at) = entry.get("flaggedAt").and_then(|v| v.as_str()) {
78 let seen_at = entry
79 .get("seenAt")
80 .and_then(|v| v.as_str())
81 .map(|s| s.to_string());
82 result.insert(
83 id.clone(),
84 FlaggedPlugin {
85 flagged_at: flagged_at.to_string(),
86 seen_at,
87 },
88 );
89 }
90 }
91 Ok(result)
92 } else {
93 Ok(HashMap::new())
94 }
95}
96
97async fn write_to_disk(
98 plugins: &HashMap<String, FlaggedPlugin>,
99) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
100 let path = get_flagged_plugins_path();
101 let temp_path = PathBuf::from(format!("{}.{}.tmp", path.display(), rand::random::<u64>()));
102
103 tokio::fs::create_dir_all(get_plugins_directory()).await?;
104
105 let data = serde_json::json!({ "plugins": plugins });
106 let content = serde_json::to_string_pretty(&data)?;
107 tokio::fs::write(&temp_path, content).await?;
108 tokio::fs::rename(&temp_path, &path).await?;
109
110 {
111 let mut cache = CACHE.lock().unwrap();
112 *cache = Some(plugins.clone());
113 }
114
115 Ok(())
116}
117
118pub fn get_flagged_plugins() -> HashMap<String, FlaggedPlugin> {
120 let cache = CACHE.lock().unwrap();
121 cache.clone().unwrap_or_default()
122}
123
124pub async fn add_flagged_plugin(
126 plugin_id: &str,
127) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
128 let mut cache = CACHE.lock().unwrap();
129 if cache.is_none() {
130 *cache = Some(read_from_disk().await.unwrap_or_default());
131 }
132
133 let now = chrono::Utc::now().to_rfc3339();
134 if let Some(ref mut plugins) = *cache {
135 plugins.insert(
136 plugin_id.to_string(),
137 FlaggedPlugin {
138 flagged_at: now,
139 seen_at: None,
140 },
141 );
142
143 write_to_disk(plugins).await?;
144 log::debug!("Flagged plugin: {}", plugin_id);
145 }
146
147 Ok(())
148}
149
150pub async fn mark_flagged_plugins_seen(
152 plugin_ids: &[String],
153) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
154 let mut cache = CACHE.lock().unwrap();
155 if cache.is_none() {
156 *cache = Some(read_from_disk().await.unwrap_or_default());
157 }
158
159 let now = chrono::Utc::now().to_rfc3339();
160 let mut changed = false;
161
162 if let Some(ref mut plugins) = *cache {
163 for id in plugin_ids {
164 if let Some(entry) = plugins.get_mut(id) {
165 if entry.seen_at.is_none() {
166 entry.seen_at = Some(now.clone());
167 changed = true;
168 }
169 }
170 }
171 }
172
173 if changed {
174 if let Some(ref plugins) = *cache {
175 write_to_disk(plugins).await?;
176 }
177 }
178
179 Ok(())
180}
181
182pub async fn remove_flagged_plugin(
184 plugin_id: &str,
185) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
186 let mut cache = CACHE.lock().unwrap();
187 if cache.is_none() {
188 *cache = Some(read_from_disk().await.unwrap_or_default());
189 }
190
191 if let Some(ref mut plugins) = *cache {
192 if plugins.remove(plugin_id).is_some() {
193 write_to_disk(plugins).await?;
194 }
195 }
196
197 Ok(())
198}