diff --git a/src/main.rs b/src/main.rs index c306618..0fe1fbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,12 @@ mod args; mod config; mod server; mod somehow; -mod state; mod util; use std::{io, path::PathBuf, process}; use clap::Parser; use directories::ProjectDirs; -use state::AppState; use tokio::{select, signal::unix::SignalKind}; use tracing::{debug, error, info, Level}; use tracing_subscriber::{ @@ -19,6 +17,7 @@ use tracing_subscriber::{ use crate::{ args::{Args, NAME, VERSION}, config::Config, + server::Server, }; fn set_up_logging(verbose: u8) { @@ -99,12 +98,12 @@ async fn run() -> somehow::Result<()> { info!("You are running {NAME} {VERSION}"); let config = load_config(args.config)?; - let state = AppState::new(config, &args.db, &args.repo).await?; + let server = Server::new(config, &args.db, &args.repo).await?; info!("Startup complete, running"); select! { _ = wait_for_signal() => {} - _ = server::run(state.clone()) => {} + _ = server.run() => {} } select! { @@ -118,7 +117,7 @@ async fn run() -> somehow::Result<()> { // In order to fix this, I could maybe register a bare signal handler // (instead of using tokio streams) that just calls process::exit(1) and // nothing else? - _ = state.shut_down() => {} + _ = server.shut_down() => {} } Ok(()) diff --git a/src/server.rs b/src/server.rs index b0cd53c..8bb9f45 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,13 +1,87 @@ mod recurring; mod web; +use std::{path::Path, sync::Arc, time::Duration}; + +use axum::extract::FromRef; +use gix::ThreadSafeRepository; +use sqlx::{ + sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}, + SqlitePool, +}; use tokio::select; +use tracing::{debug, info}; -use crate::{somehow, state::AppState}; +use crate::{config::Config, somehow}; -pub async fn run(state: AppState) -> somehow::Result<()> { - select! { - e = web::run(state.clone()) => e, - () = recurring::run(state.clone()) => Ok(()), +async fn open_db(db_path: &Path) -> sqlx::Result { + let options = SqliteConnectOptions::new() + // https://www.sqlite.org/pragma.html#pragma_journal_mode + .journal_mode(SqliteJournalMode::Wal) + // https://www.sqlite.org/pragma.html#pragma_synchronous + // NORMAL recommended when using WAL, can't cause corruption + .synchronous(SqliteSynchronous::Normal) + // https://www.sqlite.org/pragma.html#pragma_foreign_keys + .foreign_keys(true) + // https://www.sqlite.org/pragma.html#pragma_trusted_schema + // The docs recommend always turning this off + .pragma("trusted_schema", "false") + .filename(db_path) + .create_if_missing(true) + // https://www.sqlite.org/lang_analyze.html#recommended_usage_pattern + // https://www.sqlite.org/pragma.html#pragma_analysis_limit + // https://www.sqlite.org/pragma.html#pragma_optimize + .optimize_on_close(true, Some(1000)); + + info!(path = %db_path.display(), "Opening db"); + let pool = SqlitePoolOptions::new() + // Regularly optimize the db as recommended by the sqlite docs + // https://www.sqlite.org/lang_analyze.html#recommended_usage_pattern + // https://github.com/launchbadge/sqlx/issues/2111#issuecomment-1254394698 + .max_lifetime(Some(Duration::from_secs(60 * 60 * 24))) + .connect_with(options) + .await?; + + debug!("Applying outstanding db migrations"); + sqlx::migrate!().run(&pool).await?; + + Ok(pool) +} + +fn open_repo(repo_path: &Path) -> somehow::Result { + info!(path = %repo_path.display(), "Opening repo"); + Ok(ThreadSafeRepository::open(repo_path)?) +} + +#[derive(Clone, FromRef)] +pub struct Server { + pub config: &'static Config, + pub db: SqlitePool, + pub repo: Arc, +} + +impl Server { + pub async fn new( + config: &'static Config, + db_path: &Path, + repo_path: &Path, + ) -> somehow::Result { + Ok(Self { + config, + db: open_db(db_path).await?, + repo: Arc::new(open_repo(repo_path)?), + }) + } + + pub async fn run(&self) -> somehow::Result<()> { + select! { + e = web::run(self.clone()) => e, + () = recurring::run(self.clone()) => Ok(()), + } + } + + pub async fn shut_down(self) { + info!("Closing db"); + self.db.close().await; } } diff --git a/src/server/recurring.rs b/src/server/recurring.rs index 27a646a..5209499 100644 --- a/src/server/recurring.rs +++ b/src/server/recurring.rs @@ -8,9 +8,9 @@ mod repo; use tracing::{debug_span, error, Instrument}; -use crate::state::AppState; +use super::Server; -async fn recurring_task(state: &AppState) { +async fn recurring_task(state: &Server) { async { if let Err(e) = repo::update(&state.db, state.repo.clone()).await { error!("Error updating repo:\n{e:?}"); @@ -28,7 +28,7 @@ async fn recurring_task(state: &AppState) { .await; } -pub async fn run(state: AppState) { +pub async fn run(state: Server) { loop { recurring_task(&state).await; tokio::time::sleep(state.config.repo.update_delay).await; diff --git a/src/server/web.rs b/src/server/web.rs index 465a4e0..2f5d5b2 100644 --- a/src/server/web.rs +++ b/src/server/web.rs @@ -5,9 +5,11 @@ mod queue; mod queue_id; mod r#static; -use axum::{routing::get, Router, Server}; +use axum::{routing::get, Router}; -use crate::{config::Config, somehow, state::AppState}; +use crate::{config::Config, somehow}; + +use super::Server; pub enum Tab { Index, @@ -37,7 +39,7 @@ impl Base { } } -pub async fn run(state: AppState) -> somehow::Result<()> { +pub async fn run(state: Server) -> somehow::Result<()> { // TODO Add text body to body-less status codes let app = Router::new() @@ -50,7 +52,7 @@ pub async fn run(state: AppState) -> somehow::Result<()> { .fallback(get(r#static::static_handler)) .with_state(state.clone()); - Server::bind(&"0.0.0.0:8000".parse().unwrap()) + axum::Server::bind(&"0.0.0.0:8000".parse().unwrap()) .serve(app.into_make_service()) .await?; diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 55528be..0000000 --- a/src/state.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Globally accessible application state. - -use std::{path::Path, sync::Arc, time::Duration}; - -use axum::extract::FromRef; -use gix::ThreadSafeRepository; -use sqlx::{ - sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}, - SqlitePool, -}; -use tracing::{debug, info}; - -use crate::{config::Config, somehow}; - -async fn open_db(db_path: &Path) -> sqlx::Result { - let options = SqliteConnectOptions::new() - // https://www.sqlite.org/pragma.html#pragma_journal_mode - .journal_mode(SqliteJournalMode::Wal) - // https://www.sqlite.org/pragma.html#pragma_synchronous - // NORMAL recommended when using WAL, can't cause corruption - .synchronous(SqliteSynchronous::Normal) - // https://www.sqlite.org/pragma.html#pragma_foreign_keys - .foreign_keys(true) - // https://www.sqlite.org/pragma.html#pragma_trusted_schema - // The docs recommend always turning this off - .pragma("trusted_schema", "false") - .filename(db_path) - .create_if_missing(true) - // https://www.sqlite.org/lang_analyze.html#recommended_usage_pattern - // https://www.sqlite.org/pragma.html#pragma_analysis_limit - // https://www.sqlite.org/pragma.html#pragma_optimize - .optimize_on_close(true, Some(1000)); - - info!(path = %db_path.display(), "Opening db"); - let pool = SqlitePoolOptions::new() - // Regularly optimize the db as recommended by the sqlite docs - // https://www.sqlite.org/lang_analyze.html#recommended_usage_pattern - // https://github.com/launchbadge/sqlx/issues/2111#issuecomment-1254394698 - .max_lifetime(Some(Duration::from_secs(60 * 60 * 24))) - .connect_with(options) - .await?; - - debug!("Applying outstanding db migrations"); - sqlx::migrate!().run(&pool).await?; - - Ok(pool) -} - -fn open_repo(repo_path: &Path) -> somehow::Result { - info!(path = %repo_path.display(), "Opening repo"); - Ok(ThreadSafeRepository::open(repo_path)?) -} - -#[derive(Clone, FromRef)] -pub struct AppState { - pub config: &'static Config, - pub db: SqlitePool, - pub repo: Arc, -} - -impl AppState { - pub async fn new( - config: &'static Config, - db_path: &Path, - repo_path: &Path, - ) -> somehow::Result { - Ok(Self { - config, - db: open_db(db_path).await?, - repo: Arc::new(open_repo(repo_path)?), - }) - } - - pub async fn shut_down(self) { - info!("Closing db"); - self.db.close().await; - } -}