ckb_resource/
lib.rs

1//! Bundles resources in the ckb binary.
2//!
3//! This crate bundles the files ckb.toml, ckb-miner.toml, default.db-options, and all files in the
4//! directory `specs` in the binary.
5//!
6//! The bundled files can be read via `Resource::Bundled`, for example:
7//!
8//! ```
9//! // Read bundled ckb.toml
10//! use ckb_resource::{Resource, CKB_CONFIG_FILE_NAME};
11//!
12//! let ckb_toml_bytes = Resource::bundled(CKB_CONFIG_FILE_NAME.to_string()).get().unwrap();
13//! println!("ckb.toml\n{}", String::from_utf8(ckb_toml_bytes.to_vec()).unwrap());
14//! ```
15//!
16//! These bundled files can be customized for different chains using spec branches.
17//! See [Template](struct.Template.html).
18
19mod template;
20
21#[cfg(test)]
22mod tests;
23
24pub use self::template::Template;
25pub use self::template::{
26    AVAILABLE_SPECS, DEFAULT_P2P_PORT, DEFAULT_RPC_PORT, DEFAULT_SPEC, TemplateContext,
27};
28pub use std::io::{Error, Result};
29
30use ckb_types::H256;
31use includedir::Files;
32use serde::{Deserialize, Serialize};
33use std::borrow::Cow;
34use std::fmt;
35use std::fs;
36use std::io::{self, BufReader, Cursor, Read};
37use std::path::{Path, PathBuf};
38
39use ckb_system_scripts::BUNDLED_CELL;
40
41mod bundled {
42    #![allow(missing_docs, clippy::unreadable_literal)]
43    include!(concat!(env!("OUT_DIR"), "/bundled.rs"));
44}
45/// Bundled resources in ckb binary.
46pub use bundled::BUNDLED;
47
48include!(concat!(env!("OUT_DIR"), "/code_hashes.rs"));
49
50/// CKB config file name.
51pub const CKB_CONFIG_FILE_NAME: &str = "ckb.toml";
52/// CKB miner config file name.
53pub const MINER_CONFIG_FILE_NAME: &str = "ckb-miner.toml";
54/// The relative spec file path for the dev chain.
55pub const SPEC_DEV_FILE_NAME: &str = "specs/dev.toml";
56/// The file name of the generated RocksDB options file.
57pub const DB_OPTIONS_FILE_NAME: &str = "default.db-options";
58
59/// Represents a resource, which is either bundled in the CKB binary or resident in the local file
60/// system.
61#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
62#[serde(untagged)]
63pub enum Resource {
64    /// A resource that bundled in the CKB binary.
65    Bundled {
66        /// The identifier of the bundled resource.
67        bundled: String,
68    },
69    /// A resource that resides in the local file system.
70    FileSystem {
71        /// The file path to the resource.
72        file: PathBuf,
73    },
74    /// A resource that init by user custom
75    Raw {
76        /// raw data
77        raw: String,
78    },
79}
80
81impl fmt::Display for Resource {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        match self {
84            Resource::Bundled { bundled } => write!(f, "Bundled({bundled})"),
85            Resource::FileSystem { file } => write!(f, "FileSystem({})", file.display()),
86            Resource::Raw { raw } => write!(f, "Raw({})", raw),
87        }
88    }
89}
90
91impl Resource {
92    /// Creates a reference to the bundled resource.
93    pub fn bundled(bundled: String) -> Resource {
94        Resource::Bundled { bundled }
95    }
96
97    /// Creates a reference to the resource resident in the file system.
98    pub fn file_system(file: PathBuf) -> Resource {
99        Resource::FileSystem { file }
100    }
101
102    /// Creates a reference to the resource resident in the memory.
103    pub fn raw(raw: String) -> Resource {
104        Resource::Raw { raw }
105    }
106
107    /// Creates the CKB config file resource from the file system.
108    ///
109    /// It searches the file name `CKB_CONFIG_FILE_NAME` in the directory `root_dir`.
110    pub fn ckb_config<P: AsRef<Path>>(root_dir: P) -> Resource {
111        Resource::file_system(root_dir.as_ref().join(CKB_CONFIG_FILE_NAME))
112    }
113
114    /// Creates the CKB miner config file resource from the file system.
115    ///
116    /// It searches the file name `MINER_CONFIG_FILE_NAME` in the directory `root_dir`.
117    pub fn miner_config<P: AsRef<Path>>(root_dir: P) -> Resource {
118        Resource::file_system(root_dir.as_ref().join(MINER_CONFIG_FILE_NAME))
119    }
120
121    /// Creates the RocksDB options file resource from the file system.
122    ///
123    /// It searches the file name `DB_OPTIONS_FILE_NAME` in the directory `root_dir`.
124    pub fn db_options<P: AsRef<Path>>(root_dir: P) -> Resource {
125        Resource::file_system(root_dir.as_ref().join(DB_OPTIONS_FILE_NAME))
126    }
127
128    /// Creates the bundled CKB config file resource.
129    pub fn bundled_ckb_config() -> Resource {
130        Resource::bundled(CKB_CONFIG_FILE_NAME.to_string())
131    }
132
133    /// Creates the bundled CKB miner config file resource.
134    pub fn bundled_miner_config() -> Resource {
135        Resource::bundled(MINER_CONFIG_FILE_NAME.to_string())
136    }
137
138    /// Creates the bundled RocksDB options file resource.
139    pub fn bundled_db_options() -> Resource {
140        Resource::bundled(DB_OPTIONS_FILE_NAME.to_string())
141    }
142
143    /// Checks whether any of the bundled resource has been exported in the specified directory.
144    ///
145    /// This can be used to avoid overwriting to export all the bundled resources to the specified
146    /// directory.
147    pub fn exported_in<P: AsRef<Path>>(root_dir: P) -> bool {
148        BUNDLED
149            .file_names()
150            .chain(BUNDLED_CELL.file_names())
151            .any(|name| join_bundled_key(root_dir.as_ref().to_path_buf(), name).exists())
152    }
153
154    /// Returns `true` if this is a bundled resource.
155    pub fn is_bundled(&self) -> bool {
156        matches!(self, Resource::Bundled { .. })
157    }
158
159    /// Returns `true` if the resource exists.
160    ///
161    /// The bundled resource exists only when the identifier is included in the bundle.
162    ///
163    /// The file system resource exists only when the file exists.
164    pub fn exists(&self) -> bool {
165        match self {
166            Resource::Bundled { bundled } => {
167                SourceFiles::new(&BUNDLED_CELL, &BUNDLED).is_available(bundled)
168            }
169            Resource::FileSystem { file } => file.exists(),
170            Resource::Raw { .. } => true,
171        }
172    }
173
174    /// The parent directory of the resource.
175    ///
176    /// It always returns `None` on bundled resource.
177    pub fn parent(&self) -> Option<&Path> {
178        match self {
179            Resource::FileSystem { file } => file.parent(),
180            _ => None,
181        }
182    }
183
184    /// Modifies the file system resource to ensure the path is absolute.
185    ///
186    /// If the path is relative, expand the path relative to the directory `base`.
187    pub fn absolutize<P: AsRef<Path>>(&mut self, base: P) {
188        if let Resource::FileSystem { file: path } = self {
189            if path.is_relative() {
190                *path = base.as_ref().join(&path)
191            }
192        }
193    }
194
195    /// Gets resource content.
196    pub fn get(&self) -> Result<Cow<'static, [u8]>> {
197        match self {
198            Resource::Bundled { bundled } => SourceFiles::new(&BUNDLED_CELL, &BUNDLED).get(bundled),
199            Resource::FileSystem { file } => Ok(Cow::Owned(fs::read(file)?)),
200            Resource::Raw { raw } => Ok(Cow::Owned(raw.to_owned().into_bytes())),
201        }
202    }
203
204    /// Gets resource content via an input stream.
205    pub fn read(&self) -> Result<Box<dyn Read>> {
206        match self {
207            Resource::Bundled { bundled } => {
208                SourceFiles::new(&BUNDLED_CELL, &BUNDLED).read(bundled)
209            }
210            Resource::FileSystem { file } => Ok(Box::new(BufReader::new(fs::File::open(file)?))),
211            Resource::Raw { raw } => Ok(Box::new(Cursor::new(raw.to_owned().into_bytes()))),
212        }
213    }
214
215    /// Exports a bundled resource.
216    ///
217    /// This function returns `Ok` immediately when invoked on a file system resource.
218    ///
219    /// The file is exported to the path by combining `root_dir` and the resource identifier.
220    ///
221    /// These bundled files can be customized for different chains using spec branches.
222    /// See [Template](struct.Template.html).
223    pub fn export<P: AsRef<Path>>(&self, context: &TemplateContext<'_>, root_dir: P) -> Result<()> {
224        let key = match self {
225            Resource::Bundled { bundled } => bundled,
226            _ => return Ok(()),
227        };
228        let target = join_bundled_key(root_dir.as_ref().to_path_buf(), key);
229        let template = Template::new(from_utf8(self.get()?)?);
230        if let Some(dir) = target.parent() {
231            fs::create_dir_all(dir)?;
232        }
233        let mut f = fs::File::create(&target)?;
234        template.render_to(&mut f, context)?;
235        Ok(())
236    }
237}
238
239struct SourceFiles<'a> {
240    system_cells: &'a Files,
241    config: &'a Files,
242}
243
244impl<'a> SourceFiles<'a> {
245    fn new(system_cells: &'a Files, config: &'a Files) -> Self {
246        SourceFiles {
247            system_cells,
248            config,
249        }
250    }
251
252    fn get(&self, path: &str) -> Result<Cow<'static, [u8]>> {
253        self.config
254            .get(path)
255            .or_else(|_| self.system_cells.get(path))
256    }
257
258    fn read(&self, path: &str) -> Result<Box<dyn Read>> {
259        self.config
260            .read(path)
261            .or_else(|_| self.system_cells.read(path))
262    }
263
264    fn is_available(&self, path: &str) -> bool {
265        self.config.is_available(path) || self.system_cells.is_available(path)
266    }
267}
268
269fn from_utf8(data: Cow<[u8]>) -> Result<String> {
270    String::from_utf8(data.to_vec()).map_err(|err| Error::new(io::ErrorKind::Other, err))
271}
272
273fn join_bundled_key(mut root_dir: PathBuf, key: &str) -> PathBuf {
274    key.split('/')
275        .for_each(|component| root_dir.push(component));
276    root_dir
277}