core_lib/cli/
client.rs

1use std::{fs::File, io::Read, path::PathBuf, thread, time::Duration};
2
3use anyhow::Result;
4use std::io::BufRead;
5use tokio::{
6    io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
7    net::UnixStream,
8};
9
10use crate::daemon::server::{DaemonRequest, DaemonResponse, WatchInfo};
11
12pub async fn send_watch_request(req: DaemonRequest) -> Result<(), anyhow::Error> {
13    if let DaemonRequest::None = req {
14        return Ok(());
15    }
16    let mut stream = UnixStream::connect("/tmp/fleetd.sock")
17        .await
18        .map_err(|e| anyhow::anyhow!("Failed to connect with daemon => {}", e))?;
19
20    let json = serde_json::to_string(&req)? + "\n";
21    stream.write_all(json.as_bytes()).await?;
22    stream.flush().await?;
23
24    let response = read_daemon_response(stream).await?;
25    handle_daemon_response(response)?;
26
27    Ok(())
28}
29
30/// Reads a single line response from the daemon and deserializes it into a [`DaemonResponse`].
31async fn read_daemon_response(stream: UnixStream) -> Result<DaemonResponse, anyhow::Error> {
32    let mut reader = BufReader::new(stream);
33    let mut response_line = String::new();
34    reader.read_line(&mut response_line).await?;
35    let response = serde_json::from_str(response_line.trim())?;
36    Ok(response)
37}
38
39/// Processes the [`DaemonResponse`] by printing success, error,
40/// or listing watches in a formatted table.
41fn handle_daemon_response(response: DaemonResponse) -> Result<()> {
42    match response {
43        DaemonResponse::Success(msg) => {
44            println!("✅ {msg}");
45        }
46        DaemonResponse::Error(e) => {
47            eprintln!("❌ Error: {e}");
48        }
49        DaemonResponse::ListWatches(watches) => {
50            print_watches_table(&watches);
51        }
52        DaemonResponse::LogWatch(p, f) => {
53            display_logs(&p, f)?;
54        }
55        DaemonResponse::None | DaemonResponse::Ignore => {}
56    }
57    Ok(())
58}
59
60fn display_logs(path: &str, follow: bool) -> Result<()> {
61    let log_path = PathBuf::from(path);
62    if !log_path.exists() {
63        return Err(anyhow::anyhow!("Failed to find log file : {}", path));
64    }
65    let file = File::open(log_path)?;
66    let mut reader = std::io::BufReader::new(file);
67
68    match follow {
69        true => loop {
70            let mut buffer = String::new();
71            match reader.read_line(&mut buffer) {
72                Ok(0) => {
73                    thread::sleep(Duration::from_millis(200));
74                }
75                Ok(_) => {
76                    print!("{buffer}");
77                }
78                Err(e) => return Err(anyhow::anyhow!("Failed to read: {e}")),
79            }
80        },
81        false => {
82            let mut buffer = String::new();
83            reader.read_to_string(&mut buffer)?;
84            println!("{buffer}");
85            Ok(())
86        }
87    }
88}
89
90/// Prints a formatted table of active watches.
91fn print_watches_table(watches: &[WatchInfo]) {
92    println!(
93        "{:<13} {:<10} {:<13} {:<12} {:<20} {:<30}",
94        "PROJECT ID", "NAME", "BRANCH", "COMMIT", "REMOTE URL", "DIR"
95    );
96    for w in watches {
97        println!(
98            "{:<13} {:<10} {:<13} {:<12} {:<20} {:<30}",
99            w.id.to_string(),
100            w.repo_name,
101            w.branch,
102            w.short_commit,
103            w.short_url,
104            w.project_dir
105        );
106    }
107}