pueue_lib/
task.rs

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