1use super::{Disc, eject};
2use cd_da_reader::CdReader;
3use std::{
4 collections::BTreeMap,
5 sync::{Arc, Mutex},
6};
7#[derive(Clone)]
9pub struct Drive {
10 pub id: String,
12 pub name: String,
14 pub disc: Option<Disc>,
16 pub error: Option<String>,
18 pub image: bool,
20 path: String,
21}
22#[derive(Default)]
24pub struct Library {
25 drives: Vec<Drive>,
26 next: u64,
27 gate: Arc<Mutex<()>>,
28}
29impl Library {
30 fn token(&mut self) -> Result<String, String> {
31 self.next = self
32 .next
33 .checked_add(1)
34 .ok_or("Trop de changements de CD ; redémarrez l’application.")?;
35 Ok(format!("{:016x}", self.next))
36 }
37 pub fn snapshot(&self) -> &[Drive] {
39 &self.drives
40 }
41 pub fn scan(&mut self) -> Result<&[Drive], String> {
43 let gate = self.gate.clone();
44 let _guard = gate.try_lock().map_err(|_| "Le lecteur CD est occupé.")?;
45 let drives =
46 CdReader::list_drives().map_err(|_| "Détection des lecteurs CD impossible.")?;
47 if drives.len() > 16 {
48 return Err("Trop de lecteurs CD détectés.".into());
49 }
50 let previous: BTreeMap<_, _> = self
51 .drives
52 .iter()
53 .map(|d| (d.path.clone(), d.clone()))
54 .collect();
55 let mut next = Vec::new();
56 for drive in drives {
57 let old = previous.get(&drive.path);
58 let id = match old {
59 Some(d) => d.id.clone(),
60 None => self.token()?,
61 };
62 let probe = CdReader::open(&drive).and_then(|r| r.read_toc().map(|toc| (r, toc)));
63 let mut error = None;
64 let disc = match probe {
65 Ok((reader, toc)) => match Disc::inspect(
66 &reader,
67 &toc,
68 drive.path.clone(),
69 self.token()?,
70 gate.clone(),
71 ) {
72 Ok(disc) => Some(
73 old.and_then(|d| d.disc.as_ref())
74 .filter(|d| d.matches(&toc))
75 .cloned()
76 .unwrap_or(disc),
77 ),
78 Err(reason) => {
79 error = Some(reason);
80 None
81 }
82 },
83 Err(reason) => {
84 error = Some(format!("CD absent ou inaccessible : {reason}"));
85 None
86 }
87 };
88 next.push(Drive {
89 id,
90 name: format!("Lecteur CD {}", next.len() + 1),
91 path: drive.path,
92 disc,
93 error,
94 image: false,
95 });
96 }
97 next.extend(self.drives.iter().filter(|d| d.image).cloned());
98 if next.len() > 16 {
99 return Err("Trop de sources CD ouvertes.".into());
100 }
101 self.drives = next;
102 Ok(&self.drives)
103 }
104 pub fn open_image(&mut self, path: &std::path::Path) -> Result<String, String> {
106 if self.drives.iter().filter(|d| !d.image).count() >= 16 {
107 return Err("Trop de sources CD ouvertes.".into());
108 }
109 let id = self.token()?;
110 let disc = super::image::Image::open(path, self.token()?)?;
111 if let Some(old) = self
112 .drives
113 .iter()
114 .find(|d| d.image && d.disc.as_ref().is_some_and(|old| old.same_image(&disc)))
115 {
116 return Ok(old.id.clone());
117 }
118 let name = path
119 .file_name()
120 .and_then(|n| n.to_str())
121 .unwrap_or("CUE/BIN")
122 .chars()
123 .take(90)
124 .collect::<String>();
125 let drive = Drive {
126 id: id.clone(),
127 name,
128 path: disc.path.clone(),
129 disc: Some(disc),
130 error: None,
131 image: true,
132 };
133 self.drives.retain(|d| !d.image);
134 self.drives.push(drive);
135 Ok(id)
136 }
137 pub fn refresh_image(&mut self, id: &str) -> Result<bool, String> {
139 let Some(index) = self.drives.iter().position(|d| d.id == id && d.image) else {
140 return Ok(false);
141 };
142 let path = self.drives[index]
143 .disc
144 .as_ref()
145 .and_then(|disc| disc.image.as_ref())
146 .ok_or("Image CD indisponible.")?
147 .path
148 .clone();
149 let mut disc = super::image::Image::open(&path, self.token()?)?;
150 if let Some(old) = self.drives[index]
151 .disc
152 .as_ref()
153 .filter(|old| old.same_image(&disc))
154 {
155 disc.token = old.token.clone();
156 }
157 self.drives[index].disc = Some(disc);
158 Ok(true)
159 }
160 pub fn disc(&self, token: &str) -> Result<Disc, String> {
162 self.drives
163 .iter()
164 .filter_map(|d| d.disc.as_ref())
165 .find(|d| d.token == token)
166 .cloned()
167 .ok_or_else(|| "Le CD a changé. Actualisez la liste des lecteurs.".into())
168 }
169 pub fn eject(&mut self, id: &str) -> Result<(), String> {
171 if let Some(index) = self.drives.iter().position(|d| d.id == id && d.image) {
172 self.drives.remove(index);
173 return Ok(());
174 }
175 let gate = self.gate.clone();
176 let _guard = super::access::lock(&gate, || true)?;
177 let drive = self
178 .drives
179 .iter_mut()
180 .find(|d| d.id == id)
181 .ok_or("Lecteur CD inconnu.")?;
182 eject::run(&drive.path)?;
183 drive.disc = None;
184 drive.error = None;
185 Ok(())
186 }
187}