Prepare for reminding
This commit is contained in:
parent
c1c0ced266
commit
aebbcd7815
5 changed files with 124 additions and 5 deletions
|
|
@ -13,6 +13,9 @@ pub struct Context {
|
|||
pub repo_url: url::Url,
|
||||
/// URL of the repository with authentication information attached
|
||||
pub repo_auth_url: url::Url,
|
||||
|
||||
/// Timestamp "now" to be used in comparisons
|
||||
pub timestamp: jiff::Zoned,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
|
@ -67,12 +70,15 @@ impl Context {
|
|||
let forgejo = forgejo_api::Forgejo::new(forgejo_api::Auth::Token(token), server_url)
|
||||
.context("Could not create Forgejo API access object")?;
|
||||
|
||||
let timestamp = jiff::Zoned::now();
|
||||
|
||||
Ok(Self {
|
||||
forgejo,
|
||||
owner: owner.to_owned(),
|
||||
repo: repo.to_owned(),
|
||||
repo_url,
|
||||
repo_auth_url,
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#![allow(unused)]
|
||||
|
||||
mod task;
|
||||
mod collect;
|
||||
mod ci_meta;
|
||||
mod collect;
|
||||
mod context;
|
||||
mod reminder;
|
||||
mod scheduler;
|
||||
mod task;
|
||||
mod util;
|
||||
|
||||
use context::Context;
|
||||
|
|
@ -16,6 +17,8 @@ async fn run() -> anyhow::Result<()> {
|
|||
|
||||
scheduler::reschedule_recurring_tasks(&ctx, &tasks).await?;
|
||||
|
||||
reminder::remind_due_tasks(&ctx, &tasks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
96
src/reminder.rs
Normal file
96
src/reminder.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use crate::task;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::Context as _;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum BatchingInterval {
|
||||
Monthly,
|
||||
Weekly,
|
||||
OnTheDay,
|
||||
}
|
||||
|
||||
pub async fn remind_due_tasks(ctx: &crate::Context, tasks: &[task::Task]) -> anyhow::Result<()> {
|
||||
for task in tasks {
|
||||
let Some(due_date) = task.state.due_date_if_open() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let batching_interval = find_batching_interval(ctx, due_date, task);
|
||||
log::debug!("Reminding {task} with interval {batching_interval:?}.");
|
||||
|
||||
if !is_time_to_remind(ctx, due_date, batching_interval) {
|
||||
log::debug!("Not yet time, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_overdue(ctx, due_date) {
|
||||
log::info!("Task {task} is already overdue!");
|
||||
}
|
||||
|
||||
log::warn!("TODO: Remind {task}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_batching_interval(
|
||||
ctx: &crate::Context,
|
||||
due_date: &jiff::Zoned,
|
||||
task: &task::Task,
|
||||
) -> BatchingInterval {
|
||||
let time_until_due = due_date - &ctx.timestamp;
|
||||
|
||||
if let Some(recurring) = &task.recurring {
|
||||
match &recurring.interval {
|
||||
task::RecurringInterval::Months(_) => BatchingInterval::Monthly,
|
||||
task::RecurringInterval::Weeks(_) => BatchingInterval::Weekly,
|
||||
task::RecurringInterval::Days(_) => BatchingInterval::OnTheDay,
|
||||
}
|
||||
} else {
|
||||
// For tasks that are not recurring, the batching interval is determined based on how
|
||||
// far in the future the task is due.
|
||||
|
||||
let weeks_until_due = time_until_due
|
||||
.total((jiff::Unit::Week, jiff::SpanRelativeTo::days_are_24_hours()))
|
||||
.unwrap();
|
||||
if weeks_until_due >= 3. {
|
||||
BatchingInterval::Monthly
|
||||
} else if weeks_until_due >= 1. {
|
||||
BatchingInterval::Weekly
|
||||
} else {
|
||||
BatchingInterval::OnTheDay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_time_to_remind(
|
||||
ctx: &crate::Context,
|
||||
due_date: &jiff::Zoned,
|
||||
batching_interval: BatchingInterval,
|
||||
) -> bool {
|
||||
let batch_time = match batching_interval {
|
||||
BatchingInterval::Monthly => due_date.first_of_month().unwrap(),
|
||||
BatchingInterval::Weekly => start_of_week(due_date).unwrap(),
|
||||
BatchingInterval::OnTheDay => due_date.clone(),
|
||||
};
|
||||
|
||||
let batch_time = batch_time
|
||||
.round(
|
||||
jiff::ZonedRound::new()
|
||||
.smallest(jiff::Unit::Day)
|
||||
.mode(jiff::RoundMode::Floor),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ctx.timestamp >= batch_time
|
||||
}
|
||||
|
||||
fn is_overdue(ctx: &crate::Context, due_date: &jiff::Zoned) -> bool {
|
||||
&ctx.timestamp >= due_date
|
||||
}
|
||||
|
||||
fn start_of_week(t: &jiff::Zoned) -> anyhow::Result<jiff::Zoned> {
|
||||
Ok(t.tomorrow()?
|
||||
.nth_weekday(-1, jiff::civil::Weekday::Monday)?)
|
||||
}
|
||||
|
|
@ -15,12 +15,15 @@ pub async fn reschedule_recurring_tasks(
|
|||
|
||||
let completed_date = match &task.state {
|
||||
// Already scheduled
|
||||
task::State::Open { due: Some(_) } => continue,
|
||||
task::State::Open { due: Some(_) } => {
|
||||
log::debug!("Task {task} is already scheduled. No action.");
|
||||
continue
|
||||
},
|
||||
|
||||
// Invalid state, will warn about this
|
||||
task::State::Open { due: None } => {
|
||||
log::warn!("Task {task} is recurring but has no due date. Updating from today.");
|
||||
&jiff::Zoned::now()
|
||||
log::warn!("Task {task} is recurring but has no due date. Scheduling from today.");
|
||||
&ctx.timestamp
|
||||
}
|
||||
|
||||
// Need scheduling
|
||||
|
|
@ -36,6 +39,8 @@ pub async fn reschedule_recurring_tasks(
|
|||
|
||||
if rescheduled == 0 {
|
||||
log::info!("No tasks need rescheduling.");
|
||||
} else {
|
||||
log::debug!("Rescheduled {rescheduled} tasks.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ pub enum State {
|
|||
Completed { date: jiff::Zoned },
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn due_date_if_open(&self) -> Option<&jiff::Zoned> {
|
||||
match self {
|
||||
State::Open { due } => due.as_ref(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Recurring {
|
||||
pub interval: RecurringInterval,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue