use std::path::{Path, PathBuf};
use crate::disc::DvdDisc;
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DvdUri {
AutoDetect,
Path(PathBuf),
}
pub fn parse_dvd_uri(uri: &str) -> Result<DvdUri> {
let rest = uri
.strip_prefix("dvd://")
.or_else(|| uri.strip_prefix("dvd:"))
.ok_or(Error::NotDvdVideo("not a dvd:// URI"))?;
if rest.is_empty() || rest == "/" {
return Ok(DvdUri::AutoDetect);
}
let path = if let Some(p) = rest.strip_prefix('/') {
if p.starts_with('/') {
PathBuf::from(p)
} else {
PathBuf::from(format!("/{p}"))
}
} else {
PathBuf::from(rest)
};
Ok(DvdUri::Path(path))
}
#[derive(Debug)]
pub struct DvdDiscSource {
pub disc: DvdDisc,
path: PathBuf,
}
impl DvdDiscSource {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let disc = DvdDisc::open(&path)?;
Ok(Self { disc, path })
}
pub fn path(&self) -> &Path {
&self.path
}
}
#[cfg(feature = "registry")]
pub fn open_dvd(uri: &str) -> oxideav_core::Result<Box<dyn oxideav_core::BytesSource>> {
use oxideav_core::Error as CoreError;
let parsed = parse_dvd_uri(uri).map_err(|e| CoreError::invalid(e.to_string()))?;
let path = match parsed {
DvdUri::AutoDetect => {
return Err(CoreError::invalid(
"dvd:// auto-detect is Phase 2 — pass an explicit dvd:///path/to/disc.iso",
));
}
DvdUri::Path(p) => p,
};
if !path.exists() {
return Err(CoreError::invalid(format!(
"dvd:// path {} does not exist",
path.display()
)));
}
let source =
DvdDiscSource::open(&path).map_err(|e| CoreError::invalid(format!("dvd:// open: {e}")))?;
let file = std::fs::File::open(source.path())
.map_err(|e| CoreError::invalid(format!("dvd:// reopen: {e}")))?;
Ok(Box::new(FileBytesSource { file }))
}
#[cfg(feature = "registry")]
struct FileBytesSource {
file: std::fs::File,
}
#[cfg(feature = "registry")]
impl std::io::Read for FileBytesSource {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.file.read(buf)
}
}
#[cfg(feature = "registry")]
impl std::io::Seek for FileBytesSource {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.file.seek(pos)
}
}
#[cfg(feature = "registry")]
impl std::fmt::Debug for FileBytesSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileBytesSource").finish()
}
}
#[cfg(feature = "registry")]
pub fn register(ctx: &mut oxideav_core::RuntimeContext) {
ctx.sources.register_bytes("dvd", open_dvd);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_auto_detect() {
assert_eq!(parse_dvd_uri("dvd://").unwrap(), DvdUri::AutoDetect);
assert_eq!(parse_dvd_uri("dvd:").unwrap(), DvdUri::AutoDetect);
assert_eq!(parse_dvd_uri("dvd:///").unwrap(), DvdUri::AutoDetect);
}
#[test]
fn parse_absolute_path() {
assert_eq!(
parse_dvd_uri("dvd:///tmp/disc.iso").unwrap(),
DvdUri::Path(PathBuf::from("/tmp/disc.iso"))
);
assert_eq!(
parse_dvd_uri("dvd:///dev/sr0").unwrap(),
DvdUri::Path(PathBuf::from("/dev/sr0"))
);
}
#[test]
fn rejects_wrong_scheme() {
assert!(parse_dvd_uri("file:///x").is_err());
assert!(parse_dvd_uri("http://example/").is_err());
}
}