oximedia_proxy/
media_link.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum LinkStatus {
9 Valid,
11 Broken,
13 Unverified,
15 MissingProxy,
17}
18
19impl LinkStatus {
20 pub fn is_valid(self) -> bool {
22 matches!(self, Self::Valid)
23 }
24
25 pub fn needs_attention(self) -> bool {
27 matches!(self, Self::Broken | Self::MissingProxy)
28 }
29}
30
31#[derive(Debug, Clone)]
33pub struct MediaLink {
34 pub id: u64,
36 pub original_path: String,
38 pub proxy_path: Option<String>,
40 pub status: LinkStatus,
42 pub duration_secs: f64,
44}
45
46impl MediaLink {
47 pub fn new(id: u64, original_path: &str, duration_secs: f64) -> Self {
49 Self {
50 id,
51 original_path: original_path.to_owned(),
52 proxy_path: None,
53 status: LinkStatus::MissingProxy,
54 duration_secs,
55 }
56 }
57
58 pub fn has_proxy(&self) -> bool {
60 self.proxy_path.is_some()
61 }
62
63 pub fn attach_proxy(&mut self, proxy_path: &str) {
65 self.proxy_path = Some(proxy_path.to_owned());
66 self.status = LinkStatus::Unverified;
67 }
68
69 pub fn mark_valid(&mut self) {
71 self.status = LinkStatus::Valid;
72 }
73
74 pub fn mark_broken(&mut self) {
76 self.status = LinkStatus::Broken;
77 }
78}
79
80#[derive(Debug, Default)]
82pub struct MediaLinkStore {
83 by_original: HashMap<String, MediaLink>,
85 by_proxy: HashMap<String, String>,
87 next_id: u64,
89}
90
91impl MediaLinkStore {
92 pub fn new() -> Self {
94 Self::default()
95 }
96
97 pub fn insert(
100 &mut self,
101 original_path: &str,
102 proxy_path: Option<&str>,
103 duration_secs: f64,
104 ) -> u64 {
105 let id = self.next_id;
106 self.next_id += 1;
107 let mut link = MediaLink::new(id, original_path, duration_secs);
108 if let Some(proxy) = proxy_path {
109 link.attach_proxy(proxy);
110 self.by_proxy
111 .insert(proxy.to_owned(), original_path.to_owned());
112 }
113 self.by_original.insert(original_path.to_owned(), link);
114 id
115 }
116
117 pub fn find_original(&self, original_path: &str) -> Option<&MediaLink> {
119 self.by_original.get(original_path)
120 }
121
122 pub fn find_proxy(&self, proxy_path: &str) -> Option<&MediaLink> {
124 let original = self.by_proxy.get(proxy_path)?;
125 self.by_original.get(original)
126 }
127
128 pub fn unlinked_originals(&self) -> Vec<&str> {
130 self.by_original
131 .values()
132 .filter(|link| !link.has_proxy())
133 .map(|link| link.original_path.as_str())
134 .collect()
135 }
136
137 pub fn len(&self) -> usize {
139 self.by_original.len()
140 }
141
142 pub fn is_empty(&self) -> bool {
144 self.by_original.is_empty()
145 }
146
147 pub fn verify(&mut self, original_path: &str) -> bool {
149 if let Some(link) = self.by_original.get_mut(original_path) {
150 link.mark_valid();
151 true
152 } else {
153 false
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_link_status_is_valid() {
164 assert!(LinkStatus::Valid.is_valid());
165 assert!(!LinkStatus::Broken.is_valid());
166 assert!(!LinkStatus::Unverified.is_valid());
167 assert!(!LinkStatus::MissingProxy.is_valid());
168 }
169
170 #[test]
171 fn test_link_status_needs_attention() {
172 assert!(LinkStatus::Broken.needs_attention());
173 assert!(LinkStatus::MissingProxy.needs_attention());
174 assert!(!LinkStatus::Valid.needs_attention());
175 assert!(!LinkStatus::Unverified.needs_attention());
176 }
177
178 #[test]
179 fn test_media_link_new_no_proxy() {
180 let link = MediaLink::new(1, "original.mov", 120.0);
181 assert!(!link.has_proxy());
182 assert_eq!(link.status, LinkStatus::MissingProxy);
183 }
184
185 #[test]
186 fn test_media_link_attach_proxy() {
187 let mut link = MediaLink::new(1, "original.mov", 120.0);
188 link.attach_proxy("proxy.mp4");
189 assert!(link.has_proxy());
190 assert_eq!(link.status, LinkStatus::Unverified);
191 }
192
193 #[test]
194 fn test_media_link_mark_valid() {
195 let mut link = MediaLink::new(1, "original.mov", 120.0);
196 link.attach_proxy("proxy.mp4");
197 link.mark_valid();
198 assert!(link.status.is_valid());
199 }
200
201 #[test]
202 fn test_media_link_mark_broken() {
203 let mut link = MediaLink::new(1, "original.mov", 120.0);
204 link.attach_proxy("proxy.mp4");
205 link.mark_broken();
206 assert_eq!(link.status, LinkStatus::Broken);
207 }
208
209 #[test]
210 fn test_store_insert_and_find_original() {
211 let mut store = MediaLinkStore::new();
212 store.insert("orig.mov", Some("proxy.mp4"), 60.0);
213 let link = store
214 .find_original("orig.mov")
215 .expect("should succeed in test");
216 assert_eq!(link.original_path, "orig.mov");
217 }
218
219 #[test]
220 fn test_store_find_proxy() {
221 let mut store = MediaLinkStore::new();
222 store.insert("orig.mov", Some("proxy.mp4"), 60.0);
223 let link = store
224 .find_proxy("proxy.mp4")
225 .expect("should succeed in test");
226 assert_eq!(link.original_path, "orig.mov");
227 }
228
229 #[test]
230 fn test_store_find_proxy_not_found() {
231 let store = MediaLinkStore::new();
232 assert!(store.find_proxy("nonexistent.mp4").is_none());
233 }
234
235 #[test]
236 fn test_store_unlinked_originals() {
237 let mut store = MediaLinkStore::new();
238 store.insert("a.mov", None, 10.0);
239 store.insert("b.mov", Some("b_proxy.mp4"), 10.0);
240 let unlinked = store.unlinked_originals();
241 assert_eq!(unlinked.len(), 1);
242 assert_eq!(unlinked[0], "a.mov");
243 }
244
245 #[test]
246 fn test_store_is_empty() {
247 let store = MediaLinkStore::new();
248 assert!(store.is_empty());
249 }
250
251 #[test]
252 fn test_store_len() {
253 let mut store = MediaLinkStore::new();
254 store.insert("a.mov", None, 10.0);
255 store.insert("b.mov", None, 20.0);
256 assert_eq!(store.len(), 2);
257 }
258
259 #[test]
260 fn test_store_verify_sets_valid() {
261 let mut store = MediaLinkStore::new();
262 store.insert("a.mov", Some("a_proxy.mp4"), 10.0);
263 let result = store.verify("a.mov");
264 assert!(result);
265 let link = store
266 .find_original("a.mov")
267 .expect("should succeed in test");
268 assert!(link.status.is_valid());
269 }
270
271 #[test]
272 fn test_store_verify_missing_returns_false() {
273 let mut store = MediaLinkStore::new();
274 assert!(!store.verify("nonexistent.mov"));
275 }
276}