1use std::{
2 fs,
3 io::{self},
4 path::{Path, PathBuf},
5};
6
7use ahash::HashMap;
8use indexmap::{indexmap, IndexMap};
9use once_cell::sync::OnceCell;
10use serde::{Deserialize, Serialize};
11use snafu::{ResultExt, Snafu};
12use tracing::debug;
13
14#[derive(Debug, Serialize, Deserialize)]
15struct Status {
16 branch: Box<str>,
17 component: Vec<Box<str>>,
18 mirror: IndexMap<Box<str>, Box<str>>,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22struct Branch {
23 desc: Box<str>,
24 suites: Vec<Box<str>>,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct Mirror {
29 pub desc: Box<str>,
30 pub url: Box<str>,
31}
32
33impl Default for Status {
34 fn default() -> Self {
35 Self {
36 branch: Box::from("stable"),
37 component: vec![Box::from("main")],
38 mirror: indexmap! { Box::from("origin") => Box::from("https://repo.aosc.io/") },
39 }
40 }
41}
42
43#[derive(Debug, Snafu)]
44pub enum MirrorError {
45 #[snafu(display("Failed to read file: {}", path.display()))]
46 ReadFile { path: PathBuf, source: io::Error },
47 #[snafu(display("Failed to parse file: {}", path.display()))]
48 ParseJson {
49 path: PathBuf,
50 source: serde_json::Error,
51 },
52 #[snafu(display("Failed to parse file: {}", path.display()))]
53 ParseYaml {
54 path: PathBuf,
55 source: serde_yaml::Error,
56 },
57 #[snafu(display("mirror does not exist in mirrors file: {mirror_name}"))]
58 MirrorNotExist { mirror_name: Box<str> },
59 #[snafu(display("Serialize struct failed"))]
60 SerializeJson { source: serde_json::Error },
61 #[snafu(display("Failed to write to file"))]
62 WriteFile { path: PathBuf, source: io::Error },
63 #[snafu(display("Failed to create status file: {}", path.display()))]
64 CreateFile { path: PathBuf, source: io::Error },
65}
66
67pub struct MirrorManager {
68 status: Status,
69 mirrors_data: OnceCell<HashMap<Box<str>, Mirror>>,
72 status_file_path: PathBuf,
73 mirrors_file_path: PathBuf,
74 apt_status_file: PathBuf,
75}
76
77impl MirrorManager {
78 pub fn new(rootfs: impl AsRef<Path>) -> Result<Self, MirrorError> {
79 let status_file_path = rootfs.as_ref().join("var/lib/apt/gen/status.json");
80 let mirrors_file_path = rootfs
81 .as_ref()
82 .join("usr/share/distro-repository-data/mirrors.yml");
83 let apt_status_file = rootfs.as_ref().join("etc/apt/sources.list");
84
85 let status: Status = if status_file_path.is_file() {
86 let f = fs::read(&status_file_path).context(ReadFileSnafu {
87 path: status_file_path.to_path_buf(),
88 })?;
89 match serde_json::from_slice(&f) {
90 Ok(status) => status,
91 Err(e) => {
92 debug!("{e}, creating new ...");
93 create_default_status(&status_file_path)?
94 }
95 }
96 } else {
97 create_default_status(&status_file_path)?
98 };
99
100 Ok(Self {
101 status,
102 mirrors_data: OnceCell::new(),
105 status_file_path,
106 mirrors_file_path,
107 apt_status_file,
108 })
109 }
110
111 fn try_mirrors(&self) -> Result<&HashMap<Box<str>, Mirror>, MirrorError> {
153 self.mirrors_data
154 .get_or_try_init(|| -> Result<HashMap<Box<str>, Mirror>, MirrorError> {
155 let f = fs::read(&self.mirrors_file_path).context(ReadFileSnafu {
156 path: self.mirrors_file_path.to_path_buf(),
157 })?;
158
159 let mirrors: HashMap<Box<str>, Mirror> =
160 serde_yaml::from_slice(&f).context(ParseYamlSnafu {
161 path: self.mirrors_file_path.to_path_buf(),
162 })?;
163
164 Ok(mirrors)
165 })
166 }
167
168 pub fn set(&mut self, mirror_names: &[&str]) -> Result<(), MirrorError> {
169 let mirrors = self.try_mirrors()?;
170
171 for i in mirror_names {
172 if !mirrors.contains_key(*i) {
173 return Err(MirrorError::MirrorNotExist {
174 mirror_name: Box::from(*i),
175 });
176 }
177 }
178
179 self.status.mirror.clear();
180 for i in mirror_names {
181 self.add(i)?;
182 }
183
184 Ok(())
185 }
186
187 pub fn add(&mut self, mirror_name: &str) -> Result<bool, MirrorError> {
188 if self.status.mirror.contains_key(mirror_name) {
189 return Ok(false);
190 }
191
192 let mirrors = self.try_mirrors()?;
193
194 let mirror_url = if let Some(mirror) = mirrors.get(mirror_name) {
195 mirror.url.clone()
196 } else {
197 return Err(MirrorError::MirrorNotExist {
198 mirror_name: mirror_name.into(),
199 });
200 };
201
202 self.status.mirror.insert(mirror_name.into(), mirror_url);
203
204 Ok(true)
205 }
206
207 pub fn remove(&mut self, mirror_name: &str) -> bool {
208 if !self.status.mirror.contains_key(mirror_name) {
209 return false;
210 }
211
212 self.status.mirror.shift_remove(mirror_name);
213
214 true
215 }
216
217 pub fn mirrors_iter(&self) -> Result<impl Iterator<Item = (&str, &Mirror)>, MirrorError> {
218 let mirrors = self.try_mirrors()?;
219 let iter = mirrors.iter().map(|x| (x.0.as_ref(), x.1));
220
221 Ok(iter)
222 }
223
224 pub fn enabled_mirrors(&self) -> &IndexMap<Box<str>, Box<str>> {
225 &self.status.mirror
226 }
227
228 pub fn set_order(&mut self, order: &[usize]) {
229 let mut new = IndexMap::new();
230 for i in order {
231 let (k, v) = self.status.mirror.get_index(*i).unwrap();
232 new.insert(k.to_owned(), v.to_owned());
233 }
234
235 self.status.mirror = new;
236 }
237
238 pub fn write_status(&self, custom_mirror_tips: Option<&str>) -> Result<(), MirrorError> {
239 fs::write(
240 &self.status_file_path,
241 serde_json::to_vec(&self.status).context(SerializeJsonSnafu)?,
242 )
243 .context(WriteFileSnafu {
244 path: self.status_file_path.to_path_buf(),
245 })?;
246
247 let mut result = String::new();
248
249 let tips = custom_mirror_tips.unwrap_or("# Generate by oma-mirror, DO NOT EDIT!");
250 result.push_str(tips);
251 result.push('\n');
252
253 for (_, url) in &self.status.mirror {
254 result.push_str("deb ");
255 result.push_str(url);
256
257 if !url.ends_with('/') {
258 result.push('/');
259 }
260
261 result.push_str("debs");
262 result.push(' ');
263 result.push_str(&self.status.branch);
264 result.push(' ');
265 result.push_str(&self.status.component.join(" "));
266 result.push('\n');
267 }
268
269 fs::write(&self.apt_status_file, result).context(WriteFileSnafu {
270 path: self.apt_status_file.to_path_buf(),
271 })?;
272
273 Ok(())
274 }
275}
276
277fn create_default_status(path: &Path) -> Result<Status, MirrorError> {
278 debug!("Creating status file ... ");
279 fs::create_dir_all(path.parent().unwrap()).context(CreateFileSnafu {
280 path: path.to_path_buf(),
281 })?;
282
283 let mut f = fs::File::create(path).context(CreateFileSnafu {
284 path: path.to_path_buf(),
285 })?;
286
287 let status = Status::default();
288 serde_json::to_writer(&mut f, &status).unwrap();
289
290 Ok(status)
291}