(empty) state machine exists
This commit is contained in:
		
							parent
							
								
									124c1b2a55
								
							
						
					
					
						commit
						77842f1f6d
					
				
					 1 changed files with 134 additions and 56 deletions
				
			
		
							
								
								
									
										190
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										190
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -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(¤t_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() | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 nobody
						nobody