microscope_memory/
keystore.rs1use serde::{Deserialize, Serialize};
9use std::fs;
10use std::path::Path;
11
12const MAGIC: [u8; 4] = [0x4D, 0x4B, 0x45, 0x59]; const VERSION: u8 = 1;
15
16#[derive(Debug, Serialize, Deserialize, Clone)]
18pub struct KeyStore {
19 pub magic: [u8; 4],
20 pub version: u8,
21 pub entries: Vec<KeyEntry>,
22}
23
24impl Default for KeyStore {
25 fn default() -> Self {
26 Self {
27 magic: MAGIC,
28 version: VERSION,
29 entries: Vec::new(),
30 }
31 }
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
36pub struct KeyEntry {
37 pub service: String,
39 pub key: String,
41 pub priority: u8,
43 pub quota_remaining: Option<f64>,
45 #[serde(default)]
47 pub last_error: Option<String>,
48 #[serde(default)]
50 pub disabled: bool,
51 pub created_at: i64,
53}
54
55impl KeyStore {
56 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
59 if !path.as_ref().exists() {
60 return Ok(Self {
61 magic: MAGIC,
62 version: VERSION,
63 entries: Vec::new(),
64 });
65 }
66 let bytes = fs::read(path.as_ref())?;
67 let store: KeyStore = bincode::deserialize(&bytes)?;
68 if store.magic != MAGIC {
69 return Err("Invalid keys.bin magic bytes".into());
70 }
71 if store.version != VERSION {
72 return Err(format!("Unsupported keys.bin version: {}", store.version).into());
73 }
74 Ok(store)
75 }
76
77 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
79 let bytes = bincode::serialize(self)?;
80 let tmp = path.as_ref().with_extension("bin.tmp");
81 fs::write(&tmp, &bytes)?;
82 fs::rename(&tmp, path.as_ref())?;
83 Ok(())
84 }
85
86 pub fn get(&self, service: &str) -> Option<&KeyEntry> {
88 self.entries
89 .iter()
90 .filter(|e| e.service == service && !e.disabled)
91 .min_by_key(|e| e.priority)
92 }
93
94 pub fn get_all(&self, service: &str) -> Vec<&KeyEntry> {
96 let mut v: Vec<_> = self
97 .entries
98 .iter()
99 .filter(|e| e.service == service && !e.disabled)
100 .collect();
101 v.sort_by_key(|e| e.priority);
102 v
103 }
104
105 pub fn set(&mut self, service: &str, key: String, priority: u8) {
107 if let Some(existing) = self
109 .entries
110 .iter_mut()
111 .find(|e| e.service == service && e.priority == priority)
112 {
113 existing.key = key;
114 existing.last_error = None;
115 existing.disabled = false;
116 existing.created_at = std::time::SystemTime::now()
117 .duration_since(std::time::UNIX_EPOCH)
118 .unwrap_or_default()
119 .as_secs() as i64;
120 return;
121 }
122 self.entries.push(KeyEntry {
124 service: service.to_string(),
125 key,
126 priority,
127 quota_remaining: None,
128 last_error: None,
129 disabled: false,
130 created_at: std::time::SystemTime::now()
131 .duration_since(std::time::UNIX_EPOCH)
132 .unwrap_or_default()
133 .as_secs() as i64,
134 });
135 }
136
137 pub fn remove(&mut self, service: &str, priority: u8) {
139 self.entries
140 .retain(|e| !(e.service == service && e.priority == priority));
141 }
142
143 pub fn record_error(&mut self, service: &str, priority: u8, error: &str) {
145 if let Some(entry) = self
146 .entries
147 .iter_mut()
148 .find(|e| e.service == service && e.priority == priority)
149 {
150 entry.last_error = Some(error.to_string());
151 if error.contains("429") || error.contains("401") || error.contains("403") {
153 entry.disabled = true;
154 }
155 }
156 }
157
158 pub fn record_success(&mut self, service: &str, priority: u8) {
160 if let Some(entry) = self
161 .entries
162 .iter_mut()
163 .find(|e| e.service == service && e.priority == priority)
164 {
165 entry.last_error = None;
166 entry.disabled = false;
168 }
169 }
170
171 pub fn reset_all(&mut self) {
173 for entry in &mut self.entries {
174 entry.disabled = false;
175 entry.last_error = None;
176 }
177 }
178
179 pub fn list(&self) -> Vec<KeyEntryInfo> {
181 self.entries
182 .iter()
183 .map(|e| KeyEntryInfo {
184 service: e.service.clone(),
185 priority: e.priority,
186 quota_remaining: e.quota_remaining,
187 last_error: e.last_error.clone(),
188 disabled: e.disabled,
189 created_at: e.created_at,
190 key_preview: if e.key.len() > 8 {
191 format!("{}...", &e.key[..8])
192 } else {
193 "***".to_string()
194 },
195 })
196 .collect()
197 }
198}
199
200#[derive(Debug, Serialize, Deserialize, Clone)]
202pub struct KeyEntryInfo {
203 pub service: String,
204 pub priority: u8,
205 pub quota_remaining: Option<f64>,
206 pub last_error: Option<String>,
207 pub disabled: bool,
208 pub created_at: i64,
209 pub key_preview: String,
210}
211
212pub fn default_keys_path(output_dir: &str) -> String {
214 format!("{}/keys.bin", output_dir)
215}