From bb88e68f8fbb5bce665855b2f96a1d4e5ae4cf32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20W=C3=B6lfer?= Date: Wed, 3 Sep 2025 02:08:43 +0200 Subject: [PATCH] Made tracing calls more complicated, structured code a little different --- src/main.rs | 46 ++++++++-------- src/updater.rs | 39 +++++++++----- src/watchdog.rs | 141 +++++++++++++++++++++++++++--------------------- 3 files changed, 131 insertions(+), 95 deletions(-) diff --git a/src/main.rs b/src/main.rs index f4f4eb3..6fd7f2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,10 @@ reason = "Disable this for most of the time, enable this for cleanup later" )] +#![feature(never_type)] + use mwbot::{ - Bot, + Bot, ConfigError, generators::{Generator, SortDirection, categories::CategoryMemberSort}, }; use std::path::Path; @@ -46,30 +48,32 @@ pub fn app_signature() -> String { format!("{} [{}]", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) } -fn main() { +#[derive(thiserror::Error, Debug)] +pub enum AppError { + #[error("Runtime error: {0}")] + RuntimeError(#[from] std::io::Error), + #[error("Bot initialization error: {0}")] + BotError(#[from] ConfigError), +} + +fn main() -> Result<(), AppError> { tracing_subscriber::fmt() .with_level(true) .with_max_level(tracing::Level::INFO) .init(); tracing::info!("Starting {}", app_signature()); - let rt = match tokio::runtime::Builder::new_current_thread() + + let rt = tokio::runtime::Builder::new_current_thread() .enable_all() - .build() - { - Ok(o) => o, - Err(e) => { - tracing::error!("Could not start runtime: {e}"); - return; - } - }; - rt.block_on(async { - let bot = match Bot::from_path(Path::new("./mwbot.toml")).await { - Ok(x) => x, - Err(e) => { - dbg!(e); - return; - } - }; - futures::join!(watch_wanted(bot.clone()), updater::update_wsdc(bot)); - }); + .build()?; + + let bot = rt.block_on(Bot::from_path(Path::new("./mwbot.toml")))?; + + #[allow( + unreachable_code, + reason = "This is a false positive I think, I just want to loop infinitely on two futures" + )] + rt.block_on(async { futures::join!(watch_wanted(bot.clone()), updater::update_wsdc(bot)) }); + + Ok(()) } diff --git a/src/updater.rs b/src/updater.rs index 078dd51..b350f47 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -2,23 +2,38 @@ use std::time::Duration; use mwbot::Bot; use rand::seq::SliceRandom as _; +use tokio::time::sleep; use crate::{watchdog::generate_page, wikiinfo::index_wsdc_ids}; pub async fn update_wsdc(bot: Bot) -> ! { loop { - let mut l = index_wsdc_ids(&bot).await; - l.shuffle(&mut rand::rng()); - tracing::info!("We have to update {} pages", l.len()); - let wait_duration = Duration::from_secs(6 * 3600); - for (index, page) in l { - tracing::info!("Next up: #{index}"); - tokio::time::sleep(wait_duration).await; - if generate_page(index, page).await { - tracing::info!("Updated {index}"); - } else { - tracing::error!("Error updating {index}"); - } + update_all_teachers(&bot).await; + } +} + +/// Updates all teachers once +async fn update_all_teachers(bot: &Bot) { + let mut l = index_wsdc_ids(bot).await; + l.shuffle(&mut rand::rng()); + tracing::info!("We have to update {} pages", l.len()); + let wait_duration = Duration::from_hours(6); + + for (index, page) in l { + process_page(wait_duration, index, page).await; + } + tracing::info!("Updates all pages"); +} + +#[tracing::instrument(skip_all, fields(index))] +async fn process_page(wait_duration: Duration, index: u32, page: mwbot::Page) { + tracing::info!("Next up"); + sleep(wait_duration).await; + + match generate_page(index, page).await { + Ok(()) => (), + Err(err) => { + tracing::error!("Error updating: {err}"); } } } diff --git a/src/watchdog.rs b/src/watchdog.rs index f8d84db..6159c8c 100644 --- a/src/watchdog.rs +++ b/src/watchdog.rs @@ -1,75 +1,92 @@ use std::time::Duration; use crate::app_signature; +use crate::wikipage::InfoCompileError; +use crate::worldsdc::DanceInfoError; use crate::{wikiinfo::wanted_ids, wikipage::page_from_info, worldsdc::fetch_wsdc_info}; -use mwbot::Bot; use mwbot::SaveOptions; -use tracing::Level; +use mwbot::{Bot, Page}; -pub async fn watch_wanted(bot: Bot) -> ! { - let span = tracing::span!(Level::INFO, "wanted_watchdog"); - let _enter = span.enter(); - - let mut ignored_ids = vec![]; - let mut watch_count = 0; - loop { - watch_count += 1; - if watch_count >= 120 { - watch_count = 0; - let failed_id_info = if ignored_ids.is_empty() { - String::new() - } else { - format!("[{} failed ids]", ignored_ids.len()) - }; - tracing::info!("Watchdog check{failed_id_info}..."); - } - let wanted = wanted_ids(bot.clone()).await; - let mut new_ignored = vec![]; - for (id, page) in wanted.into_iter().filter(|(x, _)| ignored_ids.contains(x)) { - if !generate_page(id, page).await { - new_ignored.push(id); - } - } - if !new_ignored.is_empty() { - ignored_ids.extend(new_ignored); - } - tokio::time::sleep(Duration::from_secs(30)).await; - } +pub struct Ticker { + count: usize, + max: usize, } -pub async fn generate_page(id: u32, page: mwbot::Page) -> bool { - tracing::info!("Generating page for {id}"); - let info = match fetch_wsdc_info(id).await { - Ok(o) => o, - Err(e) => { - tracing::error!("Error fetching wsdc info for {id}: {e}"); - return false; - } - }; - let code = match page_from_info(info) { - Ok(o) => o, - Err(e) => { - tracing::error!("Creating wikicode for {id}: {e}"); - return false; - } - }; +impl Ticker { + pub const fn new(max: usize) -> Self { + Self { count: 0, max } + } - match page - .save( - code, - &SaveOptions::summary(&format!( - "Created WSDC info from worldsdc.com -- {}", - app_signature() - )) - .mark_as_bot(true) - .mark_as_minor(false), - ) - .await - { - Ok(_) => true, - Err(e) => { - tracing::error!("Could not save page for {id}: {e}"); + /// Returns `true` if the ticker has "ticked" (i.e., reached `max` and reset). + pub const fn tick(&mut self) -> bool { + self.count += 1; + if self.count >= self.max { + self.count = 0; + true + } else { false } } } + +/// Continuously monitors teacher IDs that do not have a corresponding teacher WSDC page, ignoring those that fail processing. +#[tracing::instrument(skip_all)] +pub async fn watch_wanted(bot: Bot) -> ! { + let mut ignored_ids = vec![]; + let mut heartbeat_ticker = Ticker::new(120); + loop { + if heartbeat_ticker.tick() { + tracing::info!(failed_id_count = ignored_ids.len(), "Watchdog check..."); + } + let wanted = wanted_ids(bot.clone()).await; + let new_ignored = update_wanted_ids(&wanted, &ignored_ids).await; + ignored_ids.extend(new_ignored); + tokio::time::sleep(Duration::from_secs(30)).await; + } +} + +async fn update_wanted_ids(wanted: &[(u32, Page)], ignored_ids: &[u32]) -> Vec { + let mut new_ignored = vec![]; + + for (id, page) in wanted.iter().filter(|(x, _)| ignored_ids.contains(x)) { + let span = tracing::info_span!("update", id); + let _enter = span.enter(); + if let Err(e) = generate_page(*id, page.clone()).await { + tracing::error!("{e}"); + new_ignored.push(*id); + } + } + new_ignored +} + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum GeneratePageError { + #[error("Error fetching WSDC info for {0}")] + Fetch(#[from] DanceInfoError), + #[error("Error creating wikicode for {0}")] + Wikicode(#[from] InfoCompileError), + #[error("Error saving page for {0}")] + Save(#[from] mwbot::Error), +} + +pub async fn generate_page(id: u32, page: mwbot::Page) -> Result<(), GeneratePageError> { + tracing::info!("Generating page for {id}"); + let info = fetch_wsdc_info(id).await?; + + let code = page_from_info(info)?; + + page.save( + code, + &SaveOptions::summary(&format!( + "Created WSDC info from worldsdc.com -- {}", + app_signature() + )) + .mark_as_bot(true) + .mark_as_minor(false), + ) + .await?; + + Ok(()) +}