diff --git a/CHANGELOG.md b/CHANGELOG.md index be2c3166..9536b51e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ TLDR: The new task state representation is more verbose but significantly cleane ### Add - Add `--all` and `--group` to `pueue log`. [#509](https://github.com/Nukesor/pueue/issues/509) +- Add `--all` and `--group` to `pueue enqueue`. [#558](https://github.com/Nukesor/pueue/issues/558) +- Add `--all` and `--group` to `pueue stash`. [#558](https://github.com/Nukesor/pueue/issues/558) - Add `pueue reset --groups [group_names]` to allow resetting individual groups. [#482](https://github.com/Nukesor/pueue/issues/482) \ This also refactors the way resets are done internally, resulting in a cleaner code architecture. - Ability to set the Unix socket permissions through the new `unix_socket_permissions` configuration option. [#544](https://github.com/Nukesor/pueue/pull/544) diff --git a/docs/Pueue State Diagram.svg b/docs/Pueue State Diagram.svg new file mode 100644 index 00000000..cbc23436 --- /dev/null +++ b/docs/Pueue State Diagram.svg @@ -0,0 +1,4 @@ + + + +
explicit start
or task scheduler
Queued
explicit start
Stashed
Success,
Failure,
manual kill,
Running
Paused
Locked
add
add --stashed
stash
explicit enqueue
or scheduled enqueue
Edit
Edit
pause
start
restart
Done
restart --stashed
\ No newline at end of file diff --git a/pueue/src/client/cli.rs b/pueue/src/client/cli.rs index 463f623b..df2d2096 100644 --- a/pueue/src/client/cli.rs +++ b/pueue/src/client/cli.rs @@ -96,10 +96,23 @@ pub enum SubCommand { /// You have to enqueue them or start them by hand. Stash { /// Stash these specific tasks. - #[arg(required = true)] task_ids: Vec, + + /// Stash all queued tasks in a group + #[arg(short, long, conflicts_with = "all")] + group: Option, + + /// Stash all queued tasks across all groups. + #[arg(short, long)] + all: bool, + + /// Delay enqueuing these tasks until 'delay' elapses. See DELAY FORMAT below. + #[arg(name = "delay", short, long, value_parser = parse_delay_until)] + delay_until: Option>, }, /// Enqueue stashed tasks. They'll be handled normally afterwards. + /// + /// Enqueues all stashed task in the default group if no arguments are given. #[command(after_help = "DELAY FORMAT: The --delay argument must be either a number of seconds or a \"date expression\" similar to GNU \ @@ -126,6 +139,14 @@ pub enum SubCommand { /// Enqueue these specific tasks. task_ids: Vec, + /// Enqueue all stashed tasks in a group + #[arg(short, long, conflicts_with = "all")] + group: Option, + + /// Enqueue all stashed tasks across all groups. + #[arg(short, long)] + all: bool, + /// Delay enqueuing these tasks until 'delay' elapses. See DELAY FORMAT below. #[arg(name = "delay", short, long, value_parser = parse_delay_until)] delay_until: Option>, diff --git a/pueue/src/client/client.rs b/pueue/src/client/client.rs index d20bce36..cbbe54da 100644 --- a/pueue/src/client/client.rs +++ b/pueue/src/client/client.rs @@ -456,7 +456,19 @@ impl Client { } Message::Remove(task_ids.clone()) } - SubCommand::Stash { task_ids } => Message::Stash(task_ids.clone()), + SubCommand::Stash { + task_ids, + group, + all, + delay_until, + } => { + let selection = selection_from_params(*all, group, task_ids); + StashMessage { + tasks: selection, + enqueue_at: *delay_until, + } + .into() + } SubCommand::Switch { task_id_1, task_id_2, @@ -467,10 +479,15 @@ impl Client { .into(), SubCommand::Enqueue { task_ids, + group, + all, delay_until, - } => EnqueueMessage { - task_ids: task_ids.clone(), - enqueue_at: *delay_until, + } => { + let selection = selection_from_params(*all, group, task_ids); + EnqueueMessage { + tasks: selection, + enqueue_at: *delay_until, + } } .into(), SubCommand::Start { diff --git a/pueue/src/daemon/network/message_handler/add.rs b/pueue/src/daemon/network/message_handler/add.rs index 0846eff6..8310b3bf 100644 --- a/pueue/src/daemon/network/message_handler/add.rs +++ b/pueue/src/daemon/network/message_handler/add.rs @@ -83,7 +83,7 @@ pub fn add_task(settings: &Settings, state: &SharedState, message: AddMessage) - let mut response = if message.print_task_id { task_id.to_string() } else if let Some(enqueue_at) = message.enqueue_at { - let enqueue_at = enqueue_at.format("%Y-%m-%d %H:%M:%S"); + let enqueue_at = format_datetime(settings, &enqueue_at); format!("New task added (id {task_id}). It will be enqueued at {enqueue_at}") } else { format!("New task added (id {task_id}).") diff --git a/pueue/src/daemon/network/message_handler/enqueue.rs b/pueue/src/daemon/network/message_handler/enqueue.rs index a1dfb663..e3d8c72c 100644 --- a/pueue/src/daemon/network/message_handler/enqueue.rs +++ b/pueue/src/daemon/network/message_handler/enqueue.rs @@ -1,25 +1,62 @@ use chrono::Local; -use pueue_lib::network::message::*; -use pueue_lib::state::SharedState; -use pueue_lib::task::TaskStatus; +use pueue_lib::{ + network::message::*, settings::Settings, state::SharedState, success_msg, task::TaskStatus, +}; use crate::daemon::network::response_helper::*; +use super::format_datetime; + /// Invoked when calling `pueue enqueue`. /// Enqueue specific stashed tasks. -pub fn enqueue(state: &SharedState, message: EnqueueMessage) -> Message { +pub fn enqueue(settings: &Settings, state: &SharedState, message: EnqueueMessage) -> Message { let mut state = state.lock().unwrap(); - let filtered_tasks = state.filter_tasks( - |task| { - matches!( - task.status, - TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } - ) - }, - Some(message.task_ids), - ); - - for task_id in &filtered_tasks.matching_ids { + // Get the affected task ids, based on the task selection. + let selected_task_ids = match message.tasks { + TaskSelection::TaskIds(ref task_ids) => state + .tasks + .iter() + .filter(|(task_id, task)| { + if !task_ids.contains(task_id) { + return false; + } + + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + TaskSelection::Group(ref group) => state + .tasks + .iter() + .filter(|(_, task)| { + if task.group != *group { + return false; + } + + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + TaskSelection::All => state + .tasks + .iter() + .filter(|(_, task)| { + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + }; + + for task_id in &selected_task_ids { // We just checked that they're there and the state is locked. It's safe to unwrap. let task = state.tasks.get_mut(task_id).expect("Task should be there."); @@ -36,12 +73,48 @@ pub fn enqueue(state: &SharedState, message: EnqueueMessage) -> Message { } } - let text = if let Some(enqueue_at) = message.enqueue_at { - let enqueue_at = enqueue_at.format("%Y-%m-%d %H:%M:%S"); - format!("Tasks will be enqueued at {enqueue_at}") - } else { - String::from("Tasks are enqueued") - }; + // Construct a response depending on the selected tasks. + if let Some(enqueue_at) = &message.enqueue_at { + let enqueue_at = format_datetime(settings, enqueue_at); - compile_task_response(&text, filtered_tasks) + match &message.tasks { + TaskSelection::TaskIds(task_ids) => task_action_response_helper( + &format!("Stashed tasks will be enqueued at {enqueue_at}"), + task_ids.clone(), + |task| { + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }, + &state, + ), + TaskSelection::Group(group) => { + success_msg!("Enqueue stashed tasks of group {group} at {enqueue_at}.",) + } + TaskSelection::All => { + success_msg!("Enqueue all stashed tasks at {enqueue_at}.",) + } + } + } else { + match &message.tasks { + TaskSelection::TaskIds(task_ids) => task_action_response_helper( + "Stashed tasks have been enqueued", + task_ids.clone(), + |task| { + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }, + &state, + ), + TaskSelection::Group(group) => { + success_msg!("All stashed tasks of group \"{group}\" have been enqueued.") + } + TaskSelection::All => { + success_msg!("All stashed tasks have been enqueued.") + } + } + } } diff --git a/pueue/src/daemon/network/message_handler/mod.rs b/pueue/src/daemon/network/message_handler/mod.rs index f7404609..2dd2d781 100644 --- a/pueue/src/daemon/network/message_handler/mod.rs +++ b/pueue/src/daemon/network/message_handler/mod.rs @@ -1,5 +1,6 @@ use std::fmt::Display; +use chrono::{DateTime, Local}; use pueue_lib::failure_msg; use pueue_lib::network::message::*; use pueue_lib::settings::Settings; @@ -31,7 +32,7 @@ pub fn handle_message(message: Message, state: &SharedState, settings: &Settings Message::Edit(message) => edit::edit(settings, state, message), Message::EditRequest(task_id) => edit::edit_request(state, task_id), Message::EditRestore(task_id) => edit::edit_restore(state, task_id), - Message::Enqueue(message) => enqueue::enqueue(state, message), + Message::Enqueue(message) => enqueue::enqueue(settings, state, message), Message::Group(message) => group::group(settings, state, message), Message::Kill(message) => kill::kill(settings, state, message), Message::Log(message) => log::get_log(settings, state, message), @@ -42,7 +43,7 @@ pub fn handle_message(message: Message, state: &SharedState, settings: &Settings Message::Restart(message) => restart::restart_multiple(settings, state, message), Message::Send(message) => send::send(state, message), Message::Start(message) => start::start(settings, state, message), - Message::Stash(task_ids) => stash::stash(state, task_ids), + Message::Stash(message) => stash::stash(settings, state, message), Message::Switch(message) => switch::switch(settings, state, message), Message::Status => get_status(state), _ => create_failure_message("Not yet implemented"), @@ -56,6 +57,16 @@ fn get_status(state: &SharedState) -> Message { Message::StatusResponse(Box::new(state)) } +// If the enqueue at time is today, only show the time. Otherwise, include the date. +fn format_datetime(settings: &Settings, enqueue_at: &DateTime) -> String { + let format_string = if enqueue_at.date_naive() == Local::now().date_naive() { + &settings.client.status_time_format + } else { + &settings.client.status_datetime_format + }; + enqueue_at.format(format_string).to_string() +} + fn ok_or_failure_message(result: Result) -> Result { match result { Ok(inner) => Ok(inner), diff --git a/pueue/src/daemon/network/message_handler/pause.rs b/pueue/src/daemon/network/message_handler/pause.rs index e9f11860..428ca26d 100644 --- a/pueue/src/daemon/network/message_handler/pause.rs +++ b/pueue/src/daemon/network/message_handler/pause.rs @@ -20,7 +20,7 @@ pub fn pause(settings: &Settings, state: &SharedState, message: PauseMessage) -> // Construct a response depending on the selected tasks. let response = match &message.tasks { TaskSelection::TaskIds(task_ids) => task_action_response_helper( - "Tasks are being paused", + "Tasks have been paused", task_ids.clone(), |task| matches!(task.status, TaskStatus::Running { .. }), &state, @@ -28,7 +28,7 @@ pub fn pause(settings: &Settings, state: &SharedState, message: PauseMessage) -> TaskSelection::Group(group) => { success_msg!("Group \"{group}\" is being paused.") } - TaskSelection::All => success_msg!("All queues are being paused."), + TaskSelection::All => success_msg!("All groups are being paused."), }; // Actually execute the command diff --git a/pueue/src/daemon/network/message_handler/restart.rs b/pueue/src/daemon/network/message_handler/restart.rs index ba4b0f82..2d337dab 100644 --- a/pueue/src/daemon/network/message_handler/restart.rs +++ b/pueue/src/daemon/network/message_handler/restart.rs @@ -26,7 +26,7 @@ pub fn restart_multiple( // We have to compile the response beforehand. // Otherwise we no longer know which tasks, were actually capable of being being restarted. let response = task_action_response_helper( - "Tasks restarted", + "Tasks has restarted", task_ids.clone(), |task| task.is_done(), &state, diff --git a/pueue/src/daemon/network/message_handler/start.rs b/pueue/src/daemon/network/message_handler/start.rs index 4428376e..838aadc3 100644 --- a/pueue/src/daemon/network/message_handler/start.rs +++ b/pueue/src/daemon/network/message_handler/start.rs @@ -20,7 +20,7 @@ pub fn start(settings: &Settings, state: &SharedState, message: StartMessage) -> let response = match &message.tasks { TaskSelection::TaskIds(task_ids) => task_action_response_helper( - "Tasks are being started", + "Tasks have been started/resumed", task_ids.clone(), |task| { matches!( @@ -35,7 +35,7 @@ pub fn start(settings: &Settings, state: &SharedState, message: StartMessage) -> TaskSelection::Group(group) => { success_msg!("Group \"{group}\" is being resumed.") } - TaskSelection::All => success_msg!("All queues are being resumed."), + TaskSelection::All => success_msg!("All groups are being resumed."), }; if let Message::Success(_) = response { diff --git a/pueue/src/daemon/network/message_handler/stash.rs b/pueue/src/daemon/network/message_handler/stash.rs index d01b4505..dcc3356e 100644 --- a/pueue/src/daemon/network/message_handler/stash.rs +++ b/pueue/src/daemon/network/message_handler/stash.rs @@ -1,29 +1,112 @@ -use pueue_lib::network::message::*; -use pueue_lib::state::SharedState; -use pueue_lib::task::TaskStatus; +use pueue_lib::{ + network::message::*, settings::Settings, state::SharedState, success_msg, task::TaskStatus, +}; use crate::daemon::network::response_helper::*; +use super::format_datetime; + /// Invoked when calling `pueue stash`. /// Stash specific queued tasks. /// They won't be executed until they're enqueued or explicitly started. -pub fn stash(state: &SharedState, task_ids: Vec) -> Message { +pub fn stash(settings: &Settings, state: &SharedState, message: StashMessage) -> Message { let mut state = state.lock().unwrap(); - let filtered_tasks = state.filter_tasks( - |task| { - matches!( - task.status, - TaskStatus::Queued { .. } | TaskStatus::Locked { .. } - ) - }, - Some(task_ids), - ); - - for task_id in &filtered_tasks.matching_ids { - if let Some(ref mut task) = state.tasks.get_mut(task_id) { - task.status = TaskStatus::Stashed { enqueue_at: None }; - } + // Get the affected task ids, based on the task selection. + let selected_task_ids = match message.tasks { + TaskSelection::TaskIds(ref task_ids) => state + .tasks + .iter() + .filter(|(task_id, task)| { + if !task_ids.contains(task_id) { + return false; + } + + matches!( + task.status, + TaskStatus::Queued { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + TaskSelection::Group(ref group) => state + .tasks + .iter() + .filter(|(_, task)| { + if task.group != *group { + return false; + } + + matches!( + task.status, + TaskStatus::Queued { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + TaskSelection::All => state + .tasks + .iter() + .filter(|(_, task)| { + matches!( + task.status, + TaskStatus::Queued { .. } | TaskStatus::Locked { .. } + ) + }) + .map(|(task_id, _)| *task_id) + .collect::>(), + }; + + for task_id in &selected_task_ids { + // We just checked that they're there and the state is locked. It's safe to unwrap. + let task = state.tasks.get_mut(task_id).expect("Task should be there."); + + task.status = TaskStatus::Stashed { + enqueue_at: message.enqueue_at, + }; } - compile_task_response("Tasks are stashed", filtered_tasks) + // Construct a response depending on the selected tasks. + if let Some(enqueue_at) = &message.enqueue_at { + let enqueue_at = format_datetime(settings, enqueue_at); + + match &message.tasks { + TaskSelection::TaskIds(task_ids) => task_action_response_helper( + &format!("Stashed tasks will be enqueued at {enqueue_at}"), + task_ids.clone(), + |task| { + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }, + &state, + ), + TaskSelection::Group(group) => { + success_msg!("Enqueue stashed tasks of group {group} at {enqueue_at}.",) + } + TaskSelection::All => { + success_msg!("Enqueue all stashed tasks at {enqueue_at}.",) + } + } + } else { + match &message.tasks { + TaskSelection::TaskIds(task_ids) => task_action_response_helper( + "Stashed tasks have been enqueued", + task_ids.clone(), + |task| { + matches!( + task.status, + TaskStatus::Stashed { .. } | TaskStatus::Locked { .. } + ) + }, + &state, + ), + TaskSelection::Group(group) => { + success_msg!("All stashed tasks of group \"{group}\" have been enqueued.") + } + TaskSelection::All => { + success_msg!("All stashed tasks have been enqueued.") + } + } + } } diff --git a/pueue/tests/daemon/integration/remove.rs b/pueue/tests/daemon/integration/remove.rs index aa9979a0..6b4e2769 100644 --- a/pueue/tests/daemon/integration/remove.rs +++ b/pueue/tests/daemon/integration/remove.rs @@ -31,7 +31,14 @@ async fn test_normal_remove() -> Result<()> { pause_tasks(shared, TaskSelection::TaskIds(vec![3])).await?; // Stash task 5 - send_message(shared, Message::Stash(vec![5])).await?; + send_message( + shared, + StashMessage { + tasks: TaskSelection::TaskIds(vec![5]), + enqueue_at: None, + }, + ) + .await?; let remove_message = Message::Remove(vec![0, 1, 2, 3, 4, 5]); send_message(shared, remove_message).await?; diff --git a/pueue/tests/daemon/integration/stashed.rs b/pueue/tests/daemon/integration/stashed.rs index abf85d97..beee041a 100644 --- a/pueue/tests/daemon/integration/stashed.rs +++ b/pueue/tests/daemon/integration/stashed.rs @@ -57,7 +57,7 @@ async fn test_enqueued_tasks( // Manually enqueue the task let enqueue_message = EnqueueMessage { - task_ids: vec![0], + tasks: TaskSelection::TaskIds(vec![0]), enqueue_at: None, }; send_message(shared, enqueue_message) @@ -110,9 +110,15 @@ async fn test_stash_queued_task() -> Result<()> { add_task(shared, "sleep 10").await?; // Stash the task - send_message(shared, Message::Stash(vec![0])) - .await - .context("Failed to send STash message")?; + send_message( + shared, + StashMessage { + tasks: TaskSelection::TaskIds(vec![0]), + enqueue_at: None, + }, + ) + .await + .context("Failed to send STash message")?; let task = get_task(shared, 0).await?; assert_eq!(task.status, TaskStatus::Stashed { enqueue_at: None }); diff --git a/pueue_lib/src/network/message.rs b/pueue_lib/src/network/message.rs index eb828839..20ba1d2e 100644 --- a/pueue_lib/src/network/message.rs +++ b/pueue_lib/src/network/message.rs @@ -44,7 +44,7 @@ pub enum Message { Add(AddMessage), Remove(Vec), Switch(SwitchMessage), - Stash(Vec), + Stash(StashMessage), Enqueue(EnqueueMessage), Start(StartMessage), @@ -149,9 +149,17 @@ pub struct SwitchMessage { impl_into_message!(SwitchMessage, Message::Switch); +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +pub struct StashMessage { + pub tasks: TaskSelection, + pub enqueue_at: Option>, +} + +impl_into_message!(StashMessage, Message::Stash); + #[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] pub struct EnqueueMessage { - pub task_ids: Vec, + pub tasks: TaskSelection, pub enqueue_at: Option>, }