@@ -273,6 +273,144 @@ impl MtpConnectionManager {
273273 Ok ( total_bytes)
274274 }
275275
276+ /// Downloads a file or directory recursively with a progress/cancellation callback.
277+ ///
278+ /// Same as `download_recursive` but calls `on_progress(bytes_done, bytes_total)` per chunk.
279+ /// Returns `ControlFlow::Break(())` from the callback to cancel.
280+ pub async fn download_recursive_with_progress (
281+ & self ,
282+ device_id : & str ,
283+ storage_id : u32 ,
284+ object_path : & str ,
285+ local_dest : & Path ,
286+ on_progress : & ( dyn Fn ( u64 , u64 ) -> std:: ops:: ControlFlow < ( ) > + Send + Sync ) ,
287+ ) -> Result < u64 , MtpConnectionError > {
288+ debug ! (
289+ "MTP download_recursive_with_progress: device={}, storage={}, path={}, dest={}" ,
290+ device_id,
291+ storage_id,
292+ object_path,
293+ local_dest. display( )
294+ ) ;
295+
296+ match self . list_directory ( device_id, storage_id, object_path) . await {
297+ Ok ( entries) if !entries. is_empty ( ) => {
298+ self . download_entries_recursive_with_progress ( device_id, storage_id, & entries, local_dest, on_progress)
299+ . await
300+ }
301+ Ok ( _) => {
302+ if self . is_file_by_parent ( device_id, storage_id, object_path) . await {
303+ let operation_id = format ! ( "download-{}" , uuid:: Uuid :: new_v4( ) ) ;
304+ let result = self
305+ . download_file_with_progress (
306+ device_id,
307+ storage_id,
308+ object_path,
309+ local_dest,
310+ None ,
311+ & operation_id,
312+ Some ( on_progress) ,
313+ )
314+ . await ?;
315+ Ok ( result. bytes_transferred )
316+ } else {
317+ tokio:: fs:: create_dir_all ( local_dest)
318+ . await
319+ . map_err ( |e| MtpConnectionError :: Other {
320+ device_id : device_id. to_string ( ) ,
321+ message : format ! ( "Failed to create local directory: {}" , e) ,
322+ } ) ?;
323+ Ok ( 0 )
324+ }
325+ }
326+ Err ( e) => {
327+ if self . is_file_by_parent ( device_id, storage_id, object_path) . await {
328+ let operation_id = format ! ( "download-{}" , uuid:: Uuid :: new_v4( ) ) ;
329+ let result = self
330+ . download_file_with_progress (
331+ device_id,
332+ storage_id,
333+ object_path,
334+ local_dest,
335+ None ,
336+ & operation_id,
337+ Some ( on_progress) ,
338+ )
339+ . await ?;
340+ Ok ( result. bytes_transferred )
341+ } else {
342+ Err ( e)
343+ }
344+ }
345+ }
346+ }
347+
348+ /// Downloads directory entries recursively with a progress/cancellation callback.
349+ async fn download_entries_recursive_with_progress (
350+ & self ,
351+ device_id : & str ,
352+ storage_id : u32 ,
353+ entries : & [ FileEntry ] ,
354+ local_dest : & Path ,
355+ on_progress : & ( dyn Fn ( u64 , u64 ) -> std:: ops:: ControlFlow < ( ) > + Send + Sync ) ,
356+ ) -> Result < u64 , MtpConnectionError > {
357+ tokio:: fs:: create_dir_all ( local_dest)
358+ . await
359+ . map_err ( |e| MtpConnectionError :: Other {
360+ device_id : device_id. to_string ( ) ,
361+ message : format ! ( "Failed to create local directory: {}" , e) ,
362+ } ) ?;
363+
364+ let mut total_bytes = 0u64 ;
365+
366+ for entry in entries {
367+ let child_dest = local_dest. join ( & entry. name ) ;
368+
369+ if entry. is_directory {
370+ let children = self . list_directory ( device_id, storage_id, & entry. path ) . await ?;
371+ if children. is_empty ( ) {
372+ tokio:: fs:: create_dir_all ( & child_dest)
373+ . await
374+ . map_err ( |e| MtpConnectionError :: Other {
375+ device_id : device_id. to_string ( ) ,
376+ message : format ! ( "Failed to create local directory: {}" , e) ,
377+ } ) ?;
378+ } else {
379+ let bytes = Box :: pin ( self . download_entries_recursive_with_progress (
380+ device_id,
381+ storage_id,
382+ & children,
383+ & child_dest,
384+ on_progress,
385+ ) )
386+ . await ?;
387+ total_bytes += bytes;
388+ }
389+ } else {
390+ let operation_id = format ! ( "download-{}" , uuid:: Uuid :: new_v4( ) ) ;
391+ let result = self
392+ . download_file_with_progress (
393+ device_id,
394+ storage_id,
395+ & entry. path ,
396+ & child_dest,
397+ None ,
398+ & operation_id,
399+ Some ( on_progress) ,
400+ )
401+ . await ?;
402+ total_bytes += result. bytes_transferred ;
403+ }
404+ }
405+
406+ debug ! (
407+ "MTP download_entries_recursive_with_progress: directory {} complete, {} bytes" ,
408+ local_dest. display( ) ,
409+ total_bytes
410+ ) ;
411+ Ok ( total_bytes)
412+ }
413+
276414 /// Checks if a path is a file by looking it up in its parent directory listing.
277415 async fn is_file_by_parent ( & self , device_id : & str , storage_id : u32 , path : & str ) -> bool {
278416 let path_buf = normalize_mtp_path ( path) ;
0 commit comments