@@ -21,7 +21,9 @@ use ostree_ext::ostree::{self, Sysroot};
2121use ostree_ext:: sysroot:: SysrootLock ;
2222use ostree_ext:: tokio_util:: spawn_blocking_cancellable_flatten;
2323
24+ use crate :: progress_aggregator:: ProgressAggregatorBuilder ;
2425use crate :: progress_jsonl:: { Event , ProgressWriter , SubTaskBytes , SubTaskStep } ;
26+ use crate :: progress_renderer:: ProgressFilter ;
2527use crate :: spec:: ImageReference ;
2628use crate :: spec:: { BootOrder , HostSpec } ;
2729use crate :: status:: labels_of_config;
@@ -138,7 +140,7 @@ fn prefix_of_progress(p: &ImportProgress) -> &'static str {
138140 }
139141}
140142
141- /// Write container fetch progress to standard output .
143+ /// Write container fetch progress using JSON-first architecture .
142144async fn handle_layer_progress_print (
143145 mut layers : tokio:: sync:: mpsc:: Receiver < ostree_container:: store:: ImportProgress > ,
144146 mut layer_bytes : tokio:: sync:: watch:: Receiver < Option < ostree_container:: store:: LayerProgress > > ,
@@ -152,35 +154,23 @@ async fn handle_layer_progress_print(
152154) -> ProgressWriter {
153155 let start = std:: time:: Instant :: now ( ) ;
154156 let mut total_read = 0u64 ;
155- let bar = indicatif:: MultiProgress :: new ( ) ;
156- if quiet {
157- bar. set_draw_target ( indicatif:: ProgressDrawTarget :: hidden ( ) ) ;
158- }
159- let layers_bar = bar. add ( indicatif:: ProgressBar :: new (
160- n_layers_to_fetch. try_into ( ) . unwrap ( ) ,
161- ) ) ;
162- let byte_bar = bar. add ( indicatif:: ProgressBar :: new ( 0 ) ) ;
163- // let byte_bar = indicatif::ProgressBar::new(0);
164- // byte_bar.set_draw_target(indicatif::ProgressDrawTarget::hidden());
165- layers_bar. set_style (
166- indicatif:: ProgressStyle :: default_bar ( )
167- . template ( "{prefix} {bar} {pos}/{len} {wide_msg}" )
168- . unwrap ( ) ,
169- ) ;
170- let taskname = "Fetching layers" ;
171- layers_bar. set_prefix ( taskname) ;
172- layers_bar. set_message ( "" ) ;
173- byte_bar. set_prefix ( "Fetching" ) ;
174- byte_bar. set_style (
175- indicatif:: ProgressStyle :: default_bar ( )
176- . template (
177- " └ {prefix} {bar} {binary_bytes}/{binary_total_bytes} ({binary_bytes_per_sec}) {wide_msg}" ,
178- )
179- . unwrap ( )
180- ) ;
157+
158+ // Create JSON-first progress aggregator for pulling tasks
159+ let visual_filter = if quiet {
160+ None
161+ } else {
162+ Some ( ProgressFilter :: TasksMatching ( vec ! [ "pulling" . to_string( ) ] ) )
163+ } ;
164+
165+ let mut aggregator = ProgressAggregatorBuilder :: new ( )
166+ . with_json ( prog. clone ( ) )
167+ . with_visual ( visual_filter. unwrap_or ( ProgressFilter :: TasksMatching ( vec ! [ ] ) ) )
168+ . build ( ) ;
181169
182170 let mut subtasks = vec ! [ ] ;
183171 let mut subtask: SubTaskBytes = Default :: default ( ) ;
172+ let mut current_layer_step = 0u64 ;
173+
184174 loop {
185175 tokio:: select! {
186176 // Always handle layer changes first.
@@ -192,12 +182,6 @@ async fn handle_layer_progress_print(
192182 let short_digest = & layer. digest( ) . digest( ) [ 0 ..21 ] ;
193183 let layer_size = layer. size( ) ;
194184 if l. is_starting( ) {
195- // Reset the progress bar
196- byte_bar. reset_elapsed( ) ;
197- byte_bar. reset_eta( ) ;
198- byte_bar. set_length( layer_size) ;
199- byte_bar. set_message( format!( "{layer_type} {short_digest}" ) ) ;
200-
201185 subtask = SubTaskBytes {
202186 subtask: layer_type. into( ) ,
203187 description: format!( "{layer_type}: {short_digest}" ) . clone( ) . into( ) ,
@@ -207,24 +191,26 @@ async fn handle_layer_progress_print(
207191 bytes_total: layer_size,
208192 } ;
209193 } else {
210- byte_bar. set_position( layer_size) ;
211- layers_bar. inc( 1 ) ;
212194 total_read = total_read. saturating_add( layer_size) ;
195+ current_layer_step += 1 ;
213196 // Emit an event where bytes == total to signal completion.
214197 subtask. bytes = layer_size;
215198 subtasks. push( subtask. clone( ) ) ;
216- prog. send( Event :: ProgressBytes {
199+
200+ // Send progress event via JSON-first aggregator
201+ let event = Event :: ProgressBytes {
217202 task: "pulling" . into( ) ,
218203 description: format!( "Pulling Image: {digest}" ) . into( ) ,
219204 id: ( * digest) . into( ) ,
220205 bytes_cached: bytes_total - bytes_to_download,
221206 bytes: total_read,
222207 bytes_total: bytes_to_download,
223208 steps_cached: ( layers_total - n_layers_to_fetch) as u64 ,
224- steps: layers_bar . position ( ) ,
209+ steps: current_layer_step ,
225210 steps_total: n_layers_to_fetch as u64 ,
226211 subtasks: subtasks. clone( ) ,
227- } ) . await ;
212+ } ;
213+ let _ = aggregator. send_event( event) . await ;
228214 }
229215 } else {
230216 // If the receiver is disconnected, then we're done
@@ -241,40 +227,42 @@ async fn handle_layer_progress_print(
241227 bytes. as_ref( ) . cloned( )
242228 } ;
243229 if let Some ( bytes) = bytes {
244- byte_bar. set_position( bytes. fetched) ;
245- subtask. bytes = byte_bar. position( ) ;
246- prog. send_lossy( Event :: ProgressBytes {
230+ subtask. bytes = bytes. fetched;
231+
232+ // Send lossy progress event via JSON-first aggregator
233+ let event = Event :: ProgressBytes {
247234 task: "pulling" . into( ) ,
248235 description: format!( "Pulling Image: {digest}" ) . into( ) ,
249236 id: ( * digest) . into( ) ,
250237 bytes_cached: bytes_total - bytes_to_download,
251- bytes: total_read + byte_bar . position ( ) ,
238+ bytes: total_read + bytes . fetched ,
252239 bytes_total: bytes_to_download,
253240 steps_cached: ( layers_total - n_layers_to_fetch) as u64 ,
254- steps: layers_bar . position ( ) ,
241+ steps: current_layer_step ,
255242 steps_total: n_layers_to_fetch as u64 ,
256243 subtasks: subtasks. clone( ) . into_iter( ) . chain( [ subtask. clone( ) ] ) . collect( ) ,
257- } ) . await ;
244+ } ;
245+ let _ = aggregator. send_event( event) . await ;
258246 }
259247 }
260248 }
261249 }
262- byte_bar. finish_and_clear ( ) ;
263- layers_bar. finish_and_clear ( ) ;
264- if let Err ( e) = bar. clear ( ) {
265- tracing:: warn!( "clearing bar: {e}" ) ;
266- }
250+
251+ // Finish progress aggregator
252+ aggregator. finish ( ) ;
253+
267254 let end = std:: time:: Instant :: now ( ) ;
268255 let elapsed = end. duration_since ( start) ;
269256 let persec = total_read as f64 / elapsed. as_secs_f64 ( ) ;
270257 let persec = indicatif:: HumanBytes ( persec as u64 ) ;
271- if let Err ( e) = bar. println ( & format ! (
272- "Fetched layers: {} in {} ({}/s)" ,
273- indicatif:: HumanBytes ( total_read) ,
274- indicatif:: HumanDuration ( elapsed) ,
275- persec,
276- ) ) {
277- tracing:: warn!( "writing to stdout: {e}" ) ;
258+
259+ if !quiet {
260+ println ! (
261+ "Fetched layers: {} in {} ({}/s)" ,
262+ indicatif:: HumanBytes ( total_read) ,
263+ indicatif:: HumanDuration ( elapsed) ,
264+ persec,
265+ ) ;
278266 }
279267
280268 // Since the progress notifier closed, we know import has started
0 commit comments