upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/list.rs')
-rw-r--r--src/lib/list.rs110
1 files changed, 93 insertions, 17 deletions
diff --git a/src/lib/list.rs b/src/lib/list.rs
index ce8737c..3b37b37 100644
--- a/src/lib/list.rs
+++ b/src/lib/list.rs
@@ -3,10 +3,10 @@ use std::{
3 path::PathBuf, 3 path::PathBuf,
4 str::FromStr, 4 str::FromStr,
5 sync::{ 5 sync::{
6 Arc, 6 Arc, Mutex,
7 atomic::{AtomicU64, Ordering}, 7 atomic::{AtomicU64, Ordering},
8 }, 8 },
9 time::Duration, 9 time::{Duration, Instant},
10}; 10};
11 11
12use anyhow::{Result, anyhow}; 12use anyhow::{Result, anyhow};
@@ -16,6 +16,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle};
16use nostr::hashes::sha1::Hash as Sha1Hash; 16use nostr::hashes::sha1::Hash as Sha1Hash;
17 17
18use crate::{ 18use crate::{
19 client::is_verbose,
19 git::{ 20 git::{
20 Repo, RepoActions, 21 Repo, RepoActions,
21 nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, 22 nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol},
@@ -28,6 +29,61 @@ use crate::{
28 }, 29 },
29}; 30};
30 31
32const SPINNER_EXPAND_DELAY_SECS: u64 = 5;
33
34struct GitSpinnerState {
35 spinner: ProgressBar,
36 start_time: Instant,
37 expanded_multi: Option<MultiProgress>,
38}
39
40impl GitSpinnerState {
41 fn new() -> Self {
42 let multi_progress = MultiProgress::new();
43 let spinner = multi_progress.add(
44 ProgressBar::new_spinner()
45 .with_style(
46 ProgressStyle::with_template("{spinner} {msg}")
47 .unwrap()
48 .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"),
49 )
50 .with_message("Checking git servers..."),
51 );
52 spinner.enable_steady_tick(Duration::from_millis(100));
53 Self {
54 spinner,
55 start_time: Instant::now(),
56 expanded_multi: None,
57 }
58 }
59
60 fn should_expand(&self) -> bool {
61 self.expanded_multi.is_none()
62 && self.start_time.elapsed().as_secs() >= SPINNER_EXPAND_DELAY_SECS
63 }
64
65 fn expand(&mut self) -> &MultiProgress {
66 if self.expanded_multi.is_none() {
67 self.spinner.finish_and_clear();
68 self.expanded_multi = Some(MultiProgress::new());
69 }
70 self.expanded_multi.as_ref().unwrap()
71 }
72
73 fn finish(&self, has_errors: bool) {
74 if has_errors {
75 if let Some(ref multi) = self.expanded_multi {
76 let _ = multi.clear();
77 }
78 } else {
79 self.spinner.finish_and_clear();
80 if let Some(ref multi) = self.expanded_multi {
81 let _ = multi.clear();
82 }
83 }
84 }
85}
86
31/// Sync issues identified for a single remote 87/// Sync issues identified for a single remote
32#[derive(Default, Debug, Clone)] 88#[derive(Default, Debug, Clone)]
33pub struct RemoteIssues { 89pub struct RemoteIssues {
@@ -111,18 +167,18 @@ pub async fn list_from_remotes(
111 return HashMap::new(); 167 return HashMap::new();
112 } 168 }
113 169
114 let progress_reporter = if std::env::var("NGITTEST").is_err() { 170 let verbose = is_verbose();
115 MultiProgress::new() 171 let spinner_state = if !verbose {
172 Some(Arc::new(Mutex::new(GitSpinnerState::new())))
116 } else { 173 } else {
117 MultiProgress::with_draw_target(indicatif::ProgressDrawTarget::hidden()) 174 None
118 }; 175 };
176 let progress_reporter = MultiProgress::new();
119 177
120 // Track successful servers for adaptive timeout
121 let success_count = Arc::new(AtomicU64::new(0)); 178 let success_count = Arc::new(AtomicU64::new(0));
122 let current_timeout = Arc::new(AtomicU64::new(git_server_long_timeout())); 179 let current_timeout = Arc::new(AtomicU64::new(git_server_long_timeout()));
123 let total_servers = git_servers.len() as u64; 180 let total_servers = git_servers.len() as u64;
124 181
125 // Calculate column width for alignment
126 let server_column_width = git_servers 182 let server_column_width = git_servers
127 .iter() 183 .iter()
128 .map(|s| get_short_git_server_name(s).chars().count()) 184 .map(|s| get_short_git_server_name(s).chars().count())
@@ -139,11 +195,13 @@ pub async fn list_from_remotes(
139 let current_timeout_clone = current_timeout.clone(); 195 let current_timeout_clone = current_timeout.clone();
140 let progress_reporter_clone = progress_reporter.clone(); 196 let progress_reporter_clone = progress_reporter.clone();
141 let decoded_nostr_url = decoded_nostr_url.clone(); 197 let decoded_nostr_url = decoded_nostr_url.clone();
198 let spinner_state_clone = spinner_state.clone();
199 let verbose_for_task = verbose;
142 200
143 async move { 201 async move {
144 let server_name = get_short_git_server_name(&url); 202 let server_name = get_short_git_server_name(&url);
145 203
146 let pb = if std::env::var("NGITTEST").is_err() { 204 let pb = if verbose_for_task {
147 match git_server_pb_style(current_timeout_clone.clone()) { 205 match git_server_pb_style(current_timeout_clone.clone()) {
148 Ok(style) => { 206 Ok(style) => {
149 let pb = progress_reporter_clone.add( 207 let pb = progress_reporter_clone.add(
@@ -164,6 +222,28 @@ pub async fn list_from_remotes(
164 } 222 }
165 Err(_) => None, 223 Err(_) => None,
166 } 224 }
225 } else if let Some(ref spinner_state_arc) = spinner_state_clone {
226 let mut state = spinner_state_arc.lock().unwrap();
227 if state.should_expand() {
228 let multi = state.expand().clone();
229 let pb = multi.add(
230 ProgressBar::new(1)
231 .with_prefix(
232 console::style(format!(
233 "{: <server_column_width$} connecting",
234 &server_name
235 ))
236 .for_stderr()
237 .yellow()
238 .to_string(),
239 )
240 .with_style(git_server_pb_style(current_timeout_clone.clone()).unwrap()),
241 );
242 pb.enable_steady_tick(Duration::from_millis(300));
243 Some(pb)
244 } else {
245 None
246 }
167 } else { 247 } else {
168 None 248 None
169 }; 249 };
@@ -191,7 +271,6 @@ pub async fn list_from_remotes(
191 } 271 }
192 } 272 }
193 273
194 // Create the list operation future - spawn_blocking to avoid blocking async runtime
195 let git_repo_path = git_repo.get_path().ok().map(|p| p.to_path_buf()); 274 let git_repo_path = git_repo.get_path().ok().map(|p| p.to_path_buf());
196 let url_clone = url.clone(); 275 let url_clone = url.clone();
197 let decoded_nostr_url_clone = decoded_nostr_url.clone(); 276 let decoded_nostr_url_clone = decoded_nostr_url.clone();
@@ -199,7 +278,6 @@ pub async fn list_from_remotes(
199 278
200 let list_future = async move { 279 let list_future = async move {
201 match tokio::task::spawn_blocking(move || { 280 match tokio::task::spawn_blocking(move || {
202 // Re-open repo in blocking thread (git2::Repository is not Send)
203 let git_repo = match git_repo_path { 281 let git_repo = match git_repo_path {
204 Some(path) => Repo::from_path(&path).ok(), 282 Some(path) => Repo::from_path(&path).ok(),
205 None => None, 283 None => None,
@@ -274,11 +352,9 @@ pub async fn list_from_remotes(
274 Err((url, error)) 352 Err((url, error))
275 } 353 }
276 Ok(state) => { 354 Ok(state) => {
277 // Determine sync status message and styling using existing functions
278 let status_msg = if state.is_empty() { 355 let status_msg = if state.is_empty() {
279 "empty repository".to_string() 356 "empty repository".to_string()
280 } else if let Some(nostr_state) = nostr_state { 357 } else if let Some(nostr_state) = nostr_state {
281 // Use existing generate_remote_sync_warnings to get detailed status
282 let mut temp_states = HashMap::new(); 358 let mut temp_states = HashMap::new();
283 temp_states.insert(url.clone(), (state.clone(), is_grasp_server)); 359 temp_states.insert(url.clone(), (state.clone(), is_grasp_server));
284 let remote_issues = identify_remote_sync_issues(git_repo, nostr_state, &temp_states); 360 let remote_issues = identify_remote_sync_issues(git_repo, nostr_state, &temp_states);
@@ -287,7 +363,6 @@ pub async fn list_from_remotes(
287 if warnings.is_empty() { 363 if warnings.is_empty() {
288 "in sync".to_string() 364 "in sync".to_string()
289 } else { 365 } else {
290 // Extract the message after "WARNING: <server> "
291 let warning = &warnings[0]; 366 let warning = &warnings[0];
292 let server_name = get_short_git_server_name(&url); 367 let server_name = get_short_git_server_name(&url);
293 let prefix = format!("WARNING: {} ", server_name); 368 let prefix = format!("WARNING: {} ", server_name);
@@ -296,7 +371,6 @@ pub async fn list_from_remotes(
296 .to_string() 371 .to_string()
297 } 372 }
298 } else { 373 } else {
299 // No nostr state to compare against
300 "success".to_string() 374 "success".to_string()
301 }; 375 };
302 376
@@ -333,20 +407,22 @@ pub async fn list_from_remotes(
333 .await; 407 .await;
334 408
335 let mut remote_states = HashMap::new(); 409 let mut remote_states = HashMap::new();
336 let mut all_succeeded = true; 410 let mut has_errors = false;
337 for result in results { 411 for result in results {
338 match result { 412 match result {
339 Ok((url, state, is_grasp_server)) => { 413 Ok((url, state, is_grasp_server)) => {
340 remote_states.insert(url, (state, is_grasp_server)); 414 remote_states.insert(url, (state, is_grasp_server));
341 } 415 }
342 Err((url, error)) => { 416 Err((url, error)) => {
343 all_succeeded = false; 417 has_errors = true;
344 let _ = term.write_line(&format!("failed to list from {}: {}", url, error)); 418 let _ = term.write_line(&format!("failed to list from {}: {}", url, error));
345 } 419 }
346 } 420 }
347 } 421 }
348 422
349 if all_succeeded { 423 if let Some(ref spinner_state_arc) = spinner_state {
424 spinner_state_arc.lock().unwrap().finish(has_errors);
425 } else if !has_errors {
350 let _ = progress_reporter.clear(); 426 let _ = progress_reporter.clear();
351 } 427 }
352 428