Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#[cfg(target_os = "macos")]
fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
return;
}

println!("cargo:rerun-if-changed=build.rs");
const NATIVE_DIR: &str = "src/platform/macos/native";

println!("cargo:rerun-if-changed={NATIVE_DIR}/shim_common.h");
println!("cargo:rerun-if-changed={NATIVE_DIR}/device_service.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/list_drives.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/toc_reader.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/track_information.c");
println!("cargo:rerun-if-changed={NATIVE_DIR}/read_cd.c");

println!("cargo:rustc-link-lib=framework=IOKit");
Expand All @@ -15,6 +20,7 @@ fn main() {
.file(format!("{NATIVE_DIR}/device_service.c"))
.file(format!("{NATIVE_DIR}/list_drives.c"))
.file(format!("{NATIVE_DIR}/toc_reader.c"))
.file(format!("{NATIVE_DIR}/track_information.c"))
.file(format!("{NATIVE_DIR}/read_cd.c"))
.include(NATIVE_DIR)
// force C compilation
Expand Down
14 changes: 14 additions & 0 deletions examples/detect_track_formats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Detects the default read format for every track on the inserted disc.
use cd_da_reader::CdReader;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = CdReader::open_default()?;
let toc = reader.read_toc()?;

for track in &toc.tracks {
let format = reader.detect_track_format(track)?;
println!("Track #{:>2}: {format:?}", track.number);
}

Ok(())
}
175 changes: 0 additions & 175 deletions src/data_reader.rs

This file was deleted.

79 changes: 79 additions & 0 deletions src/data_reader/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::{CdReader, CdReaderError, SectorReadFormat, Track};

impl CdReader {
/// Detect the default read format for a track.
///
/// Audio tracks are identified directly from the TOC. Data tracks are
/// queried with MMC READ TRACK INFORMATION and mapped to their cooked
/// sector representation.
pub fn detect_track_format(&self, track: &Track) -> Result<SectorReadFormat, CdReaderError> {
if track.is_audio {
return Ok(SectorReadFormat::Audio);
}

let information = self.drive.read_track_information(track.number)?;
format_from_data_mode(information.data_mode).ok_or(CdReaderError::CannotDetectTrackFormat {
track_number: track.number,
data_mode: Some(information.data_mode),
})
}
}

/// Map the MMC READ TRACK INFORMATION Data Mode field to an unambiguous
/// default read format.
///
/// MMC reports `0x01` for Mode 1 and `0x02` for Mode 2. Track Information does
/// not identify the per-sector Mode 2 form, so Mode 2 is exposed as complete
/// raw sectors.
fn format_from_data_mode(data_mode: u8) -> Option<SectorReadFormat> {
match data_mode {
0x01 => Some(SectorReadFormat::Mode1Cooked),
0x02 => Some(SectorReadFormat::Mode2Raw),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn audio_tracks_do_not_require_drive_detection() {
let reader = CdReader::test_reader();
let track = Track {
number: 1,
start_lba: 0,
start_msf: (0, 2, 0),
is_audio: true,
};

assert_eq!(
reader.detect_track_format(&track).unwrap(),
SectorReadFormat::Audio
);
}

#[test]
fn maps_mode1_to_its_cooked_format() {
assert_eq!(
format_from_data_mode(0x01),
Some(SectorReadFormat::Mode1Cooked)
);
}

#[test]
fn maps_mode2_to_raw_sectors() {
assert_eq!(
format_from_data_mode(0x02),
Some(SectorReadFormat::Mode2Raw)
);
}

#[test]
fn rejects_unknown_or_reserved_mmc_data_modes() {
assert_eq!(format_from_data_mode(0x00), None);
for data_mode in 0x03..=0x0F {
assert_eq!(format_from_data_mode(data_mode), None);
}
}
}
90 changes: 90 additions & 0 deletions src/data_reader/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
mod detect;
mod sector_read_format;
pub(crate) mod track_information;

pub use sector_read_format::SectorReadFormat;

use crate::retry::RetryConfig;

/// Sector format and retry options for track and sector-range reads.
///
/// The defaults read audio sectors using the default retry policy. Use the
/// builder methods to override only the options you need.
#[derive(Debug, Clone)]
pub struct ReadOptions {
format: SectorReadFormat,
retry: RetryConfig,
}

impl ReadOptions {
/// Select the sector format requested from the drive.
pub fn with_format(mut self, format: SectorReadFormat) -> Self {
self.format = format;
self
}

/// Set the retry policy applied to each read command.
pub fn with_retry(mut self, retry: RetryConfig) -> Self {
self.retry = retry;
self
}

pub(crate) fn format(&self) -> SectorReadFormat {
self.format
}

pub(crate) fn retry(&self) -> &RetryConfig {
&self.retry
}
}

impl Default for ReadOptions {
fn default() -> Self {
Self {
format: SectorReadFormat::Audio,
retry: RetryConfig::default(),
}
}
}

/// Build a READ CD (0xBE) command descriptor block for Linux and Windows.
#[cfg(any(target_os = "linux", target_os = "windows", test))]
pub(crate) fn build_read_cd_cdb(lba: u32, sectors: u32, format: SectorReadFormat) -> [u8; 12] {
let mut cdb = [0u8; 12];
cdb[0] = 0xBE;
cdb[1] = format.cdb_byte1();
cdb[2..6].copy_from_slice(&lba.to_be_bytes());
cdb[6] = ((sectors >> 16) & 0xFF) as u8;
cdb[7] = ((sectors >> 8) & 0xFF) as u8;
cdb[8] = (sectors & 0xFF) as u8;
cdb[9] = format.cdb_byte9();
cdb
}

#[cfg(test)]
mod tests {
use super::{ReadOptions, SectorReadFormat, build_read_cd_cdb};

#[test]
fn read_options_builders_override_individual_defaults() {
assert_eq!(ReadOptions::default().format(), SectorReadFormat::Audio);

let retry = crate::RetryConfig::default().with_max_attempts(9);
let options = ReadOptions::default()
.with_format(SectorReadFormat::Mode1Raw)
.with_retry(retry);

assert_eq!(options.format(), SectorReadFormat::Mode1Raw);
assert_eq!(options.retry().max_attempts, 9);
}

#[test]
fn builds_read_cd_cdb() {
assert_eq!(
build_read_cd_cdb(0x1234_5678, 0x0000_ABCD, SectorReadFormat::Mode1Raw),
[
0xBE, 0x08, 0x12, 0x34, 0x56, 0x78, 0x00, 0xAB, 0xCD, 0xF8, 0x00, 0x00,
]
);
}
}
Loading
Loading