mithril_client/cardano_database_client/download_unpack/
download_unpack_options.rs

1use std::ops::RangeInclusive;
2use std::path::Path;
3
4use anyhow::anyhow;
5
6use mithril_cardano_node_internal_database::{IMMUTABLE_DIR, LEDGER_DIR, VOLATILE_DIR};
7
8use crate::common::ImmutableFileNumber;
9use crate::MithrilResult;
10
11/// Options for downloading and unpacking a Cardano database
12#[derive(Debug, Copy, Clone)]
13pub struct DownloadUnpackOptions {
14    /// Allow overriding the destination directory
15    pub allow_override: bool,
16
17    /// Include ancillary files in the download
18    pub include_ancillary: bool,
19
20    /// Maximum number of parallel downloads
21    pub max_parallel_downloads: usize,
22}
23
24impl Default for DownloadUnpackOptions {
25    fn default() -> Self {
26        Self {
27            allow_override: false,
28            include_ancillary: false,
29            max_parallel_downloads: 20,
30        }
31    }
32}
33
34impl DownloadUnpackOptions {
35    /// Verify if the download options are compatible with the immutable file range.
36    pub fn verify_compatibility(
37        &self,
38        immutable_file_range: &RangeInclusive<ImmutableFileNumber>,
39        last_immutable_file_number: ImmutableFileNumber,
40    ) -> MithrilResult<()> {
41        if self.include_ancillary && !immutable_file_range.contains(&last_immutable_file_number) {
42            return Err(anyhow!(
43                "The last immutable file number {last_immutable_file_number} is outside the range: {immutable_file_range:?}"
44            ));
45        }
46
47        Ok(())
48    }
49
50    /// Verify if the target directory is writable.
51    pub fn verify_can_write_to_target_directory(&self, target_dir: &Path) -> MithrilResult<()> {
52        fn subdir_should_not_exist(
53            parent: &Path,
54            name_in_error: &str,
55            subdir: &str,
56        ) -> MithrilResult<()> {
57            if parent.join(subdir).exists() {
58                anyhow::bail!("{name_in_error} target directory already exists in: {parent:?}")
59            }
60            Ok(())
61        }
62
63        if !self.allow_override {
64            subdir_should_not_exist(target_dir, "Immutable files", IMMUTABLE_DIR)?;
65            if self.include_ancillary {
66                subdir_should_not_exist(target_dir, "Volatile", VOLATILE_DIR)?;
67                subdir_should_not_exist(target_dir, "Ledger", LEDGER_DIR)?;
68            }
69        }
70
71        Ok(())
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use crate::cardano_database_client::ImmutableFileRange;
78
79    use super::*;
80
81    mod verify_compatibility {
82        use super::*;
83
84        #[test]
85        fn succeeds_if_without_ancillary_download() {
86            let download_options = DownloadUnpackOptions {
87                include_ancillary: false,
88                ..DownloadUnpackOptions::default()
89            };
90            let immutable_file_range = ImmutableFileRange::Range(1, 10);
91            let last_immutable_file_number = 10;
92
93            download_options
94                .verify_compatibility(
95                    &immutable_file_range
96                        .to_range_inclusive(last_immutable_file_number)
97                        .unwrap(),
98                    last_immutable_file_number,
99                )
100                .unwrap();
101        }
102
103        #[test]
104        fn succeeds_if_with_ancillary_download_and_compatible_range() {
105            let download_options = DownloadUnpackOptions {
106                include_ancillary: true,
107                ..DownloadUnpackOptions::default()
108            };
109            let immutable_file_range = ImmutableFileRange::Range(7, 10);
110            let last_immutable_file_number = 10;
111
112            download_options
113                .verify_compatibility(
114                    &immutable_file_range
115                        .to_range_inclusive(last_immutable_file_number)
116                        .unwrap(),
117                    last_immutable_file_number,
118                )
119                .unwrap();
120        }
121
122        #[test]
123        fn fails_if_with_ancillary_download_and_incompatible_range() {
124            let download_options = DownloadUnpackOptions {
125                include_ancillary: true,
126                ..DownloadUnpackOptions::default()
127            };
128            let immutable_file_range = ImmutableFileRange::Range(7, 10);
129            let last_immutable_file_number = 123;
130
131            download_options
132            .verify_compatibility(
133            &immutable_file_range
134                .to_range_inclusive(last_immutable_file_number)
135                .unwrap(),
136            last_immutable_file_number,
137        )
138            .expect_err("verify_download_options_compatibility should fail as the last immutable file number is outside the range");
139        }
140    }
141
142    mod verify_can_write_to_target_directory {
143        use std::fs;
144
145        use mithril_common::temp_dir_create;
146
147        use super::*;
148
149        #[test]
150        fn always_succeeds_with_allow_overwrite() {
151            let target_dir = temp_dir_create!();
152
153            let download_options = DownloadUnpackOptions {
154                allow_override: true,
155                include_ancillary: false,
156                ..DownloadUnpackOptions::default()
157            };
158
159            download_options
160                .verify_can_write_to_target_directory(&target_dir)
161                .unwrap();
162
163            fs::create_dir_all(target_dir.join(IMMUTABLE_DIR)).unwrap();
164            fs::create_dir_all(target_dir.join(VOLATILE_DIR)).unwrap();
165            fs::create_dir_all(target_dir.join(LEDGER_DIR)).unwrap();
166            download_options
167                .verify_can_write_to_target_directory(&target_dir)
168                .unwrap();
169
170            DownloadUnpackOptions {
171                allow_override: true,
172                include_ancillary: true,
173                ..DownloadUnpackOptions::default()
174            }
175            .verify_can_write_to_target_directory(&target_dir)
176            .unwrap();
177        }
178
179        #[test]
180        fn fails_without_allow_overwrite_and_non_empty_immutable_target_dir() {
181            let target_dir = temp_dir_create!();
182            fs::create_dir_all(target_dir.join(IMMUTABLE_DIR)).unwrap();
183
184            DownloadUnpackOptions {
185                allow_override: false,
186                include_ancillary: false,
187                ..DownloadUnpackOptions::default()
188            }
189            .verify_can_write_to_target_directory(&target_dir)
190            .expect_err("verify_can_write_to_target_directory should fail");
191
192            DownloadUnpackOptions {
193                allow_override: false,
194                include_ancillary: true,
195                ..DownloadUnpackOptions::default()
196            }
197            .verify_can_write_to_target_directory(&target_dir)
198            .expect_err("verify_can_write_to_target_directory should fail");
199        }
200
201        #[test]
202        fn fails_without_allow_overwrite_and_non_empty_ledger_target_dir() {
203            let target_dir = temp_dir_create!();
204            fs::create_dir_all(target_dir.join(LEDGER_DIR)).unwrap();
205
206            DownloadUnpackOptions {
207                allow_override: false,
208                include_ancillary: true,
209                ..DownloadUnpackOptions::default()
210            }
211            .verify_can_write_to_target_directory(&target_dir)
212            .expect_err("verify_can_write_to_target_directory should fail");
213
214            DownloadUnpackOptions {
215                allow_override: false,
216                include_ancillary: false,
217                ..DownloadUnpackOptions::default()
218            }
219            .verify_can_write_to_target_directory(&target_dir)
220            .unwrap();
221        }
222
223        #[test]
224        fn fails_without_allow_overwrite_and_non_empty_volatile_target_dir() {
225            let target_dir = temp_dir_create!();
226            fs::create_dir_all(target_dir.join(VOLATILE_DIR)).unwrap();
227
228            DownloadUnpackOptions {
229                allow_override: false,
230                include_ancillary: true,
231                ..DownloadUnpackOptions::default()
232            }
233            .verify_can_write_to_target_directory(&target_dir)
234            .expect_err("verify_can_write_to_target_directory should fail");
235
236            DownloadUnpackOptions {
237                allow_override: false,
238                include_ancillary: false,
239                ..DownloadUnpackOptions::default()
240            }
241            .verify_can_write_to_target_directory(&target_dir)
242            .unwrap();
243        }
244    }
245}