1#![allow(dead_code)]
2use std::{collections::HashMap, path::PathBuf};
3
4use crate::{core::watcher::WatchContext, log::logger::Logger};
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7use tokio::{fs, sync::RwLock};
8
9#[doc = include_str!("docs/app_state.md")]
10#[derive(Default)]
11pub struct AppState {
12 pub watches: RwLock<HashMap<String, WatchContext>>,
13}
14
15impl AppState {
16 pub async fn load_from_disk() -> Result<Self, anyhow::Error> {
17 let registry = AppState::load_watches().await?;
18 let mut watches: HashMap<String, WatchContext> = HashMap::new();
19
20 for mut ctx in registry.projects {
21 let logger = Logger::new(&ctx.log_path()).await?;
22 ctx.logger = logger;
23 watches.insert(ctx.id.clone(), ctx);
24 }
25
26 Ok(Self {
27 watches: RwLock::new(watches),
28 })
29 }
30
31 pub async fn save_to_disk(&self) -> Result<()> {
32 let guard = self.watches.read().await;
33 let registry = WatchRegistry {
34 projects: guard.values().cloned().collect(),
35 };
36 save_watches(®istry).await
37 }
38
39 pub async fn init_watch_file() -> Result<()> {
44 let path = get_watch_path();
45 tracing::info!(
46 target: "fleet",
47 message = %format!("watch file at: {}", path.to_str().unwrap()),
48 );
49 if !fs::try_exists(&path).await? {
50 if let Some(parent) = path.parent() {
51 fs::create_dir_all(parent).await?;
52 }
53 let empty = WatchRegistry::default();
54 let json = serde_json::to_string_pretty(&empty)?;
55 fs::write(path, json).await?;
56 }
57 Ok(())
58 }
59
60 pub async fn load_watches() -> Result<WatchRegistry> {
62 let path = get_watch_path();
63 let data = fs::read_to_string(&path).await?;
64 let watches = serde_json::from_str(&data)?;
65 Ok(watches)
66 }
67
68 pub async fn add_watch(ctx: &WatchContext) -> Result<()> {
70 let mut watches = AppState::load_watches().await?;
71
72 if let Some(existing) = watches
73 .projects
74 .iter_mut()
75 .find(|p| p.project_dir == ctx.project_dir)
76 {
77 *existing = ctx.clone();
78 } else {
79 watches.projects.push(ctx.clone());
80 }
81
82 save_watches(&watches).await?;
83 Ok(())
84 }
85
86 pub async fn remove_watch_by_id(id: &str) -> Result<()> {
88 let mut watches = AppState::load_watches().await?;
89 watches.projects.retain(|p| p.id != id); save_watches(&watches).await?;
91 Ok(())
92 }
93}
94
95#[derive(Serialize, Deserialize, Clone, Default)]
96pub struct WatchRegistry {
97 pub projects: Vec<WatchContext>,
98}
99
100pub async fn get_id_by_name(name: &str) -> Result<Option<String>> {
101 let watches = AppState::load_watches().await?;
102 Ok(watches
103 .projects
104 .iter()
105 .find(|p| p.repo.name == name)
106 .map(|p| p.id.clone()))
107}
108
109pub async fn get_name_by_id(id: &str) -> Result<Option<String>> {
110 let watches = AppState::load_watches().await?;
111 Ok(watches
112 .projects
113 .iter()
114 .find(|p| p.id == id)
115 .map(|p| p.repo.name.clone()))
116}
117
118fn get_watch_path() -> PathBuf {
123 let path = dirs::data_local_dir()
124 .unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory"));
125 path.join("fleetd").join("watches.json")
126}
127
128async fn save_watches(watches: &WatchRegistry) -> Result<()> {
129 let path = get_watch_path();
130 let json = serde_json::to_string_pretty(watches)?;
131 fs::write(path, json).await?;
132 Ok(())
133}