Restructure server around Server struct

This commit is contained in:
Joscha 2023-08-07 14:43:36 +02:00
parent 45abda2b6d
commit 9bdfc79c8b
5 changed files with 92 additions and 95 deletions

View file

@ -2,14 +2,12 @@ mod args;
mod config; mod config;
mod server; mod server;
mod somehow; mod somehow;
mod state;
mod util; mod util;
use std::{io, path::PathBuf, process}; use std::{io, path::PathBuf, process};
use clap::Parser; use clap::Parser;
use directories::ProjectDirs; use directories::ProjectDirs;
use state::AppState;
use tokio::{select, signal::unix::SignalKind}; use tokio::{select, signal::unix::SignalKind};
use tracing::{debug, error, info, Level}; use tracing::{debug, error, info, Level};
use tracing_subscriber::{ use tracing_subscriber::{
@ -19,6 +17,7 @@ use tracing_subscriber::{
use crate::{ use crate::{
args::{Args, NAME, VERSION}, args::{Args, NAME, VERSION},
config::Config, config::Config,
server::Server,
}; };
fn set_up_logging(verbose: u8) { fn set_up_logging(verbose: u8) {
@ -99,12 +98,12 @@ async fn run() -> somehow::Result<()> {
info!("You are running {NAME} {VERSION}"); info!("You are running {NAME} {VERSION}");
let config = load_config(args.config)?; 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"); info!("Startup complete, running");
select! { select! {
_ = wait_for_signal() => {} _ = wait_for_signal() => {}
_ = server::run(state.clone()) => {} _ = server.run() => {}
} }
select! { select! {
@ -118,7 +117,7 @@ async fn run() -> somehow::Result<()> {
// In order to fix this, I could maybe register a bare signal handler // 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 // (instead of using tokio streams) that just calls process::exit(1) and
// nothing else? // nothing else?
_ = state.shut_down() => {} _ = server.shut_down() => {}
} }
Ok(()) Ok(())

View file

@ -1,13 +1,87 @@
mod recurring; mod recurring;
mod web; 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 tokio::select;
use tracing::{debug, info};
use crate::{somehow, state::AppState}; use crate::{config::Config, somehow};
pub async fn run(state: AppState) -> somehow::Result<()> { async fn open_db(db_path: &Path) -> sqlx::Result<SqlitePool> {
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<ThreadSafeRepository> {
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<ThreadSafeRepository>,
}
impl Server {
pub async fn new(
config: &'static Config,
db_path: &Path,
repo_path: &Path,
) -> somehow::Result<Self> {
Ok(Self {
config,
db: open_db(db_path).await?,
repo: Arc::new(open_repo(repo_path)?),
})
}
pub async fn run(&self) -> somehow::Result<()> {
select! { select! {
e = web::run(state.clone()) => e, e = web::run(self.clone()) => e,
() = recurring::run(state.clone()) => Ok(()), () = recurring::run(self.clone()) => Ok(()),
}
}
pub async fn shut_down(self) {
info!("Closing db");
self.db.close().await;
} }
} }

View file

@ -8,9 +8,9 @@ mod repo;
use tracing::{debug_span, error, Instrument}; 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 { async {
if let Err(e) = repo::update(&state.db, state.repo.clone()).await { if let Err(e) = repo::update(&state.db, state.repo.clone()).await {
error!("Error updating repo:\n{e:?}"); error!("Error updating repo:\n{e:?}");
@ -28,7 +28,7 @@ async fn recurring_task(state: &AppState) {
.await; .await;
} }
pub async fn run(state: AppState) { pub async fn run(state: Server) {
loop { loop {
recurring_task(&state).await; recurring_task(&state).await;
tokio::time::sleep(state.config.repo.update_delay).await; tokio::time::sleep(state.config.repo.update_delay).await;

View file

@ -5,9 +5,11 @@ mod queue;
mod queue_id; mod queue_id;
mod r#static; 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 { pub enum Tab {
Index, 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 // TODO Add text body to body-less status codes
let app = Router::new() let app = Router::new()
@ -50,7 +52,7 @@ pub async fn run(state: AppState) -> somehow::Result<()> {
.fallback(get(r#static::static_handler)) .fallback(get(r#static::static_handler))
.with_state(state.clone()); .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()) .serve(app.into_make_service())
.await?; .await?;

View file

@ -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<SqlitePool> {
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<ThreadSafeRepository> {
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<ThreadSafeRepository>,
}
impl AppState {
pub async fn new(
config: &'static Config,
db_path: &Path,
repo_path: &Path,
) -> somehow::Result<Self> {
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;
}
}