v0.20.1: delete SectorReader, extract Disc::patch, doc/stub cleanup
WO-2 (delete SectorReader trait): - The 0.18 trait split into SectorSource (read-only) and SectorSink (write-only) is final; the legacy SectorReader alias was a bridge. - Renames every internal &mut dyn SectorReader (~25 sites) to &mut dyn SectorSource. The trait method capacity() becomes capacity_sectors() with a default of 0 (preserves SectorReader's default-0 behavior). - Deletes the SectorReader trait, its blanket-to-Source bridge, and the FileSectorReader type alias. Adds explicit forwarding impls for Box<dyn SectorSource> and &mut dyn SectorSource so generic decorators like DecryptingSectorSource<S: SectorSource> compose. WO-3a (extract Disc::patch): - Moves Disc::patch (1230 lines) and bytes_bad_in_title from disc/mod.rs into disc/patch.rs as a split inherent impl. Zero behavior change — pure mechanical relocation. disc/mod.rs drops from 3,945 to 2,714 LOC. WO-6 (partial): - Deletes src/labels/png_filenames.rs — was a 72-LOC stub with detect() returning false, never wired into the PARSERS registry. project docs doc drift fixes (audited 2026-05-13): - JUMP_BASE_SECTORS: 256→1024 (64 MB base for UHD, not 8 MB) - PASSN_DAMAGE_THRESHOLD_PCT: 12→6 - PASSN_SKIP_SECTORS_BASE: 64→32 - MAX_RANGE_SECS=180: replaced by proportional range_sectors × 25, capped at RANGE_BUDGET_CAP_SECS=1800.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libfreemkv"
|
name = "libfreemkv"
|
||||||
version = "0.20.0"
|
version = "0.20.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.86"
|
rust-version = "1.86"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ pub mod lfsr;
|
|||||||
pub(crate) mod tables;
|
pub(crate) mod tables;
|
||||||
|
|
||||||
use crate::disc::Extent;
|
use crate::disc::Extent;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
|
|
||||||
/// CSS decryption state for a DVD title.
|
/// CSS decryption state for a DVD title.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -34,7 +34,7 @@ pub struct CssState {
|
|||||||
/// 0x80 (start of the encrypted region). This only happens when a new PES
|
/// 0x80 (start of the encrypted region). This only happens when a new PES
|
||||||
/// packet begins at exactly sector offset 128. We scan up to 50000
|
/// packet begins at exactly sector offset 128. We scan up to 50000
|
||||||
/// scrambled sectors sequentially across all extents.
|
/// scrambled sectors sequentially across all extents.
|
||||||
pub fn crack_key(reader: &mut dyn SectorReader, extents: &[Extent]) -> Option<CssState> {
|
pub fn crack_key(reader: &mut dyn SectorSource, extents: &[Extent]) -> Option<CssState> {
|
||||||
let mut tried = 0u32;
|
let mut tried = 0u32;
|
||||||
let max_tries = 50_000;
|
let max_tries = 50_000;
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -3,13 +3,13 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::clpi;
|
use crate::clpi;
|
||||||
use crate::mpls;
|
use crate::mpls;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf;
|
use crate::udf;
|
||||||
|
|
||||||
impl Disc {
|
impl Disc {
|
||||||
/// Scan Blu-ray titles from MPLS playlists.
|
/// Scan Blu-ray titles from MPLS playlists.
|
||||||
pub(super) fn scan_bluray_titles(
|
pub(super) fn scan_bluray_titles(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
) -> Vec<DiscTitle> {
|
) -> Vec<DiscTitle> {
|
||||||
let mut titles = Vec::new();
|
let mut titles = Vec::new();
|
||||||
@@ -31,7 +31,7 @@ impl Disc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn parse_playlist(
|
pub(super) fn parse_playlist(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
filename: &str,
|
filename: &str,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
@@ -199,7 +199,7 @@ impl Disc {
|
|||||||
/// Prefers English, falls back to first available language.
|
/// Prefers English, falls back to first available language.
|
||||||
/// Returns None if META directory is empty or XML has no usable title.
|
/// Returns None if META directory is empty or XML has no usable title.
|
||||||
pub(super) fn read_meta_title(
|
pub(super) fn read_meta_title(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let meta_dir = udf_fs.find_dir("/BDMV/META")?;
|
let meta_dir = udf_fs.find_dir("/BDMV/META")?;
|
||||||
|
|||||||
+2
-2
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ifo;
|
use crate::ifo;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf;
|
use crate::udf;
|
||||||
|
|
||||||
impl Disc {
|
impl Disc {
|
||||||
/// Scan DVD titles from IFO files (VIDEO_TS.IFO + VTS_XX_0.IFO).
|
/// Scan DVD titles from IFO files (VIDEO_TS.IFO + VTS_XX_0.IFO).
|
||||||
pub(super) fn scan_dvd_titles(
|
pub(super) fn scan_dvd_titles(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
) -> Vec<DiscTitle> {
|
) -> Vec<DiscTitle> {
|
||||||
let dvd_info = match ifo::parse_vmg(reader, udf_fs) {
|
let dvd_info = match ifo::parse_vmg(reader, udf_fs) {
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf;
|
use crate::udf;
|
||||||
|
|
||||||
/// Result of SCSI AACS handshake (ECDH authentication).
|
/// Result of SCSI AACS handshake (ECDH authentication).
|
||||||
@@ -113,12 +113,12 @@ impl Disc {
|
|||||||
|
|
||||||
/// Resolve disc encryption — AACS 1.0, AACS 2.0, CSS, or none.
|
/// Resolve disc encryption — AACS 1.0, AACS 2.0, CSS, or none.
|
||||||
///
|
///
|
||||||
/// Reads AACS files from UDF (via SectorReader), resolves keys through
|
/// Reads AACS files from UDF (via SectorSource), resolves keys through
|
||||||
/// whatever path works: KEYDB VUK lookup, media key derivation, processing
|
/// whatever path works: KEYDB VUK lookup, media key derivation, processing
|
||||||
/// keys, device keys. Uses handshake result (volume ID, bus key) if available.
|
/// keys, device keys. Uses handshake result (volume ID, bus key) if available.
|
||||||
pub(super) fn resolve_encryption(
|
pub(super) fn resolve_encryption(
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
keydb_path: &std::path::Path,
|
keydb_path: &std::path::Path,
|
||||||
handshake: Option<&HandshakeResult>,
|
handshake: Option<&HandshakeResult>,
|
||||||
) -> Result<AacsState> {
|
) -> Result<AacsState> {
|
||||||
|
|||||||
+11
-1243
File diff suppressed because it is too large
Load Diff
+1243
-3
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
//! - Mapfile is single-writer (consumer-only). No locking.
|
//! - Mapfile is single-writer (consumer-only). No locking.
|
||||||
//! - All `read_error::ReadCtx` state stays on the producer thread.
|
//! - All `read_error::ReadCtx` state stays on the producer thread.
|
||||||
//! - `set_speed` calls happen on the producer thread (same thread that
|
//! - `set_speed` calls happen on the producer thread (same thread that
|
||||||
//! owns the `SectorReader`). No new SCSI concurrency.
|
//! owns the `SectorSource`). No new SCSI concurrency.
|
||||||
//! - Per-iteration ordering of file-write → mapfile-record is kept
|
//! - Per-iteration ordering of file-write → mapfile-record is kept
|
||||||
//! intact in the consumer (write before record), so the on-disk
|
//! intact in the consumer (write before record), so the on-disk
|
||||||
//! invariant "mapfile only marks Finished what the file has
|
//! invariant "mapfile only marks Finished what the file has
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ use crate::platform::PlatformDriver;
|
|||||||
use crate::platform::mt1959::Mt1959;
|
use crate::platform::mt1959::Mt1959;
|
||||||
use crate::profile::{self, DriveProfile};
|
use crate::profile::{self, DriveProfile};
|
||||||
use crate::scsi::ScsiTransport;
|
use crate::scsi::ScsiTransport;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
@@ -707,7 +707,7 @@ fn open_block_device_for_sg(sg_path: &Path) -> Option<std::os::unix::io::RawFd>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for Drive {
|
impl SectorSource for Drive {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
use crate::disc::{Codec, Resolution};
|
use crate::disc::{Codec, Resolution};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
|
|
||||||
// ── Public types ────────────────────────────────────────────────────────────
|
// ── Public types ────────────────────────────────────────────────────────────
|
||||||
@@ -184,7 +184,7 @@ fn bcd_byte(b: u8) -> u32 {
|
|||||||
///
|
///
|
||||||
/// Reads the VMG (Video Manager) to discover title sets, then reads each
|
/// Reads the VMG (Video Manager) to discover title sets, then reads each
|
||||||
/// VTS IFO to extract PGC chains, cell addresses, and stream attributes.
|
/// VTS IFO to extract PGC chains, cell addresses, and stream attributes.
|
||||||
pub fn parse_vmg(reader: &mut dyn SectorReader, udf: &UdfFs) -> Result<DvdInfo> {
|
pub fn parse_vmg(reader: &mut dyn SectorSource, udf: &UdfFs) -> Result<DvdInfo> {
|
||||||
let vmg_data = udf.read_file(reader, "/VIDEO_TS/VIDEO_TS.IFO")?;
|
let vmg_data = udf.read_file(reader, "/VIDEO_TS/VIDEO_TS.IFO")?;
|
||||||
|
|
||||||
// Validate VMG magic
|
// Validate VMG magic
|
||||||
@@ -263,7 +263,7 @@ pub fn parse_vmg(reader: &mut dyn SectorReader, udf: &UdfFs) -> Result<DvdInfo>
|
|||||||
///
|
///
|
||||||
/// `titles_info` is a list of (chapter_count, vts_title_number) from TT_SRPT.
|
/// `titles_info` is a list of (chapter_count, vts_title_number) from TT_SRPT.
|
||||||
fn parse_vts(
|
fn parse_vts(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf: &UdfFs,
|
udf: &UdfFs,
|
||||||
vts_number: u8,
|
vts_number: u8,
|
||||||
titles_info: &[(u16, u8)],
|
titles_info: &[(u16, u8)],
|
||||||
|
|||||||
+2
-2
@@ -26,7 +26,7 @@
|
|||||||
// The module wiring (registry hook + public re-export) is added
|
// The module wiring (registry hook + public re-export) is added
|
||||||
// separately. Until then the parse/detect entry points have no
|
// separately. Until then the parse/detect entry points have no
|
||||||
use super::xml;
|
use super::xml;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
/// Read every `bdmt_<lang>.xml` under `/BDMV/META/DL/` and return the
|
/// Read every `bdmt_<lang>.xml` under `/BDMV/META/DL/` and return the
|
||||||
/// aggregated [`DiscMetadata`]. Returns `None` if no titles could be
|
/// aggregated [`DiscMetadata`]. Returns `None` if no titles could be
|
||||||
/// extracted from any file.
|
/// extracted from any file.
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<DiscMetadata> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<DiscMetadata> {
|
||||||
let dir = udf.find_dir("/BDMV/META/DL")?;
|
let dir = udf.find_dir("/BDMV/META/DL")?;
|
||||||
let mut out = DiscMetadata::default();
|
let mut out = DiscMetadata::default();
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
//! report. Surfaced via the labels-analyze tool — not part of the
|
//! report. Surfaced via the labels-analyze tool — not part of the
|
||||||
//! `analyze()` pipeline (no impact on the label output).
|
//! `analyze()` pipeline (no impact on the label output).
|
||||||
|
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ impl ClpiVsMplsAudit {
|
|||||||
/// dedup-by-PID table of (CLPI fields, MPLS fields), return the
|
/// dedup-by-PID table of (CLPI fields, MPLS fields), return the
|
||||||
/// merged view. Missing files (read errors, parse failures) are
|
/// merged view. Missing files (read errors, parse failures) are
|
||||||
/// silently skipped — this is diagnostic, not correctness-critical.
|
/// silently skipped — this is diagnostic, not correctness-critical.
|
||||||
pub fn audit(reader: &mut dyn SectorReader, udf: &UdfFs) -> ClpiVsMplsAudit {
|
pub fn audit(reader: &mut dyn SectorSource, udf: &UdfFs) -> ClpiVsMplsAudit {
|
||||||
// Aggregate by PID across all CLPI files. If a PID appears in
|
// Aggregate by PID across all CLPI files. If a PID appears in
|
||||||
// multiple clips (typical — main movie clip + trailers reference
|
// multiple clips (typical — main movie clip + trailers reference
|
||||||
// the same audio stream PIDs), first encountered wins (they should
|
// the same audio stream PIDs), first encountered wins (they should
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! stream number mapping via playbackconfig.
|
//! stream number mapping via playbackconfig.
|
||||||
|
|
||||||
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, xml};
|
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, xml};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
super::jar_file_exists(udf, "streamproperties.xml")
|
super::jar_file_exists(udf, "streamproperties.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
let sp_data = super::read_jar_file(reader, udf, "streamproperties.xml")?;
|
let sp_data = super::read_jar_file(reader, udf, "streamproperties.xml")?;
|
||||||
let sp_text = std::str::from_utf8(&sp_data).ok()?;
|
let sp_text = std::str::from_utf8(&sp_data).ok()?;
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -5,7 +5,7 @@
|
|||||||
//! menu_base.prop provides stream number → button name mapping.
|
//! menu_base.prop provides stream number → button name mapping.
|
||||||
|
|
||||||
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, vocab};
|
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, vocab};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
|| super::jar_file_exists(udf, "language_streams.txt")
|
|| super::jar_file_exists(udf, "language_streams.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
// Try language_streams.txt first (richer structured data)
|
// Try language_streams.txt first (richer structured data)
|
||||||
let ls_labels = parse_language_streams(reader, udf);
|
let ls_labels = parse_language_streams(reader, udf);
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ fn merge(ls: Vec<StreamLabel>, mb: Vec<StreamLabel>) -> Vec<StreamLabel> {
|
|||||||
|
|
||||||
// ── language_streams.txt parser ────────────────────────────────────────────
|
// ── language_streams.txt parser ────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse_language_streams(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<Vec<StreamLabel>> {
|
fn parse_language_streams(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<Vec<StreamLabel>> {
|
||||||
let data = super::read_jar_file(reader, udf, "language_streams.txt")?;
|
let data = super::read_jar_file(reader, udf, "language_streams.txt")?;
|
||||||
let text = std::str::from_utf8(&data).ok()?;
|
let text = std::str::from_utf8(&data).ok()?;
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ mod tests {
|
|||||||
|
|
||||||
/// Build a minimal menu_base.prop text and run `parse_menu_base`'s
|
/// Build a minimal menu_base.prop text and run `parse_menu_base`'s
|
||||||
/// inner logic via a temporary closure. This isolates the prop
|
/// inner logic via a temporary closure. This isolates the prop
|
||||||
/// parsing without needing a SectorReader.
|
/// parsing without needing a SectorSource.
|
||||||
fn parse_props(text: &str) -> Vec<StreamLabel> {
|
fn parse_props(text: &str) -> Vec<StreamLabel> {
|
||||||
// Mirror the inner loop of parse_menu_base exactly. Kept
|
// Mirror the inner loop of parse_menu_base exactly. Kept
|
||||||
// separate so the test doesn't need disc fixtures.
|
// separate so the test doesn't need disc fixtures.
|
||||||
@@ -338,7 +338,7 @@ mod tests {
|
|||||||
|
|
||||||
// ── menu_base.prop parser ──────────────────────────────────────────────────
|
// ── menu_base.prop parser ──────────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse_menu_base(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<Vec<StreamLabel>> {
|
fn parse_menu_base(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<Vec<StreamLabel>> {
|
||||||
let data = super::read_jar_file(reader, udf, "menu_base.prop")?;
|
let data = super::read_jar_file(reader, udf, "menu_base.prop")?;
|
||||||
let text = std::str::from_utf8(&data).ok()?;
|
let text = std::str::from_utf8(&data).ok()?;
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -35,11 +35,11 @@
|
|||||||
|
|
||||||
use super::class_reader::CpInfo;
|
use super::class_reader::CpInfo;
|
||||||
use super::{ParseResult, StreamLabel, StreamLabelType, jar, vocab};
|
use super::{ParseResult, StreamLabel, StreamLabelType, jar, vocab};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// dbp detect can't peek inside a jar without a SectorReader (the
|
/// dbp detect can't peek inside a jar without a SectorSource (the
|
||||||
/// trait function only takes `&UdfFs`), so we trigger on the cheap
|
/// trait function only takes `&UdfFs`), so we trigger on the cheap
|
||||||
/// signal "any top-level .jar in /BDMV/JAR/." That fires on every
|
/// signal "any top-level .jar in /BDMV/JAR/." That fires on every
|
||||||
/// BD-J disc, but parse() does the real `com/dbp/` check and
|
/// BD-J disc, but parse() does the real `com/dbp/` check and
|
||||||
@@ -49,7 +49,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
jar::has_any_top_level_jar(udf)
|
jar::has_any_top_level_jar(udf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
jar::for_each_jar(reader, udf, |_entry_name, archive| {
|
jar::for_each_jar(reader, udf, |_entry_name, archive| {
|
||||||
if !jar::has_path_prefix(archive, "com/dbp/") {
|
if !jar::has_path_prefix(archive, "com/dbp/") {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -84,18 +84,18 @@ use super::class_reader::{
|
|||||||
ICONST_2, ICONST_3, ICONST_4, ICONST_5, ICONST_M1, INVOKESPECIAL, LDC, LDC_W, NEW, SIPUSH,
|
ICONST_2, ICONST_3, ICONST_4, ICONST_5, ICONST_M1, INVOKESPECIAL, LDC, LDC_W, NEW, SIPUSH,
|
||||||
};
|
};
|
||||||
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, jar, vocab};
|
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, jar, vocab};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
pub fn detect(udf: &UdfFs) -> bool {
|
pub fn detect(udf: &UdfFs) -> bool {
|
||||||
// Cheap pre-check at the dir level; the real signal is
|
// Cheap pre-check at the dir level; the real signal is
|
||||||
// `com/bydeluxe/` inside any top-level jar's central directory,
|
// `com/bydeluxe/` inside any top-level jar's central directory,
|
||||||
// which `parse()` confirms when given a `SectorReader`.
|
// which `parse()` confirms when given a `SectorSource`.
|
||||||
jar::has_any_top_level_jar(udf)
|
jar::has_any_top_level_jar(udf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
jar::for_each_jar(reader, udf, |entry_name, archive| {
|
jar::for_each_jar(reader, udf, |entry_name, archive| {
|
||||||
if !jar::has_path_prefix(archive, "com/bydeluxe/") {
|
if !jar::has_path_prefix(archive, "com/bydeluxe/") {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
+3
-3
@@ -13,7 +13,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use super::class_reader::ClassFile;
|
use super::class_reader::ClassFile;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
@@ -26,7 +26,7 @@ pub type Jar = ZipArchive<Cursor<Vec<u8>>>;
|
|||||||
/// True if `/BDMV/JAR/` contains at least one top-level `.jar` file
|
/// True if `/BDMV/JAR/` contains at least one top-level `.jar` file
|
||||||
/// (not under a subdir). Used by `detect()` in parsers whose real
|
/// (not under a subdir). Used by `detect()` in parsers whose real
|
||||||
/// signal lives inside a jar — they can't open the jar without a
|
/// signal lives inside a jar — they can't open the jar without a
|
||||||
/// `SectorReader`, so they use this cheap pre-check and do the real
|
/// `SectorSource`, so they use this cheap pre-check and do the real
|
||||||
/// `com/<vendor>/` discriminator in `parse()`.
|
/// `com/<vendor>/` discriminator in `parse()`.
|
||||||
pub fn has_any_top_level_jar(udf: &UdfFs) -> bool {
|
pub fn has_any_top_level_jar(udf: &UdfFs) -> bool {
|
||||||
let Some(jar_dir) = udf.find_dir("/BDMV/JAR") else {
|
let Some(jar_dir) = udf.find_dir("/BDMV/JAR") else {
|
||||||
@@ -49,7 +49,7 @@ pub fn has_any_top_level_jar(udf: &UdfFs) -> bool {
|
|||||||
///
|
///
|
||||||
/// Entries that fail to read from UDF or that aren't valid zips are
|
/// Entries that fail to read from UDF or that aren't valid zips are
|
||||||
/// silently skipped — same defensive shape as the existing dbp parser.
|
/// silently skipped — same defensive shape as the existing dbp parser.
|
||||||
pub fn for_each_jar<R, F>(reader: &mut dyn SectorReader, udf: &UdfFs, mut f: F) -> Option<R>
|
pub fn for_each_jar<R, F>(reader: &mut dyn SectorSource, udf: &UdfFs, mut f: F) -> Option<R>
|
||||||
where
|
where
|
||||||
F: FnMut(&str, &mut Jar) -> Option<R>,
|
F: FnMut(&str, &mut Jar) -> Option<R>,
|
||||||
{
|
{
|
||||||
|
|||||||
+11
-12
@@ -4,7 +4,7 @@
|
|||||||
//! To add a new format:
|
//! To add a new format:
|
||||||
//! 1. Create `src/labels/myformat.rs`
|
//! 1. Create `src/labels/myformat.rs`
|
||||||
//! 2. Implement `pub fn detect(udf: &UdfFs) -> bool`
|
//! 2. Implement `pub fn detect(udf: &UdfFs) -> bool`
|
||||||
//! 3. Implement `pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<Vec<StreamLabel>>`
|
//! 3. Implement `pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<Vec<StreamLabel>>`
|
||||||
//! 4. Add `mod myformat;` below and one line to `PARSERS` array
|
//! 4. Add `mod myformat;` below and one line to `PARSERS` array
|
||||||
|
|
||||||
mod bdmt;
|
mod bdmt;
|
||||||
@@ -18,13 +18,12 @@ pub(crate) mod jar;
|
|||||||
mod mpls_universal;
|
mod mpls_universal;
|
||||||
mod paramount;
|
mod paramount;
|
||||||
mod pixelogic;
|
mod pixelogic;
|
||||||
mod png_filenames;
|
|
||||||
pub(crate) mod text;
|
pub(crate) mod text;
|
||||||
pub mod vocab;
|
pub mod vocab;
|
||||||
pub(crate) mod xml;
|
pub(crate) mod xml;
|
||||||
|
|
||||||
use crate::disc::{DiscTitle, Stream};
|
use crate::disc::{DiscTitle, Stream};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
|
|
||||||
// Re-export bdmt's public type so callers can construct/inspect
|
// Re-export bdmt's public type so callers can construct/inspect
|
||||||
@@ -88,7 +87,7 @@ pub enum LabelQualifier {
|
|||||||
// to array order on confidence ties.
|
// to array order on confidence ties.
|
||||||
|
|
||||||
type DetectFn = fn(&UdfFs) -> bool;
|
type DetectFn = fn(&UdfFs) -> bool;
|
||||||
type ParseFn = fn(&mut dyn SectorReader, &UdfFs) -> Option<ParseResult>;
|
type ParseFn = fn(&mut dyn SectorSource, &UdfFs) -> Option<ParseResult>;
|
||||||
|
|
||||||
/// Per-parser claim of how reliable its output is. Used by the
|
/// Per-parser claim of how reliable its output is. Used by the
|
||||||
/// registry to pick between parsers when more than one matches (e.g.
|
/// registry to pick between parsers when more than one matches (e.g.
|
||||||
@@ -176,7 +175,7 @@ const PARSERS: &[(&str, DetectFn, ParseFn)] = &[
|
|||||||
|
|
||||||
/// Search disc for config files, extract labels, apply to streams.
|
/// Search disc for config files, extract labels, apply to streams.
|
||||||
/// This is 100% optional — if anything fails, streams are untouched.
|
/// This is 100% optional — if anything fails, streams are untouched.
|
||||||
pub fn apply(reader: &mut dyn SectorReader, udf: &UdfFs, titles: &mut [DiscTitle]) {
|
pub fn apply(reader: &mut dyn SectorSource, udf: &UdfFs, titles: &mut [DiscTitle]) {
|
||||||
let labels = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| extract(reader, udf)))
|
let labels = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| extract(reader, udf)))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if labels.is_empty() {
|
if labels.is_empty() {
|
||||||
@@ -192,7 +191,7 @@ pub fn apply(reader: &mut dyn SectorReader, udf: &UdfFs, titles: &mut [DiscTitle
|
|||||||
/// `forced` flag.
|
/// `forced` flag.
|
||||||
///
|
///
|
||||||
/// Extracted from `apply()` so the matching logic is unit-testable
|
/// Extracted from `apply()` so the matching logic is unit-testable
|
||||||
/// without needing a SectorReader / UdfFs.
|
/// without needing a SectorSource / UdfFs.
|
||||||
pub(crate) fn apply_labels(labels: &[StreamLabel], titles: &mut [DiscTitle]) {
|
pub(crate) fn apply_labels(labels: &[StreamLabel], titles: &mut [DiscTitle]) {
|
||||||
for title in titles.iter_mut() {
|
for title in titles.iter_mut() {
|
||||||
let mut audio_idx: u16 = 0;
|
let mut audio_idx: u16 = 0;
|
||||||
@@ -370,7 +369,7 @@ fn generate_audio_label(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec<StreamLabel> {
|
fn extract(reader: &mut dyn SectorSource, udf: &UdfFs) -> Vec<StreamLabel> {
|
||||||
let mut best: Option<(&'static str, ParseResult)> = None;
|
let mut best: Option<(&'static str, ParseResult)> = None;
|
||||||
for (name, detect, parse) in PARSERS {
|
for (name, detect, parse) in PARSERS {
|
||||||
if !detect(udf) {
|
if !detect(udf) {
|
||||||
@@ -483,7 +482,7 @@ fn type_tag(t: StreamLabelType) -> u8 {
|
|||||||
/// authoring tool left out of the published playlist.
|
/// authoring tool left out of the published playlist.
|
||||||
fn append_clpi_orphans(
|
fn append_clpi_orphans(
|
||||||
labels: &mut Vec<StreamLabel>,
|
labels: &mut Vec<StreamLabel>,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf: &UdfFs,
|
udf: &UdfFs,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// Index existing labels by PID — but StreamLabel doesn't carry
|
// Index existing labels by PID — but StreamLabel doesn't carry
|
||||||
@@ -608,7 +607,7 @@ fn append_clpi_orphans(
|
|||||||
/// the return shape is richer (includes confidence, all detected
|
/// the return shape is richer (includes confidence, all detected
|
||||||
/// parsers, and any parsers that produced empty results).
|
/// parsers, and any parsers that produced empty results).
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis {
|
pub fn analyze(reader: &mut dyn SectorSource, udf: &UdfFs) -> LabelAnalysis {
|
||||||
let inventory = jar_inventory(udf);
|
let inventory = jar_inventory(udf);
|
||||||
let mut parsers_detected: Vec<&'static str> = Vec::new();
|
let mut parsers_detected: Vec<&'static str> = Vec::new();
|
||||||
let mut all_results: Vec<(&'static str, ParseResult)> = Vec::new();
|
let mut all_results: Vec<(&'static str, ParseResult)> = Vec::new();
|
||||||
@@ -699,7 +698,7 @@ pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis {
|
|||||||
/// playlist filename. Skipped entries (read error, parse error, no
|
/// playlist filename. Skipped entries (read error, parse error, no
|
||||||
/// marks) silently dropped — this is a diagnostic field, not a
|
/// marks) silently dropped — this is a diagnostic field, not a
|
||||||
/// correctness-critical one.
|
/// correctness-critical one.
|
||||||
fn collect_chapter_summary(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec<ChapterSummary> {
|
fn collect_chapter_summary(reader: &mut dyn SectorSource, udf: &UdfFs) -> Vec<ChapterSummary> {
|
||||||
let Some(playlist_dir) = udf.find_dir("/BDMV/PLAYLIST") else {
|
let Some(playlist_dir) = udf.find_dir("/BDMV/PLAYLIST") else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
@@ -842,7 +841,7 @@ pub(crate) fn find_jar_file(udf: &UdfFs, filename: &str) -> Option<String> {
|
|||||||
|
|
||||||
/// Read a file from any BDMV/JAR subdirectory by filename.
|
/// Read a file from any BDMV/JAR subdirectory by filename.
|
||||||
pub(crate) fn read_jar_file(
|
pub(crate) fn read_jar_file(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
udf: &UdfFs,
|
udf: &UdfFs,
|
||||||
filename: &str,
|
filename: &str,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
@@ -1073,7 +1072,7 @@ mod gap_fill_tests {
|
|||||||
// ── apply() integration tests ──────────────────────────────────────────────
|
// ── apply() integration tests ──────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// End-to-end coverage for the apply_labels + fill_defaults pipeline
|
// End-to-end coverage for the apply_labels + fill_defaults pipeline
|
||||||
// without needing a SectorReader / UdfFs. Synthetic DiscTitle +
|
// without needing a SectorSource / UdfFs. Synthetic DiscTitle +
|
||||||
// StreamLabel inputs, assert on the resulting Stream field values.
|
// StreamLabel inputs, assert on the resulting Stream field values.
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use super::{
|
|||||||
LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType,
|
LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType,
|
||||||
vocab::{self, LangInfo},
|
vocab::{self, LangInfo},
|
||||||
};
|
};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
|
|
||||||
/// True iff `/BDMV/PLAYLIST/` exists and contains at least one
|
/// True iff `/BDMV/PLAYLIST/` exists and contains at least one
|
||||||
@@ -45,7 +45,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
/// Returns `None` if no labels could be produced (e.g. no .mpls files
|
/// Returns `None` if no labels could be produced (e.g. no .mpls files
|
||||||
/// parsed successfully, or every parsed stream was a type we skip
|
/// parsed successfully, or every parsed stream was a type we skip
|
||||||
/// like IG / DV EL).
|
/// like IG / DV EL).
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
let playlist_dir = udf.find_dir("/BDMV/PLAYLIST")?;
|
let playlist_dir = udf.find_dir("/BDMV/PLAYLIST")?;
|
||||||
|
|
||||||
// Collect mpls filenames first so we don't hold a borrow on udf
|
// Collect mpls filenames first so we don't hold a borrow on udf
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, xml};
|
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, xml};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
|
|
||||||
pub fn detect(udf: &UdfFs) -> bool {
|
pub fn detect(udf: &UdfFs) -> bool {
|
||||||
super::jar_file_exists(udf, "playlists.xml")
|
super::jar_file_exists(udf, "playlists.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
let data = super::read_jar_file(reader, udf, "playlists.xml")?;
|
let data = super::read_jar_file(reader, udf, "playlists.xml")?;
|
||||||
let text = std::str::from_utf8(&data).ok()?;
|
let text = std::str::from_utf8(&data).ok()?;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use super::{
|
|||||||
Confidence, LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, text,
|
Confidence, LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, text,
|
||||||
vocab,
|
vocab,
|
||||||
};
|
};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use crate::udf::UdfFs;
|
use crate::udf::UdfFs;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ pub fn detect(udf: &UdfFs) -> bool {
|
|||||||
super::jar_file_exists(udf, "bluray_project.bin")
|
super::jar_file_exists(udf, "bluray_project.bin")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(reader: &mut dyn SectorReader, udf: &UdfFs) -> Option<ParseResult> {
|
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||||
let data = super::read_jar_file(reader, udf, "bluray_project.bin")?;
|
let data = super::read_jar_file(reader, udf, "bluray_project.bin")?;
|
||||||
// min_len=4 matches the prior local extract_strings impl. The token
|
// min_len=4 matches the prior local extract_strings impl. The token
|
||||||
// grammar is `{lang3}_{codec?}_{purpose?}_{region?}_` so the
|
// grammar is `{lang3}_{codec?}_{purpose?}_{region?}_` so the
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
//! PNG-filename language token parser — stubbed (noop) pending need.
|
|
||||||
//!
|
|
||||||
//! ## What this would do
|
|
||||||
//!
|
|
||||||
//! Some discs encode per-language menu localization as pre-rendered PNG
|
|
||||||
//! menu buttons, one per language, with the language token embedded in
|
|
||||||
//! the filename. Examples observed in the 2026-05-10 corpus:
|
|
||||||
//!
|
|
||||||
//! - **disc-01 (The Amateur)** — `<region>_<lang>_<context>_<format>.png`
|
|
||||||
//! Region prefix: `USA` / `UK` / `JPN` / etc.
|
|
||||||
//! Lang tokens (3-char, uppercase): `ENG`, `FRC`, `FRP`, `DEU`, `DUT`,
|
|
||||||
//! `ITA`, `JPN`, `LAS`, `CSP`, `POL`, `CZE` (11 languages)
|
|
||||||
//!
|
|
||||||
//! - **disc-09 (Dune orig)** — `<title>_<variant>_<lang>_Composite<N>.png`
|
|
||||||
//! Lang tokens (3-char, mixed-case): `Eng`, `Ger` (2 languages)
|
|
||||||
//!
|
|
||||||
//! ## Why stubbed
|
|
||||||
//!
|
|
||||||
//! MPLS already gives per-stream `language` + `coding_type` + stream-type
|
|
||||||
//! (audio vs subtitle) on every disc. For the 2 unknown-framework discs
|
|
||||||
//! that PNG filenames would close (disc-01, disc-09), MPLS will produce
|
|
||||||
//! a strict superset of what filenames could give us, because MPLS knows
|
|
||||||
//! per-stream attribution while filenames only know "the disc offers
|
|
||||||
//! these N language buttons."
|
|
||||||
//!
|
|
||||||
//! The **only** thing PNG filenames give us that MPLS doesn't is **studio
|
|
||||||
//! variant disambiguation**:
|
|
||||||
//! - `FRC` (French Canadian) vs `FRP` (French Parisian) — MPLS just says `fra`
|
|
||||||
//! - `LAS` (Latin American Spanish) vs `CSP` (Castilian Spanish) — MPLS just says `spa`
|
|
||||||
//!
|
|
||||||
//! That's niche enough that it doesn't justify implementing right now.
|
|
||||||
//! Reactivate this parser only when:
|
|
||||||
//! 1. We hit a disc where MPLS is malformed/empty AND PNG filenames are
|
|
||||||
//! the only language hint, OR
|
|
||||||
//! 2. A downstream consumer needs the studio variant suffix for output
|
|
||||||
//! naming (e.g. `Title (French Canadian).mkv` vs `Title (French).mkv`).
|
|
||||||
//!
|
|
||||||
//! ## When reactivating
|
|
||||||
//!
|
|
||||||
//! Implement `parse` to:
|
|
||||||
//! 1. Iterate top-level PNG paths in `/BDMV/JAR/` (and `<id>/` subdirs).
|
|
||||||
//! 2. Tokenize each filename on `_` / `-` / `.`
|
|
||||||
//! 3. Match each token against an alias table:
|
|
||||||
//! - ISO 639-1 / 639-2 standard codes
|
|
||||||
//! - Studio variants: `FRC`/`FRP` → `fra-CA`/`fra-FR`,
|
|
||||||
//! `LAS`/`CSP` → `spa-419`/`spa-ES`,
|
|
||||||
//! mixed-case shortforms `Eng`/`Ger`/`Fra`/`Spa`/`Jpn` → ISO 639-2
|
|
||||||
//! - Country prefix filter: drop `USA`/`UK`/`JPN`/`AUS`/`GER`/`FR` when
|
|
||||||
//! they appear in position 0 (those are region markers, not langs).
|
|
||||||
//! 4. Deduplicate. Confidence stays `Low` because we still don't know
|
|
||||||
//! per-stream codec or audio/subtitle attribution.
|
|
||||||
//!
|
|
||||||
//! Wire as ENRICHMENT after MPLS in `mod.rs::analyze`, not as a primary
|
|
||||||
//! parser: PNG filenames upgrade `lang=fra` to `lang=fra-CA` when both
|
|
||||||
//! sources agree on the disc; they should never overwrite MPLS data.
|
|
||||||
|
|
||||||
use super::ParseResult;
|
|
||||||
use crate::sector::SectorReader;
|
|
||||||
use crate::udf::UdfFs;
|
|
||||||
|
|
||||||
/// Stub: returns false so the dispatcher never calls `parse`. Reactivate
|
|
||||||
/// by checking for the patterns described in the module docs.
|
|
||||||
#[allow(dead_code)] // module-level noop, not wired into PARSERS until needed
|
|
||||||
pub fn detect(_udf: &UdfFs) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stub: returns None. See module docs for the implementation sketch.
|
|
||||||
#[allow(dead_code)] // module-level noop, not wired into PARSERS until needed
|
|
||||||
pub fn parse(_reader: &mut dyn SectorReader, _udf: &UdfFs) -> Option<ParseResult> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
+4
-8
@@ -179,7 +179,7 @@ pub use disc::{
|
|||||||
// All stream types implement `pes::Stream` — read PES frames from a source,
|
// All stream types implement `pes::Stream` — read PES frames from a source,
|
||||||
// write PES frames to a sink. Pick the right type at construction:
|
// write PES frames to a sink. Pick the right type at construction:
|
||||||
//
|
//
|
||||||
// - `DiscStream` — physical drive or ISO (any `SectorReader`). Read-only.
|
// - `DiscStream` — physical drive or ISO (any `SectorSource`). Read-only.
|
||||||
// - `MkvStream` — Matroska container. Read on `open()`, write on `create()`.
|
// - `MkvStream` — Matroska container. Read on `open()`, write on `create()`.
|
||||||
// - `M2tsStream` — Blu-ray Transport Stream. Read on `open()`, write on `create()`.
|
// - `M2tsStream` — Blu-ray Transport Stream. Read on `open()`, write on `create()`.
|
||||||
// - `NetworkStream` — TCP. Read on `listen()`, write on `connect()`.
|
// - `NetworkStream` — TCP. Read on `listen()`, write on `connect()`.
|
||||||
@@ -206,18 +206,14 @@ pub use mux::{InputOptions, StreamUrl, input, output, parse_url};
|
|||||||
// ─── Lower-level surfaces ───────────────────────────────────────────────────
|
// ─── Lower-level surfaces ───────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// `ScsiTransport` is the platform-abstraction trait Drive uses; expose for
|
// `ScsiTransport` is the platform-abstraction trait Drive uses; expose for
|
||||||
// out-of-tree platform backends. `SectorSource` / `SectorSink` are the 0.18
|
// out-of-tree platform backends. `SectorSource` / `SectorSink` are the
|
||||||
// direction-typed read/write traits; `FileSectorSource` and `FileSectorSink`
|
// direction-typed read/write traits; `FileSectorSource` and `FileSectorSink`
|
||||||
// are the ISO-on-disk implementations. [`DecryptingSectorSource`] is the
|
// are the ISO-on-disk implementations. [`DecryptingSectorSource`] is the
|
||||||
// single decrypt-on-read decorator (AACS / CSS / none) — wrap any
|
// single decrypt-on-read decorator (AACS / CSS / none) — wrap any
|
||||||
// `SectorSource` to get plaintext sectors out. The legacy `SectorReader` /
|
// `SectorSource` to get plaintext sectors out.
|
||||||
// `FileSectorReader` names stay re-exported through the 0.18 migration
|
|
||||||
// window so existing call sites compile unchanged; a blanket impl makes
|
|
||||||
// every `SectorReader` automatically usable as a `SectorSource`.
|
|
||||||
pub use scsi::{DriveInfo, ScsiSense, ScsiTransport, drive_has_disc, list_drives};
|
pub use scsi::{DriveInfo, ScsiSense, ScsiTransport, drive_has_disc, list_drives};
|
||||||
pub use sector::{
|
pub use sector::{
|
||||||
DecryptingSectorSource, FileSectorReader, FileSectorSink, FileSectorSource, SectorReader,
|
DecryptingSectorSource, FileSectorSink, FileSectorSource, SectorSink, SectorSource,
|
||||||
SectorSink, SectorSource,
|
|
||||||
};
|
};
|
||||||
pub use speed::DriveSpeed;
|
pub use speed::DriveSpeed;
|
||||||
pub use udf::{UdfFs, read_filesystem};
|
pub use udf::{UdfFs, read_filesystem};
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
//! DiscStream — read any disc (physical drive or ISO file) → PES frames.
|
//! DiscStream — read any disc (physical drive or ISO file) → PES frames.
|
||||||
//!
|
//!
|
||||||
//! One stream type for all disc sources. The source is a SectorReader —
|
//! One stream type for all disc sources. The source is a SectorSource —
|
||||||
//! Drive (hardware) or IsoSectorReader (file). DiscStream doesn't care.
|
//! Drive (hardware) or IsoSectorReader (file). DiscStream doesn't care.
|
||||||
//!
|
//!
|
||||||
//! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`.
|
//! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`.
|
||||||
@@ -9,7 +9,7 @@ use crate::disc::{Disc, DiscTitle, Extent};
|
|||||||
use crate::drive::extract_scsi_context;
|
use crate::drive::extract_scsi_context;
|
||||||
use crate::event::{BatchSizeReason, Event, EventKind};
|
use crate::event::{BatchSizeReason, Event, EventKind};
|
||||||
use crate::halt::Halt;
|
use crate::halt::Halt;
|
||||||
use crate::sector::{DecryptingSectorSource, SectorReader, SectorSource};
|
use crate::sector::{DecryptingSectorSource, SectorSource};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
@@ -97,7 +97,7 @@ impl AdaptiveBatch {
|
|||||||
|
|
||||||
/// Disc stream. Reads sectors from any source → PES frames.
|
/// Disc stream. Reads sectors from any source → PES frames.
|
||||||
///
|
///
|
||||||
/// Sources: physical drive, ISO file, or any SectorReader.
|
/// Sources: physical drive, ISO file, or any SectorSource.
|
||||||
/// Decrypt, demux, and codec parsing happen internally.
|
/// Decrypt, demux, and codec parsing happen internally.
|
||||||
pub struct DiscStream {
|
pub struct DiscStream {
|
||||||
/// Underlying sector source wrapped in the 0.18
|
/// Underlying sector source wrapped in the 0.18
|
||||||
@@ -105,7 +105,7 @@ pub struct DiscStream {
|
|||||||
/// call yields plaintext, so `fill_extents` no longer needs an
|
/// call yields plaintext, so `fill_extents` no longer needs an
|
||||||
/// inline `decrypt::decrypt_sectors` step. `DecryptKeys::None`
|
/// inline `decrypt::decrypt_sectors` step. `DecryptKeys::None`
|
||||||
/// (raw / unencrypted disc) makes the decorator a pass-through.
|
/// (raw / unencrypted disc) makes the decorator a pass-through.
|
||||||
reader: DecryptingSectorSource<Box<dyn SectorReader>>,
|
reader: DecryptingSectorSource<Box<dyn SectorSource>>,
|
||||||
title: DiscTitle,
|
title: DiscTitle,
|
||||||
disc: Option<Disc>,
|
disc: Option<Disc>,
|
||||||
/// Mirror of the keys handed in at construction. The decorator
|
/// Mirror of the keys handed in at construction. The decorator
|
||||||
@@ -160,11 +160,11 @@ pub struct DiscStream {
|
|||||||
impl DiscStream {
|
impl DiscStream {
|
||||||
/// Create a disc stream from any sector reader.
|
/// Create a disc stream from any sector reader.
|
||||||
///
|
///
|
||||||
/// Works with physical drives and ISO files — both implement SectorReader.
|
/// Works with physical drives and ISO files — both implement SectorSource.
|
||||||
/// The caller opens the source, scans for titles/keys, and passes them in.
|
/// The caller opens the source, scans for titles/keys, and passes them in.
|
||||||
/// The stream handles demuxing, decryption, and codec parsing internally.
|
/// The stream handles demuxing, decryption, and codec parsing internally.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
reader: Box<dyn SectorReader>,
|
reader: Box<dyn SectorSource>,
|
||||||
title: DiscTitle,
|
title: DiscTitle,
|
||||||
decrypt_keys: crate::decrypt::DecryptKeys,
|
decrypt_keys: crate::decrypt::DecryptKeys,
|
||||||
batch_sectors: u16,
|
batch_sectors: u16,
|
||||||
@@ -177,7 +177,7 @@ impl DiscStream {
|
|||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
target: "mux",
|
target: "mux",
|
||||||
"DiscStream constructed with reader type: {}",
|
"DiscStream constructed with reader type: {}",
|
||||||
std::any::type_name::<dyn SectorReader>()
|
std::any::type_name::<dyn SectorSource>()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut pids = Vec::new();
|
let mut pids = Vec::new();
|
||||||
@@ -595,14 +595,14 @@ mod tests {
|
|||||||
|
|
||||||
/// Static-assert `DiscStream: Send`. The `Stream` trait has `Send` as a
|
/// Static-assert `DiscStream: Send`. The `Stream` trait has `Send` as a
|
||||||
/// supertrait — if a future field on `DiscStream` is non-`Send` (e.g.
|
/// supertrait — if a future field on `DiscStream` is non-`Send` (e.g.
|
||||||
/// a `Box<dyn Read>` instead of `Box<dyn SectorReader>`), this fails
|
/// a `Box<dyn Read>` instead of `Box<dyn SectorSource>`), this fails
|
||||||
/// at compile time, before the runtime trait-object test below.
|
/// at compile time, before the runtime trait-object test below.
|
||||||
fn _assert_disc_stream_is_send() {
|
fn _assert_disc_stream_is_send() {
|
||||||
fn requires_send<T: Send>() {}
|
fn requires_send<T: Send>() {}
|
||||||
requires_send::<DiscStream>();
|
requires_send::<DiscStream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trivial `SectorReader` that yields zeroed sectors. Empty title means
|
/// Trivial `SectorSource` that yields zeroed sectors. Empty title means
|
||||||
/// the demuxer produces no PES frames, so `read()` walks the extents to
|
/// the demuxer produces no PES frames, so `read()` walks the extents to
|
||||||
/// EOF and returns `Ok(None)`. That's enough to exercise the trait-object
|
/// EOF and returns `Ok(None)`. That's enough to exercise the trait-object
|
||||||
/// dispatch — the goal here is the bridge, not the demuxer.
|
/// dispatch — the goal here is the bridge, not the demuxer.
|
||||||
@@ -610,7 +610,7 @@ mod tests {
|
|||||||
capacity: u32,
|
capacity: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::sector::SectorReader for ZeroReader {
|
impl crate::sector::SectorSource for ZeroReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
_lba: u32,
|
_lba: u32,
|
||||||
@@ -623,7 +623,7 @@ mod tests {
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
//! ISO sector reader — file-backed SectorReader for Blu-ray ISO images.
|
//! ISO sector reader — file-backed SectorSource for Blu-ray ISO images.
|
||||||
//!
|
//!
|
||||||
//! An ISO is a flat image of 2048-byte sectors. Sector N starts at byte offset N * 2048.
|
//! An ISO is a flat image of 2048-byte sectors. Sector N starts at byte offset N * 2048.
|
||||||
//! Used by DiscStream::open_iso() and Disc::scan_image().
|
//! Used by DiscStream::open_iso() and Disc::scan_image().
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -32,12 +32,12 @@ impl IsoSectorReader {
|
|||||||
Ok(Self { file, capacity })
|
Ok(Self { file, capacity })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn capacity(&self) -> u32 {
|
pub fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for IsoSectorReader {
|
impl SectorSource for IsoSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
@@ -73,7 +73,7 @@ mod tests {
|
|||||||
std::fs::write(&dir, &data).unwrap();
|
std::fs::write(&dir, &data).unwrap();
|
||||||
|
|
||||||
let mut reader = IsoSectorReader::open(dir.to_str().unwrap()).unwrap();
|
let mut reader = IsoSectorReader::open(dir.to_str().unwrap()).unwrap();
|
||||||
assert_eq!(reader.capacity(), 4);
|
assert_eq!(reader.capacity_sectors(), 4);
|
||||||
|
|
||||||
let mut buf = [0u8; 2048];
|
let mut buf = [0u8; 2048];
|
||||||
reader.read_sectors(0, 1, &mut buf, true).unwrap();
|
reader.read_sectors(0, 1, &mut buf, true).unwrap();
|
||||||
@@ -94,7 +94,7 @@ mod tests {
|
|||||||
std::fs::write(&dir, &data).unwrap();
|
std::fs::write(&dir, &data).unwrap();
|
||||||
|
|
||||||
let reader = IsoSectorReader::open(dir.to_str().unwrap()).unwrap();
|
let reader = IsoSectorReader::open(dir.to_str().unwrap()).unwrap();
|
||||||
assert_eq!(reader.capacity(), 10);
|
assert_eq!(reader.capacity_sectors(), 10);
|
||||||
|
|
||||||
std::fs::remove_file(&dir).ok();
|
std::fs::remove_file(&dir).ok();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -186,7 +186,7 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
|
|||||||
None => crate::disc::ScanOptions::default(),
|
None => crate::disc::ScanOptions::default(),
|
||||||
};
|
};
|
||||||
let mut reader = super::iso::IsoSectorReader::open(&path.to_string_lossy())?;
|
let mut reader = super::iso::IsoSectorReader::open(&path.to_string_lossy())?;
|
||||||
let capacity = reader.capacity();
|
let capacity = reader.capacity_sectors();
|
||||||
let disc = crate::disc::Disc::scan_image(&mut reader, capacity, &scan_opts)
|
let disc = crate::disc::Disc::scan_image(&mut reader, capacity, &scan_opts)
|
||||||
.map_err(|e| -> io::Error { e.into() })?;
|
.map_err(|e| -> io::Error { e.into() })?;
|
||||||
if disc.titles.is_empty() {
|
if disc.titles.is_empty() {
|
||||||
|
|||||||
+7
-7
@@ -13,7 +13,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
use super::{SectorReader, SectorSink};
|
use super::{SectorSink, SectorSource};
|
||||||
|
|
||||||
/// SectorSource backed by a file (ISO image).
|
/// SectorSource backed by a file (ISO image).
|
||||||
///
|
///
|
||||||
@@ -49,14 +49,14 @@ impl FileSectorSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the legacy `SectorReader` trait. The blanket impl in
|
// Implement the legacy `SectorSource` trait. The blanket impl in
|
||||||
// `super` produces the `SectorSource` impl automatically — no need
|
// `super` produces the `SectorSource` impl automatically — no need
|
||||||
// to write both, and writing both would conflict. This keeps the
|
// to write both, and writing both would conflict. This keeps the
|
||||||
// 0.17 method-resolution path intact (callers with `SectorReader`
|
// 0.17 method-resolution path intact (callers with `SectorSource`
|
||||||
// in scope can still write `fsr.read_sectors(..)` against a
|
// in scope can still write `fsr.read_sectors(..)` against a
|
||||||
// `FileSectorSource`).
|
// `FileSectorSource`).
|
||||||
impl SectorReader for FileSectorSource {
|
impl SectorSource for FileSectorSource {
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +147,8 @@ impl SectorSink for FileSectorSink {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// Bring the 0.18 trait into scope (not super::*: the super
|
// Bring the 0.18 trait into scope (not super::*: the super
|
||||||
// module also re-exports the legacy `SectorReader`, and
|
// module also re-exports the legacy `SectorSource`, and
|
||||||
// having both `SectorReader::read_sectors` and
|
// having both `SectorSource::read_sectors` and
|
||||||
// `SectorSource::read_sectors` visible would force every
|
// `SectorSource::read_sectors` visible would force every
|
||||||
// call site to disambiguate). External consumers see the
|
// call site to disambiguate). External consumers see the
|
||||||
// same surface this test exercises.
|
// same surface this test exercises.
|
||||||
|
|||||||
+60
-149
@@ -1,29 +1,17 @@
|
|||||||
//! Sector-level I/O traits.
|
//! Sector-level I/O traits.
|
||||||
//!
|
//!
|
||||||
//! 0.18 splits the unidirectional read trait from a write trait at
|
//! The sector layer is direction-typed: [`SectorSource`] reads
|
||||||
//! the sector layer, so the type system catches "wrong direction"
|
//! 2048-byte sectors, [`SectorSink`] writes them. Concrete impls
|
||||||
//! mistakes at compile time instead of runtime. See
|
//! never do both — physical drives are read-only, file-backed
|
||||||
//! `(internal)/memory/0_18_redesign.md`.
|
//! ISO images are opened for read OR write at construction time.
|
||||||
//!
|
//!
|
||||||
//! - [`SectorSource`] reads 2048-byte sectors. Implemented by
|
//! - [`SectorSource`] is implemented by `Drive` (hardware) and
|
||||||
//! `Drive` (via the legacy [`SectorReader`] alias) and
|
//! [`FileSectorSource`] / `IsoSectorReader` (file-backed).
|
||||||
//! [`FileSectorSource`] (ISO-backed).
|
//! - [`SectorSink`] is implemented by [`FileSectorSink`]
|
||||||
//! - [`SectorSink`] writes 2048-byte sectors. Implemented by
|
//! (ISO-backed) and sweep/patch consumer adapters.
|
||||||
//! [`FileSectorSink`] (ISO-backed) and, in later commits, by
|
|
||||||
//! sweep/patch consumer adapters.
|
|
||||||
//! - [`DecryptingSectorSource`] is a decorator that wraps any
|
//! - [`DecryptingSectorSource`] is a decorator that wraps any
|
||||||
//! `SectorSource` and applies the existing AACS / CSS in-place
|
//! `SectorSource` and applies AACS / CSS in-place decrypt to
|
||||||
//! decrypt to plaintext-out.
|
//! yield plaintext sectors.
|
||||||
//!
|
|
||||||
//! [`SectorReader`] is the 0.17 read trait. It stays on through
|
|
||||||
//! the 0.18 migration window so existing call sites
|
|
||||||
//! (`Drive`, `IsoSectorReader`, `BufferedSectorReader`,
|
|
||||||
//! `DiscStream`, `verify`) compile unchanged. A blanket impl
|
|
||||||
//! forwards every `SectorReader` impl to `SectorSource`, so new
|
|
||||||
//! code should target `SectorSource` / `SectorSink` directly. The
|
|
||||||
//! formal `#[deprecated]` attribute lands once the internal
|
|
||||||
//! callers have migrated; see the comment on `SectorReader` for
|
|
||||||
//! why this commit holds it back.
|
|
||||||
|
|
||||||
pub mod decrypting;
|
pub mod decrypting;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
@@ -32,13 +20,14 @@ use crate::error::Result;
|
|||||||
|
|
||||||
/// Read 2048-byte sectors from a disc, image, or composed source.
|
/// Read 2048-byte sectors from a disc, image, or composed source.
|
||||||
///
|
///
|
||||||
/// Direction-typed: a `SectorSource` cannot be written to. Wrap the
|
/// Wrap the inner source in [`DecryptingSectorSource`] to get
|
||||||
/// inner source in [`DecryptingSectorSource`] to get plaintext
|
/// plaintext sectors out of an encrypted disc.
|
||||||
/// sectors out of an encrypted disc.
|
|
||||||
pub trait SectorSource: Send {
|
pub trait SectorSource: Send {
|
||||||
/// Total capacity in sectors, if known. Returns 0 when unknown
|
/// Total capacity in sectors, if known. Default `0` = unknown
|
||||||
/// (e.g. live drives that haven't completed `READ CAPACITY` yet).
|
/// (e.g. live drives that haven't completed `READ CAPACITY` yet).
|
||||||
fn capacity_sectors(&self) -> u32;
|
fn capacity_sectors(&self) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
/// Read `count` sectors starting at `lba` into `buf`.
|
/// Read `count` sectors starting at `lba` into `buf`.
|
||||||
/// `buf` must be at least `count * 2048` bytes.
|
/// `buf` must be at least `count * 2048` bytes.
|
||||||
@@ -60,10 +49,52 @@ pub trait SectorSource: Send {
|
|||||||
fn set_speed(&mut self, _kbs: u16) {}
|
fn set_speed(&mut self, _kbs: u16) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forwarding impls so `Box<dyn SectorSource>` and `&mut dyn SectorSource`
|
||||||
|
// satisfy the `SectorSource` trait bound when wrapped by generic
|
||||||
|
// decorators like `DecryptingSectorSource<S: SectorSource>`.
|
||||||
|
impl SectorSource for Box<dyn SectorSource> {
|
||||||
|
fn capacity_sectors(&self) -> u32 {
|
||||||
|
(**self).capacity_sectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_sectors(
|
||||||
|
&mut self,
|
||||||
|
lba: u32,
|
||||||
|
count: u16,
|
||||||
|
buf: &mut [u8],
|
||||||
|
recovery: bool,
|
||||||
|
) -> Result<usize> {
|
||||||
|
(**self).read_sectors(lba, count, buf, recovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_speed(&mut self, kbs: u16) {
|
||||||
|
(**self).set_speed(kbs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectorSource for &mut (dyn SectorSource + '_) {
|
||||||
|
fn capacity_sectors(&self) -> u32 {
|
||||||
|
(**self).capacity_sectors()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_sectors(
|
||||||
|
&mut self,
|
||||||
|
lba: u32,
|
||||||
|
count: u16,
|
||||||
|
buf: &mut [u8],
|
||||||
|
recovery: bool,
|
||||||
|
) -> Result<usize> {
|
||||||
|
(**self).read_sectors(lba, count, buf, recovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_speed(&mut self, kbs: u16) {
|
||||||
|
(**self).set_speed(kbs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write 2048-byte sectors to a disc image or composed sink.
|
/// Write 2048-byte sectors to a disc image or composed sink.
|
||||||
///
|
///
|
||||||
/// Direction-typed: a `SectorSink` cannot be read from. The
|
/// The terminal [`finish`] takes `Box<Self>` so it can run on `dyn
|
||||||
/// terminal [`finish`] takes `Box<Self>` so it can run on `dyn
|
|
||||||
/// SectorSink` and consume the sink (`fsync` + close).
|
/// SectorSink` and consume the sink (`fsync` + close).
|
||||||
///
|
///
|
||||||
/// [`finish`]: SectorSink::finish
|
/// [`finish`]: SectorSink::finish
|
||||||
@@ -78,125 +109,5 @@ pub trait SectorSink: Send {
|
|||||||
fn finish(self: Box<Self>) -> Result<()>;
|
fn finish(self: Box<Self>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0.17 read trait. Slated for removal once internal call sites
|
|
||||||
/// migrate to [`SectorSource`] in follow-up commits; until then
|
|
||||||
/// it remains the trait that `Drive`, `IsoSectorReader`,
|
|
||||||
/// `BufferedSectorReader`, and existing `&mut dyn SectorReader`
|
|
||||||
/// signatures use unchanged.
|
|
||||||
///
|
|
||||||
/// New code should implement [`SectorSource`] directly. The
|
|
||||||
/// blanket impl below makes any `SectorReader` automatically
|
|
||||||
/// usable wherever a `SectorSource` is expected, so a one-way
|
|
||||||
/// migration off `SectorReader` is possible per-callsite without
|
|
||||||
/// touching the impls.
|
|
||||||
//
|
|
||||||
// NOTE: not marked `#[deprecated]` in this commit — `cargo clippy
|
|
||||||
// -- -D warnings` (the CI gauntlet) treats deprecation as an
|
|
||||||
// error, and the existing `Drive` / `udf::BufferedSectorReader` /
|
|
||||||
// `mux::DiscStream` / `verify` call sites all go through this
|
|
||||||
// trait. The deprecation attribute lands together with the
|
|
||||||
// migration commits that move those call sites to
|
|
||||||
// `SectorSource`. The behavioural contract — "this trait is
|
|
||||||
// going away in 0.18" — is documented above and tracked in
|
|
||||||
// `(internal)/memory/0_18_redesign.md`.
|
|
||||||
pub trait SectorReader: Send {
|
|
||||||
/// Read `count` sectors starting at `lba` into `buf`.
|
|
||||||
/// See [`SectorSource::read_sectors`] for semantics.
|
|
||||||
fn read_sectors(
|
|
||||||
&mut self,
|
|
||||||
lba: u32,
|
|
||||||
count: u16,
|
|
||||||
buf: &mut [u8],
|
|
||||||
recovery: bool,
|
|
||||||
) -> Result<usize>;
|
|
||||||
|
|
||||||
/// Total capacity in sectors, if known.
|
|
||||||
fn capacity(&self) -> u32 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_speed(&mut self, _kbs: u16) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blanket impl: anything implementing the legacy `SectorReader`
|
|
||||||
// trait automatically satisfies `SectorSource`. This is what keeps
|
|
||||||
// existing impls (`Drive`, `IsoSectorReader`, `BufferedSectorReader`,
|
|
||||||
// etc.) compiling without source changes during the migration. The
|
|
||||||
// reverse direction (impl SectorReader for SectorSource) is
|
|
||||||
// intentionally NOT provided — new code targets the new trait.
|
|
||||||
impl<T: SectorReader + ?Sized> SectorSource for T {
|
|
||||||
fn capacity_sectors(&self) -> u32 {
|
|
||||||
<T as SectorReader>::capacity(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_sectors(
|
|
||||||
&mut self,
|
|
||||||
lba: u32,
|
|
||||||
count: u16,
|
|
||||||
buf: &mut [u8],
|
|
||||||
recovery: bool,
|
|
||||||
) -> Result<usize> {
|
|
||||||
<T as SectorReader>::read_sectors(self, lba, count, buf, recovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_speed(&mut self, kbs: u16) {
|
|
||||||
<T as SectorReader>::set_speed(self, kbs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forwarding impls so callers can wrap `&mut dyn SectorReader` /
|
|
||||||
// `Box<dyn SectorReader>` in [`DecryptingSectorSource`] without
|
|
||||||
// having to unbox or re-borrow inside the lib's hot paths. The
|
|
||||||
// generic `&mut T` / `Box<T>` blankets would conflict with the
|
|
||||||
// `SectorReader → SectorSource` blanket above (a downstream crate
|
|
||||||
// could `impl SectorReader for &mut U`); the specific
|
|
||||||
// `dyn SectorReader` instantiations are unambiguous because
|
|
||||||
// `SectorReader` is the very trait whose `dyn` we're targeting.
|
|
||||||
impl SectorSource for &mut (dyn SectorReader + '_) {
|
|
||||||
fn capacity_sectors(&self) -> u32 {
|
|
||||||
<dyn SectorReader as SectorReader>::capacity(*self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_sectors(
|
|
||||||
&mut self,
|
|
||||||
lba: u32,
|
|
||||||
count: u16,
|
|
||||||
buf: &mut [u8],
|
|
||||||
recovery: bool,
|
|
||||||
) -> Result<usize> {
|
|
||||||
<dyn SectorReader as SectorReader>::read_sectors(*self, lba, count, buf, recovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_speed(&mut self, kbs: u16) {
|
|
||||||
<dyn SectorReader as SectorReader>::set_speed(*self, kbs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SectorSource for Box<dyn SectorReader> {
|
|
||||||
fn capacity_sectors(&self) -> u32 {
|
|
||||||
<dyn SectorReader as SectorReader>::capacity(&**self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_sectors(
|
|
||||||
&mut self,
|
|
||||||
lba: u32,
|
|
||||||
count: u16,
|
|
||||||
buf: &mut [u8],
|
|
||||||
recovery: bool,
|
|
||||||
) -> Result<usize> {
|
|
||||||
<dyn SectorReader as SectorReader>::read_sectors(&mut **self, lba, count, buf, recovery)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_speed(&mut self, kbs: u16) {
|
|
||||||
<dyn SectorReader as SectorReader>::set_speed(&mut **self, kbs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use decrypting::DecryptingSectorSource;
|
pub use decrypting::DecryptingSectorSource;
|
||||||
pub use file::{FileSectorSink, FileSectorSource};
|
pub use file::{FileSectorSink, FileSectorSource};
|
||||||
|
|
||||||
// Backwards-compat alias for the public API. `FileSectorReader` is
|
|
||||||
// the 0.17 name; new code uses `FileSectorSource`. Both point at
|
|
||||||
// the same type. The `#[deprecated]` attribute lands together with
|
|
||||||
// the migration commits that retire the alias from internal uses.
|
|
||||||
pub type FileSectorReader = FileSectorSource;
|
|
||||||
|
|||||||
+17
-17
@@ -19,7 +19,7 @@
|
|||||||
//! BD-ROM Part 3 — Blu-ray filesystem profile
|
//! BD-ROM Part 3 — Blu-ray filesystem profile
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
|
|
||||||
/// A UDF filesystem parsed from disc.
|
/// A UDF filesystem parsed from disc.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -84,7 +84,7 @@ impl UdfFs {
|
|||||||
/// Reads sector by sector from disc — no buffering.
|
/// Reads sector by sector from disc — no buffering.
|
||||||
/// Get the absolute starting LBA of a file on disc.
|
/// Get the absolute starting LBA of a file on disc.
|
||||||
/// Used by the rip pipeline to locate m2ts content sectors.
|
/// Used by the rip pipeline to locate m2ts content sectors.
|
||||||
pub fn file_start_lba(&self, reader: &mut dyn SectorReader, path: &str) -> Result<u32> {
|
pub fn file_start_lba(&self, reader: &mut dyn SectorSource, path: &str) -> Result<u32> {
|
||||||
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
||||||
let mut current = &self.root;
|
let mut current = &self.root;
|
||||||
for part in &parts[..parts.len() - 1] {
|
for part in &parts[..parts.len() - 1] {
|
||||||
@@ -115,7 +115,7 @@ impl UdfFs {
|
|||||||
Ok(self.partition_start + data_lba)
|
Ok(self.partition_start + data_lba)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file(&self, reader: &mut dyn SectorReader, path: &str) -> Result<Vec<u8>> {
|
pub fn read_file(&self, reader: &mut dyn SectorSource, path: &str) -> Result<Vec<u8>> {
|
||||||
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
||||||
let mut current = &self.root;
|
let mut current = &self.root;
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ impl UdfFs {
|
|||||||
///
|
///
|
||||||
/// Skips: STREAM/ (video), BACKUP/, DUPLICATE/,
|
/// Skips: STREAM/ (video), BACKUP/, DUPLICATE/,
|
||||||
/// MKB_RO.inf, ContentHash*, ContentRevocation*
|
/// MKB_RO.inf, ContentHash*, ContentRevocation*
|
||||||
pub fn metadata_sector_ranges(&self, reader: &mut dyn SectorReader) -> Result<Vec<(u32, u32)>> {
|
pub fn metadata_sector_ranges(&self, reader: &mut dyn SectorSource) -> Result<Vec<(u32, u32)>> {
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
|
|
||||||
// UDF structure: sector 0 through end of metadata partition
|
// UDF structure: sector 0 through end of metadata partition
|
||||||
@@ -194,7 +194,7 @@ impl UdfFs {
|
|||||||
|
|
||||||
/// All sector ranges that contain data (metadata + all files including STREAM).
|
/// All sector ranges that contain data (metadata + all files including STREAM).
|
||||||
/// For full disc-to-ISO dumps — reads only allocated sectors, skips gaps.
|
/// For full disc-to-ISO dumps — reads only allocated sectors, skips gaps.
|
||||||
pub fn all_sector_ranges(&self, reader: &mut dyn SectorReader) -> Result<Vec<(u32, u32)>> {
|
pub fn all_sector_ranges(&self, reader: &mut dyn SectorSource) -> Result<Vec<(u32, u32)>> {
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
|
|
||||||
// UDF structure sectors
|
// UDF structure sectors
|
||||||
@@ -212,7 +212,7 @@ impl UdfFs {
|
|||||||
|
|
||||||
fn collect_all_file_ranges(
|
fn collect_all_file_ranges(
|
||||||
&self,
|
&self,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
entry: &DirEntry,
|
entry: &DirEntry,
|
||||||
ranges: &mut Vec<(u32, u32)>,
|
ranges: &mut Vec<(u32, u32)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -238,7 +238,7 @@ impl UdfFs {
|
|||||||
|
|
||||||
fn collect_file_ranges(
|
fn collect_file_ranges(
|
||||||
&self,
|
&self,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
entry: &DirEntry,
|
entry: &DirEntry,
|
||||||
ranges: &mut Vec<(u32, u32)>,
|
ranges: &mut Vec<(u32, u32)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -276,7 +276,7 @@ impl UdfFs {
|
|||||||
/// Read an Extended File Entry (tag 266) or File Entry (tag 261)
|
/// Read an Extended File Entry (tag 266) or File Entry (tag 261)
|
||||||
/// and return its first allocation extent: (data_lba, data_length).
|
/// and return its first allocation extent: (data_lba, data_length).
|
||||||
/// The data_lba is partition-relative.
|
/// The data_lba is partition-relative.
|
||||||
fn read_icb_extent(&self, reader: &mut dyn SectorReader, meta_lba: u32) -> Result<(u32, u32)> {
|
fn read_icb_extent(&self, reader: &mut dyn SectorSource, meta_lba: u32) -> Result<(u32, u32)> {
|
||||||
let extents = self.read_icb_extents(reader, meta_lba)?;
|
let extents = self.read_icb_extents(reader, meta_lba)?;
|
||||||
extents.first().copied().ok_or(Error::DiscRead {
|
extents.first().copied().ok_or(Error::DiscRead {
|
||||||
sector: 0,
|
sector: 0,
|
||||||
@@ -290,7 +290,7 @@ impl UdfFs {
|
|||||||
/// Handles files with many extents (e.g. 88 GB m2ts files have ~90 extents).
|
/// Handles files with many extents (e.g. 88 GB m2ts files have ~90 extents).
|
||||||
fn read_icb_extents(
|
fn read_icb_extents(
|
||||||
&self,
|
&self,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
meta_lba: u32,
|
meta_lba: u32,
|
||||||
) -> Result<Vec<(u32, u32)>> {
|
) -> Result<Vec<(u32, u32)>> {
|
||||||
let mut icb = [0u8; 2048];
|
let mut icb = [0u8; 2048];
|
||||||
@@ -367,7 +367,7 @@ impl UdfFs {
|
|||||||
/// Returns Vec of (absolute_lba, sector_count) covering the entire file.
|
/// Returns Vec of (absolute_lba, sector_count) covering the entire file.
|
||||||
pub fn file_extents(
|
pub fn file_extents(
|
||||||
&self,
|
&self,
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Vec<(u32, u32)>> {
|
) -> Result<Vec<(u32, u32)>> {
|
||||||
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
|
||||||
@@ -417,7 +417,7 @@ impl UdfFs {
|
|||||||
/// 3. Metadata partition file → metadata content location
|
/// 3. Metadata partition file → metadata content location
|
||||||
/// 4. FSD → root directory ICB
|
/// 4. FSD → root directory ICB
|
||||||
/// 5. Root directory → file tree
|
/// 5. Root directory → file tree
|
||||||
pub fn read_filesystem(reader: &mut dyn SectorReader) -> Result<UdfFs> {
|
pub fn read_filesystem(reader: &mut dyn SectorSource) -> Result<UdfFs> {
|
||||||
// Step 1: Anchor Volume Descriptor Pointer at sector 256
|
// Step 1: Anchor Volume Descriptor Pointer at sector 256
|
||||||
// ECMA-167 §10.2 — always at sector 256
|
// ECMA-167 §10.2 — always at sector 256
|
||||||
let mut avdp = [0u8; 2048];
|
let mut avdp = [0u8; 2048];
|
||||||
@@ -593,7 +593,7 @@ pub fn read_filesystem(reader: &mut dyn SectorReader) -> Result<UdfFs> {
|
|||||||
/// and points to its ICB.
|
/// and points to its ICB.
|
||||||
#[allow(clippy::only_used_in_recursion)]
|
#[allow(clippy::only_used_in_recursion)]
|
||||||
fn read_directory(
|
fn read_directory(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
part_start: u32,
|
part_start: u32,
|
||||||
meta_start: u32,
|
meta_start: u32,
|
||||||
meta_lba: u32,
|
meta_lba: u32,
|
||||||
@@ -758,7 +758,7 @@ fn read_directory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read file size (info_length) from an Extended File Entry ICB.
|
/// Read file size (info_length) from an Extended File Entry ICB.
|
||||||
fn read_file_size(reader: &mut dyn SectorReader, meta_start: u32, meta_lba: u32) -> Result<u64> {
|
fn read_file_size(reader: &mut dyn SectorSource, meta_start: u32, meta_lba: u32) -> Result<u64> {
|
||||||
let mut icb = [0u8; 2048];
|
let mut icb = [0u8; 2048];
|
||||||
read_sector(reader, meta_start + meta_lba, &mut icb)?;
|
read_sector(reader, meta_start + meta_lba, &mut icb)?;
|
||||||
|
|
||||||
@@ -874,7 +874,7 @@ fn parse_dstring(data: &[u8]) -> String {
|
|||||||
/// Each SCSI command has ~500ms overhead on USB drives, so reading 32 sectors
|
/// Each SCSI command has ~500ms overhead on USB drives, so reading 32 sectors
|
||||||
/// at once (one command) is 32x faster than 32 individual reads.
|
/// at once (one command) is 32x faster than 32 individual reads.
|
||||||
pub(crate) struct BufferedSectorReader<'a> {
|
pub(crate) struct BufferedSectorReader<'a> {
|
||||||
inner: &'a mut dyn SectorReader,
|
inner: &'a mut dyn SectorSource,
|
||||||
cache_start: u32,
|
cache_start: u32,
|
||||||
cache: Vec<u8>,
|
cache: Vec<u8>,
|
||||||
cache_sectors: u32,
|
cache_sectors: u32,
|
||||||
@@ -884,7 +884,7 @@ pub(crate) struct BufferedSectorReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BufferedSectorReader<'a> {
|
impl<'a> BufferedSectorReader<'a> {
|
||||||
pub(crate) fn new(inner: &'a mut dyn SectorReader, batch: u16) -> Self {
|
pub(crate) fn new(inner: &'a mut dyn SectorSource, batch: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
cache_start: u32::MAX,
|
cache_start: u32::MAX,
|
||||||
@@ -952,7 +952,7 @@ impl BufferedSectorReader<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for BufferedSectorReader<'_> {
|
impl SectorSource for BufferedSectorReader<'_> {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
@@ -996,7 +996,7 @@ impl SectorReader for BufferedSectorReader<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_sector(reader: &mut dyn SectorReader, lba: u32, buf: &mut [u8]) -> Result<()> {
|
fn read_sector(reader: &mut dyn SectorSource, lba: u32, buf: &mut [u8]) -> Result<()> {
|
||||||
reader.read_sectors(lba, 1, buf, true)?;
|
reader.read_sectors(lba, 1, buf, true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::disc::{Chapter, DiscTitle};
|
use crate::disc::{Chapter, DiscTitle};
|
||||||
use crate::progress::Progress;
|
use crate::progress::Progress;
|
||||||
use crate::sector::SectorReader;
|
use crate::sector::SectorSource;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
/// Health status of a single sector read.
|
/// Health status of a single sector read.
|
||||||
@@ -82,7 +82,7 @@ impl VerifyResult {
|
|||||||
/// Reads in batches for speed, falls back to single-sector on failure.
|
/// Reads in batches for speed, falls back to single-sector on failure.
|
||||||
/// The progress callback returns false to request early stop.
|
/// The progress callback returns false to request early stop.
|
||||||
pub fn verify_title(
|
pub fn verify_title(
|
||||||
reader: &mut dyn SectorReader,
|
reader: &mut dyn SectorSource,
|
||||||
title: &DiscTitle,
|
title: &DiscTitle,
|
||||||
batch_sectors: u16,
|
batch_sectors: u16,
|
||||||
on_progress: Option<&dyn Progress>,
|
on_progress: Option<&dyn Progress>,
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
//! Disc scanning pipeline tests.
|
//! Disc scanning pipeline tests.
|
||||||
|
|
||||||
use libfreemkv::SectorReader;
|
use libfreemkv::SectorSource;
|
||||||
use libfreemkv::error::Result;
|
use libfreemkv::error::Result;
|
||||||
use libfreemkv::{Disc, DiscTitle, ScanOptions};
|
use libfreemkv::{Disc, DiscTitle, ScanOptions};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -20,7 +20,7 @@ impl MockSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for MockSectorReader {
|
impl SectorSource for MockSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use libfreemkv::disc::{CopyOptions, DiscRegion};
|
|||||||
use libfreemkv::error::Result;
|
use libfreemkv::error::Result;
|
||||||
use libfreemkv::pes::Stream as PesStream;
|
use libfreemkv::pes::Stream as PesStream;
|
||||||
use libfreemkv::{
|
use libfreemkv::{
|
||||||
ContentFormat, Disc, DiscFormat, DiscStream, DiscTitle, EventKind, Extent, FileSectorReader,
|
ContentFormat, Disc, DiscFormat, DiscStream, DiscTitle, EventKind, Extent, FileSectorSource,
|
||||||
SectorReader,
|
SectorSource,
|
||||||
};
|
};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -32,7 +32,7 @@ impl ZeroSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for ZeroSectorReader {
|
impl SectorSource for ZeroSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
_lba: u32,
|
_lba: u32,
|
||||||
@@ -46,7 +46,7 @@ impl SectorReader for ZeroSectorReader {
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ impl SlowZeroSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for SlowZeroSectorReader {
|
impl SectorSource for SlowZeroSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
_lba: u32,
|
_lba: u32,
|
||||||
@@ -81,7 +81,7 @@ impl SectorReader for SlowZeroSectorReader {
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ fn test_drop_impls_do_not_panic_or_block() {
|
|||||||
panic!("DiscStream drop did not complete within 100ms");
|
panic!("DiscStream drop did not complete within 100ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 5. FileSectorReader round trip ────────────────────────────────────────
|
// ── 5. FileSectorSource round trip ────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_file_sector_reader_round_trip() {
|
fn test_file_sector_reader_round_trip() {
|
||||||
@@ -321,9 +321,13 @@ fn test_file_sector_reader_round_trip() {
|
|||||||
tmp.flush().expect("flush");
|
tmp.flush().expect("flush");
|
||||||
|
|
||||||
let path = tmp.path().to_path_buf();
|
let path = tmp.path().to_path_buf();
|
||||||
let mut fsr = FileSectorReader::open(&path).expect("open FileSectorReader");
|
let mut fsr = FileSectorSource::open(&path).expect("open FileSectorSource");
|
||||||
|
|
||||||
assert_eq!(fsr.capacity(), N_SECTORS as u32, "capacity mismatch");
|
assert_eq!(
|
||||||
|
fsr.capacity_sectors(),
|
||||||
|
N_SECTORS as u32,
|
||||||
|
"capacity mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
// Read each sector individually and compare.
|
// Read each sector individually and compare.
|
||||||
let mut buf = vec![0u8; SECTOR_SIZE];
|
let mut buf = vec![0u8; SECTOR_SIZE];
|
||||||
@@ -391,7 +395,7 @@ impl FailingSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for FailingSectorReader {
|
impl SectorSource for FailingSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
_lba: u32,
|
_lba: u32,
|
||||||
@@ -418,7 +422,7 @@ impl SectorReader for FailingSectorReader {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +571,7 @@ struct BlockSizeFailingReader {
|
|||||||
capacity: u32,
|
capacity: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for BlockSizeFailingReader {
|
impl SectorSource for BlockSizeFailingReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
@@ -593,7 +597,7 @@ impl SectorReader for BlockSizeFailingReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use libfreemkv::disc::CopyOptions;
|
|||||||
use libfreemkv::disc::DiscRegion;
|
use libfreemkv::disc::DiscRegion;
|
||||||
use libfreemkv::disc::mapfile::{Mapfile, SectorStatus};
|
use libfreemkv::disc::mapfile::{Mapfile, SectorStatus};
|
||||||
use libfreemkv::error::Result;
|
use libfreemkv::error::Result;
|
||||||
use libfreemkv::{ContentFormat, Disc, DiscFormat, SectorReader};
|
use libfreemkv::{ContentFormat, Disc, DiscFormat, SectorSource};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ impl PatternedSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for PatternedSectorReader {
|
impl SectorSource for PatternedSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
@@ -82,7 +82,7 @@ impl SectorReader for PatternedSectorReader {
|
|||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> u32 {
|
fn capacity_sectors(&self) -> u32 {
|
||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
//! UDF parser tests using a MockSectorReader.
|
//! UDF parser tests using a MockSectorReader.
|
||||||
|
|
||||||
use libfreemkv::error::Result;
|
use libfreemkv::error::Result;
|
||||||
use libfreemkv::{SectorReader, read_filesystem};
|
use libfreemkv::{SectorSource, read_filesystem};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 2048;
|
const SECTOR_SIZE: usize = 2048;
|
||||||
@@ -38,7 +38,7 @@ impl MockSectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectorReader for MockSectorReader {
|
impl SectorSource for MockSectorReader {
|
||||||
fn read_sectors(
|
fn read_sectors(
|
||||||
&mut self,
|
&mut self,
|
||||||
lba: u32,
|
lba: u32,
|
||||||
@@ -463,11 +463,11 @@ fn find_dir_case_insensitive() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sector_reader_is_object_safe() {
|
fn sector_reader_is_object_safe() {
|
||||||
// Verify SectorReader can be used as a trait object
|
// Verify SectorSource can be used as a trait object
|
||||||
let mut reader = MockSectorReader::new();
|
let mut reader = MockSectorReader::new();
|
||||||
reader.set_sector(0, vec![42u8; SECTOR_SIZE]);
|
reader.set_sector(0, vec![42u8; SECTOR_SIZE]);
|
||||||
|
|
||||||
let dyn_reader: &mut dyn SectorReader = &mut reader;
|
let dyn_reader: &mut dyn SectorSource = &mut reader;
|
||||||
let mut buf = vec![0u8; SECTOR_SIZE];
|
let mut buf = vec![0u8; SECTOR_SIZE];
|
||||||
let n = dyn_reader.read_sectors(0, 1, &mut buf, true).unwrap();
|
let n = dyn_reader.read_sectors(0, 1, &mut buf, true).unwrap();
|
||||||
assert_eq!(n, SECTOR_SIZE);
|
assert_eq!(n, SECTOR_SIZE);
|
||||||
|
|||||||
Reference in New Issue
Block a user