Skip to main content

conduit_cli/core/
verify.rs

1use crate::core::error::CoreResult;
2use crate::core::io::load_lock;
3use crate::core::paths::CorePaths;
4use sha1::{Digest as Sha1Digest, Sha1};
5use sha2::Sha256;
6use std::fs;
7use std::io::Read;
8use std::path::Path;
9
10#[derive(Debug, Clone, Copy)]
11pub enum VerifyScope {
12    Modrinth,
13    Local,
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct VerifyReport {
18    pub ok: usize,
19    pub mismatch: Vec<VerifyMismatch>,
20    pub missing: Vec<VerifyMissing>,
21}
22
23#[derive(Debug, Clone)]
24pub struct VerifyMismatch {
25    pub key: String,
26    pub filename: String,
27    pub expected: String,
28    pub actual: String,
29}
30
31#[derive(Debug, Clone)]
32pub struct VerifyMissing {
33    pub key: String,
34    pub filename: String,
35}
36
37pub fn verify_project(paths: &CorePaths, scope: VerifyScope) -> CoreResult<VerifyReport> {
38    let lock = load_lock(paths)?;
39
40    let mut report = VerifyReport::default();
41
42    for (key, m) in &lock.locked_mods {
43        let is_local = m.url == "local";
44        match scope {
45            VerifyScope::Modrinth if is_local => continue,
46            VerifyScope::Local if !is_local => continue,
47            _ => {}
48        }
49
50        let mod_path = paths.mods_dir().join(&m.filename);
51        if !mod_path.exists() {
52            report.missing.push(VerifyMissing {
53                key: key.clone(),
54                filename: m.filename.clone(),
55            });
56            continue;
57        }
58
59        let actual = if is_local {
60            sha256_file(&mod_path)?
61        } else {
62            sha1_file(&mod_path)?
63        };
64
65        if actual.eq_ignore_ascii_case(&m.hash) {
66            report.ok += 1;
67        } else {
68            report.mismatch.push(VerifyMismatch {
69                key: key.clone(),
70                filename: m.filename.clone(),
71                expected: m.hash.clone(),
72                actual,
73            });
74        }
75    }
76
77    Ok(report)
78}
79
80fn sha1_file(path: &Path) -> CoreResult<String> {
81    let mut file = fs::File::open(path)?;
82    let mut hasher = Sha1::new();
83    let mut buf = [0u8; 8192];
84    loop {
85        let n = file.read(&mut buf)?;
86        if n == 0 {
87            break;
88        }
89        hasher.update(&buf[..n]);
90    }
91    Ok(format!("{:x}", hasher.finalize()))
92}
93
94fn sha256_file(path: &Path) -> CoreResult<String> {
95    let mut file = fs::File::open(path)?;
96    let mut hasher = Sha256::new();
97    let mut buf = [0u8; 8192];
98    loop {
99        let n = file.read(&mut buf)?;
100        if n == 0 {
101            break;
102        }
103        hasher.update(&buf[..n]);
104    }
105    Ok(format!("{:x}", hasher.finalize()))
106}