pueue_lib/
task.rs

1//! Everything regarding Pueue's [Task]s.
2use std::{collections::HashMap, path::PathBuf};
3
4use chrono::prelude::*;
5use serde::{Deserialize, Serialize};
6use strum::Display;
7
8/// This enum represents the status of the internal task handling of Pueue.
9/// They basically represent the internal task life-cycle.
10#[derive(PartialEq, Eq, Clone, Debug, Display, Serialize, Deserialize)]
11pub enum TaskStatus {
12    /// Used while the command of a task is edited (to prevent starting the task)
13    Locked { previous_status: Box<TaskStatus> },
14    /// The task has been manually stashed. It won't be executed until it's manually enqueued
15    Stashed { enqueue_at: Option<DateTime<Local>> },
16    /// The task is queued and waiting for a free slot
17    Queued { enqueued_at: DateTime<Local> },
18    /// The task is started and running
19    Running {
20        enqueued_at: DateTime<Local>,
21        start: DateTime<Local>,
22    },
23    /// A previously running task has been paused
24    Paused {
25        enqueued_at: DateTime<Local>,
26        start: DateTime<Local>,
27    },
28    /// Task finished. The actual result of the task is handled by the [TaskResult] enum.
29    Done {
30        enqueued_at: DateTime<Local>,
31        start: DateTime<Local>,
32        end: DateTime<Local>,
33        result: TaskResult,
34    },
35}
36
37/// This enum represents the exit status of an actually spawned program.
38/// It's only used, once a task finished or failed in some kind of way.
39#[derive(PartialEq, Eq, Clone, Debug, Display, Serialize, Deserialize)]
40pub enum TaskResult {
41    /// Task exited with 0
42    Success,
43    /// The task failed in some other kind of way (error code != 0)
44    Failed(i32),
45    /// The task couldn't be spawned. Probably a typo in the command
46    FailedToSpawn(String),
47    /// Task has been actively killed by either the user or the daemon on shutdown
48    Killed,
49    /// Some kind of IO error. This should barely ever happen. Please check the daemon logs.
50    Errored,
51    /// A dependency of the task failed.
52    DependencyFailed,
53}
54
55/// Representation of a task.
56#[derive(PartialEq, Eq, Clone, Deserialize, Serialize)]
57pub struct Task {
58    pub id: usize,
59    pub created_at: DateTime<Local>,
60    pub original_command: String,
61    pub command: String,
62    pub path: PathBuf,
63    pub envs: HashMap<String, String>,
64    pub group: String,
65    pub dependencies: Vec<usize>,
66    pub priority: i32,
67    pub label: Option<String>,
68    pub status: TaskStatus,
69}
70
71impl Task {
72    #[allow(clippy::too_many_arguments)]
73    pub fn new(
74        original_command: String,
75        path: PathBuf,
76        envs: HashMap<String, String>,
77        group: String,
78        starting_status: TaskStatus,
79        dependencies: Vec<usize>,
80        priority: i32,
81        label: Option<String>,
82    ) -> Task {
83        Task {
84            id: 0,
85            created_at: Local::now(),
86            original_command: original_command.clone(),
87            command: original_command,
88            path,
89            envs,
90            group,
91            dependencies,
92            priority,
93            label,
94            status: starting_status.clone(),
95        }
96    }
97
98    pub fn start_and_end(&self) -> (Option<DateTime<Local>>, Option<DateTime<Local>>) {
99        match self.status {
100            TaskStatus::Running { start, .. } => (Some(start), None),
101            TaskStatus::Paused { start, .. } => (Some(start), None),
102            TaskStatus::Done { start, end, .. } => (Some(start), Some(end)),
103            _ => (None, None),
104        }
105    }
106
107    /// Whether the task is having a running process managed by the TaskHandler
108    pub fn is_running(&self) -> bool {
109        matches!(
110            self.status,
111            TaskStatus::Running { .. } | TaskStatus::Paused { .. }
112        )
113    }
114
115    /// Whether the task is a running, but paused process managed by the TaskHandler.
116    pub fn is_paused(&self) -> bool {
117        matches!(self.status, TaskStatus::Paused { .. })
118    }
119
120    /// Whether the task's process finished.
121    pub fn is_done(&self) -> bool {
122        matches!(self.status, TaskStatus::Done { .. })
123    }
124
125    /// Check if the task errored. \
126    /// It either:
127    /// 1. Finished successfully
128    /// 2. Didn't finish yet.
129    pub fn failed(&self) -> bool {
130        match &self.status {
131            TaskStatus::Done { result, .. } => !matches!(result, TaskResult::Success),
132            _ => false,
133        }
134    }
135
136    /// Convenience helper on whether a task is stashed
137    pub fn is_stashed(&self) -> bool {
138        matches!(self.status, TaskStatus::Stashed { .. })
139    }
140
141    /// Check whether a task is queued or might soon be enqueued.
142    pub fn is_queued(&self) -> bool {
143        matches!(
144            self.status,
145            TaskStatus::Queued { .. }
146                | TaskStatus::Stashed {
147                    enqueue_at: Some(_)
148                }
149        )
150    }
151}
152
153/// We use a custom `Debug` implementation for [Task], as the `envs` field just has too much
154/// info in it and makes the log output much too verbose.
155///
156/// Furthermore, there might be secrets in the environment, resulting in a possible leak if
157/// users copy-paste their log output for debugging.
158impl std::fmt::Debug for Task {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        f.debug_struct("Task")
161            .field("id", &self.id)
162            .field("original_command", &self.original_command)
163            .field("command", &self.command)
164            .field("path", &self.path)
165            .field("envs", &"hidden")
166            .field("group", &self.group)
167            .field("dependencies", &self.dependencies)
168            .field("label", &self.label)
169            .field("status", &self.status)
170            .field("priority", &self.priority)
171            .finish()
172    }
173}