(empty) state machine exists

This commit is contained in:
nobody 2024-08-09 16:14:37 +02:00 committed by murmeldin
parent 124c1b2a55
commit 77842f1f6d

View file

@ -179,14 +179,22 @@ fn main() -> Result<(), Box<dyn Error>> {
is_dry_run(), is_dry_run(),
); );
// figure out where we are // figure out where we are
let last_state = ProgramState::parse(&config["state-name"]); let mut last_state = ProgramState::parse(&config["state-name"]);
let last_run = config.get("state-last-run").unwrap_or_default(); let last_run = config.get("state-last-run").unwrap_or_default();
let last_run = NaiveDate::parse_from_str(&last_run, "%Y-%m-%d").unwrap_or_default(); let last_run = NaiveDate::parse_from_str(&last_run, "%Y-%m-%d").unwrap_or_default();
// figure out where we should be // figure out where we should be
let plenum_spec = date::parse_spec(&config["date-spec"])?;
let today = Local::now().date_naive(); let today = Local::now().date_naive();
if (today - last_run).num_days() > 10 {
if !matches!(last_state, ProgramState::Normal) {
eprintln!("WARNING: last run was a long time ago, resetting state.");
last_state = ProgramState::Normal;
do_cleanup(999, &config, &hedgedoc, &email, &wiki)?;
}
}
let last_state = last_state;
let plenum_spec = date::parse_spec(&config["date-spec"])?;
let nearest_plenum_days = date::get_matching_dates_around(today, plenum_spec); let nearest_plenum_days = date::get_matching_dates_around(today, plenum_spec);
// deltas has either 2 or 3 days, if 3 then the middle one is == 0 // deltas has either 2 or 3 days, if 3 then the middle one is == 0 (i.e. today)
let deltas: Vec<i64> = nearest_plenum_days.iter().map(|&d| (d - today).num_days()).collect(); let deltas: Vec<i64> = nearest_plenum_days.iter().map(|&d| (d - today).num_days()).collect();
// find the relevant one: // find the relevant one:
let delta = *match last_state { let delta = *match last_state {
@ -215,20 +223,16 @@ fn main() -> Result<(), Box<dyn Error>> {
ProgramState::Logged // after that, we want to log it to the list & the wiki ProgramState::Logged // after that, we want to log it to the list & the wiki
}; };
println!("aktueller Zustand: {}", last_state); let action: TransitionFunction = TRANSITION_LUT[last_state.index()][intended_state.index()];
println!("letzter Durchlauf: {}", last_run); action(delta, &config, &hedgedoc, &email, &wiki)?;
println!("relevante Tage: {:#?}", nearest_plenum_days);
println!("Abweichungen: {:#?}", deltas);
println!("Geschätzter Termin (Δ): {}", delta);
println!("Gewollter Zustand: {}", intended_state);
// TODO: state-matrix: was macht sinn? // TODO: cleanup / write new state
// Normal Announced Reminded Waiting Logged
// Normal nop announce reminder(!) nop log if config.has_errors() {
// Announced log nop reminder nop log return Err("There were errors while writing config values.".into());
// Reminded log log;announce nop nop log } else {
// Waiting log log;announce log;reminder(!) nop log return Ok(());
// Logged nop announce reminder(!) nop nop }
// Dienstage diesen Monat // Dienstage diesen Monat
let all_tuesdays: Vec<NaiveDate> = get_all_weekdays(0, Weekday::Tue); let all_tuesdays: Vec<NaiveDate> = get_all_weekdays(0, Weekday::Tue);
@ -299,8 +303,6 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Pad-content geladen!"); println!("Pad-content geladen!");
let current_pad_link = hedgedoc.format_url(&current_pad_id); let current_pad_link = hedgedoc.format_url(&current_pad_id);
let future_pad_link = hedgedoc.format_url(&future_pad_id); let future_pad_link = hedgedoc.format_url(&future_pad_id);
let email_greeting = &config["text-email-greeting"];
let email_signature = &config["text-email-signature"];
if in_1_day_is_plenum { if in_1_day_is_plenum {
println!("In 1 Tag ist Plenum, deshalb wird eine Erinnerung raus geschickt!"); println!("In 1 Tag ist Plenum, deshalb wird eine Erinnerung raus geschickt!");
let tldr_vec = create_tldr(&pad_content_without_top_instructions); let tldr_vec = create_tldr(&pad_content_without_top_instructions);
@ -313,37 +315,29 @@ fn main() -> Result<(), Box<dyn Error>> {
// Mail an alle senden, findet statt // Mail an alle senden, findet statt
let betreff = "Morgen ist Plenum!".to_string(); // ADJ_TIMEYWIMEY let betreff = "Morgen ist Plenum!".to_string(); // ADJ_TIMEYWIMEY
let message: String = format!( let message: String = format!(
r#"{email_greeting} r#"Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
{current_pad_link} {current_pad_link}
Und hier ein TL;DR von den aktuellen Themen: Und hier ein TL;DR von den aktuellen Themen:
{tldr} {tldr}
Bis morgen, 20 Uhr! Bis morgen, 20 Uhr!"#
{email_signature}"#
); // ADJ_TIMEYWIMEY ); // ADJ_TIMEYWIMEY
message_id = email.send_email(betreff, message).ok() message_id = send_email(&betreff, &message, &email, &config);
// .expect("Plenum findet statt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!") // .expect("Plenum findet statt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!")
} else { } else {
// Mail an alle senden und absagen // Mail an alle senden und absagen
let betreff = format!("Plenum am {} fällt mangels Themen aus", nächster_plenumtermin); let betreff = format!("Plenum am {} fällt mangels Themen aus", nächster_plenumtermin);
let message: String = format!( let message: String = format!(
r#"{email_greeting} r#"Es gibt keine Themen, deshalb wird das morgige Plenum leider nicht statt finden.
Es gibt keine Themen, deshalb wird das morgige Plenum leider nicht statt finden.
Hier ist der Link zum Pad vom nächsten Plenum, das am {} statt finden wird: Hier ist der Link zum Pad vom nächsten Plenum, das am {} statt finden wird:
{future_pad_link} {future_pad_link}
Bis zum nächsten Plenum. Bis zum nächsten Plenum."#,
{email_signature}"#,
nächster_plenumtermin nächster_plenumtermin
); );
message_id = email.send_email(betreff, message).ok() message_id = send_email(&betreff, &message, &email, &config);
// .expect("Plenum wird abgesagt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!")) // .expect("Plenum wird abgesagt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!"))
} }
} else if in_3_days_is_plenum { } else if in_3_days_is_plenum {
@ -352,16 +346,12 @@ Bis zum nächsten Plenum.
// Mail an alle senden und sagen, dass es noch keine Themen gibt // Mail an alle senden und sagen, dass es noch keine Themen gibt
let betreff = format!("Plenum vom {}: Bisher noch keine Themen", nächster_plenumtermin); let betreff = format!("Plenum vom {}: Bisher noch keine Themen", nächster_plenumtermin);
let message: String = format!( let message: String = format!(
r#"{email_greeting} r#"Es sind bisher leider keine Themen zusammengekommen. Wenn es bis Sonntag Abend keine Themen gibt, wird das Plenum voraussichtlich nicht statt finden.
Es sind bisher leider keine Themen zusammengekommen. Wenn es bis Sonntag Abend keine Themen gibt, wird das Plenum voraussichtlich nicht statt finden.
Hier ist der Link zum Pad, wo ihr noch Themen eintragen könnt: Hier ist der Link zum Pad, wo ihr noch Themen eintragen könnt:
{current_pad_link} {current_pad_link}"#
{email_signature}"#
); );
message_id = email.send_email(betreff, message).ok() message_id = send_email(&betreff, &message, &email, &config);
// .expect("Noch nicht genug Themen. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!")) // .expect("Noch nicht genug Themen. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!"))
} }
} else if yesterday_was_plenum { } else if yesterday_was_plenum {
@ -381,9 +371,7 @@ Hier ist der Link zum Pad, wo ihr noch Themen eintragen könnt:
let tldr = tldr_vec.join("\n"); let tldr = tldr_vec.join("\n");
// XXX nächstes/übernächstes? // XXX nächstes/übernächstes?
let message: String = format!( let message: String = format!(
r#"{email_greeting} r#"Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
{current_pad_link} {current_pad_link}
und zum übernächsten Plenum: und zum übernächsten Plenum:
{future_pad_link} {future_pad_link}
@ -393,26 +381,25 @@ TL;DR:
Und hier ist das Protokoll des letzten Plenums: Und hier ist das Protokoll des letzten Plenums:
{old_pad_content_without_top_instructions} {old_pad_content_without_top_instructions}"#
{email_signature}"#
); );
let betreff: String = let betreff: String =
format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl); format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl);
// XXX option x expect // XXX option x expect
message_id = email.send_email(betreff, message).ok(); message_id = send_email(&betreff, &message, &email, &config);
// .expect("Mail mit Plenumsprotokoll wurde versucht zu senden, konnte aber nicht gesendet werden!")); // .expect("Mail mit Plenumsprotokoll wurde versucht zu senden, konnte aber nicht gesendet werden!"));
mediawiki::pad_ins_wiki(old_pad_content_without_top_instructions); mediawiki::pad_ins_wiki(old_pad_content_without_top_instructions);
} }
println!("message id: {:?}", message_id); println!("message id: {:?}", message_id);
println!("[ENDE]\n{}", "Aktueller Zustand der DB:".bold()); println!("[ENDE]\n{}", "Aktueller Zustand der DB:".bold());
config.dump_redacting(&["email-password", "wiki-http-password", "wiki-api-secret", "matrix-password"]).ok(); config
.dump_redacting(&[
if config.has_errors() { "email-password",
Err("There were errors.".into()) "wiki-http-password",
} else { "wiki-api-secret",
Ok(()) "matrix-password",
} ])
.ok();
} }
fn get_all_weekdays(month_offset: i32, week_day: Weekday) -> Vec<NaiveDate> { fn get_all_weekdays(month_offset: i32, week_day: Weekday) -> Vec<NaiveDate> {
@ -525,12 +512,82 @@ fn try_to_remove_top_instructions(pad_content: String) -> String {
result.to_string() // Wenn es nicht geklappt hat, wird einfach das Pad mit dem Kommentar zurückgegeben result.to_string() // Wenn es nicht geklappt hat, wird einfach das Pad mit dem Kommentar zurückgegeben
} }
/// State machine of the announcement logic, ensuring we can deal with inconsistent runs /* ***** transition actions ***** */
/// (e.g. multiple runs in a day, or skipping days). The states are:
fn do_announcement(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// use TTP to adjust text if needed (in {ttp} days)
todo!()
}
fn do_reminder(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// use TTP to adjust text if needed (tomorrow or today / in {ttp} days)
todo!()
}
fn do_protocol(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// use TTP to adjust text if needed ({-ttp} days ago)
todo!()
}
fn do_cleanup(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
todo!()
}
/* ***** state machine ***** */
type TransitionFunction = fn(
ttp: i64,
config: &KV,
hedgedoc: &HedgeDoc,
email: &SimpleEmail,
wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>>;
#[rustfmt::skip]
const TRANSITION_LUT: [[TransitionFunction; 5]; 5] = [
/* NORMAL ANNOUNCED REMINDED WAITING LOGGED */
/* NORMAL */ [nop, do_announcement, do_reminder, nop, nop],
/* ANNOUNCED */ [do_cleanup, nop, do_reminder, nop, do_protocol],
/* REMINDED */ [do_cleanup, do_clean_announcement, nop, nop, do_protocol],
/* WAITING */ [do_cleanup, do_clean_announcement, do_clean_reminder, nop, do_protocol],
/* LOGGED */ [do_cleanup, do_clean_announcement, do_clean_reminder, nop, do_cleanup],
];
fn nop(_: i64, _: &KV, _: &HedgeDoc, _: &SimpleEmail, _: &Mediawiki) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn do_clean_announcement(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
do_cleanup(ttp, config, hedgedoc, email, wiki)?;
do_announcement(ttp, config, hedgedoc, email, wiki)
}
fn do_clean_reminder(
ttp: i64, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail, wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
do_cleanup(ttp, config, hedgedoc, email, wiki)?;
do_reminder(ttp, config, hedgedoc, email, wiki)
}
/// State machine type for the announcement logic, ensuring we can deal with
/// inconsistent runs (e.g. multiple runs in a day, or skipping days). The
/// states are:
/// ///
/// - Normal (default) in between dates, with nothing to do /// - Normal (default) in between dates, with nothing to do
/// - Announced the first announcement was sent /// - Announced the first announcement was sent
/// - Reminded the reminder was sent /// - Reminded the reminder was sent
/// - Waiting just a day of delay (after it makes sense to send a reminder)
/// - Logged protocol was written to wiki & mailing list
/// ///
/// The bot knows in which state it is at all times. It knows this because it /// The bot knows in which state it is at all times. It knows this because it
/// knows in which state it isn't. By comparing where it is with where it /// knows in which state it isn't. By comparing where it is with where it
@ -574,10 +631,20 @@ impl ProgramState {
"announced" => ProgramState::Announced, "announced" => ProgramState::Announced,
"reminded" => ProgramState::Reminded, "reminded" => ProgramState::Reminded,
"waiting" => ProgramState::Waiting, "waiting" => ProgramState::Waiting,
// "logged" also becomes Normal "logged" => ProgramState::Logged,
_ => ProgramState::Normal, _ => ProgramState::Normal,
} }
} }
fn index(&self) -> usize {
match self {
ProgramState::Normal => 0,
ProgramState::Announced => 1,
ProgramState::Reminded => 2,
ProgramState::Waiting => 3,
ProgramState::Logged => 4,
}
}
} }
impl std::fmt::Display for ProgramState { impl std::fmt::Display for ProgramState {
@ -592,3 +659,14 @@ impl std::fmt::Display for ProgramState {
write!(f, "{}", str) write!(f, "{}", str)
} }
} }
/* ***** wrappers ***** */
fn send_email(subject: &str, body: &str, email: &SimpleEmail, config: &KV) -> Option<String> {
let full_subject = format!("[PleB] {}", subject);
let full_body = format!(
"{}\n\n{}\n\n{}",
&config["text-email-greeting"], body, &config["text-email-signature"]
);
email.send_email(full_subject, full_body).ok()
}