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