(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(),
);
// 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 = NaiveDate::parse_from_str(&last_run, "%Y-%m-%d").unwrap_or_default();
// figure out where we should be
let plenum_spec = date::parse_spec(&config["date-spec"])?;
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);
// 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();
// find the relevant one:
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
};
println!("aktueller Zustand: {}", last_state);
println!("letzter Durchlauf: {}", last_run);
println!("relevante Tage: {:#?}", nearest_plenum_days);
println!("Abweichungen: {:#?}", deltas);
println!("Geschätzter Termin (Δ): {}", delta);
println!("Gewollter Zustand: {}", intended_state);
let action: TransitionFunction = TRANSITION_LUT[last_state.index()][intended_state.index()];
action(delta, &config, &hedgedoc, &email, &wiki)?;
// TODO: state-matrix: was macht sinn?
// Normal Announced Reminded Waiting Logged
// Normal nop announce reminder(!) nop log
// Announced log nop reminder nop log
// Reminded log log;announce nop nop log
// Waiting log log;announce log;reminder(!) nop log
// Logged nop announce reminder(!) nop nop
// TODO: cleanup / write new state
if config.has_errors() {
return Err("There were errors while writing config values.".into());
} else {
return Ok(());
}
// Dienstage diesen Monat
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!");
let current_pad_link = hedgedoc.format_url(&current_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 {
println!("In 1 Tag ist Plenum, deshalb wird eine Erinnerung raus geschickt!");
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
let betreff = "Morgen ist Plenum!".to_string(); // ADJ_TIMEYWIMEY
let message: String = format!(
r#"{email_greeting}
Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
r#"Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
{current_pad_link}
Und hier ein TL;DR von den aktuellen Themen:
{tldr}
Bis morgen, 20 Uhr!
{email_signature}"#
Bis morgen, 20 Uhr!"#
); // 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!")
} else {
// Mail an alle senden und absagen
let betreff = format!("Plenum am {} fällt mangels Themen aus", nächster_plenumtermin);
let message: String = format!(
r#"{email_greeting}
Es gibt keine Themen, deshalb wird das morgige Plenum leider nicht statt finden.
r#"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:
{future_pad_link}
Bis zum nächsten Plenum.
{email_signature}"#,
Bis zum nächsten Plenum."#,
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!"))
}
} 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
let betreff = format!("Plenum vom {}: Bisher noch keine Themen", nächster_plenumtermin);
let message: String = format!(
r#"{email_greeting}
Es sind bisher leider keine Themen zusammengekommen. Wenn es bis Sonntag Abend keine Themen gibt, wird das Plenum voraussichtlich nicht statt finden.
r#"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:
{current_pad_link}
{email_signature}"#
{current_pad_link}"#
);
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!"))
}
} 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");
// XXX nächstes/übernächstes?
let message: String = format!(
r#"{email_greeting}
Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
r#"Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
{current_pad_link}
und zum übernächsten Plenum:
{future_pad_link}
@ -393,26 +381,25 @@ TL;DR:
Und hier ist das Protokoll des letzten Plenums:
{old_pad_content_without_top_instructions}
{email_signature}"#
{old_pad_content_without_top_instructions}"#
);
let betreff: String =
format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl);
// 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!"));
mediawiki::pad_ins_wiki(old_pad_content_without_top_instructions);
}
println!("message id: {:?}", message_id);
println!("[ENDE]\n{}", "Aktueller Zustand der DB:".bold());
config.dump_redacting(&["email-password", "wiki-http-password", "wiki-api-secret", "matrix-password"]).ok();
if config.has_errors() {
Err("There were errors.".into())
} else {
Ok(())
}
config
.dump_redacting(&[
"email-password",
"wiki-http-password",
"wiki-api-secret",
"matrix-password",
])
.ok();
}
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
}
/// State machine of the announcement logic, ensuring we can deal with inconsistent runs
/// (e.g. multiple runs in a day, or skipping days). The states are:
/* ***** transition actions ***** */
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
/// - Announced the first announcement 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
/// knows in which state it isn't. By comparing where it is with where it
@ -574,10 +631,20 @@ impl ProgramState {
"announced" => ProgramState::Announced,
"reminded" => ProgramState::Reminded,
"waiting" => ProgramState::Waiting,
// "logged" also becomes Normal
"logged" => ProgramState::Logged,
_ => 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 {
@ -592,3 +659,14 @@ impl std::fmt::Display for ProgramState {
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()
}