use crate::command_client::CommandClient;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PendingOp {
Initiate,
Cancel,
}
#[derive(Debug, Clone)]
pub struct Shutdown {
pub busy: bool,
pub done: bool,
pub error: bool,
pub error_message: String,
pending_tid: Option<u32>,
pending_op: Option<PendingOp>,
}
impl Shutdown {
pub fn new() -> Self {
Self {
busy: false,
done: false,
error: false,
error_message: String::new(),
pending_tid: None,
pending_op: None,
}
}
pub fn initiate(&mut self, client: &mut CommandClient) {
if self.busy {
return;
}
let tid = client.send("system.full_shutdown", serde_json::json!({}));
self.pending_tid = Some(tid);
self.pending_op = Some(PendingOp::Initiate);
self.busy = true;
self.done = false;
self.error = false;
self.error_message.clear();
}
pub fn cancel(&mut self, client: &mut CommandClient) {
if self.busy {
return;
}
let tid = client.send("system.cancel_full_shutdown", serde_json::json!({}));
self.pending_tid = Some(tid);
self.pending_op = Some(PendingOp::Cancel);
self.busy = true;
self.done = false;
self.error = false;
self.error_message.clear();
}
pub fn call(&mut self, client: &mut CommandClient) {
if self.done || self.error {
self.done = false;
self.error = false;
self.error_message.clear();
}
let tid = match self.pending_tid {
Some(tid) => tid,
None => return,
};
if let Some(response) = client.take_response(tid) {
self.busy = false;
self.pending_tid = None;
self.pending_op = None;
if response.success {
self.done = true;
} else {
self.error = true;
self.error_message = response.error_message;
}
}
}
pub fn is_initiating(&self) -> bool {
self.pending_op == Some(PendingOp::Initiate)
}
pub fn is_cancelling(&self) -> bool {
self.pending_op == Some(PendingOp::Cancel)
}
}
impl Default for Shutdown {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use mechutil::ipc::CommandMessage;
use serde_json::json;
use tokio::sync::mpsc;
fn make_client() -> (CommandClient, mpsc::UnboundedSender<CommandMessage>) {
let (write_tx, _write_rx) = mpsc::unbounded_channel();
let (response_tx, response_rx) = mpsc::unbounded_channel();
(CommandClient::new(write_tx, response_rx), response_tx)
}
#[test]
fn test_initiate_sends_command() {
let (mut client, _response_tx) = make_client();
let mut shutdown = Shutdown::new();
assert!(!shutdown.busy);
shutdown.initiate(&mut client);
assert!(shutdown.busy);
assert!(shutdown.is_initiating());
assert!(!shutdown.is_cancelling());
assert_eq!(client.pending_count(), 1);
}
#[test]
fn test_cancel_sends_command() {
let (mut client, _response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.cancel(&mut client);
assert!(shutdown.busy);
assert!(shutdown.is_cancelling());
assert!(!shutdown.is_initiating());
assert_eq!(client.pending_count(), 1);
}
#[test]
fn test_ignores_while_busy() {
let (mut client, _response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.initiate(&mut client);
assert_eq!(client.pending_count(), 1);
shutdown.initiate(&mut client);
assert_eq!(client.pending_count(), 1);
shutdown.cancel(&mut client);
assert_eq!(client.pending_count(), 1);
}
#[test]
fn test_success_response() {
let (mut client, response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.initiate(&mut client);
let tid = shutdown.pending_tid.unwrap();
response_tx.send(CommandMessage::response(tid, json!({"status": "shutdown_scheduled"}))).unwrap();
client.poll();
shutdown.call(&mut client);
assert!(!shutdown.busy);
assert!(shutdown.done);
assert!(!shutdown.error);
}
#[test]
fn test_error_response() {
let (mut client, response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.initiate(&mut client);
let tid = shutdown.pending_tid.unwrap();
let mut resp = CommandMessage::response(tid, json!(null));
resp.success = false;
resp.error_message = "Shutdown already scheduled".to_string();
response_tx.send(resp).unwrap();
client.poll();
shutdown.call(&mut client);
assert!(!shutdown.busy);
assert!(!shutdown.done);
assert!(shutdown.error);
assert_eq!(shutdown.error_message, "Shutdown already scheduled");
}
#[test]
fn test_done_clears_after_one_cycle() {
let (mut client, response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.initiate(&mut client);
let tid = shutdown.pending_tid.unwrap();
response_tx.send(CommandMessage::response(tid, json!({"status": "shutdown_scheduled"}))).unwrap();
client.poll();
shutdown.call(&mut client);
assert!(shutdown.done);
shutdown.call(&mut client);
assert!(!shutdown.done);
}
#[test]
fn test_cancel_after_initiate_completes() {
let (mut client, response_tx) = make_client();
let mut shutdown = Shutdown::new();
shutdown.initiate(&mut client);
let tid1 = shutdown.pending_tid.unwrap();
response_tx.send(CommandMessage::response(tid1, json!({"status": "shutdown_scheduled"}))).unwrap();
client.poll();
shutdown.call(&mut client);
assert!(shutdown.done);
shutdown.call(&mut client);
shutdown.cancel(&mut client);
assert!(shutdown.busy);
assert!(shutdown.is_cancelling());
let tid2 = shutdown.pending_tid.unwrap();
response_tx.send(CommandMessage::response(tid2, json!({"status": "shutdown_cancelled"}))).unwrap();
client.poll();
shutdown.call(&mut client);
assert!(shutdown.done);
assert!(!shutdown.busy);
}
}