use std::fmt;
use anyhow::{Result, bail};
use clap::{Args, Subcommand};
use io_pimdir::codec::PimdirAction;
use log::warn;
use pimalaya_cli::{
printer::Printer,
table::{Cell, ContentArrangement, Table, presets::UTF8_FULL},
};
use serde::Serialize;
use crate::cli::{StoreFlags, confirm, or_dash, report};
#[derive(Debug, Subcommand)]
pub enum QueueCommand {
List(QueueListCommand),
Cancel(QueueCancelCommand),
}
impl QueueCommand {
pub fn execute(self, printer: &mut impl Printer, store: &StoreFlags) -> Result<()> {
match self {
Self::List(cmd) => cmd.execute(printer, store),
Self::Cancel(cmd) => cmd.execute(printer, store),
}
}
}
#[derive(Debug, Args)]
pub struct QueueListCommand {
#[arg(long)]
pub parked: bool,
}
impl QueueListCommand {
pub fn execute(self, printer: &mut impl Printer, store: &StoreFlags) -> Result<()> {
let store = store.read()?;
let mut rows = Vec::new();
let mut undecodable = Vec::new();
if self.parked {
for action in store.parked_actions().map_err(report)? {
rows.push(QueueRow {
id: action.id,
created_at: action.created_at,
producer: action.producer,
collection: action.collection,
kind: action.action,
summary: action.payload,
attempts: action.attempts,
error: Some(action.error),
});
}
} else {
for collection in store.queued_collections().map_err(report)? {
match store.pending_actions(&collection) {
Ok(actions) => {
for action in actions {
rows.push(QueueRow {
id: action.id,
created_at: action.created_at,
producer: action.producer,
collection: collection.clone(),
kind: action.action.kind().to_string(),
summary: summary(&action.action),
attempts: action.attempts,
error: None,
});
}
}
Err(err) => {
warn!("cannot decode the pending actions of {collection}: {err}");
undecodable.push(collection);
}
}
}
rows.sort_by_key(|row| row.id);
}
printer.out(QueueOutput {
parked: self.parked,
actions: rows,
undecodable,
})
}
}
#[derive(Debug, Args)]
pub struct QueueCancelCommand {
#[arg(value_name = "ID")]
pub id: i64,
#[arg(long, short = 'y')]
pub yes: bool,
}
impl QueueCancelCommand {
pub fn execute(self, printer: &mut impl Printer, store: &StoreFlags) -> Result<()> {
confirm(
printer,
self.yes,
&format!("Drop queued action {} for good?", self.id),
)?;
let dropped = store.owner()?.drop_action(self.id).map_err(report)?;
if !dropped {
bail!("no queued action with id {}", self.id);
}
printer.out(QueueCancelOutput { id: self.id })
}
}
fn summary(action: &PimdirAction) -> String {
match action {
PimdirAction::Add {
link_id, object, ..
} => {
let link = link_id
.as_ref()
.map(|link| link.0.clone())
.unwrap_or_else(|| String::from("(from object)"));
match object {
Some(hash) => format!("link {link}, object {}", hash.0),
None => format!("link {link}"),
}
}
PimdirAction::SetFlags { seq, flags } => {
let flags: Vec<&str> = flags
.known()
.into_iter()
.flatten()
.map(String::as_str)
.collect();
format!("seq {seq} -> [{}]", flags.join(" "))
}
PimdirAction::Remove { seq } => format!("seq {seq}"),
PimdirAction::Move { seq, to } => format!("seq {seq} -> {}", to.0),
PimdirAction::Copy { seq, to } => format!("seq {seq} -> {}", to.0),
PimdirAction::Update { seq, object, .. } => format!("seq {seq}, object {}", object.0),
PimdirAction::Unknown { payload, .. } => payload.clone(),
}
}
#[derive(Debug, Serialize)]
pub struct QueueRow {
pub id: i64,
pub created_at: String,
pub producer: String,
pub collection: String,
pub kind: String,
pub summary: String,
pub attempts: i64,
pub error: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct QueueOutput {
pub parked: bool,
pub actions: Vec<QueueRow>,
pub undecodable: Vec<String>,
}
impl fmt::Display for QueueOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.actions.is_empty() {
let what = if self.parked { "parked" } else { "pending" };
writeln!(f, "No {what} action in this store's queue")?;
} else {
let mut table = Table::new();
let mut header = vec![
Cell::new("ID"),
Cell::new("CREATED"),
Cell::new("PRODUCER"),
Cell::new("COLLECTION"),
Cell::new("ACTION"),
Cell::new("TARGET"),
Cell::new("TRIES"),
];
if self.parked {
header.push(Cell::new("ERROR"));
}
table
.load_style(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(header);
for action in &self.actions {
let mut row = vec![
Cell::new(action.id),
Cell::new(&action.created_at),
Cell::new(&action.producer),
Cell::new(&action.collection),
Cell::new(&action.kind),
Cell::new(&action.summary),
Cell::new(action.attempts),
];
if self.parked {
row.push(Cell::new(or_dash(action.error.as_deref())));
}
table.add_row(row);
}
writeln!(f, "{table}")?;
}
for collection in &self.undecodable {
writeln!(
f,
"The pending actions of {collection} could not be decoded and are not listed; the next drain will park them"
)?;
}
Ok(())
}
}
#[derive(Debug, Serialize)]
pub struct QueueCancelOutput {
pub id: i64,
}
impl fmt::Display for QueueCancelOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Dropped queued action {}", self.id)
}
}