Skip to main content

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}