use super::prelude::*;
use inotify::{Inotify, WatchMask};
use tokio::process::Command;
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub interval: Seconds,
pub warning_threshold: u32,
pub critical_threshold: u32,
pub filters: Vec<Filter>,
pub format: FormatConfig,
pub format_singular: FormatConfig,
pub format_everything_done: FormatConfig,
pub data_location: ShellString,
}
impl Default for Config {
fn default() -> Self {
Self {
interval: Seconds::new(600),
warning_threshold: 10,
critical_threshold: 20,
filters: vec![Filter {
name: "pending".into(),
filter: "-COMPLETED -DELETED".into(),
config_override: Default::default(),
}],
format: default(),
format_singular: default(),
format_everything_done: default(),
data_location: ShellString::new("~/.task"),
}
}
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut actions = api.get_actions()?;
api.set_default_actions(&[(MouseButton::Right, None, "next_filter")])?;
let format = config.format.with_default(" $icon $count.eng(w:1) ")?;
let format_singular = config
.format_singular
.with_default(" $icon $count.eng(w:1) ")?;
let format_everything_done = config
.format_everything_done
.with_default(" $icon $count.eng(w:1) ")?;
let mut filters = config.filters.iter().cycle();
let mut filter = filters.next().error("`filters` is empty")?;
let notify = Inotify::init().error("Failed to start inotify")?;
notify
.watches()
.add(&*config.data_location.expand()?, WatchMask::MODIFY)
.error("Failed to watch data location")?;
let mut updates = notify
.into_event_stream([0; 1024])
.error("Failed to create event stream")?;
loop {
let number_of_tasks = get_number_of_tasks(filter).await?;
let mut widget = Widget::new();
widget.set_format(match number_of_tasks {
0 => format_everything_done.clone(),
1 => format_singular.clone(),
_ => format.clone(),
});
widget.set_values(map! {
"icon" => Value::icon("tasks"),
"count" => Value::number(number_of_tasks),
"filter_name" => Value::text(filter.name.clone()),
});
widget.state = match number_of_tasks {
x if x >= config.critical_threshold => State::Critical,
x if x >= config.warning_threshold => State::Warning,
_ => State::Idle,
};
api.set_widget(widget)?;
loop {
select! {
_ = sleep(config.interval.0) => break,
Some(Ok(event)) = updates.next() => {
if let Some(name) = event.name {
let name_str = name.to_string_lossy();
if name_str.ends_with("-shm") || name_str.ends_with("-wal") || name_str.ends_with("-journal") {
continue;
}
}
break;
}
_ = api.wait_for_update_request() => break,
Some(action) = actions.recv() => {
match action.as_ref() {
"next_filter" => {
filter = filters.next().unwrap();
}
_ => (),
}
break;
}
}
}
}
}
async fn get_number_of_tasks(filter: &Filter) -> Result<u32> {
let args_iter = filter.config_override.iter().map(String::as_str).chain([
"rc.gc=off",
&filter.filter,
"count",
]);
let output = Command::new("task")
.args(args_iter)
.output()
.await
.error("failed to run taskwarrior for getting the number of tasks")?
.stdout;
std::str::from_utf8(&output)
.error("failed to get the number of tasks from taskwarrior (invalid UTF-8)")?
.trim()
.parse::<u32>()
.error("could not parse the result of taskwarrior")
}
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct Filter {
pub name: String,
pub filter: String,
#[serde(default)]
pub config_override: Vec<String>,
}