1
- use human_bytes :: human_bytes ;
1
+ use std :: ops :: Not ;
2
2
use std:: {
3
- fs:: { create_dir_all , remove_dir } ,
3
+ fs,
4
4
path:: { Path , PathBuf } ,
5
5
} ;
6
+
7
+ use anyhow:: Context ;
8
+ use human_bytes:: human_bytes;
6
9
use thiserror:: Error ;
7
10
8
11
use mithril_client:: { common:: CompressionAlgorithm , MithrilError , MithrilResult } ;
@@ -29,37 +32,83 @@ pub enum CardanoDbUnpackerError {
29
32
archive_size : f64 ,
30
33
} ,
31
34
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 ) ,
37
40
38
41
/// Cannot write in the given directory.
39
42
#[ error( "Unpack directory '{0}' is not writable, please check own or parents' permissions and ownership." ) ]
40
43
UnpackDirectoryIsNotWritable ( PathBuf , #[ source] MithrilError ) ,
41
44
}
42
45
43
46
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
+
44
58
/// Check all prerequisites are met before starting to download and unpack
45
59
/// big cardano db archive.
46
60
pub fn check_prerequisites (
47
61
pathdir : & Path ,
48
62
size : u64 ,
49
63
compression_algorithm : CompressionAlgorithm ,
50
64
) -> 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 ( ) ) ;
55
86
}
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| {
57
100
CardanoDbUnpackerError :: UnpackDirectoryIsNotWritable ( pathdir. to_owned ( ) , e. into ( ) )
58
101
} ) ?;
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) ?;
62
102
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 ;
63
112
if free_space < compression_algorithm. free_space_snapshot_ratio ( ) * size as f64 {
64
113
return Err ( CardanoDbUnpackerError :: NotEnoughSpace {
65
114
left_space : free_space,
@@ -68,79 +117,90 @@ impl CardanoDbUnpacker {
68
117
}
69
118
. into ( ) ) ;
70
119
}
71
-
72
120
Ok ( ( ) )
73
121
}
74
122
}
75
123
76
124
#[ cfg( test) ]
77
125
mod test {
78
- use super :: * ;
79
126
use mithril_common:: test_utils:: TempDir ;
80
127
128
+ use super :: * ;
129
+
81
130
fn create_temporary_empty_directory ( name : & str ) -> PathBuf {
82
- TempDir :: create ( "client-cli" , name)
131
+ TempDir :: create ( "client-cli-unpacker " , name)
83
132
}
84
133
85
134
#[ 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" ) ;
88
138
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( ) ) ;
91
142
}
92
143
93
144
#[ 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 ( ) ;
96
149
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
+ }
100
154
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" ) ;
109
163
}
110
164
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" ) ) ]
114
165
#[ 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 ( ) ;
117
170
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
+ }
121
175
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 ( ) ;
123
181
182
+ CardanoDbUnpacker :: ensure_dir_exist ( & pathdir) . unwrap ( ) ;
124
183
let error =
125
- CardanoDbUnpacker :: check_prerequisites ( & targetdir , 12 , CompressionAlgorithm :: default ( ) )
184
+ CardanoDbUnpacker :: check_prerequisites ( & pathdir , 12 , CompressionAlgorithm :: default ( ) )
126
185
. expect_err ( "check_prerequisites should fail" ) ;
127
186
128
187
assert ! (
129
188
matches!(
130
189
error. downcast_ref:: <CardanoDbUnpackerError >( ) ,
131
- Some ( CardanoDbUnpackerError :: UnpackDirectoryIsNotWritable ( _ , _) )
190
+ Some ( CardanoDbUnpackerError :: UnpackDirectoryNotEmpty ( _) )
132
191
) ,
133
192
"Unexpected error: {:?}" ,
134
193
error
135
194
) ;
136
195
}
137
196
138
197
#[ test]
139
- fn should_return_error_if_not_enough_available_space ( ) {
198
+ fn return_error_if_not_enough_available_space ( ) {
140
199
let pathdir =
141
200
create_temporary_empty_directory ( "enough_available_space" ) . join ( "target_directory" ) ;
142
201
let archive_size = u64:: MAX ;
143
202
203
+ CardanoDbUnpacker :: ensure_dir_exist ( & pathdir) . unwrap ( ) ;
144
204
let error = CardanoDbUnpacker :: check_prerequisites (
145
205
& pathdir,
146
206
archive_size,
@@ -161,4 +221,62 @@ mod test {
161
221
error
162
222
) ;
163
223
}
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
+ }
164
282
}
0 commit comments