Skip to main content

conduit_cli/core/engine/
store.rs

1use sha1::Sha1;
2use sha2::{Digest, Sha256, Sha512};
3use std::path::{Path, PathBuf};
4use tokio::fs;
5use tokio::io::AsyncReadExt;
6
7use crate::core::schemas::lock::HashKind;
8use crate::errors::ConduitResult;
9
10#[derive(Clone, Debug)]
11pub struct Store {
12    root: PathBuf,
13}
14
15impl Store {
16    pub fn new(root: PathBuf) -> Self {
17        Self { root }
18    }
19
20    pub fn object_path(&self, hash: &str, kind: HashKind) -> PathBuf {
21        let prefix = match kind {
22            HashKind::Sha1 => "sha1",
23            HashKind::Sha256 => "sha256",
24            HashKind::Sha512 => "sha512",
25        };
26        self.root
27            .join("objects")
28            .join(prefix)
29            .join(&hash[..2])
30            .join(hash)
31    }
32
33    pub async fn calculate_hash<P: AsRef<Path>>(
34        &self,
35        path: P,
36        kind: HashKind,
37    ) -> ConduitResult<String> {
38        let mut file = fs::File::open(path).await?;
39        let mut buffer = vec![0u8; 65536].into_boxed_slice();
40
41        match kind {
42            HashKind::Sha1 => {
43                let mut hasher = Sha1::new();
44                loop {
45                    let n = file.read(&mut buffer).await?;
46                    if n == 0 {
47                        break;
48                    }
49                    hasher.update(&buffer[..n]);
50                }
51                Ok(format!("{:x}", hasher.finalize()))
52            }
53            HashKind::Sha256 => {
54                let mut hasher = Sha256::new();
55                loop {
56                    let n = file.read(&mut buffer).await?;
57                    if n == 0 {
58                        break;
59                    }
60                    hasher.update(&buffer[..n]);
61                }
62                Ok(format!("{:x}", hasher.finalize()))
63            }
64            HashKind::Sha512 => {
65                let mut hasher = Sha512::new();
66                loop {
67                    let n = file.read(&mut buffer).await?;
68                    if n == 0 {
69                        break;
70                    }
71                    hasher.update(&buffer[..n]);
72                }
73                Ok(format!("{:x}", hasher.finalize()))
74            }
75        }
76    }
77
78    pub async fn add_file<P: AsRef<Path>>(
79        &self,
80        source: P,
81        hash: &str,
82        kind: HashKind,
83    ) -> ConduitResult<()> {
84        let target = self.object_path(hash, kind);
85
86        if target.exists() {
87            return Ok(());
88        }
89
90        if let Some(parent) = target.parent() {
91            let _ = fs::create_dir_all(parent).await;
92        }
93
94        let temp_target = target.with_extension(format!("{}.tmp", uuid::Uuid::new_v4()));
95
96        fs::copy(&source, &temp_target).await?;
97
98        if let Err(e) = fs::rename(&temp_target, &target).await {
99            let _ = fs::remove_file(&temp_target).await;
100            if !target.exists() {
101                return Err(e.into());
102            }
103        }
104
105        Ok(())
106    }
107
108    pub async fn link_object<P: AsRef<Path>>(
109        &self,
110        hash: &str,
111        kind: HashKind,
112        target: P,
113    ) -> ConduitResult<()> {
114        let source = self.object_path(hash, kind);
115
116        if let Some(parent) = target.as_ref().parent() {
117            fs::create_dir_all(parent).await?;
118        }
119
120        if target.as_ref().exists() {
121            fs::remove_file(&target).await?;
122        }
123
124        fs::hard_link(source, target).await?;
125        Ok(())
126    }
127
128    pub fn get_project_root(&self) -> PathBuf {
129        std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
130    }
131
132    pub fn get_mods_path(&self) -> PathBuf {
133        self.get_project_root().join("mods")
134    }
135
136    pub fn get_plugins_path(&self) -> PathBuf {
137        self.get_project_root().join("plugins")
138    }
139
140    pub fn get_world_path(&self) -> PathBuf {
141        self.get_project_root().join("world")
142    }
143
144    pub async fn install_to_project(
145        &self,
146        hash: &str,
147        kind: HashKind,
148        rel_path: PathBuf,
149    ) -> ConduitResult<()> {
150        let target = self.get_project_root().join(rel_path);
151        self.link_object(hash, kind, target).await
152    }
153}