staticrocket 0.3.1

Simple http server for serving static content.
use qsu::{
  log,
  rt::{InitCtx, RocketServiceHandler, RunEnv, SrvAppRt, SvcEvt, TermCtx},
  tracing
};

use tokio::sync::mpsc;

use rocket::{fairing::AdHoc, fs::FileServer, Build, Ignite, Rocket};

use crate::err::Error;

pub fn create() -> SrvAppRt<Error> {
  let (tx_svcevt, rx_svcevt) = mpsc::unbounded_channel();

  let handler = MyService { rx_svcevt };

  SrvAppRt::Rocket {
    svcevt_handler: Box::new(move |msg| {
      if let Err(e) = tx_svcevt.send(msg) {
        log::error!("Unable to forward svcevt; {e}");
      }
    }),
    rt_handler: Box::new(handler)
  }
}

pub struct MyService {
  rx_svcevt: mpsc::UnboundedReceiver<SvcEvt>
}

#[qsu::async_trait]
impl RocketServiceHandler for MyService {
  type AppErr = Error;

  /// 🚀 🏭
  async fn init(
    &mut self,
    ictx: InitCtx
  ) -> Result<Vec<Rocket<Build>>, Self::AppErr> {
    tracing::trace!("Running init()");

    let mut rockets = vec![];

    ictx.report(Some("Building rocket".into()));

    let rocket =
      rocket::build().attach(AdHoc::try_on_ignite("Config", |rocket| async {
        // Load, and expand, "htdocs" key from config.
        let htdocsdir = match rocket
          .figment()
          .extract_inner::<String>("htdocs")
        {
          Ok(htdocsdir) => htdocsdir,
          Err(e) => {
            log::error!("Unable to extract htdocs from configuration; {}", e);
            return Err(rocket);
          }
        };
        let htdocsdir = match shellexpand::full(&htdocsdir) {
          Ok(dir) => dir.into_owned(),
          Err(e) => {
            log::error!("Unable to expand htdocs from configuration; {}", e);
            return Err(rocket);
          }
        };

        // Mount / as a static file share against the htdocs dir.
        let rocket = rocket.mount("/", FileServer::from(htdocsdir));

        Ok(rocket)
      }));

    rockets.push(rocket);

    Ok(rockets)
  }

  async fn run(
    &mut self,
    rockets: Vec<Rocket<Ignite>>,
    _re: &RunEnv
  ) -> Result<(), Self::AppErr> {
    for rocket in rockets {
      tokio::task::spawn(async {
        rocket.launch().await.unwrap();
      });
    }

    loop {
      tokio::select! {
        evt = self.rx_svcevt.recv() => {
          let Some(evt) = evt else {
            // channel closed -- assume there's no reason to keep going
            break;
          };

          #[allow(clippy::single_match)]
          match evt {
            SvcEvt::Shutdown(_) => {
              break;
            }
            _ => { }
          }
        }
      }
    }

    Ok(())
  }

  async fn shutdown(&mut self, tctx: TermCtx) -> Result<(), Self::AppErr> {
    tracing::trace!("Running shutdown()");
    tctx.report(Some("Shutting down".into()));
    Ok(())
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :