@@ -45,6 +45,19 @@ pub fn write_file(content: &[u8], file_path: &Path, context: &str) -> Result<(),
4545 "Failed to write file [{context}, path:{}]" ,
4646 file_path. display( )
4747 ) ;
48+ if file_path. exists ( ) {
49+ match fs:: remove_file ( file_path) {
50+ Ok ( _) => {
51+ info ! ( "File deleted [path:{}]" , file_path. display( ) ) ;
52+ }
53+ Err ( e) => {
54+ error ! (
55+ "Failed to delete file [path:{}, error:{e}]" ,
56+ file_path. display( )
57+ ) ;
58+ }
59+ }
60+ }
4861 Err ( e)
4962 }
5063 }
@@ -54,7 +67,7 @@ pub fn write_file(content: &[u8], file_path: &Path, context: &str) -> Result<(),
5467///
5568/// If the download or any file operation fails, the function logs an appropriate error
5669/// and returns `None`. It also ensures the parent directory exists, creating it if necessary.
57- /// If the directory is newly created but the file write fails , it is cleaned up (deleted).
70+ /// If the file write fails and a partial file was created , it will be cleaned up (deleted).
5871///
5972/// # Arguments
6073///
@@ -115,23 +128,9 @@ pub fn download_file(url: &str, parent_dir: &str, filename: &str) -> Option<Path
115128
116129 let file_path = parent_path. join ( filename) ;
117130
118- if write_file ( & bytes, & file_path, & format ! ( "url:{url}" ) ) . is_ok ( ) {
119- Some ( file_path)
120- } else {
121- if !parent_existed {
122- match fs:: remove_dir_all ( parent_path) {
123- Ok ( _) => {
124- info ! ( "Folder deleted [path:{}]" , parent_path. display( ) ) ;
125- }
126- Err ( _) => {
127- error ! (
128- "Folder does not exist, nothing to delete [path:{}]" ,
129- parent_path. display( )
130- ) ;
131- }
132- }
133- }
134- None
131+ match write_file ( & bytes, & file_path, & format ! ( "url:{url}" ) ) {
132+ Ok ( _) => Some ( file_path) ,
133+ Err ( _) => None ,
135134 }
136135}
137136
@@ -312,6 +311,55 @@ mod tests {
312311 assert ! ( result. is_none( ) ) ;
313312 }
314313
314+ #[ test]
315+ #[ cfg( unix) ]
316+ fn download_from_url_fails_and_preserves_parent_dir_when_file_write_error ( ) {
317+ use std:: os:: unix:: fs:: PermissionsExt ;
318+ let ( _container, container_url) = start_container ( ) ;
319+
320+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
321+ let nested_path = temp_dir. path ( ) . join ( "new_dir" ) ;
322+
323+ // Create the directory and make it read-only to prevent file creation
324+ fs:: create_dir_all ( & nested_path) . unwrap ( ) ;
325+ let mut perms = fs:: metadata ( & nested_path) . unwrap ( ) . permissions ( ) ;
326+ perms. set_mode ( 0o555 ) ;
327+ fs:: set_permissions ( & nested_path, perms) . unwrap ( ) ;
328+
329+ let result = download_file ( & container_url, nested_path. to_str ( ) . unwrap ( ) , "test.json" ) ;
330+
331+ // Download should fail due to inability to write file. Parent directory should still exist
332+ assert ! ( result. is_none( ) ) ;
333+ assert ! ( nested_path. exists( ) ) ;
334+ }
335+
336+ #[ test]
337+ #[ cfg( unix) ]
338+ fn download_from_url_fails_and_file_deleted_when_write_partialy_fails ( ) {
339+ use std:: os:: unix:: fs:: PermissionsExt ;
340+ let ( _container, container_url) = start_container ( ) ;
341+
342+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
343+ let parent_path = temp_dir. path ( ) ;
344+ let file_path = parent_path. join ( "test.json" ) ;
345+
346+ // Pre-create a read-only file to force write failure
347+ fs:: write ( & file_path, b"old content" ) . unwrap ( ) ;
348+ let mut perms = fs:: metadata ( & file_path) . unwrap ( ) . permissions ( ) ;
349+ perms. set_mode ( 0o444 ) ;
350+ fs:: set_permissions ( & file_path, perms) . unwrap ( ) ;
351+
352+ let result = download_file ( & container_url, parent_path. to_str ( ) . unwrap ( ) , "test.json" ) ;
353+ assert ! ( result. is_none( ) ) ;
354+
355+ // Parent directory should still exist. File should be deleted on cleanup
356+ assert ! ( parent_path. exists( ) ) ;
357+ assert ! (
358+ !file_path. exists( ) ,
359+ "File should have been cleaned up after write failure"
360+ ) ;
361+ }
362+
315363 #[ test]
316364 fn test_download_from_url_with_server_error ( ) {
317365 let rt = tokio:: runtime:: Runtime :: new ( ) . unwrap ( ) ;
0 commit comments