oma_mirror/
lib.rs

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    // branches_data: OnceCell<HashMap<Box<str>, Branch>>,
70    // components_data: OnceCell<HashMap<Box<str>, Box<str>>>,
71    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            // branches_data: OnceCell::new(),
103            // components_data: OnceCell::new(),
104            mirrors_data: OnceCell::new(),
105            status_file_path,
106            mirrors_file_path,
107            apt_status_file,
108        })
109    }
110
111    // fn try_branches_data(&self) -> Result<&HashMap<Box<str>, Branch>, MirrorError> {
112    //     self.branches_data
113    //         .get_or_try_init(|| -> Result<HashMap<Box<str>, Branch>, MirrorError> {
114    //             let f = fs::read(Self::BRANCHES_FILE).context(ReadFileSnafu {
115    //                 path: Self::BRANCHES_FILE,
116    //             })?;
117
118    //             let branches_data: HashMap<Box<str>, Branch> =
119    //                 serde_json::from_slice(&f).context(ParseSnafu {
120    //                     path: Self::BRANCHES_FILE,
121    //                 })?;
122
123    //             Ok(branches_data)
124    //         })
125    // }
126
127    // fn branches_data(&self) -> &HashMap<Box<str>, Branch> {
128    //     self.branches_data.get().unwrap()
129    // }
130
131    // fn try_comps(&self) -> Result<&HashMap<Box<str>, Box<str>>, MirrorError> {
132    //     self.components_data.get_or_try_init(
133    //         || -> Result<HashMap<Box<str>, Box<str>>, MirrorError> {
134    //             let f = fs::read(Self::COMPS_FILE).context(ReadFileSnafu {
135    //                 path: Self::COMPS_FILE,
136    //             })?;
137
138    //             let comps: HashMap<Box<str>, Box<str>> =
139    //                 serde_json::from_slice(&f).context(ParseSnafu {
140    //                     path: Self::COMPS_FILE,
141    //                 })?;
142
143    //             Ok(comps)
144    //         },
145    //     )
146    // }
147
148    // fn comps(&self) -> &HashMap<Box<str>, Box<str>> {
149    //     self.components_data.get().unwrap()
150    // }
151
152    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}