cd_da_reader/
discovery.rs1use crate::{CdReader, CdReaderError};
2
3#[derive(Debug, Clone)]
6pub struct DriveInfo {
7 pub path: String,
10 pub display_name: Option<String>,
12 pub has_audio_cd: bool,
14}
15
16impl CdReader {
17 #[cfg(target_os = "macos")]
22 pub fn list_drives() -> Result<Vec<DriveInfo>, CdReaderError> {
23 crate::macos::list_drives().map_err(CdReaderError::Io)
24 }
25
26 #[cfg(not(target_os = "macos"))]
31 pub fn list_drives() -> Result<Vec<DriveInfo>, CdReaderError> {
32 let mut paths = {
33 #[cfg(target_os = "windows")]
34 {
35 crate::windows::list_drive_paths().map_err(CdReaderError::Io)?
36 }
37
38 #[cfg(target_os = "linux")]
39 {
40 crate::linux::list_drive_paths().map_err(CdReaderError::Io)?
41 }
42
43 #[cfg(not(any(target_os = "windows", target_os = "linux")))]
44 {
45 compile_error!("Unsupported platform")
46 }
47 };
48
49 paths.sort();
50 paths.dedup();
51
52 let mut drives = Vec::with_capacity(paths.len());
53 for path in paths {
54 let has_audio_cd = match Self::open(&path) {
55 Ok(reader) => match reader.read_toc() {
56 Ok(toc) => toc.tracks.iter().any(|track| track.is_audio),
57 Err(_) => false,
58 },
59 Err(_) => false,
60 };
61
62 drives.push(DriveInfo {
63 display_name: Some(path.clone()),
64 path,
65 has_audio_cd,
66 });
67 }
68
69 Ok(drives)
70 }
71
72 #[cfg(target_os = "macos")]
77 pub fn open_default() -> Result<Self, CdReaderError> {
78 let drives = Self::list_drives()?;
79 let chosen = drives
80 .iter()
81 .find(|drive| drive.has_audio_cd)
82 .map(|drive| drive.path.as_str())
83 .ok_or_else(|| {
84 CdReaderError::Io(std::io::Error::new(
85 std::io::ErrorKind::NotFound,
86 "no usable audio CD drive found",
87 ))
88 })?;
89
90 Self::open(chosen).map_err(CdReaderError::Io)
91 }
92
93 #[cfg(not(target_os = "macos"))]
98 pub fn open_default() -> Result<Self, CdReaderError> {
99 let drives = Self::list_drives()?;
100 let chosen = pick_default_drive_path(&drives).ok_or_else(|| {
101 CdReaderError::Io(std::io::Error::new(
102 std::io::ErrorKind::NotFound,
103 "no usable audio CD drive found",
104 ))
105 })?;
106
107 Self::open(chosen).map_err(CdReaderError::Io)
108 }
109}
110
111#[cfg(any(test, not(target_os = "macos")))]
112fn pick_default_drive_path(drives: &[DriveInfo]) -> Option<&str> {
113 drives
114 .iter()
115 .find(|drive| drive.has_audio_cd)
116 .map(|drive| drive.path.as_str())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::{DriveInfo, pick_default_drive_path};
122
123 #[test]
124 fn chooses_first_audio_drive() {
125 let drives = vec![
126 DriveInfo {
127 path: "disk10".to_string(),
128 display_name: None,
129 has_audio_cd: false,
130 },
131 DriveInfo {
132 path: "disk11".to_string(),
133 display_name: None,
134 has_audio_cd: true,
135 },
136 DriveInfo {
137 path: "disk12".to_string(),
138 display_name: None,
139 has_audio_cd: true,
140 },
141 ];
142
143 assert_eq!(pick_default_drive_path(&drives), Some("disk11"));
144 }
145
146 #[test]
147 fn returns_none_when_no_audio_drive() {
148 let drives = vec![DriveInfo {
149 path: "/dev/sr0".to_string(),
150 display_name: None,
151 has_audio_cd: false,
152 }];
153
154 assert_eq!(pick_default_drive_path(&drives), None);
155 }
156}