upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/lib/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/client.rs')
-rw-r--r--src/lib/client.rs173
1 files changed, 72 insertions, 101 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs
index 583f01c..7c83e19 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -16,10 +16,10 @@ use std::{
16 fs::create_dir_all, 16 fs::create_dir_all,
17 path::Path, 17 path::Path,
18 sync::{ 18 sync::{
19 Arc, Mutex, RwLock, 19 Arc, RwLock,
20 atomic::{AtomicU64, Ordering}, 20 atomic::{AtomicU64, Ordering},
21 }, 21 },
22 time::{Duration, Instant}, 22 time::Duration,
23}; 23};
24 24
25use anyhow::{Context, Result, anyhow, bail}; 25use anyhow::{Context, Result, anyhow, bail};
@@ -69,60 +69,7 @@ pub fn is_verbose() -> bool {
69 std::env::var("NGIT_VERBOSE").is_ok() 69 std::env::var("NGIT_VERBOSE").is_ok()
70} 70}
71 71
72const SPINNER_EXPAND_DELAY_SECS: u64 = 5; 72const SPINNER_EXPAND_DELAY_MS: u64 = 5000;
73
74struct SpinnerState {
75 spinner: ProgressBar,
76 start_time: Instant,
77 expanded_multi: Option<MultiProgress>,
78}
79
80impl SpinnerState {
81 fn new() -> Self {
82 let multi_progress = MultiProgress::new();
83 let spinner = multi_progress.add(
84 ProgressBar::new_spinner()
85 .with_style(
86 ProgressStyle::with_template("{spinner} {msg}")
87 .unwrap()
88 .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"),
89 )
90 .with_message("Checking relays and git servers..."),
91 );
92 spinner.enable_steady_tick(Duration::from_millis(100));
93 Self {
94 spinner,
95 start_time: Instant::now(),
96 expanded_multi: None,
97 }
98 }
99
100 fn should_expand(&self) -> bool {
101 self.expanded_multi.is_none()
102 && self.start_time.elapsed().as_secs() >= SPINNER_EXPAND_DELAY_SECS
103 }
104
105 fn expand(&mut self) -> &MultiProgress {
106 if self.expanded_multi.is_none() {
107 self.spinner.finish_and_clear();
108 self.expanded_multi = Some(MultiProgress::new());
109 }
110 self.expanded_multi.as_ref().unwrap()
111 }
112
113 fn finish(&self, has_errors: bool) {
114 if has_errors {
115 if let Some(ref multi) = self.expanded_multi {
116 let _ = multi.clear();
117 }
118 } else {
119 self.spinner.finish_and_clear();
120 if let Some(ref multi) = self.expanded_multi {
121 let _ = multi.clear();
122 }
123 }
124 }
125}
126 73
127#[allow(clippy::struct_field_names)] 74#[allow(clippy::struct_field_names)]
128pub struct Client { 75pub struct Client {
@@ -430,12 +377,54 @@ impl Connect for Client {
430 .await?; 377 .await?;
431 378
432 let verbose = is_verbose(); 379 let verbose = is_verbose();
433 let spinner_state = if !verbose { 380 let is_test = std::env::var("NGITTEST").is_ok();
434 Some(Arc::new(Mutex::new(SpinnerState::new()))) 381
382 // Set up the two-MultiProgress pattern:
383 // 1. A spinner MultiProgress shown immediately (concise mode only)
384 // 2. A detail MultiProgress that starts hidden and becomes visible after a delay
385 let spinner_multi = if !verbose && !is_test {
386 let m = MultiProgress::new();
387 let spinner = m.add(
388 ProgressBar::new_spinner()
389 .with_style(
390 ProgressStyle::with_template("{spinner} {msg}")
391 .unwrap()
392 .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"),
393 )
394 .with_message("Checking relays and git servers..."),
395 );
396 spinner.enable_steady_tick(Duration::from_millis(100));
397 Some((m, spinner))
398 } else {
399 None
400 };
401
402 let progress_reporter = if is_test {
403 MultiProgress::with_draw_target(ProgressDrawTarget::hidden())
404 } else if verbose {
405 MultiProgress::new()
406 } else {
407 MultiProgress::with_draw_target(ProgressDrawTarget::hidden())
408 };
409
410 // Spawn a background timer that transitions from spinner to detail view
411 let detail_multi_for_timer = progress_reporter.clone();
412 let spinner_for_timer = spinner_multi.as_ref().map(|(_, s)| s.clone());
413 let timer_handle = if !verbose && !is_test {
414 let handle = tokio::spawn(async move {
415 tokio::time::sleep(Duration::from_millis(SPINNER_EXPAND_DELAY_MS)).await;
416 // Transition: finish spinner, show heading, reveal detail bars
417 if let Some(spinner) = spinner_for_timer {
418 spinner.finish_and_clear();
419 }
420 eprintln!("fetching updates...");
421 detail_multi_for_timer
422 .set_draw_target(ProgressDrawTarget::stderr());
423 });
424 Some(handle)
435 } else { 425 } else {
436 None 426 None
437 }; 427 };
438 let progress_reporter = MultiProgress::new();
439 428
440 let success_count = Arc::new(AtomicU64::new(0)); 429 let success_count = Arc::new(AtomicU64::new(0));
441 let current_timeout = Arc::new(AtomicU64::new(long_timeout())); 430 let current_timeout = Arc::new(AtomicU64::new(long_timeout()));
@@ -471,7 +460,6 @@ impl Connect for Client {
471 let success_count_for_loop = success_count.clone(); 460 let success_count_for_loop = success_count.clone();
472 let current_timeout_for_loop = current_timeout.clone(); 461 let current_timeout_for_loop = current_timeout.clone();
473 let total_relays = relays.len() as u64; 462 let total_relays = relays.len() as u64;
474 let spinner_state_clone = spinner_state.clone();
475 463
476 let futures: Vec<_> = relays 464 let futures: Vec<_> = relays
477 .iter() 465 .iter()
@@ -506,8 +494,6 @@ impl Connect for Client {
506 let current_timeout_clone = current_timeout_for_loop.clone(); 494 let current_timeout_clone = current_timeout_for_loop.clone();
507 let progress_reporter_clone = progress_reporter.clone(); 495 let progress_reporter_clone = progress_reporter.clone();
508 let total_relays_clone = total_relays; 496 let total_relays_clone = total_relays;
509 let spinner_state_for_task = spinner_state_clone.clone();
510 let verbose_for_task = verbose;
511 async move { 497 async move {
512 let relay_column_width = request.relay_column_width; 498 let relay_column_width = request.relay_column_width;
513 499
@@ -516,43 +502,23 @@ impl Connect for Client {
516 .clone() 502 .clone()
517 .context("fetch_all_from_relay called without a relay")?; 503 .context("fetch_all_from_relay called without a relay")?;
518 504
519 let pb = if verbose_for_task { 505 // Always create a real progress bar added to the detail
520 let pb = progress_reporter_clone.add( 506 // multi. In test mode the multi has a hidden draw target
521 ProgressBar::new(1) 507 // so nothing is displayed. In concise mode the multi
522 .with_prefix( 508 // starts hidden and the background timer reveals it.
523 format!( 509 let pb = progress_reporter_clone.add(
524 "{: <relay_column_width$} connecting", 510 ProgressBar::new(1)
525 &relay_url 511 .with_prefix(
526 ) 512 format!(
527 .to_string(), 513 "{: <relay_column_width$} connecting",
514 &relay_url
528 ) 515 )
529 .with_style(pb_style(current_timeout_clone.clone())?), 516 .to_string(),
530 ); 517 )
531 pb.enable_steady_tick(Duration::from_millis(300)); 518 .with_style(pb_style(current_timeout_clone.clone())?),
532 Some(pb) 519 );
533 } else if let Some(ref state) = spinner_state_for_task { 520 pb.enable_steady_tick(Duration::from_millis(300));
534 let mut state = state.lock().unwrap(); 521 let pb = Some(pb);
535 if state.should_expand() {
536 let multi = state.expand().clone();
537 let pb = multi.add(
538 ProgressBar::new(1)
539 .with_prefix(
540 format!(
541 "{: <relay_column_width$} connecting",
542 &relay_url
543 )
544 .to_string(),
545 )
546 .with_style(pb_style(current_timeout_clone.clone())?),
547 );
548 pb.enable_steady_tick(Duration::from_millis(300));
549 Some(pb)
550 } else {
551 None
552 }
553 } else {
554 None
555 };
556 522
557 fn update_progress_bar_with_error( 523 fn update_progress_bar_with_error(
558 relay_column_width: usize, 524 relay_column_width: usize,
@@ -689,9 +655,14 @@ impl Connect for Client {
689 }; 655 };
690 } 656 }
691 657
692 if let Some(ref state) = spinner_state { 658 // Cancel the background timer if it hasn't fired yet, and clean up
693 let has_errors = relay_reports.iter().any(Result::is_err); 659 // the spinner. If the timer already fired, the abort is a no-op.
694 state.lock().unwrap().finish(has_errors); 660 if let Some(handle) = timer_handle {
661 handle.abort();
662 }
663 // Clear the spinner (no-op if timer already cleared it)
664 if let Some((_, spinner)) = &spinner_multi {
665 spinner.finish_and_clear();
695 } 666 }
696 667
697 Ok((relay_reports, progress_reporter)) 668 Ok((relay_reports, progress_reporter))