1+ use std:: collections:: HashMap ;
2+ use std:: io:: IsTerminal ;
3+ use std:: sync:: { Arc , Mutex } ;
4+
15use box_format:: { BoxFileReader , BoxPath , ExtractOptions , ExtractProgress , ExtractStats } ;
2- use indicatif:: ProgressBar ;
6+ use indicatif:: { MultiProgress , ProgressBar , ProgressStyle } ;
37
48use crate :: cli:: ExtractArgs ;
59use crate :: error:: { Error , Result } ;
6- use crate :: util:: create_progress_bar;
710
811pub async fn run ( args : ExtractArgs ) -> Result < ( ) > {
912 let bf = BoxFileReader :: open ( & args. archive )
@@ -26,11 +29,7 @@ pub async fn run(args: ExtractArgs) -> Result<()> {
2629 } ) ?;
2730
2831 let total_files = bf. metadata ( ) . iter ( ) . count ( ) as u64 ;
29- let progress: Option < ProgressBar > = if args. quiet {
30- None
31- } else {
32- Some ( create_progress_bar ( total_files, "Extracting" ) )
33- } ;
32+ let show_progress = !args. quiet && std:: io:: stdout ( ) . is_terminal ( ) ;
3433
3534 let options = ExtractOptions {
3635 verify_checksums : !args. no_checksum ,
@@ -39,50 +38,91 @@ pub async fn run(args: ExtractArgs) -> Result<()> {
3938 let stats: ExtractStats = if args. files . is_empty ( ) {
4039 // Extract all files
4140 if args. serial {
42- // Sequential extraction
41+ // Sequential extraction (simple progress bar)
42+ let progress = if !show_progress {
43+ None
44+ } else {
45+ let style = ProgressStyle :: default_bar ( )
46+ . template ( " {bar:40.cyan/blue} {pos:>7}/{len:7} files" )
47+ . unwrap ( )
48+ . progress_chars ( "━━─" ) ;
49+ let pb = ProgressBar :: new ( total_files) ;
50+ pb. set_style ( style) ;
51+ Some ( pb)
52+ } ;
53+
4354 let stats = bf
4455 . extract_all_with_options ( & output_path, options)
4556 . await
4657 . map_err ( |source| Error :: Extract { source } ) ?;
4758
4859 if let Some ( pb) = progress {
49- pb. finish_with_message ( "Done" ) ;
60+ pb. finish_and_clear ( ) ;
5061 }
5162 stats
5263 } else {
53- // Parallel extraction (default)
64+ // Parallel extraction (default) with multi-progress display
5465 let concurrency = args. jobs . unwrap_or_else ( num_cpus:: get) ;
5566
5667 // Set up progress channel
5768 let ( progress_tx, mut progress_rx) =
5869 tokio:: sync:: mpsc:: unbounded_channel :: < ExtractProgress > ( ) ;
5970
71+ // Set up multi-progress display
72+ let multi = MultiProgress :: new ( ) ;
73+ let main_style = ProgressStyle :: default_bar ( )
74+ . template ( " {bar:40.cyan/blue} {pos:>7}/{len:7} files" )
75+ . unwrap ( )
76+ . progress_chars ( "━━─" ) ;
77+ let main_bar = multi. add ( ProgressBar :: new ( total_files) ) ;
78+ main_bar. set_style ( main_style) ;
79+
80+ let file_style = ProgressStyle :: default_spinner ( )
81+ . template ( " {spinner:.green} {msg}" )
82+ . unwrap ( ) ;
83+
84+ // Track active file spinners
85+ let active_bars: Arc < Mutex < HashMap < String , ProgressBar > > > =
86+ Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
87+
6088 // Spawn progress handler task
61- let progress_clone = progress. clone ( ) ;
62- let progress_task = tokio:: spawn ( async move {
63- while let Some ( update) = progress_rx. recv ( ) . await {
64- if let Some ( ref pb) = progress_clone {
89+ let progress_task = if !show_progress {
90+ tokio:: spawn ( async move { while progress_rx. recv ( ) . await . is_some ( ) { } } )
91+ } else {
92+ let active_bars = active_bars. clone ( ) ;
93+ let multi = multi. clone ( ) ;
94+ let file_style = file_style. clone ( ) ;
95+ let main_bar = main_bar. clone ( ) ;
96+
97+ tokio:: spawn ( async move {
98+ while let Some ( update) = progress_rx. recv ( ) . await {
6599 match update {
66100 ExtractProgress :: Started { total_files, .. } => {
67- pb. set_length ( total_files) ;
68- pb. set_message ( "Starting..." ) ;
101+ main_bar. set_length ( total_files) ;
69102 }
70103 ExtractProgress :: Extracting { ref path } => {
71- pb. set_message ( format ! ( "Extracting: {}" , path) ) ;
104+ let pb = multi. add ( ProgressBar :: new_spinner ( ) ) ;
105+ pb. set_style ( file_style. clone ( ) ) ;
106+ pb. set_message ( path. to_string ( ) ) ;
107+ pb. enable_steady_tick ( std:: time:: Duration :: from_millis ( 80 ) ) ;
108+ active_bars. lock ( ) . unwrap ( ) . insert ( path. to_string ( ) , pb) ;
72109 }
73- ExtractProgress :: Extracted {
74- files_extracted, ..
75- } => {
76- pb. set_position ( files_extracted) ;
110+ ExtractProgress :: Extracted { ref path, .. } => {
111+ let path_str = path. to_string ( ) ;
112+ if let Some ( pb) = active_bars. lock ( ) . unwrap ( ) . remove ( & path_str) {
113+ pb. finish_and_clear ( ) ;
114+ multi. remove ( & pb) ;
115+ }
116+ main_bar. inc ( 1 ) ;
77117 }
78118 ExtractProgress :: Finished => {
79- pb . finish_with_message ( "Done" ) ;
119+ main_bar . finish_and_clear ( ) ;
80120 }
81121 _ => { }
82122 }
83123 }
84- }
85- } ) ;
124+ } )
125+ } ;
86126
87127 let stats = bf
88128 . extract_all_parallel_with_progress (
@@ -101,6 +141,18 @@ pub async fn run(args: ExtractArgs) -> Result<()> {
101141 }
102142 } else {
103143 // Extract specific files (always sequential for now)
144+ let progress = if !show_progress {
145+ None
146+ } else {
147+ let style = ProgressStyle :: default_bar ( )
148+ . template ( " {bar:40.cyan/blue} {pos:>7}/{len:7} files" )
149+ . unwrap ( )
150+ . progress_chars ( "━━─" ) ;
151+ let pb = ProgressBar :: new ( args. files . len ( ) as u64 ) ;
152+ pb. set_style ( style) ;
153+ Some ( pb)
154+ } ;
155+
104156 let mut total_stats = ExtractStats :: default ( ) ;
105157 for file_path in & args. files {
106158 let box_path = BoxPath :: new ( file_path) . map_err ( |source| Error :: InvalidPath {
@@ -121,7 +173,7 @@ pub async fn run(args: ExtractArgs) -> Result<()> {
121173 }
122174
123175 if let Some ( pb) = progress {
124- pb. finish_with_message ( "Done" ) ;
176+ pb. finish_and_clear ( ) ;
125177 }
126178 total_stats
127179 } ;
@@ -140,15 +192,16 @@ pub async fn run(args: ExtractArgs) -> Result<()> {
140192 ) ;
141193 }
142194
143- // Print timing breakdown
144- let timing = & stats. timing ;
145- let total = timing. collect + timing. directories + timing. decompress + timing. symlinks ;
146- println ! ( "\n Timing:" ) ;
147- println ! ( " Collect: {:>8.2?}" , timing. collect) ;
148- println ! ( " Directories: {:>8.2?}" , timing. directories) ;
149- println ! ( " Decompress: {:>8.2?}" , timing. decompress) ;
150- println ! ( " Symlinks: {:>8.2?}" , timing. symlinks) ;
151- println ! ( " Total: {:>8.2?}" , total) ;
195+ if args. timings {
196+ let timing = & stats. timing ;
197+ let total = timing. collect + timing. directories + timing. decompress + timing. symlinks ;
198+ println ! ( "\n Timing:" ) ;
199+ println ! ( " Collect: {:>8.2?}" , timing. collect) ;
200+ println ! ( " Directories: {:>8.2?}" , timing. directories) ;
201+ println ! ( " Decompress: {:>8.2?}" , timing. decompress) ;
202+ println ! ( " Symlinks: {:>8.2?}" , timing. symlinks) ;
203+ println ! ( " Total: {:>8.2?}" , total) ;
204+ }
152205 }
153206
154207 if stats. checksum_failures > 0 {
0 commit comments