Skip to main content

purple_ssh/handler/
sync.rs

1use std::sync::Arc;
2use std::sync::atomic::AtomicBool;
3use std::sync::mpsc;
4
5use log::{error, info, warn};
6
7use crate::event::AppEvent;
8
9pub fn spawn_provider_sync(
10    section: &crate::providers::config::ProviderSection,
11    tx: mpsc::Sender<AppEvent>,
12    cancel: Arc<AtomicBool>,
13    env: Arc<crate::runtime::env::Env>,
14) {
15    // Use full ProviderConfigId (provider[:label]) as the event/sync key so
16    // two labeled configs of the same provider don't collide on the syncing
17    // HashMap or in event payloads.
18    let name = section.id.to_string();
19    let token = section.token.clone();
20    let section_clone = section.clone();
21    let tx_fallback = tx.clone();
22    let name_fallback = name.clone();
23    log::debug!("Spawning provider sync thread: {name}");
24    if let Err(e) = std::thread::Builder::new()
25        .name(format!("sync-{}", name))
26        .spawn(move || {
27            let provider = match crate::providers::get_provider_with_config(&section_clone) {
28                Some(p) => p,
29                None => {
30                    warn!("[config] Unknown provider requested for sync: {name}");
31                    let _ = tx.send(AppEvent::SyncError {
32                        provider: name,
33                        message: crate::messages::SYNC_UNKNOWN_PROVIDER.to_string(),
34                    });
35                    return;
36                }
37            };
38            info!("Provider sync started: {name}");
39            let progress_tx = tx.clone();
40            let progress_name = name.clone();
41            let progress = move |msg: &str| {
42                let _ = progress_tx.send(AppEvent::SyncProgress {
43                    provider: progress_name.clone(),
44                    message: msg.to_string(),
45                });
46            };
47            match provider.fetch_hosts_with_progress(&token, &cancel, &env, &progress) {
48                Ok(hosts) => {
49                    if hosts.is_empty() {
50                        warn!("[config] Provider sync returned 0 hosts: {name} (check API token permissions)");
51                    } else {
52                        info!("Provider sync completed: {name}, {} hosts found", hosts.len());
53                    }
54                    let _ = tx.send(AppEvent::SyncComplete {
55                        provider: name,
56                        hosts,
57                    });
58                }
59                Err(crate::providers::ProviderError::PartialResult {
60                    hosts,
61                    failures,
62                    total,
63                }) => {
64                    warn!("[external] Provider sync partial: {name}, {} hosts, {} failures", hosts.len(), failures);
65                    let _ = tx.send(AppEvent::SyncPartial {
66                        provider: name,
67                        hosts,
68                        failures,
69                        total,
70                    });
71                }
72                Err(e) => {
73                    error!("[external] Provider sync failed: {name}: {e}");
74                    let _ = tx.send(AppEvent::SyncError {
75                        provider: name,
76                        message: e.to_string(),
77                    });
78                }
79            }
80        })
81    {
82        error!(
83            "[purple] Failed to spawn sync thread for {}: {}",
84            name_fallback, e
85        );
86        let _ = tx_fallback.send(AppEvent::SyncError {
87            provider: name_fallback,
88            message: crate::messages::SYNC_THREAD_SPAWN_FAILED.to_string(),
89        });
90    }
91}