oxideav_source/lib.rs
1//! Generic source registry for oxideav.
2//!
3//! Containers in oxideav take a `Box<dyn ReadSeek>`; this crate is what
4//! turns a URI into one. The built-in `file` driver handles bare paths
5//! and `file://` URIs; external drivers (e.g. `oxideav-http`) register
6//! themselves into a [`SourceRegistry`] for additional schemes.
7//!
8//! ```no_run
9//! use oxideav_source::SourceRegistry;
10//!
11//! let reg = SourceRegistry::with_defaults();
12//! let _input = reg.open("/tmp/video.mp4").unwrap();
13//! ```
14
15use std::collections::HashMap;
16
17pub use oxideav_container::ReadSeek;
18use oxideav_core::{Error, Result};
19
20mod buffered;
21mod file;
22mod uri;
23
24pub use buffered::BufferedSource;
25pub use file::open_file;
26
27/// Function signature for a source driver. Receives the full URI string
28/// and returns an opened reader.
29pub type OpenSourceFn = fn(uri: &str) -> Result<Box<dyn ReadSeek>>;
30
31/// Registry mapping URI schemes to opener functions.
32pub struct SourceRegistry {
33 schemes: HashMap<String, OpenSourceFn>,
34}
35
36impl SourceRegistry {
37 /// Empty registry. Callers must register at least the `file` driver
38 /// before calling [`open`](Self::open).
39 pub fn new() -> Self {
40 Self {
41 schemes: HashMap::new(),
42 }
43 }
44
45 /// Registry pre-populated with the built-in `file` driver. Bare paths
46 /// (without a scheme) also dispatch to it.
47 pub fn with_defaults() -> Self {
48 let mut r = Self::new();
49 r.register("file", open_file);
50 r
51 }
52
53 /// Register an opener for a scheme. Schemes are normalised to ASCII
54 /// lowercase. Replaces any prior registration.
55 pub fn register(&mut self, scheme: &str, opener: OpenSourceFn) {
56 self.schemes.insert(scheme.to_ascii_lowercase(), opener);
57 }
58
59 /// Open a URI. The URI's scheme determines which opener runs; bare
60 /// paths (no scheme) and unrecognised schemes both fall back to the
61 /// `file` driver if it is registered.
62 pub fn open(&self, uri_str: &str) -> Result<Box<dyn ReadSeek>> {
63 let (scheme, _) = uri::split(uri_str);
64 let scheme = scheme.to_ascii_lowercase();
65 if let Some(opener) = self.schemes.get(&scheme) {
66 return opener(uri_str);
67 }
68 // Fall back to file driver for unknown schemes — useful when a
69 // caller hands us "/path" or a Windows drive letter that uri::split
70 // already mapped to "file" anyway.
71 if let Some(opener) = self.schemes.get("file") {
72 return opener(uri_str);
73 }
74 Err(Error::Unsupported(format!(
75 "no source driver for scheme '{scheme}' (URI: {uri_str})"
76 )))
77 }
78
79 /// Iterate the registered schemes (for diagnostics).
80 pub fn schemes(&self) -> impl Iterator<Item = &str> {
81 self.schemes.keys().map(|s| s.as_str())
82 }
83}
84
85impl Default for SourceRegistry {
86 fn default() -> Self {
87 Self::with_defaults()
88 }
89}