(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(), |         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(¤t_pad_id); |     let current_pad_link = hedgedoc.format_url(¤t_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() | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 nobody
						nobody