pueue_lib/
state.rs

1//! The representation of the pueue daemon's current [State].
2//! Contains all [`Task`]s and [`Group`]s of the daemon.
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::task::Task;
8
9pub const PUEUE_DEFAULT_GROUP: &str = "default";
10
11/// Represents the current status of a group.
12/// Each group acts as a queue and can be managed individually.
13#[derive(PartialEq, Eq, Clone, Debug, Copy, Deserialize, Serialize)]
14pub enum GroupStatus {
15    Running,
16    Paused,
17    // This state is set, if this group is being reset.
18    // This means that all tasks are being killed and removed.
19    Reset,
20}
21
22/// The representation of a group.
23#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)]
24pub struct Group {
25    pub status: GroupStatus,
26    pub parallel_tasks: usize,
27}
28
29/// This is the full representation of the current state of the Pueue daemon.
30///
31/// This includes
32/// - The currently used settings.
33/// - The full task list
34/// - The current status of all tasks
35/// - All known groups.
36///
37/// However, the State does NOT include:
38/// - Information about child processes
39/// - Handles to child processes
40///
41/// That information is saved in the daemon's TaskHandler.
42///
43/// Most functions implemented on the state shouldn't be used by third party software.
44/// The daemon is constantly changing and persisting the state. \
45/// Any changes applied to a state and saved to disk, will most likely be overwritten
46/// after a short time.
47///
48///
49/// The daemon uses the state as a piece of shared memory between it's threads.
50/// It's wrapped in a MutexGuard, which allows us to guarantee sequential access to any crucial
51/// information, such as status changes and incoming commands by the client.
52#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
53pub struct State {
54    /// All tasks currently managed by the daemon.
55    pub tasks: BTreeMap<usize, Task>,
56    /// All groups with their current state a configuration.
57    pub groups: BTreeMap<String, Group>,
58}
59
60// Implement a custom Clone, as the child processes don't implement Clone.
61impl Clone for State {
62    fn clone(&self) -> Self {
63        State {
64            tasks: self.tasks.clone(),
65            groups: self.groups.clone(),
66        }
67    }
68}
69
70/// A little helper struct that's returned by the state's task filter functions.
71/// Contains all task ids of tasks that matched and didn't match a given condition.
72#[derive(Debug, Default)]
73pub struct FilteredTasks {
74    pub matching_ids: Vec<usize>,
75    pub non_matching_ids: Vec<usize>,
76}
77
78impl State {
79    /// Create a new default state.
80    pub fn new() -> State {
81        Self::default()
82    }
83
84    /// Add a new task
85    pub fn add_task(&mut self, mut task: Task) -> usize {
86        let next_id = match self.tasks.keys().max() {
87            None => 0,
88            Some(id) => id + 1,
89        };
90        task.id = next_id;
91        self.tasks.insert(next_id, task);
92
93        next_id
94    }
95
96    /// Get all ids of task inside a specific group.
97    pub fn task_ids_in_group(&self, group: &str) -> Vec<usize> {
98        self.tasks
99            .iter()
100            .filter(|(_, task)| task.group.eq(group))
101            .map(|(id, _)| *id)
102            .collect()
103    }
104
105    /// This checks, whether some tasks match the expected filter criteria. \
106    /// The first result is the list of task_ids that match these statuses. \
107    /// The second result is the list of task_ids that don't match these statuses. \
108    ///
109    /// By default, this checks all tasks in the current state. If a list of task_ids is
110    /// provided as the third parameter, only those tasks will be checked.
111    pub fn filter_tasks<F>(&self, condition: F, task_ids: Option<Vec<usize>>) -> FilteredTasks
112    where
113        F: Fn(&Task) -> bool,
114    {
115        // Either use all tasks or only the explicitly specified ones.
116        let task_ids = match task_ids {
117            Some(ids) => ids,
118            None => self.tasks.keys().cloned().collect(),
119        };
120
121        self.filter_task_ids(condition, task_ids)
122    }
123
124    /// Same as [State::filter_tasks], but only checks for tasks of a specific group.
125    pub fn filter_tasks_of_group<F>(&self, condition: F, group: &str) -> FilteredTasks
126    where
127        F: Fn(&Task) -> bool,
128    {
129        // Return empty vectors, if there's no such group.
130        if !self.groups.contains_key(group) {
131            return FilteredTasks::default();
132        }
133
134        // Filter all task ids of tasks that match the given group.
135        let task_ids = self
136            .tasks
137            .iter()
138            .filter(|(_, task)| task.group == group)
139            .map(|(id, _)| *id)
140            .collect();
141
142        self.filter_task_ids(condition, task_ids)
143    }
144
145    /// Internal function used to check which of the given tasks match the provided filter.
146    ///
147    /// Returns a tuple of all (matching_task_ids, non_matching_task_ids).
148    fn filter_task_ids<F>(&self, condition: F, task_ids: Vec<usize>) -> FilteredTasks
149    where
150        F: Fn(&Task) -> bool,
151    {
152        let mut matching_ids = Vec::new();
153        let mut non_matching_ids = Vec::new();
154
155        // Filter all task id's that match the provided statuses.
156        for task_id in task_ids.iter() {
157            // Check whether the task exists and save all non-existing task ids.
158            match self.tasks.get(task_id) {
159                None => {
160                    non_matching_ids.push(*task_id);
161                    continue;
162                }
163                Some(task) => {
164                    // Check whether the task status matches the filter.
165                    if condition(task) {
166                        matching_ids.push(*task_id);
167                    } else {
168                        non_matching_ids.push(*task_id);
169                    }
170                }
171            };
172        }
173
174        FilteredTasks {
175            matching_ids,
176            non_matching_ids,
177        }
178    }
179}