diff --git a/docs/README.md b/docs/README.md index fcfc3d8..45a5335 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ Technical documentation for [libfreemkv](https://github.com/freemkv/libfreemkv), |----------|---------------| | [Architecture](architecture.md) | Module map, design principles, error codes, platform support | | [Drive Access](drive-access.md) | Drive, SCSI transport, profiles, unlock, why raw mode is needed | +| [Rip Recovery](rip-recovery.md) | Three-layer recovery model: Disc::patch, single-shot Drive::read, DiscStream batch halving | | [AACS Encryption](aacs.md) | Key resolution (4 paths), content decryption, bus encryption, SCSI handshake | | [UDF Filesystem](udf.md) | UDF 2.50 with metadata partitions, pointer chain, how files are read from disc | | [MPLS Playlists](mpls.md) | Playlist format, play items, STN stream table, coding types | diff --git a/docs/api-design.md b/docs/api-design.md index acb4d6a..94c6682 100644 --- a/docs/api-design.md +++ b/docs/api-design.md @@ -126,15 +126,45 @@ pub struct Event { } pub enum EventKind { + // Init / scan + DriveOpened { device: String }, + DriveReady, + InitComplete { success: bool }, + ProbeComplete { success: bool }, + ScanComplete { titles: usize }, + + // Read pipeline BytesRead { bytes: u64, total: u64 }, ReadError { sector: u64, error: Error }, - Retry { attempt: u32 }, SpeedChange { speed_kbs: u16 }, ExtentStart { index: usize, start_sector: u64, sector_count: u64 }, + SectorSkipped { sector: u64 }, + BatchSizeChanged { new_size: u16, reason: BatchSizeReason }, Complete { bytes: u64, errors: u32 }, + + // Kept for forward-compat; not emitted in 0.13.6+ + Retry { attempt: u32 }, + SectorRecovered { sector: u64 }, } ``` +Emission notes: + +- `BytesRead { bytes, total }` is emitted from `DiscStream::fill_extents` + after each successful sector read. `bytes` is the cumulative running + total; `total` is the precomputed extent sum (0 if unknown). +- `SpeedChange` is emitted from the public `Drive::set_speed` API path. + It is no longer emitted from a recovery hot loop (recovery loop removed + in 0.13.6). +- `BatchSizeChanged` fires from the `DiscStream` adaptive sizer on shrink + (read failed at a larger size) and on probe-up (clean-read streak hit + the threshold). Consumers use it to display a "recovering" state + distinct from "ripping normally". +- `Retry` and `SectorRecovered` are NOT emitted in 0.13.6+. They were + tied to the inline `Drive::read` recovery phases that were removed; the + variants are kept for forward compatibility so consumers' match arms + don't need conditional compilation. + Events report what happened. App decides what to do. GUI shows a dialog. CLI prints a line. Server logs to file. @@ -145,8 +175,8 @@ libfreemkv/src/ ├── lib.rs Public exports ├── error.rs Error codes (no English) ├── event.rs Event types for callbacks -├── drive/ Drive (open, init, read with recovery) -│ ├── mod.rs Drive struct, init, read, reset, eject +├── drive/ Drive (open, init, single-shot read) +│ ├── mod.rs Drive struct, init, read (single-shot), reset, eject │ ├── capture.rs Drive profile capture for contribution │ ├── linux.rs Linux drive discovery │ ├── macos.rs macOS drive discovery diff --git a/docs/architecture.md b/docs/architecture.md index dd33d94..e7c2656 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,7 +41,7 @@ AACS keys are derived internally, and all SCSI communication is handled in-proce libfreemkv (lib.rs) │ ├── Drive Access -│ ├── drive Drive — open, identify, init, unlock, read (with recovery) +│ ├── drive Drive — open, identify, init, unlock, single-shot read │ ├── scsi ScsiTransport trait + platform backends (sg async, IOKit, SPTI) │ ├── platform/ Platform trait — per-chipset command handlers │ │ └── mt1959 MediaTek MT1959 driver (LG, ASUS, HP) @@ -93,10 +93,17 @@ Drive::open(Path::new("/dev/sg4")) After open: - `init()` -- unlock + firmware upload + speed calibration - `probe_disc()` -- probe disc surface for optimal speeds -- `read(lba, count, buf)` -- single read method with built-in error recovery +- `read(lba, count, buf, recovery)` -- single-shot read; `recovery` only selects the per-CDB timeout (1.5 s vs. 30 s) - `wait_ready()` -- wait for disc insertion - `eject()` -- eject tray +Recovery is layered above `Drive::read`, not inside it. Layer 1 +(`Disc::patch`) handles bad-range retry by replaying the ddrescue mapfile. +Layer 3 (`DiscStream::fill_extents` adaptive batch sizer) handles in-loop +request-size adaptation. Inline recovery (gentle retry → SCSI reset → retry) +was removed in 0.13.6 — see [`rip-recovery.md`](rip-recovery.md) and +`freemkv-private/postmortems/2026-04-25-stop-wedge-and-zero-kbs.md`. + --- ## Disc Scanning Flow diff --git a/docs/disc-to-rip.md b/docs/disc-to-rip.md index cb2f55e..d3feea1 100644 --- a/docs/disc-to-rip.md +++ b/docs/disc-to-rip.md @@ -77,7 +77,9 @@ Insert disc 10. Stream content (mux/disc.rs → DiscStream) │ Read sectors → decrypt → TS demux → PES frames │ Or: read sectors → decrypt → raw bytes (for ISO output) - │ Drive::read() handles error recovery (min speed → reset → retry) + │ Drive::read() is single-shot. DiscStream::fill_extents adapts the + │ batch size on failure (halve / probe-up). Bad-range retry is layer + │ 1 above this — Disc::patch re-runs against the mapfile. │ ▼ PES frames → output stream (MKV, M2TS, network, etc.) @@ -110,7 +112,7 @@ output.finish()?; | Module | Doc | Purpose | |--------|-----|---------| -| drive/ | [drive-access.md](drive-access.md) | Open, identify, init, unlock, read (with recovery) | +| drive/ | [drive-access.md](drive-access.md) | Open, identify, init, unlock, single-shot read | | scsi/ | [drive-access.md](drive-access.md) | Platform SCSI transport (Linux, macOS, Windows) | | udf.rs | [udf.md](udf.md) | UDF 2.50 filesystem | | mpls.rs | [mpls.md](mpls.md) | MPLS playlists + STN streams |