v0.11.16: API cleanup — one method per action

This commit is contained in:
Matt Jackson
2026-04-21 19:17:50 +00:00
parent 8843833ea7
commit 40ec1e1eba
18 changed files with 83 additions and 99 deletions
+9 -2
View File
@@ -1,11 +1,18 @@
# Changelog # Changelog
## 0.11.16 (2026-04-21)
### API cleanup — one method per action
- **SectorReader::read_sectors(lba, count, buf, recovery)** — single method with `recovery: bool`. Removes `read_sectors_recover()`.
- **parser_for_codec(codec, codec_data)** — single constructor. Removes `parser_for_codec_with_data()`.
- **DvdSubParser::new(codec_data)** — single constructor. Removes `with_codec_data()`.
- **MkvMuxer::new(writer, tracks, title, duration, chapters)** — single constructor. Removes `new_with_chapters()`.
## 0.11.15 (2026-04-21) ## 0.11.15 (2026-04-21)
### Lint cleanup ### Lint cleanup
- Fix all `cargo fmt` and `cargo clippy -D warnings` across codebase. - Fix all `cargo fmt` and `cargo clippy -D warnings` across codebase.
- Remove unused imports (Codec, HdrFormat, ScanOptions, detect_max_batch_sectors, Extent). - Remove unused imports, dead code, collapsible if-statements, div_ceil reimplementation.
- Fix CSS tuple pattern deref, collapsible if-statement, div_ceil reimplementation.
## 0.11.14 (2026-04-21) ## 0.11.14 (2026-04-21)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "libfreemkv" name = "libfreemkv"
version = "0.11.15" version = "0.11.16"
edition = "2021" edition = "2021"
rust-version = "1.86" rust-version = "1.86"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
+5 -1
View File
@@ -42,7 +42,11 @@ pub fn crack_key(reader: &mut dyn SectorReader, extents: &[Extent]) -> Option<Cs
let mut i = 0; let mut i = 0;
while i < ext.sector_count && tried < max_tries { while i < ext.sector_count && tried < max_tries {
let mut buf = vec![0u8; 2048]; let mut buf = vec![0u8; 2048];
if reader.read_sectors(ext.start_lba + i, 1, &mut buf).is_ok() && is_scrambled(&buf) { if reader
.read_sectors(ext.start_lba + i, 1, &mut buf, true)
.is_ok()
&& is_scrambled(&buf)
{
if let Some(key) = crack::crack_title_key(&buf) { if let Some(key) = crack::crack_title_key(&buf) {
return Some(CssState { title_key: key }); return Some(CssState { title_key: key });
} }
+4 -2
View File
@@ -1029,7 +1029,9 @@ impl Disc {
{ {
let lba = disc.titles[0].extents.iter().find_map(|ext| { let lba = disc.titles[0].extents.iter().find_map(|ext| {
let mut buf = vec![0u8; 2048]; let mut buf = vec![0u8; 2048];
if session.read_sectors(ext.start_lba, 1, &mut buf).is_ok() if session
.read_sectors(ext.start_lba, 1, &mut buf, true)
.is_ok()
&& crate::css::is_scrambled(&buf) && crate::css::is_scrambled(&buf)
{ {
return Some(ext.start_lba); return Some(ext.start_lba);
@@ -1268,7 +1270,7 @@ impl Disc {
let bytes = count as usize * 2048; let bytes = count as usize * 2048;
reader reader
.read_sectors(lba, count, &mut buf[..bytes]) .read_sectors(lba, count, &mut buf[..bytes], true)
.map_err(|e| Error::IoError { .map_err(|e| Error::IoError {
source: std::io::Error::other(e.to_string()), source: std::io::Error::other(e.to_string()),
})?; })?;
+1 -5
View File
@@ -710,11 +710,7 @@ impl Drop for Drive {
} }
impl SectorReader for Drive { impl SectorReader for Drive {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(
self.read(lba, count, buf, true)
}
fn read_sectors_recover(
&mut self, &mut self,
lba: u32, lba: u32,
count: u16, count: u16,
+7 -18
View File
@@ -14,19 +14,8 @@ pub struct DvdSubParser {
codec_data: Option<Vec<u8>>, codec_data: Option<Vec<u8>>,
} }
impl Default for DvdSubParser {
fn default() -> Self {
Self::new()
}
}
impl DvdSubParser { impl DvdSubParser {
pub fn new() -> Self { pub fn new(codec_data: Option<Vec<u8>>) -> Self {
Self { codec_data: None }
}
/// Create a parser with pre-formatted codec private data (palette header).
pub fn with_codec_data(codec_data: Option<Vec<u8>>) -> Self {
Self { codec_data } Self { codec_data }
} }
} }
@@ -109,7 +98,7 @@ mod tests {
#[test] #[test]
fn passthrough_data() { fn passthrough_data() {
let mut parser = DvdSubParser::new(); let mut parser = DvdSubParser::new(None);
let sub_data = vec![0x00, 0x0A, 0x00, 0x08, 0x01, 0xFF, 0x02, 0x03, 0x04, 0x05]; let sub_data = vec![0x00, 0x0A, 0x00, 0x08, 0x01, 0xFF, 0x02, 0x03, 0x04, 0x05];
let pes = make_pes(sub_data.clone(), Some(90000)); let pes = make_pes(sub_data.clone(), Some(90000));
let frames = parser.parse(&pes); let frames = parser.parse(&pes);
@@ -124,7 +113,7 @@ mod tests {
#[test] #[test]
fn always_keyframe() { fn always_keyframe() {
let mut parser = DvdSubParser::new(); let mut parser = DvdSubParser::new(None);
for i in 0..3u8 { for i in 0..3u8 {
let data = vec![0x00, i, 0x00, i + 1]; let data = vec![0x00, i, 0x00, i + 1];
let pes = make_pes(data, Some(90000 * i as i64)); let pes = make_pes(data, Some(90000 * i as i64));
@@ -139,21 +128,21 @@ mod tests {
#[test] #[test]
fn empty_pes_returns_no_frames() { fn empty_pes_returns_no_frames() {
let mut parser = DvdSubParser::new(); let mut parser = DvdSubParser::new(None);
let pes = make_pes(Vec::new(), Some(0)); let pes = make_pes(Vec::new(), Some(0));
assert!(parser.parse(&pes).is_empty()); assert!(parser.parse(&pes).is_empty());
} }
#[test] #[test]
fn codec_private_none_by_default() { fn codec_private_none_by_default() {
let parser = DvdSubParser::new(); let parser = DvdSubParser::new(None);
assert!(parser.codec_private().is_none()); assert!(parser.codec_private().is_none());
} }
#[test] #[test]
fn codec_private_returns_palette_when_set() { fn codec_private_returns_palette_when_set() {
let palette_data = b"palette: 000000, ffffff\n".to_vec(); let palette_data = b"palette: 000000, ffffff\n".to_vec();
let parser = DvdSubParser::with_codec_data(Some(palette_data.clone())); let parser = DvdSubParser::new(Some(palette_data.clone()));
let cp = parser.codec_private(); let cp = parser.codec_private();
assert!(cp.is_some()); assert!(cp.is_some());
assert_eq!(cp.unwrap(), palette_data); assert_eq!(cp.unwrap(), palette_data);
@@ -161,7 +150,7 @@ mod tests {
#[test] #[test]
fn no_pts_defaults_to_zero() { fn no_pts_defaults_to_zero() {
let mut parser = DvdSubParser::new(); let mut parser = DvdSubParser::new(None);
let pes = make_pes(vec![0x01, 0x02], None); let pes = make_pes(vec![0x01, 0x02], None);
let frames = parser.parse(&pes); let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1); assert_eq!(frames.len(), 1);
+2 -10
View File
@@ -79,18 +79,10 @@ impl CodecParser for PassthroughParser {
} }
} }
/// Create the appropriate parser for a codec.
pub fn parser_for_codec(codec: Codec) -> Box<dyn CodecParser> {
parser_for_codec_with_data(codec, None)
}
/// Create the appropriate parser for a codec, with optional codec private data. /// Create the appropriate parser for a codec, with optional codec private data.
/// ///
/// For DvdSub, `codec_data` should be the pre-formatted VobSub .idx palette header. /// For DvdSub, `codec_data` should be the pre-formatted VobSub .idx palette header.
pub fn parser_for_codec_with_data( pub fn parser_for_codec(codec: Codec, codec_data: Option<Vec<u8>>) -> Box<dyn CodecParser> {
codec: Codec,
codec_data: Option<Vec<u8>>,
) -> Box<dyn CodecParser> {
match codec { match codec {
Codec::H264 => Box::new(h264::H264Parser::new()), Codec::H264 => Box::new(h264::H264Parser::new()),
Codec::Hevc => Box::new(hevc::HevcParser::new()), Codec::Hevc => Box::new(hevc::HevcParser::new()),
@@ -101,7 +93,7 @@ pub fn parser_for_codec_with_data(
Codec::TrueHd => Box::new(truehd::TrueHdParser::new()), Codec::TrueHd => Box::new(truehd::TrueHdParser::new()),
Codec::Pgs => Box::new(pgs::PgsParser::new()), Codec::Pgs => Box::new(pgs::PgsParser::new()),
Codec::Lpcm => Box::new(lpcm::LpcmParser::new()), Codec::Lpcm => Box::new(lpcm::LpcmParser::new()),
Codec::DvdSub => Box::new(dvdsub::DvdSubParser::with_codec_data(codec_data)), Codec::DvdSub => Box::new(dvdsub::DvdSubParser::new(codec_data)),
_ => Box::new(PassthroughParser::new(true)), _ => Box::new(PassthroughParser::new(true)),
} }
} }
+4 -4
View File
@@ -72,7 +72,7 @@ impl DiscStream {
}; };
pids.push(pid); pids.push(pid);
pid_to_track.push((pid, idx)); pid_to_track.push((pid, idx));
parsers.push((pid, super::codec::parser_for_codec(codec))); parsers.push((pid, super::codec::parser_for_codec(codec, None)));
} }
let mut ts_demuxer = None; let mut ts_demuxer = None;
@@ -146,7 +146,7 @@ impl DiscStream {
} }
if self if self
.reader .reader
.read_sectors_recover(lba, 1, &mut self.read_buf[offset..offset + 2048], false) .read_sectors(lba, 1, &mut self.read_buf[offset..offset + 2048], false)
.is_ok() .is_ok()
{ {
self.emit(EventKind::SectorRecovered { sector: lba as u64 }); self.emit(EventKind::SectorRecovered { sector: lba as u64 });
@@ -171,7 +171,7 @@ impl DiscStream {
let offset = self.buf_valid; let offset = self.buf_valid;
if self if self
.reader .reader
.read_sectors_recover( .read_sectors(
lba, lba,
count, count,
&mut self.read_buf[offset..offset + bytes], &mut self.read_buf[offset..offset + bytes],
@@ -228,7 +228,7 @@ impl DiscStream {
if self if self
.reader .reader
.read_sectors_recover(lba, sectors, &mut self.read_buf[..bytes], false) .read_sectors(lba, sectors, &mut self.read_buf[..bytes], false)
.is_ok() .is_ok()
{ {
// Fast path: batch succeeded // Fast path: batch succeeded
+9 -3
View File
@@ -39,7 +39,13 @@ impl IsoSectorReader {
} }
impl SectorReader for IsoSectorReader { impl SectorReader for IsoSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
let bytes = count as usize * SECTOR_SIZE as usize; let bytes = count as usize * SECTOR_SIZE as usize;
self.file self.file
.seek(SeekFrom::Start(lba as u64 * SECTOR_SIZE)) .seek(SeekFrom::Start(lba as u64 * SECTOR_SIZE))
@@ -71,11 +77,11 @@ mod tests {
assert_eq!(reader.capacity(), 4); assert_eq!(reader.capacity(), 4);
let mut buf = [0u8; 2048]; let mut buf = [0u8; 2048];
reader.read_sectors(0, 1, &mut buf).unwrap(); reader.read_sectors(0, 1, &mut buf, true).unwrap();
assert_eq!(buf[0], 1); assert_eq!(buf[0], 1);
assert_eq!(buf[2047], 100); assert_eq!(buf[2047], 100);
reader.read_sectors(2, 1, &mut buf).unwrap(); reader.read_sectors(2, 1, &mut buf, true).unwrap();
assert_eq!(buf[0], 3); assert_eq!(buf[0], 3);
assert_eq!(buf[2047], 102); assert_eq!(buf[2047], 102);
+1 -1
View File
@@ -103,7 +103,7 @@ impl M2tsStream {
}; };
pids.push(pid); pids.push(pid);
pid_to_track.push((pid, i)); pid_to_track.push((pid, i));
parsers.push((pid, super::codec::parser_for_codec(codec))); parsers.push((pid, super::codec::parser_for_codec(codec, None)));
} }
(pids, parsers, pid_to_track) (pids, parsers, pid_to_track)
} }
+11 -23
View File
@@ -182,16 +182,6 @@ const CLUSTER_DURATION_MS: i64 = 5000;
impl<W: Write + Seek> MkvMuxer<W> { impl<W: Write + Seek> MkvMuxer<W> {
/// Create a new MKV muxer: writes EBML header, Segment start, Info, Tracks, Chapters. /// Create a new MKV muxer: writes EBML header, Segment start, Info, Tracks, Chapters.
pub fn new( pub fn new(
writer: W,
tracks: &[MkvTrack],
title: Option<&str>,
duration_secs: f64,
) -> io::Result<Self> {
Self::new_with_chapters(writer, tracks, title, duration_secs, &[])
}
/// Create a new MKV muxer with chapters: writes EBML header, Segment start, Info, Tracks, Chapters.
pub fn new_with_chapters(
mut writer: W, mut writer: W,
tracks: &[MkvTrack], tracks: &[MkvTrack],
title: Option<&str>, title: Option<&str>,
@@ -580,7 +570,7 @@ mod tests {
fn mkv_writes_ebml_header() { fn mkv_writes_ebml_header() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let muxer = MkvMuxer::new(buf, &tracks, Some("Test"), 120.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, Some("Test"), 120.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// EBML header element ID: 0x1A45DFA3 // EBML header element ID: 0x1A45DFA3
assert!(data.len() >= 4); assert!(data.len() >= 4);
@@ -591,7 +581,7 @@ mod tests {
fn mkv_writes_segment() { fn mkv_writes_segment() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let muxer = MkvMuxer::new(buf, &tracks, None, 0.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, None, 0.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// Segment element ID: 0x18538067 // Segment element ID: 0x18538067
assert!( assert!(
@@ -604,7 +594,7 @@ mod tests {
fn mkv_write_frame_creates_cluster() { fn mkv_write_frame_creates_cluster() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let mut muxer = MkvMuxer::new(buf, &tracks, None, 60.0).unwrap(); let mut muxer = MkvMuxer::new(buf, &tracks, None, 60.0, &[]).unwrap();
muxer muxer
.write_frame(0, 0, true, &[0xDE, 0xAD, 0xBE, 0xEF]) .write_frame(0, 0, true, &[0xDE, 0xAD, 0xBE, 0xEF])
.unwrap(); .unwrap();
@@ -643,7 +633,7 @@ mod tests {
let shared = Arc::new(Mutex::new(Cursor::new(Vec::new()))); let shared = Arc::new(Mutex::new(Cursor::new(Vec::new())));
let writer = SharedWriter(shared.clone()); let writer = SharedWriter(shared.clone());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let mut muxer = MkvMuxer::new(writer, &tracks, Some("Cue Test"), 60.0).unwrap(); let mut muxer = MkvMuxer::new(writer, &tracks, Some("Cue Test"), 60.0, &[]).unwrap();
muxer.write_frame(0, 0, true, &[0x01, 0x02, 0x03]).unwrap(); muxer.write_frame(0, 0, true, &[0x01, 0x02, 0x03]).unwrap();
muxer.finish().unwrap(); muxer.finish().unwrap();
@@ -658,7 +648,7 @@ mod tests {
fn mkv_multiple_tracks() { fn mkv_multiple_tracks() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track(), make_audio_track()]; let tracks = [make_video_track(), make_audio_track()];
let mut muxer = MkvMuxer::new(buf, &tracks, Some("Multi"), 120.0).unwrap(); let mut muxer = MkvMuxer::new(buf, &tracks, Some("Multi"), 120.0, &[]).unwrap();
// Write frames to both tracks // Write frames to both tracks
muxer.write_frame(0, 0, true, &[0x00, 0x00, 0x01]).unwrap(); muxer.write_frame(0, 0, true, &[0x00, 0x00, 0x01]).unwrap();
muxer.write_frame(1, 0, false, &[0x0B, 0x77, 0x00]).unwrap(); muxer.write_frame(1, 0, false, &[0x0B, 0x77, 0x00]).unwrap();
@@ -677,7 +667,7 @@ mod tests {
fn mkv_keyframe_flag() { fn mkv_keyframe_flag() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let mut muxer = MkvMuxer::new(buf, &tracks, None, 10.0).unwrap(); let mut muxer = MkvMuxer::new(buf, &tracks, None, 10.0, &[]).unwrap();
// Record position before first frame // Record position before first frame
let pos_before_kf = muxer.writer.position(); let pos_before_kf = muxer.writer.position();
@@ -751,9 +741,7 @@ mod tests {
name: "Chapter 3".into(), name: "Chapter 3".into(),
}, },
]; ];
let muxer = let muxer = MkvMuxer::new(buf, &tracks, Some("Chapter Test"), 900.0, &chapters).unwrap();
MkvMuxer::new_with_chapters(buf, &tracks, Some("Chapter Test"), 900.0, &chapters)
.unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// Chapters element ID: 0x1043A770 // Chapters element ID: 0x1043A770
@@ -777,7 +765,7 @@ mod tests {
fn mkv_no_chapters_when_empty() { fn mkv_no_chapters_when_empty() {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [make_video_track()]; let tracks = [make_video_track()];
let muxer = MkvMuxer::new(buf, &tracks, Some("No Chapters"), 60.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, Some("No Chapters"), 60.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
assert!( assert!(
find_id(&data, ebml::CHAPTERS).is_none(), find_id(&data, ebml::CHAPTERS).is_none(),
@@ -796,7 +784,7 @@ mod tests {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [video, audio1, audio2]; let tracks = [video, audio1, audio2];
let muxer = MkvMuxer::new(buf, &tracks, None, 60.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, None, 60.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// FlagDefault ID is 0x88. When is_default is true, FlagDefault is NOT written // FlagDefault ID is 0x88. When is_default is true, FlagDefault is NOT written
@@ -828,7 +816,7 @@ mod tests {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [video, forced_sub]; let tracks = [video, forced_sub];
let muxer = MkvMuxer::new(buf, &tracks, None, 60.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, None, 60.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// FlagForced ID: 0x55AA (2-byte ID) // FlagForced ID: 0x55AA (2-byte ID)
@@ -853,7 +841,7 @@ mod tests {
let buf = Cursor::new(Vec::new()); let buf = Cursor::new(Vec::new());
let tracks = [video, sub]; let tracks = [video, sub];
let muxer = MkvMuxer::new(buf, &tracks, None, 60.0).unwrap(); let muxer = MkvMuxer::new(buf, &tracks, None, 60.0, &[]).unwrap();
let data = muxer.writer.into_inner(); let data = muxer.writer.into_inner();
// FlagForced should NOT be written for non-forced tracks // FlagForced should NOT be written for non-forced tracks
+1 -1
View File
@@ -64,7 +64,7 @@ impl MkvStream {
tracks.push(track); tracks.push(track);
} }
let muxer = MkvMuxer::new_with_chapters( let muxer = MkvMuxer::new(
writer, writer,
&tracks, &tracks,
Some(&title.playlist), Some(&title.playlist),
+1 -1
View File
@@ -37,7 +37,7 @@ impl<R: Read> TsDemuxReader<R> {
}; };
pids.push(pid); pids.push(pid);
pid_to_track.push((pid, i)); pid_to_track.push((pid, i));
parsers.push((pid, codec::parser_for_codec(c))); parsers.push((pid, codec::parser_for_codec(c, None)));
} }
Self { Self {
+11 -13
View File
@@ -10,23 +10,15 @@ use crate::error::Result;
pub trait SectorReader: Send { pub trait SectorReader: Send {
/// 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.
/// Full recovery enabled (retry + reset on failure). /// `recovery`: true = full retry/reset loop (ripping), false = single attempt (verify).
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize>; /// File-backed readers ignore the flag.
fn read_sectors(
/// Read with explicit recovery flag.
/// true = full retry/reset loop (for ripping). false = single attempt, fast fail (for verify).
/// Default: delegates to read_sectors (recovery=true behavior).
fn read_sectors_recover(
&mut self, &mut self,
lba: u32, lba: u32,
count: u16, count: u16,
buf: &mut [u8], buf: &mut [u8],
recovery: bool, recovery: bool,
) -> Result<usize> { ) -> Result<usize>;
// Default ignores flag — file-backed readers don't have recovery
let _ = recovery;
self.read_sectors(lba, count, buf)
}
/// Total capacity in sectors, if known. /// Total capacity in sectors, if known.
fn capacity(&self) -> u32 { fn capacity(&self) -> u32 {
@@ -61,7 +53,13 @@ impl FileSectorReader {
} }
impl SectorReader for FileSectorReader { impl SectorReader for FileSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
_recovery: bool,
) -> Result<usize> {
use std::io::{Read, Seek, SeekFrom}; use std::io::{Read, Seek, SeekFrom};
let offset = lba as u64 * 2048; let offset = lba as u64 * 2048;
let bytes = count as usize * 2048; let bytes = count as usize * 2048;
+7 -5
View File
@@ -879,6 +879,7 @@ impl BufferedSectorReader<'_> {
start_lba + offset, start_lba + offset,
batch, batch,
&mut self.cache[buf_off..buf_off + batch as usize * 2048], &mut self.cache[buf_off..buf_off + batch as usize * 2048],
true,
) )
.is_err() .is_err()
{ {
@@ -902,7 +903,7 @@ impl BufferedSectorReader<'_> {
let bytes = batch as usize * 2048; let bytes = batch as usize * 2048;
if self if self
.inner .inner
.read_sectors(start + offset, batch, &mut tmp[..bytes]) .read_sectors(start + offset, batch, &mut tmp[..bytes], true)
.is_err() .is_err()
{ {
break; break;
@@ -924,6 +925,7 @@ impl SectorReader for BufferedSectorReader<'_> {
lba: u32, lba: u32,
count: u16, count: u16,
buf: &mut [u8], buf: &mut [u8],
_recovery: bool,
) -> std::result::Result<usize, crate::error::Error> { ) -> std::result::Result<usize, crate::error::Error> {
if count == 1 { if count == 1 {
// Check permanent prefetch cache first (HashMap) // Check permanent prefetch cache first (HashMap)
@@ -939,7 +941,7 @@ impl SectorReader for BufferedSectorReader<'_> {
} }
let block = self.batch; let block = self.batch;
self.cache.resize(block as usize * 2048, 0); self.cache.resize(block as usize * 2048, 0);
match self.inner.read_sectors(lba, block, &mut self.cache) { match self.inner.read_sectors(lba, block, &mut self.cache, true) {
Ok(_) => { Ok(_) => {
self.cache_start = lba; self.cache_start = lba;
self.cache_sectors = block as u32; self.cache_sectors = block as u32;
@@ -947,7 +949,7 @@ impl SectorReader for BufferedSectorReader<'_> {
Err(_) => { Err(_) => {
// Near end of disc or error — single sector fallback // Near end of disc or error — single sector fallback
self.cache.resize(2048, 0); self.cache.resize(2048, 0);
self.inner.read_sectors(lba, 1, &mut self.cache)?; self.inner.read_sectors(lba, 1, &mut self.cache, true)?;
self.cache_start = lba; self.cache_start = lba;
self.cache_sectors = 1; self.cache_sectors = 1;
} }
@@ -956,12 +958,12 @@ impl SectorReader for BufferedSectorReader<'_> {
Ok(2048) Ok(2048)
} else { } else {
// Multi-sector read — pass through // Multi-sector read — pass through
self.inner.read_sectors(lba, count, buf) self.inner.read_sectors(lba, count, buf, true)
} }
} }
} }
fn read_sector(reader: &mut dyn SectorReader, lba: u32, buf: &mut [u8]) -> Result<()> { fn read_sector(reader: &mut dyn SectorReader, lba: u32, buf: &mut [u8]) -> Result<()> {
reader.read_sectors(lba, 1, buf)?; reader.read_sectors(lba, 1, buf, true)?;
Ok(()) Ok(())
} }
+3 -3
View File
@@ -113,7 +113,7 @@ pub fn verify_title(
let batch_start = Instant::now(); let batch_start = Instant::now();
let batch_ok = reader let batch_ok = reader
.read_sectors_recover(lba, count, &mut buf[..bytes], false) .read_sectors(lba, count, &mut buf[..bytes], false)
.is_ok(); .is_ok();
let batch_ms = batch_start.elapsed().as_millis(); let batch_ms = batch_start.elapsed().as_millis();
@@ -155,7 +155,7 @@ pub fn verify_title(
let s1 = Instant::now(); let s1 = Instant::now();
let first_ok = reader let first_ok = reader
.read_sectors_recover( .read_sectors(
sector_lba, sector_lba,
1, 1,
&mut buf[sector_offset..sector_offset + 2048], &mut buf[sector_offset..sector_offset + 2048],
@@ -174,7 +174,7 @@ pub fn verify_title(
// Retry once more after brief pause // Retry once more after brief pause
std::thread::sleep(std::time::Duration::from_secs(2)); std::thread::sleep(std::time::Duration::from_secs(2));
if reader if reader
.read_sectors_recover( .read_sectors(
sector_lba, sector_lba,
1, 1,
&mut buf[sector_offset..sector_offset + 2048], &mut buf[sector_offset..sector_offset + 2048],
+1 -1
View File
@@ -21,7 +21,7 @@ impl MockSectorReader {
} }
impl SectorReader for MockSectorReader { impl SectorReader for MockSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8], _recovery: bool) -> Result<usize> {
let total = count as usize * SECTOR_SIZE; let total = count as usize * SECTOR_SIZE;
for i in 0..count as u32 { for i in 0..count as u32 {
let offset = i as usize * SECTOR_SIZE; let offset = i as usize * SECTOR_SIZE;
+5 -5
View File
@@ -39,7 +39,7 @@ impl MockSectorReader {
} }
impl SectorReader for MockSectorReader { impl SectorReader for MockSectorReader {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8], _recovery: bool) -> Result<usize> {
let total = count as usize * SECTOR_SIZE; let total = count as usize * SECTOR_SIZE;
assert!(buf.len() >= total, "buffer too small"); assert!(buf.len() >= total, "buffer too small");
for i in 0..count as u32 { for i in 0..count as u32 {
@@ -215,7 +215,7 @@ fn mock_sector_reader_roundtrip() {
// Read it back // Read it back
let mut buf = vec![0u8; SECTOR_SIZE]; let mut buf = vec![0u8; SECTOR_SIZE];
let n = reader.read_sectors(100, 1, &mut buf).unwrap(); let n = reader.read_sectors(100, 1, &mut buf, true).unwrap();
assert_eq!(n, SECTOR_SIZE); assert_eq!(n, SECTOR_SIZE);
assert_eq!(buf[0], 0xAB); assert_eq!(buf[0], 0xAB);
assert_eq!(buf[1], 0xCD); assert_eq!(buf[1], 0xCD);
@@ -223,7 +223,7 @@ fn mock_sector_reader_roundtrip() {
// Reading an unmapped sector returns zeros // Reading an unmapped sector returns zeros
let mut buf2 = vec![0xFFu8; SECTOR_SIZE]; let mut buf2 = vec![0xFFu8; SECTOR_SIZE];
let n2 = reader.read_sectors(999, 1, &mut buf2).unwrap(); let n2 = reader.read_sectors(999, 1, &mut buf2, true).unwrap();
assert_eq!(n2, SECTOR_SIZE); assert_eq!(n2, SECTOR_SIZE);
assert_eq!(buf2[0], 0); assert_eq!(buf2[0], 0);
assert_eq!(buf2[2047], 0); assert_eq!(buf2[2047], 0);
@@ -243,7 +243,7 @@ fn mock_sector_reader_multi_sector() {
// Read 2 consecutive sectors // Read 2 consecutive sectors
let mut buf = vec![0u8; SECTOR_SIZE * 2]; let mut buf = vec![0u8; SECTOR_SIZE * 2];
let n = reader.read_sectors(10, 2, &mut buf).unwrap(); let n = reader.read_sectors(10, 2, &mut buf, true).unwrap();
assert_eq!(n, SECTOR_SIZE * 2); assert_eq!(n, SECTOR_SIZE * 2);
assert_eq!(buf[0], 10); assert_eq!(buf[0], 10);
assert_eq!(buf[SECTOR_SIZE], 11); assert_eq!(buf[SECTOR_SIZE], 11);
@@ -463,7 +463,7 @@ fn sector_reader_is_object_safe() {
let dyn_reader: &mut dyn SectorReader = &mut reader; let dyn_reader: &mut dyn SectorReader = &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).unwrap(); let n = dyn_reader.read_sectors(0, 1, &mut buf, true).unwrap();
assert_eq!(n, SECTOR_SIZE); assert_eq!(n, SECTOR_SIZE);
assert_eq!(buf[0], 42); assert_eq!(buf[0], 42);
} }