oximedia_proxy/link/
database.rs1use crate::{ProxyError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8pub struct LinkDatabase {
10 db_path: PathBuf,
12
13 links: HashMap<PathBuf, ProxyLinkRecord>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ProxyLinkRecord {
20 pub proxy_path: PathBuf,
22
23 pub original_path: PathBuf,
25
26 pub scale_factor: f32,
28
29 pub codec: String,
31
32 pub duration: f64,
34
35 pub timecode: Option<String>,
37
38 pub created_at: i64,
40
41 pub verified_at: Option<i64>,
43
44 pub metadata: HashMap<String, String>,
46}
47
48impl LinkDatabase {
49 pub async fn new(db_path: impl AsRef<Path>) -> Result<Self> {
55 let db_path = db_path.as_ref().to_path_buf();
56
57 if let Some(parent) = db_path.parent() {
59 std::fs::create_dir_all(parent)?;
60 }
61
62 let links = if db_path.exists() {
64 Self::load_from_file(&db_path)?
65 } else {
66 HashMap::new()
67 };
68
69 Ok(Self { db_path, links })
70 }
71
72 pub fn add_link(&mut self, record: ProxyLinkRecord) -> Result<()> {
74 self.links.insert(record.proxy_path.clone(), record);
75 self.save()?;
76 Ok(())
77 }
78
79 #[must_use]
81 pub fn get_link(&self, proxy_path: &Path) -> Option<&ProxyLinkRecord> {
82 self.links.get(proxy_path)
83 }
84
85 #[must_use]
87 pub fn get_link_by_original(&self, original_path: &Path) -> Option<&ProxyLinkRecord> {
88 self.links
89 .values()
90 .find(|link| link.original_path == original_path)
91 }
92
93 pub fn remove_link(&mut self, proxy_path: &Path) -> Result<Option<ProxyLinkRecord>> {
95 let result = self.links.remove(proxy_path);
96 self.save()?;
97 Ok(result)
98 }
99
100 pub fn update_verification(&mut self, proxy_path: &Path, timestamp: i64) -> Result<()> {
102 if let Some(link) = self.links.get_mut(proxy_path) {
103 link.verified_at = Some(timestamp);
104 self.save()?;
105 Ok(())
106 } else {
107 Err(ProxyError::LinkNotFound(proxy_path.display().to_string()))
108 }
109 }
110
111 #[must_use]
113 pub fn all_links(&self) -> Vec<&ProxyLinkRecord> {
114 self.links.values().collect()
115 }
116
117 #[must_use]
119 pub fn count(&self) -> usize {
120 self.links.len()
121 }
122
123 fn save(&self) -> Result<()> {
125 let json = serde_json::to_string_pretty(&self.links)
126 .map_err(|e| ProxyError::DatabaseError(format!("Failed to serialize database: {e}")))?;
127
128 std::fs::write(&self.db_path, json)?;
129 Ok(())
130 }
131
132 fn load_from_file(path: &Path) -> Result<HashMap<PathBuf, ProxyLinkRecord>> {
134 let content = std::fs::read_to_string(path)?;
135 let links = serde_json::from_str(&content).map_err(|e| {
136 ProxyError::DatabaseError(format!("Failed to deserialize database: {e}"))
137 })?;
138 Ok(links)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[tokio::test]
147 async fn test_database_creation() {
148 let temp_dir = std::env::temp_dir();
149 let db_path = temp_dir.join("test_links.json");
150
151 let db = LinkDatabase::new(&db_path).await;
152 assert!(db.is_ok());
153
154 let _ = std::fs::remove_file(db_path);
156 }
157
158 #[tokio::test]
159 async fn test_add_and_get_link() {
160 let temp_dir = std::env::temp_dir();
161 let db_path = temp_dir.join("test_links2.json");
162
163 let mut db = LinkDatabase::new(&db_path)
164 .await
165 .expect("should succeed in test");
166
167 let record = ProxyLinkRecord {
168 proxy_path: PathBuf::from("proxy.mp4"),
169 original_path: PathBuf::from("original.mov"),
170 scale_factor: 0.25,
171 codec: "h264".to_string(),
172 duration: 10.0,
173 timecode: Some("01:00:00:00".to_string()),
174 created_at: 123456789,
175 verified_at: None,
176 metadata: HashMap::new(),
177 };
178
179 db.add_link(record.clone()).expect("should succeed in test");
180
181 let retrieved = db.get_link(Path::new("proxy.mp4"));
182 assert!(retrieved.is_some());
183 assert_eq!(
184 retrieved.expect("should succeed in test").original_path,
185 PathBuf::from("original.mov")
186 );
187
188 let _ = std::fs::remove_file(db_path);
190 }
191
192 #[tokio::test]
193 async fn test_remove_link() {
194 let temp_dir = std::env::temp_dir();
195 let db_path = temp_dir.join("test_links3.json");
196
197 let mut db = LinkDatabase::new(&db_path)
198 .await
199 .expect("should succeed in test");
200
201 let record = ProxyLinkRecord {
202 proxy_path: PathBuf::from("proxy.mp4"),
203 original_path: PathBuf::from("original.mov"),
204 scale_factor: 0.25,
205 codec: "h264".to_string(),
206 duration: 10.0,
207 timecode: None,
208 created_at: 123456789,
209 verified_at: None,
210 metadata: HashMap::new(),
211 };
212
213 db.add_link(record).expect("should succeed in test");
214 assert_eq!(db.count(), 1);
215
216 db.remove_link(Path::new("proxy.mp4"))
217 .expect("should succeed in test");
218 assert_eq!(db.count(), 0);
219
220 let _ = std::fs::remove_file(db_path);
222 }
223}