1use crate::core::config::Config;
4use crate::core::error::{Error, Result};
5use crate::daemon::protocol::{Method, Request, Response, ResponseResult};
6use std::io::{BufRead, BufReader, Write};
7use std::path::Path;
8use std::time::Duration;
9
10#[cfg(unix)]
11use std::os::unix::net::UnixStream;
12
13#[cfg(windows)]
14use std::net::TcpStream;
15
16const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
18
19const INDEX_TIMEOUT: Duration = Duration::from_secs(600);
21
22#[cfg(unix)]
24pub fn is_running() -> Result<bool> {
25 let socket_path = Config::socket_path()?;
26 Ok(socket_path.exists())
27}
28
29#[cfg(windows)]
31pub fn is_running() -> Result<bool> {
32 let port_path = Config::port_path()?;
33 Ok(port_path.exists())
34}
35
36#[cfg(unix)]
38fn connect_with_timeout(read_timeout: Duration) -> Result<UnixStream> {
39 let socket_path = Config::socket_path()?;
40 let stream = UnixStream::connect(&socket_path).map_err(|e| Error::DaemonError {
41 message: format!("Failed to connect to daemon: {}", e),
42 })?;
43 stream
44 .set_read_timeout(Some(read_timeout))
45 .map_err(|e| Error::DaemonError {
46 message: format!("Failed to set read timeout: {}", e),
47 })?;
48 stream
49 .set_write_timeout(Some(Duration::from_secs(5)))
50 .map_err(|e| Error::DaemonError {
51 message: format!("Failed to set write timeout: {}", e),
52 })?;
53 Ok(stream)
54}
55
56#[cfg(windows)]
58fn connect_with_timeout(read_timeout: Duration) -> Result<TcpStream> {
59 let port_path = Config::port_path()?;
60 let port_str = std::fs::read_to_string(&port_path).map_err(|e| Error::DaemonError {
61 message: format!("Failed to read daemon port file: {}", e),
62 })?;
63 let port: u16 = port_str.trim().parse().map_err(|e| Error::DaemonError {
64 message: format!("Invalid port in daemon port file: {}", e),
65 })?;
66
67 let addr = format!("127.0.0.1:{}", port);
68 let stream = TcpStream::connect_timeout(&addr.parse().unwrap(), Duration::from_secs(5))
69 .map_err(|e| Error::DaemonError {
70 message: format!("Failed to connect to daemon at {}: {}", addr, e),
71 })?;
72 stream
73 .set_read_timeout(Some(read_timeout))
74 .map_err(|e| Error::DaemonError {
75 message: format!("Failed to set read timeout: {}", e),
76 })?;
77 stream
78 .set_write_timeout(Some(Duration::from_secs(5)))
79 .map_err(|e| Error::DaemonError {
80 message: format!("Failed to set write timeout: {}", e),
81 })?;
82 Ok(stream)
83}
84
85fn send_request<S: Write + std::io::Read>(stream: &mut S, request: &Request) -> Result<Response> {
87 let json = serde_json::to_string(request).map_err(|e| Error::DaemonError {
88 message: format!("Failed to serialize request: {}", e),
89 })?;
90
91 stream
92 .write_all(json.as_bytes())
93 .map_err(|e| Error::DaemonError {
94 message: format!("Failed to send request: {}", e),
95 })?;
96 stream.write_all(b"\n").map_err(|e| Error::DaemonError {
97 message: format!("Failed to send newline: {}", e),
98 })?;
99 stream.flush().map_err(|e| Error::DaemonError {
100 message: format!("Failed to flush: {}", e),
101 })?;
102
103 let mut reader = BufReader::new(stream);
104 let mut response_line = String::new();
105 reader
106 .read_line(&mut response_line)
107 .map_err(|e| Error::DaemonError {
108 message: format!("Failed to read response: {}", e),
109 })?;
110
111 serde_json::from_str(&response_line).map_err(|e| Error::DaemonError {
112 message: format!("Invalid response from daemon: {}", e),
113 })
114}
115
116pub async fn search(
118 query: &str,
119 project: &Path,
120 limit: usize,
121) -> Result<crate::search::SearchResponse> {
122 let mut stream = connect_with_timeout(REQUEST_TIMEOUT)?;
123
124 let request = Request {
125 id: uuid::Uuid::new_v4().to_string(),
126 method: Method::Search {
127 query: query.to_string(),
128 project: project.to_string_lossy().to_string(),
129 limit,
130 },
131 };
132
133 let response = send_request(&mut stream, &request)?;
134
135 match response.result {
136 ResponseResult::Search(search_response) => Ok(search_response),
137 ResponseResult::Error { message } => Err(Error::DaemonError { message }),
138 _ => Err(Error::DaemonError {
139 message: "Unexpected response type".to_string(),
140 }),
141 }
142}
143
144pub async fn index(project: &Path, force: bool) -> Result<(usize, usize, f64)> {
146 let mut stream = connect_with_timeout(INDEX_TIMEOUT)?;
147
148 let request = Request {
149 id: uuid::Uuid::new_v4().to_string(),
150 method: Method::Index {
151 project: project.to_string_lossy().to_string(),
152 force,
153 },
154 };
155
156 let response = send_request(&mut stream, &request)?;
157
158 match response.result {
159 ResponseResult::Index {
160 file_count,
161 chunk_count,
162 elapsed_ms,
163 ..
164 } => Ok((file_count, chunk_count, elapsed_ms)),
165 ResponseResult::Error { message } => Err(Error::DaemonError { message }),
166 _ => Err(Error::DaemonError {
167 message: "Unexpected response type".to_string(),
168 }),
169 }
170}
171
172pub async fn stop() -> Result<bool> {
174 let mut stream = connect_with_timeout(REQUEST_TIMEOUT)?;
175
176 let request = Request {
177 id: uuid::Uuid::new_v4().to_string(),
178 method: Method::Stop,
179 };
180
181 let response = send_request(&mut stream, &request)?;
182
183 match response.result {
184 ResponseResult::Stop { success } => Ok(success),
185 ResponseResult::Error { message } => Err(Error::DaemonError { message }),
186 _ => Err(Error::DaemonError {
187 message: "Unexpected response type".to_string(),
188 }),
189 }
190}
191
192pub async fn status() -> Result<(bool, u32, Vec<crate::daemon::protocol::ProjectInfo>)> {
194 let mut stream = connect_with_timeout(REQUEST_TIMEOUT)?;
195
196 let request = Request {
197 id: uuid::Uuid::new_v4().to_string(),
198 method: Method::Status,
199 };
200
201 let response = send_request(&mut stream, &request)?;
202
203 match response.result {
204 ResponseResult::Status {
205 running,
206 pid,
207 projects,
208 } => Ok((running, pid, projects)),
209 ResponseResult::Error { message } => Err(Error::DaemonError { message }),
210 _ => Err(Error::DaemonError {
211 message: "Unexpected response type".to_string(),
212 }),
213 }
214}