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}