conduit_cli/core/
verify.rs1use 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}