1#![allow(dead_code)]
2use std::sync::Arc;
3
4use crate::{
5 config::ProjectConfig,
6 core::{
7 id::short_id,
8 manager::get_watch_ctx,
9 state::{AppState, get_id_by_name, get_name_by_id},
10 watcher::{WatchContext, WatchContextBuilder},
11 },
12 daemon::utils::extract_repo_path,
13 exec::{metrics::ExecMetrics, pipeline::run_pipeline},
14 git::repo::Repo,
15 log::logger::Logger,
16};
17use anyhow::Result;
18use serde::{Deserialize, Serialize};
19use tokio::{
20 fs::{File, OpenOptions},
21 io::{AsyncWriteExt, WriteHalf},
22 net::UnixStream,
23};
24
25#[derive(Serialize, Deserialize, Debug, PartialEq)]
26#[serde(tag = "action")]
27pub enum DaemonRequest {
28 #[serde(rename = "add_watch")]
29 AddWatch {
30 project_dir: String,
31 repo: Box<Repo>,
33 config: Box<ProjectConfig>,
34 },
35
36 #[serde(rename = "run_pipeline")]
37 RunPipeline {
38 id: String,
39 },
40
41 #[serde(rename = "stop_watch")]
42 StopWatch {
43 id: String,
44 },
45
46 #[serde(rename = "up_watch")]
47 UpWatch {
48 id: String,
49 },
50
51 #[serde(rename = "rm_watch")]
52 RmWatch {
53 id: String,
54 },
55
56 #[serde(rename = "list_watch")]
57 ListWatches {
58 all: bool,
59 },
60
61 #[serde(rename = "logs_watch")]
62 LogsWatches {
63 id: String,
64 f: bool,
65 },
66
67 None,
68}
69
70#[derive(Serialize, Deserialize, Debug, PartialEq)]
71pub struct WatchInfo {
72 pub branch: String,
73 pub project_dir: String,
74 pub short_commit: String,
75 pub short_url: String,
76 pub repo_name: String,
77 pub id: String,
78 pub paused: bool,
79}
80
81#[derive(Serialize, Deserialize, Debug, PartialEq)]
82pub enum DaemonResponse {
83 Success(String),
84 Error(String),
85 ListWatches(Vec<WatchInfo>),
86 LogWatch(String, bool),
87 Ignore,
88 None,
89}
90
91pub async fn get_log_file(ctx: &WatchContext) -> Result<File> {
92 let log_path = ctx.log_path();
93 let log_file = OpenOptions::new()
94 .create(true)
95 .append(true)
96 .open(&log_path)
97 .await?;
98 Ok(log_file)
99}
100
101async fn get_logs_by_id(id: &str) -> Result<String> {
102 let log_path = WatchContext::log_path_by_id(id);
103
104 match log_path.to_str() {
105 Some(p) => Ok(String::from(p)),
106 None => Err(anyhow::anyhow!("Failed to find log path")),
107 }
108}
109
110pub async fn handle_request(
113 req: DaemonRequest,
114 state: Arc<AppState>,
115 stream: &mut WriteHalf<UnixStream>,
116) -> Result<(), anyhow::Error> {
117 let response = match req {
118 DaemonRequest::AddWatch {
119 project_dir,
120 repo,
121 config,
122 } => handle_add_watch(state, project_dir, *repo, *config).await?,
123
124 DaemonRequest::StopWatch { id } => handle_stop_watch(state, id).await,
125
126 DaemonRequest::UpWatch { id } => handle_up_watch(state, id).await,
127
128 DaemonRequest::RmWatch { id } => handle_rm_watch(state, id).await,
129
130 DaemonRequest::ListWatches { all } => handle_list_watches(state, all).await,
131
132 DaemonRequest::LogsWatches { id, f } => handle_logs_watches(id, f).await,
133
134 DaemonRequest::RunPipeline { id } => {
135 handle_run_pipeline(&id, state, stream).await?;
136 DaemonResponse::Ignore
137 }
138 DaemonRequest::None => DaemonResponse::None,
139 };
140
141 if response != DaemonResponse::Ignore {
142 send_response(stream, response).await?;
143 }
144 Ok(())
145}
146
147async fn handle_run_pipeline(
148 id: &str,
149 state: Arc<AppState>,
150 stream: &mut WriteHalf<UnixStream>,
151) -> anyhow::Result<()> {
152 if let Some(ctx) = get_watch_ctx(&state, id).await {
153 send_response(
154 stream,
155 DaemonResponse::Success(format!("Pipeline {id} has been runed")),
156 )
157 .await?;
158 match run_pipeline(Arc::new(ctx)).await {
159 Ok(_) => {
160 tracing::info!(
161 target: "fleet",
162 log_path = %crate::log::logger::Logger::path_by_id(id).display(),
163 message = "Update succeeded",
164 );
165 }
166 Err(e) => {
167 tracing::error!(
168 target: "fleet",
169 log_path = %crate::log::logger::Logger::path_by_id(id).display(),
170 message = %format!("Update failed => {e}"),
171 );
172 }
173 }
174 }
175 Ok(())
176}
177
178async fn handle_add_watch(
181 state: Arc<AppState>,
182 project_dir: String,
183 repo: Repo,
184 config: ProjectConfig,
185) -> anyhow::Result<DaemonResponse> {
186 let id = {
187 let guard = state.watches.read().await;
188 guard
189 .iter()
190 .find(|(_, ctx)| ctx.project_dir == project_dir)
191 .map(|(id, _ctx)| id.clone())
192 .unwrap_or_else(short_id)
193 };
194
195 let ctx = WatchContextBuilder::new(repo, config, project_dir, id.clone())
196 .build()
197 .await?;
198
199 let result = async {
200 AppState::add_watch(&ctx).await?;
202 {
203 let mut guard = state.watches.write().await;
204 guard.retain(|_, existing_ctx| existing_ctx.project_dir != ctx.project_dir);
205 guard.insert(id.clone(), ctx.clone());
206 }
207 tracing::info!(
208 target: "fleet",
209 log_path = %ctx.log_path().display(),
210 message = %format!("Project registered with ID : {id}"),
211 );
212 Ok::<_, anyhow::Error>(())
213 }
214 .await;
215
216 match result {
217 Ok(_) => Ok(DaemonResponse::Success(format!(
218 "📌 Project registered with ID: {id}"
219 ))),
220 Err(e) => Ok(DaemonResponse::Error(format!("Failed to add watch: {e}"))),
221 }
222}
223
224pub async fn handle_stop_watch(state: Arc<AppState>, id: String) -> DaemonResponse {
226 match async {
227 let updated = {
228 let mut guard = state.watches.write().await;
229 match guard.get_mut(&id) {
230 Some(w) => {
231 w.stop();
232 Some(w.clone())
233 }
234 None => None,
235 }
236 };
237 match updated {
238 Some(w) => {
239 AppState::add_watch(&w).await?;
240 Ok::<_, anyhow::Error>(format!("🛑 Watch stopped for ID: {id}"))
241 }
242 None => Err(anyhow::anyhow!("âš ID not found: {}", id)),
243 }
244 }
245 .await
246 {
247 Ok(msg) => DaemonResponse::Success(msg),
248 Err(e) => DaemonResponse::Error(format!("Failed to stop watch: {e}")),
249 }
250}
251
252pub async fn handle_up_watch(state: Arc<AppState>, id: String) -> DaemonResponse {
254 match async {
255 let updated = {
256 let mut guard = state.watches.write().await;
257 match guard.get_mut(&id) {
258 Some(w) => {
259 w.run();
260 Some(w.clone())
261 }
262 None => None,
263 }
264 };
265 match updated {
266 Some(w) => {
267 AppState::add_watch(&w).await?;
268 Ok::<_, anyhow::Error>(format!("🟢 Watch up for ID: {id}"))
269 }
270 None => Err(anyhow::anyhow!("âš ID not found: {}", id)),
271 }
272 }
273 .await
274 {
275 Ok(msg) => DaemonResponse::Success(msg),
276 Err(e) => DaemonResponse::Error(format!("Failed to stop watch: {e}")),
277 }
278}
279
280pub async fn handle_rm_watch(state: Arc<AppState>, id: String) -> DaemonResponse {
282 match async {
283 let removed = {
284 let mut guard = state.watches.write().await;
285 guard.remove(&id)
286 };
287 if let Some(w) = removed {
288 ExecMetrics::rm_metrics_by_id(&id)?; Logger::rm_logs_by_id(&id)?; AppState::remove_watch_by_id(&id).await?; Ok::<_, anyhow::Error>(format!("Project: {} was deleted", w.repo.name))
293 } else {
294 Err(anyhow::anyhow!("âš ID not found: {}", id))
295 }
296 }
297 .await
298 {
299 Ok(msg) => DaemonResponse::Success(msg),
300 Err(e) => DaemonResponse::Error(format!("Failed to stop watch: {e}")),
301 }
302}
303
304pub async fn handle_list_watches(state: Arc<AppState>, all: bool) -> DaemonResponse {
306 match async {
307 let guard = state.watches.read().await;
308 let result: Result<Vec<WatchInfo>, anyhow::Error> = guard
309 .iter()
310 .filter(|(_, ctx)| all || !ctx.paused) .map(|(id, ctx)| {
312 let short_commit = ctx
313 .repo
314 .branches
315 .last_commit
316 .chars()
317 .take(8)
318 .collect::<String>();
319 let short_url = extract_repo_path(&ctx.repo.remote)?;
320 let short_branch = if ctx.repo.branches.name.len() > 12 {
321 format!("{}...", &ctx.repo.branches.name[..9])
322 } else {
323 ctx.repo.branches.name.clone()
324 };
325 Ok(WatchInfo {
326 branch: short_branch,
327 project_dir: ctx.project_dir.clone(),
328 short_commit,
329 short_url,
330 repo_name: ctx.repo.name.clone(),
331 id: id.clone(),
332 paused: ctx.paused,
333 })
334 })
335 .collect();
336 Ok::<_, anyhow::Error>(DaemonResponse::ListWatches(result?))
337 }
338 .await
339 {
340 Ok(resp) => resp,
341 Err(e) => DaemonResponse::Error(format!("Failed to list watches: {e}")),
342 }
343}
344
345async fn handle_logs_watches(id: String, follow: bool) -> DaemonResponse {
349 match async {
350 let id = match get_name_by_id(&id).await {
351 Ok(Some(_)) => id,
352 Err(_) | Ok(None) => match get_id_by_name(&id).await? {
353 Some(uuid) => uuid,
354 None => anyhow::bail!("No repo with this name exists"),
355 },
356 };
357 let logs = get_logs_by_id(&id).await?;
358 Ok::<_, anyhow::Error>(DaemonResponse::LogWatch(logs, follow))
359 }
360 .await
361 {
362 Ok(resp) => resp,
363 Err(e) => DaemonResponse::Error(format!("Failed to fetch logs: {e}")),
364 }
365}
366
367async fn send_response(
368 stream: &mut WriteHalf<UnixStream>,
369 response: DaemonResponse,
370) -> Result<(), anyhow::Error> {
371 let response_str = serde_json::to_string(&response)? + "\n";
372 stream.write_all(response_str.as_bytes()).await?;
373 stream.flush().await?;
374 Ok(())
375}
376
377async fn send_error_response(
378 stream: &mut WriteHalf<UnixStream>,
379 message: &str,
380) -> Result<(), anyhow::Error> {
381 let response = DaemonResponse::Error(message.to_string());
382 let response_str = serde_json::to_string(&response)? + "\n";
383 stream.write_all(response_str.as_bytes()).await?;
384 stream.flush().await?;
385 Ok(())
386}