Skip to main content

ek_station/
pickfile.rs

1//! pick 清单(Launcher↔bin 的共享地面;S3 自 BD-UI-15 从 bin/ek-ui 抽世统一)。
2//!
3//! 格式:一行一工厂名;`#` 起注释;空行忽略;行内空白拒(错行号指出);
4//! 全空 → Empty;重名/未知名延迟到装配期 boot 硬报错(BD-U4)。
5//! 生态纪律:自研 crate 之间以装版引用 —— 本模块发布后,ek-ui 亦走此地面
6//! (其 `pick` 门面即本模块的薄转发)。
7
8use std::path::{Path, PathBuf};
9
10/// 清单文件名前缀。
11pub const PICK_PREFIX: &str = "ek-pick-";
12/// 清单文件名后缀。
13pub const PICK_SUFFIX: &str = ".list";
14
15/// 清单读写错(业务枚举;launcher/text bin 面可读可断言)。
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum PickListErr {
18    /// 空清单(启动至少一个插件)。
19    Empty,
20    /// 行内含空白/前缀不然(带行号与内容)。
21    BadLine(usize, String),
22    /// IO 叙文。
23    Io(String),
24}
25
26impl std::fmt::Display for PickListErr {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::Empty => write!(f, "pick list empty (select at least one plugin)"),
30            Self::BadLine(n, line) => {
31                write!(f, "pick list line {n} has unexpected whitespace: {line:?}")
32            }
33            Self::Io(e) => write!(f, "pick list io: {e}"),
34        }
35    }
36}
37
38impl std::error::Error for PickListErr {}
39
40/// 唯一文件名(进程 id + 纳秒;同目录并发不撞)。
41fn unique_name() -> String {
42    let nanos = std::time::SystemTime::now()
43        .duration_since(std::time::UNIX_EPOCH)
44        .map(|d| d.as_nanos() as u64)
45        .unwrap_or(0);
46    format!("{PICK_PREFIX}{}{}{PICK_SUFFIX}", std::process::id(), nanos)
47}
48
49/// 读清单(bin `--from` 面)。
50pub fn read(path: &Path) -> Result<Vec<String>, PickListErr> {
51    let text = std::fs::read_to_string(path).map_err(|e| PickListErr::Io(e.to_string()))?;
52    // BOM 剥离(Notepad 系 UTF-8 的编码叙文,不是内容;不剥会把首行注释打成 BadLine)。
53    let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
54    let mut names = Vec::new();
55    for (i, raw) in text.lines().enumerate() {
56        let line = raw.trim();
57        if line.is_empty() || line.starts_with('#') {
58            continue;
59        }
60        if line.chars().any(|c| c.is_whitespace()) {
61            return Err(PickListErr::BadLine(i + 1, raw.to_string()));
62        }
63        names.push(line.to_string());
64    }
65    if names.is_empty() {
66        return Err(PickListErr::Empty);
67    }
68    Ok(names)
69}
70
71/// 写清单到指定目录(launcher 面),返回文件路径。
72pub fn write_in(dir: &Path, names: &[&str]) -> Result<PathBuf, PickListErr> {
73    if names.is_empty() {
74        return Err(PickListErr::Empty);
75    }
76    for (i, n) in names.iter().enumerate() {
77        if n.chars().any(|c| c.is_whitespace()) || n.is_empty() || n.starts_with('#') {
78            return Err(PickListErr::BadLine(i + 1, (*n).to_string()));
79        }
80    }
81    std::fs::create_dir_all(dir).map_err(|e| PickListErr::Io(e.to_string()))?;
82    let path = dir.join(unique_name());
83    let mut text = String::from("# ek launch pick\n");
84    for n in names {
85        text.push_str(n);
86        text.push('\n');
87    }
88    std::fs::write(&path, text).map_err(|e| PickListErr::Io(e.to_string()))?;
89    Ok(path)
90}