audb_core/features/device/
list.rs

1use crate::features::config::{device_store::DeviceStore, state::DeviceState};
2use crate::tools::ssh::SshClient;
3use anyhow::Result;
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::task::JoinSet;
7
8pub async fn execute(active_only: bool) -> Result<()> {
9    if active_only {
10        list_active_devices().await
11    } else {
12        list_all_devices().await
13    }
14}
15
16/// Try to get live status from server
17async fn get_server_status() -> Option<HashMap<String, String>> {
18    use std::path::PathBuf;
19    use tokio::net::UnixStream;
20    
21    let uid = unsafe { libc::getuid() };
22    let socket_path = PathBuf::from(format!("/tmp/audb-server-{}.sock", uid));
23    
24    if !socket_path.exists() {
25        return None;
26    }
27    
28    let mut stream = UnixStream::connect(&socket_path).await.ok()?;
29    
30    // Send ServerStatus command
31    let request = audb_protocol::Request {
32        id: 1,
33        command: audb_protocol::Command::ServerStatus,
34    };
35    
36    audb_protocol::send_message(&mut stream, &request).await.ok()?;
37    let response: audb_protocol::Response = audb_protocol::recv_message(&mut stream).await.ok()?;
38    
39    if let audb_protocol::CommandResult::Success { output: audb_protocol::CommandOutput::Status(status) } = response.result {
40        let mut map = HashMap::new();
41        for device in status.devices {
42            let state_str = match device.state {
43                audb_protocol::ConnectionStateInfo::Disconnected => "disconnected".to_string(),
44                audb_protocol::ConnectionStateInfo::Connecting { attempt } => format!("connecting({})", attempt),
45                audb_protocol::ConnectionStateInfo::Connected { duration_secs } => format!("connected({}s)", duration_secs),
46                audb_protocol::ConnectionStateInfo::Errored { ref error, .. } => {
47                    // Shorten error message
48                    let short_err = if error.len() > 20 { &error[..20] } else { error };
49                    format!("error:{}", short_err)
50                },
51                audb_protocol::ConnectionStateInfo::Disabled => "disabled".to_string(),
52            };
53            map.insert(device.host, state_str);
54        }
55        Some(map)
56    } else {
57        None
58    }
59}
60
61async fn list_all_devices() -> Result<()> {
62    let devices = DeviceStore::list()?;
63
64    if devices.is_empty() {
65        println!("No devices configured. Use 'audb device add' to add a device.");
66        return Ok(());
67    }
68
69    let current_host = DeviceState::get_current().ok();
70    
71    // Try to get live status from server
72    let live_status = get_server_status().await;
73
74    // Header
75    println!("\x1b[1m{:<5} {:<20} {:<18} {:<6} {:<15} {:<10}\x1b[0m",
76        "Index", "Name", "Host", "Port", "Platform", "Status");
77    println!("{}", "-".repeat(80));
78
79    for (idx, device) in devices.iter().enumerate() {
80        let name = device.name.as_deref().unwrap_or("-");
81        
82        // Use live status if available, otherwise show config status
83        let status = if let Some(ref live) = live_status {
84            if let Some(state) = live.get(&device.host) {
85                if state.starts_with("connected") {
86                    format!("\x1b[32m{}\x1b[0m", state)
87                } else if state.starts_with("error") || state == "disconnected" {
88                    format!("\x1b[31m{}\x1b[0m", state)
89                } else {
90                    format!("\x1b[33m{}\x1b[0m", state)
91                }
92            } else if device.enabled {
93                "\x1b[90mnot in server\x1b[0m".to_string()
94            } else {
95                "\x1b[90mdisabled\x1b[0m".to_string()
96            }
97        } else if device.enabled {
98            "\x1b[32menabled\x1b[0m".to_string()
99        } else {
100            "\x1b[90mdisabled\x1b[0m".to_string()
101        };
102
103        let is_current = current_host.as_ref() == Some(&device.host);
104        let marker = if is_current { " *" } else { "" };
105
106        println!("{:<5} {:<20} {:<18} {:<6} {:<15} {}{}",
107            idx,
108            name,
109            device.host,
110            device.port,
111            device.platform,
112            status,
113            marker
114        );
115    }
116
117    if let Some(host) = current_host {
118        println!("\n\x1b[36m*\x1b[0m Currently selected device: {}", host);
119    }
120    
121    if live_status.is_some() {
122        println!("\x1b[90m(live status from server)\x1b[0m");
123    }
124
125    Ok(())
126}
127
128async fn list_active_devices() -> Result<()> {
129    let devices = DeviceStore::list_enabled()?;
130
131    if devices.is_empty() {
132        println!("No enabled devices configured.");
133        return Ok(());
134    }
135
136    println!("Testing connections to {} device(s)...\n", devices.len());
137
138    let current_host = DeviceState::get_current().ok();
139
140    // Test connections concurrently
141    let mut join_set = JoinSet::new();
142
143    for (idx, device) in devices.iter().enumerate() {
144        let device = Arc::new(device.clone());
145        join_set.spawn(async move {
146            let is_online = SshClient::test_connection(
147                &device.host,
148                device.port,
149                &device.auth_path(),
150            );
151            (idx, device, is_online)
152        });
153    }
154
155    let mut results = Vec::new();
156    while let Some(result) = join_set.join_next().await {
157        if let Ok(data) = result {
158            results.push(data);
159        }
160    }
161
162    // Sort by index to maintain order
163    results.sort_by_key(|(idx, _, _)| *idx);
164
165    // Display only active devices
166    let active_results: Vec<_> = results.iter().filter(|(_, _, is_online)| *is_online).collect();
167
168    if active_results.is_empty() {
169        println!("No devices are currently reachable.");
170        return Ok(());
171    }
172
173    // Header
174    println!("\x1b[1m{:<5} {:<20} {:<18} {:<6} {:<15} {:<10}\x1b[0m",
175        "Index", "Name", "Host", "Port", "Platform", "Status");
176    println!("{}", "-".repeat(80));
177
178    for (idx, device, _) in active_results {
179        let name = device.name.as_deref().unwrap_or("-");
180        let is_current = current_host.as_ref() == Some(&device.host);
181        let marker = if is_current { " *" } else { "" };
182
183        println!("{:<5} {:<20} {:<18} {:<6} {:<15} \x1b[32monline\x1b[0m{}",
184            idx,
185            name,
186            device.host,
187            device.port,
188            device.platform,
189            marker
190        );
191    }
192
193    if let Some(host) = current_host {
194        println!("\n\x1b[36m*\x1b[0m Currently selected device: {}", host);
195    }
196
197    Ok(())
198}