Skip to content

Commit cfc8307

Browse files
committed
Enhance client-client snapshot download to accept existing dir as target
The directory still needs to be empty.
1 parent 57ab7b1 commit cfc8307

File tree

3 files changed

+169
-56
lines changed

3 files changed

+169
-56
lines changed

mithril-client-cli/src/commands/cardano_db/download.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ impl CardanoDbDownloadCommand {
135135
cardano_db: &Snapshot,
136136
) -> MithrilResult<()> {
137137
progress_printer.report_step(step_number, "Checking local disk info…")?;
138+
139+
CardanoDbUnpacker::ensure_dir_exist(db_dir)?;
138140
if let Err(e) = CardanoDbUnpacker::check_prerequisites(
139141
db_dir,
140142
cardano_db.size,
@@ -144,13 +146,6 @@ impl CardanoDbDownloadCommand {
144146
.report_step(step_number, &CardanoDbUtils::check_disk_space_error(e)?)?;
145147
}
146148

147-
std::fs::create_dir_all(db_dir).with_context(|| {
148-
format!(
149-
"Download: could not create target directory '{}'.",
150-
db_dir.display()
151-
)
152-
})?;
153-
154149
Ok(())
155150
}
156151

mithril-client-cli/src/utils/cardano_db.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ mod test {
6666

6767
#[test]
6868
fn check_disk_space_error_should_return_error_if_error_is_not_error_not_enough_space() {
69-
let error = CardanoDbUnpackerError::UnpackDirectoryAlreadyExists(PathBuf::new());
69+
let error = CardanoDbUnpackerError::UnpackDirectoryNotEmpty(PathBuf::new());
7070

7171
let error = CardanoDbUtils::check_disk_space_error(anyhow!(error))
7272
.expect_err("check_disk_space_error should fail");
7373

7474
assert!(
7575
matches!(
7676
error.downcast_ref::<CardanoDbUnpackerError>(),
77-
Some(CardanoDbUnpackerError::UnpackDirectoryAlreadyExists(_))
77+
Some(CardanoDbUnpackerError::UnpackDirectoryNotEmpty(_))
7878
),
7979
"Unexpected error: {:?}",
8080
error
Lines changed: 165 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
use human_bytes::human_bytes;
1+
use std::ops::Not;
22
use std::{
3-
fs::{create_dir_all, remove_dir},
3+
fs,
44
path::{Path, PathBuf},
55
};
6+
7+
use anyhow::Context;
8+
use human_bytes::human_bytes;
69
use thiserror::Error;
710

811
use mithril_client::{common::CompressionAlgorithm, MithrilError, MithrilResult};
@@ -29,37 +32,83 @@ pub enum CardanoDbUnpackerError {
2932
archive_size: f64,
3033
},
3134

32-
/// The directory where the files from cardano db are expanded already exists.
33-
/// An error is raised because it lets the user a chance to preserve a
34-
/// previous work.
35-
#[error("Unpack directory '{0}' already exists, please move or delete it.")]
36-
UnpackDirectoryAlreadyExists(PathBuf),
35+
/// The directory where the files from cardano db are expanded is not empty.
36+
/// An error is raised to let the user handle what it wants to do with those
37+
/// files.
38+
#[error("Unpack directory '{0}' is not empty, please clean up it's content.")]
39+
UnpackDirectoryNotEmpty(PathBuf),
3740

3841
/// Cannot write in the given directory.
3942
#[error("Unpack directory '{0}' is not writable, please check own or parents' permissions and ownership.")]
4043
UnpackDirectoryIsNotWritable(PathBuf, #[source] MithrilError),
4144
}
4245

4346
impl CardanoDbUnpacker {
47+
/// Ensure that the given path exist, create it otherwise
48+
pub fn ensure_dir_exist(pathdir: &Path) -> MithrilResult<()> {
49+
if !pathdir.exists() {
50+
fs::create_dir_all(pathdir).map_err(|e| {
51+
CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(pathdir.to_owned(), e.into())
52+
})?;
53+
}
54+
55+
Ok(())
56+
}
57+
4458
/// Check all prerequisites are met before starting to download and unpack
4559
/// big cardano db archive.
4660
pub fn check_prerequisites(
4761
pathdir: &Path,
4862
size: u64,
4963
compression_algorithm: CompressionAlgorithm,
5064
) -> MithrilResult<()> {
51-
if pathdir.exists() {
52-
return Err(
53-
CardanoDbUnpackerError::UnpackDirectoryAlreadyExists(pathdir.to_owned()).into(),
54-
);
65+
Self::check_path_is_dir_and_writable(pathdir)?;
66+
Self::check_dir_writable(pathdir)?;
67+
Self::check_disk_space(pathdir, size, compression_algorithm)
68+
}
69+
70+
fn check_path_is_dir_and_writable(pathdir: &Path) -> MithrilResult<()> {
71+
if pathdir.is_dir().not() {
72+
anyhow::bail!("Given path is not a directory: {}", pathdir.display());
73+
}
74+
75+
if fs::read_dir(pathdir)
76+
.with_context(|| {
77+
format!(
78+
"Could not list directory `{}` to check if it's empty",
79+
pathdir.display()
80+
)
81+
})?
82+
.next()
83+
.is_some()
84+
{
85+
return Err(CardanoDbUnpackerError::UnpackDirectoryNotEmpty(pathdir.to_owned()).into());
5586
}
56-
create_dir_all(pathdir).map_err(|e| {
87+
88+
Ok(())
89+
}
90+
91+
fn check_dir_writable(pathdir: &Path) -> MithrilResult<()> {
92+
// Check if the directory is writable by creating a temporary file
93+
let temp_file_path = pathdir.join("temp_file");
94+
fs::File::create(&temp_file_path).map_err(|e| {
95+
CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(pathdir.to_owned(), e.into())
96+
})?;
97+
98+
// Delete the temporary file
99+
fs::remove_file(temp_file_path).map_err(|e| {
57100
CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(pathdir.to_owned(), e.into())
58101
})?;
59-
let free_space = fs2::available_space(pathdir)? as f64;
60-
// `remove_dir` doesn't remove intermediate directories that could have been created by `create_dir_all`
61-
remove_dir(pathdir)?;
62102

103+
Ok(())
104+
}
105+
106+
fn check_disk_space(
107+
pathdir: &Path,
108+
size: u64,
109+
compression_algorithm: CompressionAlgorithm,
110+
) -> MithrilResult<()> {
111+
let free_space = fs2::available_space(pathdir)? as f64;
63112
if free_space < compression_algorithm.free_space_snapshot_ratio() * size as f64 {
64113
return Err(CardanoDbUnpackerError::NotEnoughSpace {
65114
left_space: free_space,
@@ -68,79 +117,90 @@ impl CardanoDbUnpacker {
68117
}
69118
.into());
70119
}
71-
72120
Ok(())
73121
}
74122
}
75123

76124
#[cfg(test)]
77125
mod test {
78-
use super::*;
79126
use mithril_common::test_utils::TempDir;
80127

128+
use super::*;
129+
81130
fn create_temporary_empty_directory(name: &str) -> PathBuf {
82-
TempDir::create("client-cli", name)
131+
TempDir::create("client-cli-unpacker", name)
83132
}
84133

85134
#[test]
86-
fn should_return_ok() {
87-
let pathdir = create_temporary_empty_directory("return_ok").join("target_directory");
135+
fn create_directory_if_it_doesnt_exist() {
136+
let pathdir =
137+
create_temporary_empty_directory("directory_does_not_exist").join("target_directory");
88138

89-
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
90-
.expect("check_prerequisites should not fail");
139+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).expect("ensure_dir_exist should not fail");
140+
141+
assert!(pathdir.exists());
91142
}
92143

93144
#[test]
94-
fn should_return_error_if_unpack_directory_already_exists() {
95-
let pathdir = create_temporary_empty_directory("existing_directory");
145+
fn return_error_if_path_is_a_file() {
146+
let pathdir =
147+
create_temporary_empty_directory("fail_if_pathdir_is_file").join("target_directory");
148+
fs::File::create(&pathdir).unwrap();
96149

97-
let error =
98-
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
99-
.expect_err("check_prerequisites should fail");
150+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).unwrap();
151+
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
152+
.expect_err("check_prerequisites should fail");
153+
}
100154

101-
assert!(
102-
matches!(
103-
error.downcast_ref::<CardanoDbUnpackerError>(),
104-
Some(CardanoDbUnpackerError::UnpackDirectoryAlreadyExists(_))
105-
),
106-
"Unexpected error: {:?}",
107-
error
108-
);
155+
#[test]
156+
fn return_ok_if_unpack_directory_does_not_exist() {
157+
let pathdir =
158+
create_temporary_empty_directory("directory_does_not_exist").join("target_directory");
159+
160+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).unwrap();
161+
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
162+
.expect("check_prerequisites should not fail");
109163
}
110164

111-
// This test is not run on Windows because `set_readonly` is not working on Windows 7+
112-
// https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly
113-
#[cfg(not(target_os = "windows"))]
114165
#[test]
115-
fn should_return_error_if_directory_could_not_be_created() {
116-
let pathdir = create_temporary_empty_directory("read_only_directory");
166+
fn return_ok_if_unpack_directory_exist_and_empty() {
167+
let pathdir =
168+
create_temporary_empty_directory("existing_directory").join("target_directory");
169+
fs::create_dir_all(&pathdir).unwrap();
117170

118-
let mut perms = std::fs::metadata(&pathdir).unwrap().permissions();
119-
perms.set_readonly(true);
120-
std::fs::set_permissions(&pathdir, perms).unwrap();
171+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).unwrap();
172+
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
173+
.expect("check_prerequisites should not fail");
174+
}
121175

122-
let targetdir = pathdir.join("target_directory");
176+
#[test]
177+
fn return_error_if_unpack_directory_exists_and_not_empty() {
178+
let pathdir = create_temporary_empty_directory("existing_directory_not_empty");
179+
fs::create_dir_all(&pathdir).unwrap();
180+
fs::File::create(pathdir.join("file.txt")).unwrap();
123181

182+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).unwrap();
124183
let error =
125-
CardanoDbUnpacker::check_prerequisites(&targetdir, 12, CompressionAlgorithm::default())
184+
CardanoDbUnpacker::check_prerequisites(&pathdir, 12, CompressionAlgorithm::default())
126185
.expect_err("check_prerequisites should fail");
127186

128187
assert!(
129188
matches!(
130189
error.downcast_ref::<CardanoDbUnpackerError>(),
131-
Some(CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(_, _))
190+
Some(CardanoDbUnpackerError::UnpackDirectoryNotEmpty(_))
132191
),
133192
"Unexpected error: {:?}",
134193
error
135194
);
136195
}
137196

138197
#[test]
139-
fn should_return_error_if_not_enough_available_space() {
198+
fn return_error_if_not_enough_available_space() {
140199
let pathdir =
141200
create_temporary_empty_directory("enough_available_space").join("target_directory");
142201
let archive_size = u64::MAX;
143202

203+
CardanoDbUnpacker::ensure_dir_exist(&pathdir).unwrap();
144204
let error = CardanoDbUnpacker::check_prerequisites(
145205
&pathdir,
146206
archive_size,
@@ -161,4 +221,62 @@ mod test {
161221
error
162222
);
163223
}
224+
225+
// Those test are not on Windows because `set_readonly` is ignored for directories on Windows 7+
226+
// https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly
227+
#[cfg(not(target_os = "windows"))]
228+
mod unix_only {
229+
use super::*;
230+
231+
fn make_readonly(path: &Path) {
232+
let mut perms = fs::metadata(path).unwrap().permissions();
233+
perms.set_readonly(true);
234+
fs::set_permissions(path, perms).unwrap();
235+
}
236+
237+
#[test]
238+
fn return_error_if_directory_could_not_be_created() {
239+
let pathdir = create_temporary_empty_directory("read_only_directory");
240+
let targetdir = pathdir.join("target_directory");
241+
make_readonly(&pathdir);
242+
243+
let error = CardanoDbUnpacker::ensure_dir_exist(&targetdir)
244+
.expect_err("ensure_dir_exist should fail");
245+
246+
assert!(
247+
matches!(
248+
error.downcast_ref::<CardanoDbUnpackerError>(),
249+
Some(CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(_, _))
250+
),
251+
"Unexpected error: {:?}",
252+
error
253+
);
254+
}
255+
256+
// This test is not run on Windows because `set_readonly` is ignored for directory on Windows 7+
257+
// https://doc.rust-lang.org/std/fs/struct.Permissions.html#method.set_readonly
258+
#[test]
259+
fn return_error_if_existing_directory_is_not_writable() {
260+
let pathdir =
261+
create_temporary_empty_directory("existing_directory_not_writable").join("db");
262+
fs::create_dir(&pathdir).unwrap();
263+
make_readonly(&pathdir);
264+
265+
let error = CardanoDbUnpacker::check_prerequisites(
266+
&pathdir,
267+
12,
268+
CompressionAlgorithm::default(),
269+
)
270+
.expect_err("check_prerequisites should fail");
271+
272+
assert!(
273+
matches!(
274+
error.downcast_ref::<CardanoDbUnpackerError>(),
275+
Some(CardanoDbUnpackerError::UnpackDirectoryIsNotWritable(_, _))
276+
),
277+
"Unexpected error: {:?}",
278+
error
279+
);
280+
}
281+
}
164282
}

0 commit comments

Comments
 (0)