From 70748e08e51413728f1417a1e675876330d8c940 Mon Sep 17 00:00:00 2001 From: Seva Zaikov Date: Sat, 14 Feb 2026 16:55:48 -0800 Subject: [PATCH] update documentation and package to 0.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/discovery.rs | 21 +++++++++++++++++++++ src/errors.rs | 19 +++++++++++++++++++ src/retry.rs | 22 ++++++++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99ad1b5..c66cc3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "cd-da-reader" -version = "0.1.0" +version = "0.2.0" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index d5e4704..659c470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cd-da-reader" -version = "0.1.0" +version = "0.2.0" edition = "2024" description = "CD-DA (audio CD) reading library" repository = "https://github.com/Bloomca/rust-cd-da-reader" diff --git a/src/discovery.rs b/src/discovery.rs index fdaad28..321298f 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -1,14 +1,25 @@ use crate::{CdReader, CdReaderError}; +/// Information about all found drives. This info is not tested extensively, and in +/// general it is encouraged to provide a disk drive directly. #[derive(Debug, Clone)] pub struct DriveInfo { + /// Path to the drive, which can be something like 'disk6' on macOS, + /// '\\.\E:' on Windows, and '/dev/sr0' on Linux pub path: String, + /// Just the device name, without the full path for the OS pub display_name: Option, + /// We load the disc and issue a TOC command, which is supported only on media CDs pub has_audio_cd: bool, } impl CdReader { /// Enumerate candidate optical drives and probe whether they currently have an audio CD. + /// + /// This method does not work on macOS due to the fact for reliable confirmation + /// whether the drive has an Audio CD we need to mount it and later release; macOS + /// can assign a different drive name afterwards, so reading that name is unreliable. + /// Instead, use open_drive(drive) or open_default(), which acquires the exclusivity #[cfg(target_os = "macos")] pub fn list_drives() -> Result, CdReaderError> { Err(CdReaderError::Io(std::io::Error::other( @@ -17,6 +28,9 @@ impl CdReader { } /// Enumerate candidate optical drives and probe whether they currently have an audio CD. + /// + /// On Windows, we try to read type of every drive from A to Z. On Linux, we read + /// /sys/class/block directory and check every entry starting with "sr" #[cfg(not(target_os = "macos"))] pub fn list_drives() -> Result, CdReaderError> { let mut paths = { @@ -60,6 +74,10 @@ impl CdReader { } /// Open the first discovered drive that currently has an audio CD. + /// + /// On macOS, we open each drive returned from `diskutil list`, and + /// evaluate each disk. Once we are able to open it and read correct TOC, + /// we return it back with already acquired exclusivity. #[cfg(target_os = "macos")] pub fn open_default() -> Result { let mut paths = crate::macos::list_drive_paths().map_err(CdReaderError::Io)?; @@ -85,6 +103,9 @@ impl CdReader { } /// Open the first discovered drive that currently has an audio CD. + /// + /// On Windows and Linux, we get the first device from the list and + /// try to open it, returning an error if it fails. #[cfg(not(target_os = "macos"))] pub fn open_default() -> Result { let drives = Self::list_drives()?; diff --git a/src/errors.rs b/src/errors.rs index 5473a34..1e38b43 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,27 +1,46 @@ use std::fmt; +/// SCSI command groups issued by this library. #[derive(Debug, Clone, Copy)] pub enum ScsiOp { + /// `READ TOC/PMA/ATIP` command (opcode `0x43`) for TOC/session metadata. ReadToc, + /// `READ CD` command (opcode `0xBE`) for CD-DA sector payload (2352 bytes/sector). ReadCd, + /// `READ SUB-CHANNEL` command for Q-channel/subcode metadata. ReadSubChannel, } +/// Structured SCSI failure context captured at the call site. +/// +/// This keeps transport/protocol details (status + sense) separate from plain I/O failures, +/// which allows retry logic and application diagnostics to branch on SCSI metadata. #[derive(Debug, Clone)] pub struct ScsiError { + /// Operation that failed. pub op: ScsiOp, + /// Starting logical block address used by the failed command, when applicable. pub lba: Option, + /// Sector count requested by the failed command, when applicable. pub sectors: Option, + /// SCSI status byte reported by the device (for example `0x02` for CHECK CONDITION). pub scsi_status: u8, + /// Sense key nibble from fixed-format sense data (if sense data was returned). pub sense_key: Option, + /// Additional Sense Code from sense data (if available). pub asc: Option, + /// Additional Sense Code Qualifier paired with `asc` (if available). pub ascq: Option, } +/// Top-level error type returned by `cd-da-reader`. #[derive(Debug)] pub enum CdReaderError { + /// OS/transport I/O error (open/ioctl/DeviceIoControl/FFI command failure, etc.). Io(std::io::Error), + /// Device reported a SCSI command failure with status/sense context. Scsi(ScsiError), + /// Parsing failure for command payloads (TOC/CD-TEXT/subchannel parsing). Parse(String), } diff --git a/src/retry.rs b/src/retry.rs index 11de0dd..ee5f1f5 100644 --- a/src/retry.rs +++ b/src/retry.rs @@ -1,9 +1,31 @@ +/// Retry policy for idempotent read operations. +/// +/// The policy is applied per failed read chunk/command and can combine +/// capped exponential backoff with adaptive chunk-size reduction. #[derive(Debug, Clone)] pub struct RetryConfig { + /// Maximum attempts per operation, including the initial attempt. + /// By default the value is 4. + /// + /// Values below `1` are normalized by callers to at least one attempt. pub max_attempts: u8, + /// Initial backoff delay in milliseconds before the second attempt. + /// First attempt is always immediate, so if there are no issues during + /// reading, we don't wait any time. pub initial_backoff_ms: u64, + /// Upper bound for exponential backoff delay in milliseconds. + /// + /// Each retry typically doubles the previous delay until this cap is reached. pub max_backoff_ms: u64, + /// Enable adaptive sector-count reduction on retry for `READ CD` operations. + /// + /// Current implementation reduces chunk size from large reads toward smaller + /// reads (for example `27 -> 8 -> 1`) to have a higher change of success. pub reduce_chunk_on_retry: bool, + /// Minimum sectors per `READ CD` command when adaptive reduction is enabled. + /// Default value is 27 for 64KB per read. + /// + /// Use `1` for maximal fault isolation; larger values can improve throughput. pub min_sectors_per_read: u32, }