Skip to main content

pitchfork_cli/
daemon_list.rs

1use crate::Result;
2use crate::daemon::Daemon;
3use crate::daemon_status::DaemonStatus;
4use crate::ipc::client::IpcClient;
5use crate::pitchfork_toml::PitchforkToml;
6use std::collections::HashSet;
7
8/// Represents a daemon entry that can be either tracked (from state file) or available (from config only)
9#[derive(Debug, Clone)]
10pub struct DaemonListEntry {
11    pub id: String,
12    pub daemon: Daemon,
13    pub is_disabled: bool,
14    pub is_available: bool, // true if daemon is only in config, not in state
15}
16
17/// Get a unified list of all daemons from IPC client and config
18///
19/// This function merges daemons from the state file (including failed daemons) with daemons
20/// defined in config files. Daemons that are only in config (not in state file) are marked
21/// as "available".
22///
23/// This logic is shared across:
24/// - `pitchfork list` command
25/// - TUI daemon list
26///
27/// # Arguments
28/// * `client` - IPC client to communicate with supervisor (used only for disabled list)
29///
30/// # Returns
31/// A vector of daemon entries with their current status
32pub async fn get_all_daemons(client: &IpcClient) -> Result<Vec<DaemonListEntry>> {
33    let config = PitchforkToml::all_merged();
34
35    // Read state file to get all daemons (including failed ones)
36    let state_file = crate::state_file::StateFile::read(&*crate::env::PITCHFORK_STATE_FILE)?;
37    let state_daemons: Vec<Daemon> = state_file.daemons.values().cloned().collect();
38
39    let disabled_daemons = client.get_disabled_daemons().await?;
40    let disabled_set: HashSet<String> = disabled_daemons.into_iter().collect();
41
42    build_daemon_list(state_daemons, disabled_set, config)
43}
44
45/// Get a unified list of all daemons from supervisor directly (for Web UI)
46///
47/// This function is used by the Web UI which runs inside the supervisor process
48/// and can access the supervisor directly without IPC.
49///
50/// # Arguments
51/// * `supervisor` - Reference to the supervisor instance
52///
53/// # Returns
54/// A vector of daemon entries with their current status
55pub async fn get_all_daemons_direct(
56    supervisor: &crate::supervisor::Supervisor,
57) -> Result<Vec<DaemonListEntry>> {
58    let config = PitchforkToml::all_merged();
59
60    // Read all daemons from state file (including failed/stopped ones)
61    // Note: Don't use supervisor.active_daemons() as it only returns daemons with PIDs
62    let state_file = supervisor.state_file.lock().await;
63    let state_daemons: Vec<Daemon> = state_file.daemons.values().cloned().collect();
64    let disabled_set: HashSet<String> = state_file.disabled.clone().into_iter().collect();
65    drop(state_file); // Release lock early
66
67    build_daemon_list(state_daemons, disabled_set, config)
68}
69
70/// Internal helper to build the daemon list from state daemons and config
71fn build_daemon_list(
72    state_daemons: Vec<Daemon>,
73    disabled_set: HashSet<String>,
74    config: PitchforkToml,
75) -> Result<Vec<DaemonListEntry>> {
76    let mut entries = Vec::new();
77    let mut seen_ids = HashSet::new();
78
79    // First, add all daemons from state file
80    for daemon in state_daemons {
81        if daemon.id == "pitchfork" {
82            continue; // Skip supervisor itself
83        }
84
85        seen_ids.insert(daemon.id.clone());
86        entries.push(DaemonListEntry {
87            id: daemon.id.clone(),
88            is_disabled: disabled_set.contains(&daemon.id),
89            is_available: false,
90            daemon,
91        });
92    }
93
94    // Then, add daemons from config that aren't in state file (available daemons)
95    for daemon_id in config.daemons.keys() {
96        if daemon_id == "pitchfork" || seen_ids.contains(daemon_id) {
97            continue;
98        }
99
100        // Create a placeholder daemon for config-only entries
101        let placeholder = Daemon {
102            id: daemon_id.clone(),
103            title: None,
104            pid: None,
105            shell_pid: None,
106            status: DaemonStatus::Stopped,
107            dir: None,
108            cmd: None,
109            autostop: false,
110            cron_schedule: None,
111            cron_retrigger: None,
112            last_cron_triggered: None,
113            last_exit_success: None,
114            retry: 0,
115            retry_count: 0,
116            ready_delay: None,
117            ready_output: None,
118            ready_http: None,
119            ready_port: None,
120            ready_cmd: None,
121            depends: vec![],
122            env: None,
123        };
124
125        entries.push(DaemonListEntry {
126            id: daemon_id.clone(),
127            daemon: placeholder,
128            is_disabled: disabled_set.contains(daemon_id),
129            is_available: true,
130        });
131    }
132
133    Ok(entries)
134}