use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
use ipnet::Ipv4Net;
use tokio::sync::mpsc;
use tokio::time::timeout;
use crate::Result;
use crate::model::DeviceInfo;
use super::probe_device;
pub async fn scan_subnet(
subnet: Ipv4Net,
http_timeout: Duration,
show_progress: bool,
on_found: impl Fn(&DeviceInfo),
) -> Result<Vec<DeviceInfo>> {
let client = reqwest::Client::builder().timeout(http_timeout).build()?;
let (tx, mut rx) = mpsc::channel::<DeviceInfo>(64);
let hosts: Vec<Ipv4Addr> = subnet.hosts().collect();
let total = hosts.len() as u32;
let completed = Arc::new(AtomicU32::new(0));
for chunk in hosts.chunks(32) {
let mut handles = Vec::new();
for &ip in chunk {
let client = client.clone();
let tx = tx.clone();
let completed = Arc::clone(&completed);
handles.push(tokio::spawn(async move {
let addr = IpAddr::V4(ip);
if let Ok(Ok(info)) = timeout(http_timeout, probe_device(addr, &client)).await {
let _ = tx.send(info).await;
}
completed.fetch_add(1, Ordering::Relaxed);
}));
}
for handle in handles {
let _ = handle.await;
}
if show_progress {
let done = completed.load(Ordering::Relaxed);
eprint!("\rScanning {subnet}... {done}/{total}");
}
}
if show_progress {
eprint!("\r{}\r", " ".repeat(60));
}
drop(tx);
let mut devices = Vec::new();
while let Some(info) = rx.recv().await {
on_found(&info);
devices.push(info);
}
devices.sort_by_key(|a| a.ip);
Ok(devices)
}
pub async fn enrich_gen1_name(info: &mut DeviceInfo, client: &reqwest::Client) -> Result<()> {
if info.name.is_some() {
return Ok(());
}
let url = format!("http://{}/settings", info.ip);
let resp = client.get(&url).send().await?;
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
return Err(crate::error::status_error(status, &url, &body));
}
let settings: serde_json::Value = resp.json().await?;
if let Some(name) = settings.get("name").and_then(|v| v.as_str())
&& !name.is_empty()
{
info.name = Some(name.to_string());
}
Ok(())
}