audb_core/features/device/
list.rs1use 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
16async 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 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 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 let live_status = get_server_status().await;
73
74 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 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 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 results.sort_by_key(|(idx, _, _)| *idx);
164
165 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 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}