1mod disabled_commands;
2mod kv_store;
3pub mod model;
4mod reddit_embed;
5mod tic_tac_toe;
6mod tiktok_embed;
7
8pub use self::tic_tac_toe::{
9 TicTacToeCreateGameError,
10 TicTacToeTryMoveError,
11 TicTacToeTryMoveResponse,
12};
13use anyhow::Context;
14use camino::{
15 Utf8Path,
16 Utf8PathBuf,
17};
18use once_cell::sync::Lazy;
19use std::{
20 os::raw::c_int,
21 sync::Arc,
22};
23use tracing::{
24 error,
25 warn,
26};
27
28const SETUP_TABLES_SQL: &str = include_str!("../sql/setup_tables.sql");
30
31static LOGGER_INIT: Lazy<Result<(), Arc<rusqlite::Error>>> = Lazy::new(|| {
32 unsafe { rusqlite::trace::config_log(Some(sqlite_logger_func)).map_err(Arc::new) }
38});
39
40fn sqlite_logger_func(error_code: c_int, msg: &str) {
41 warn!("sqlite error code ({}): {}", error_code, msg);
42}
43
44#[derive(Clone, Debug)]
46pub struct Database {
47 db: async_rusqlite::Database,
48}
49
50impl Database {
51 pub async unsafe fn new<P>(path: P, create_if_missing: bool) -> anyhow::Result<Self>
56 where
57 P: Into<Utf8PathBuf>,
58 {
59 let path = path.into();
60 tokio::task::spawn_blocking(move || Self::blocking_new(&path, create_if_missing))
61 .await
62 .context("failed to join tokio task")?
63 }
64
65 pub unsafe fn blocking_new<P>(path: P, create_if_missing: bool) -> anyhow::Result<Self>
70 where
71 P: AsRef<Utf8Path>,
72 {
73 LOGGER_INIT
74 .clone()
75 .context("failed to init sqlite logger")?;
76
77 let db = async_rusqlite::Database::blocking_open(path.as_ref(), create_if_missing, |db| {
78 db.execute_batch(SETUP_TABLES_SQL)
79 .context("failed to setup database")?;
80 Ok(())
81 })
82 .context("failed to open database")?;
83
84 Ok(Database { db })
85 }
86
87 async fn access_db<F, R>(&self, func: F) -> anyhow::Result<R>
89 where
90 F: FnOnce(&mut rusqlite::Connection) -> R + Send + 'static,
91 R: Send + 'static,
92 {
93 Ok(self.db.access_db(move |db| func(db)).await?)
94 }
95
96 pub async fn close(&self) -> anyhow::Result<()> {
98 if let Err(e) = self
100 .db
101 .access_db(|db| {
102 db.execute("PRAGMA OPTIMIZE;", [])?;
103 db.execute("VACUUM;", [])
104 })
105 .await
106 .context("failed to access db")
107 .and_then(|v| v.context("failed to execute shutdown commands"))
108 {
109 error!("{}", e);
110 }
111 self.db
112 .close()
113 .await
114 .context("failed to send close request to db")?;
115 self.db.join().await.context("failed to join db thread")?;
116
117 Ok(())
118 }
119}