prehnite_core/db/
mod.rs

1#![allow(unused)]
2#![doc = "アプリケーションのデータベース"]
3pub mod query;
4pub mod schema;
5mod util;
6
7use crate::db::migrate::migrate;
8use crate::settings::registry::SettingRegistry;
9use crate::settings::{GlobalSettingKey, SettingKey};
10use crate::util::alert::{alert_i18n_show, alert_i18n_spawn, UnwrapOrErrorAlert};
11use crate::util::app_global::global_dir;
12use crate::util::file_dialog::OpenPrehniteBookStatus;
13use chrono::Duration;
14use log::LevelFilter;
15use native_dialog::MessageLevel;
16use sqlx::pool::PoolConnection;
17use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
18use sqlx::{ConnectOptions, Sqlite, SqlitePool};
19use std::fmt::{Debug, Display, Formatter};
20use std::path::{Path, PathBuf};
21use std::sync::{Arc, LazyLock, LockResult, OnceLock};
22use strum::{EnumString, IntoStaticStr};
23use thiserror::Error;
24use tokio::sync::RwLock;
25use tracing::error;
26use tracing_unwrap::ResultExt;
27
28impl UnwrapOrErrorAlert<PoolConnection<Sqlite>> for Option<PoolConnection<Sqlite>> {
29    fn unwrap_or_alert(self) -> PoolConnection<Sqlite> {
30        self.unwrap_or_else(|| {
31            alert_i18n_show(("error", "cant-connect-database"), MessageLevel::Error);
32            panic!()
33        })
34    }
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumString, IntoStaticStr)]
38pub enum DBType {
39    #[strum(serialize = "Prehnite book")]
40    PrehniteBook,
41    #[strum(serialize = "App global settings")]
42    AppGlobal,
43}
44
45/// データベースのマイグレーション
46pub mod migrate {
47    use crate::db::DBType;
48    use sqlx::SqlitePool;
49
50    /// ブックファイル
51    pub mod prehnite_book {
52        use sqlx::migrate::Migrator;
53        use sqlx::sqlx_macros::migrate;
54
55        /// マイグレーション定義
56        pub static MIGRATOR: Migrator = migrate!("../migrations/prehnite_book");
57    }
58
59    /// アプリのグローバルデータベース
60    pub mod app_global {
61        use sqlx::migrate::Migrator;
62        use sqlx::sqlx_macros::migrate;
63
64        /// マイグレーション定義
65        pub static MIGRATOR: Migrator = migrate!("../migrations/app_global");
66    }
67
68    /// マイグレーションを実行します。
69    /// # Parameters
70    /// - `pool` マイグレーションを実行するDB接続
71    /// - `mode` 初期化したいデータベースのタイプ
72    pub async fn migrate(
73        pool: &SqlitePool,
74        mode: DBType,
75    ) -> Result<(), sqlx::migrate::MigrateError> {
76        match mode {
77            DBType::PrehniteBook => &prehnite_book::MIGRATOR,
78            DBType::AppGlobal => &app_global::MIGRATOR,
79        }
80        .run(pool)
81        .await
82    }
83}
84
85#[derive(Debug)]
86struct Pool {
87    pool: Option<SqlitePool>,
88}
89
90impl Pool {
91    fn new(pool: Option<SqlitePool>) -> Self {
92        Self { pool }
93    }
94
95    fn set_pool(&mut self, pool: Option<SqlitePool>) {
96        self.pool = pool;
97    }
98
99    fn get_pool(&self) -> &Option<SqlitePool> {
100        &self.pool
101    }
102}
103
104/// グローバルデータベース接続を初期化します。
105pub async fn initialize_db() -> Result<(), DatabaseError> {
106    let v = Database::initialize().await?;
107    DATABASE.set(RwLock::new(v));
108    Ok(())
109}
110
111static DATABASE: OnceLock<RwLock<Database>> = OnceLock::new();
112
113#[tracing::instrument]
114fn get_database() -> &'static RwLock<Database> {
115    match DATABASE.get() {
116        None => {
117            error!("Failed to get database. The database may not be initialized.");
118            panic!();
119        }
120        Some(v) => v,
121    }
122}
123
124/// グローバルデータベース接続を取得します。
125pub async fn acquire_conn(mode: DBType) -> Result<Option<PoolConnection<Sqlite>>, DatabaseError> {
126    get_database().read().await.acquire(mode.clone()).await
127}
128
129#[tracing::instrument]
130/// グローバルデータベース接続を取得します。エラーが発生した場合はログに出力します。
131pub async fn acquire_or_log(mode: DBType) -> Option<PoolConnection<Sqlite>> {
132    match acquire_conn(mode.clone()).await.ok_or_log()? {
133        Some(v) => Some(v),
134        None => {
135            error!("{} Database not connected.", <&'static str>::from(mode));
136            None
137        }
138    }
139}
140
141/// グローバルデータベース接続を取得します。エラーが発生した場合はログに出力し、アラートを表示します。
142pub async fn acquire_book_or_alert() -> PoolConnection<Sqlite> {
143    acquire_or_log(DBType::PrehniteBook).await.unwrap_or_alert()
144}
145
146#[tracing::instrument]
147/// Prehniteブックファイルを開きます。エラーが発生した場合はログに出力し、アラートを表示します。
148pub async fn open_book_or_alert(book_path: PathBuf) -> bool {
149    let r = get_database()
150        .write()
151        .await
152        .open_book(book_path.clone())
153        .await
154        .ok_or_log();
155    match r {
156        Some(_) => {
157            SettingRegistry::immediate_apply(
158                GlobalSettingKey::LastOpened.into(),
159                book_path.to_str().into(),
160            )
161            .await
162            .ok_or_log();
163            true
164        }
165        None => {
166            alert_i18n_spawn(("error", "book-open-error"), MessageLevel::Error).await;
167            false
168        }
169    }
170}
171
172/// Prehniteブックファイルを閉じます。エラーが発生した場合はログに出力します。
173pub async fn close_book_or_log() {
174    get_database()
175        .write()
176        .await
177        .prehnite_book_db_pool
178        .write()
179        .await
180        .set_pool(None);
181    SettingRegistry::immediate_apply(
182        GlobalSettingKey::LastOpened.into(),
183        Option::<String>::from(None).into(),
184    )
185    .await
186    .ok_or_log();
187}
188
189static IS_PREHNITE_BOOK_OPENED: LazyLock<Arc<std::sync::RwLock<DbOpenedStatus>>> =
190    LazyLock::new(|| Arc::new(std::sync::RwLock::new(DbOpenedStatus::default())));
191
192struct DbOpenedStatus(bool);
193
194impl Default for DbOpenedStatus {
195    fn default() -> Self {
196        Self(false)
197    }
198}
199
200impl DbOpenedStatus {
201    fn set(&mut self, v: bool) {
202        self.0 = v;
203    }
204}
205
206#[derive(Debug)]
207/// グローバルデータベース接続の構造体
208pub struct Database {
209    app_global_db_pool: Arc<RwLock<Pool>>,
210    prehnite_book_db_pool: Arc<RwLock<Pool>>,
211}
212
213#[derive(Error, Debug)]
214/// グローバルデータベース接続のエラー
215pub enum DatabaseError {
216    #[error("Failed to execute statement.")]
217    DBError(#[from] sqlx::Error),
218    #[error("Failed to execute database migrations.")]
219    MigrateError(#[from] sqlx::migrate::MigrateError),
220    #[error("Failed to decode item_type.")]
221    ItemTypeDecodeError,
222}
223
224impl Database {
225    fn get_app_global_database_url() -> PathBuf {
226        let mut db_file = global_dir();
227        db_file.push("app_global.db");
228        db_file
229    }
230
231    fn connect_option(file: impl AsRef<Path>) -> SqliteConnectOptions {
232        SqliteConnectOptions::default()
233            .filename(file)
234            .foreign_keys(true)
235            .create_if_missing(true)
236            .log_slow_statements(
237                LevelFilter::Warn,
238                Duration::milliseconds(300).to_std().unwrap(),
239            )
240            .log_statements(LevelFilter::Trace)
241    }
242
243    /// 接続を初期化し、マイグレーションを実行します。
244    pub async fn initialize() -> Result<Self, DatabaseError> {
245        let app_global_path = Self::get_app_global_database_url();
246        let mut pool = SqlitePoolOptions::new()
247            .connect_with(Self::connect_option(app_global_path))
248            .await?;
249
250        migrate(&mut pool, DBType::AppGlobal).await?;
251
252        Ok(Self {
253            app_global_db_pool: Arc::new(RwLock::new(Pool::new(Some(pool)))),
254            prehnite_book_db_pool: Arc::new(RwLock::new(Pool::new(None))),
255        })
256    }
257
258    #[tracing::instrument]
259    /// Prehniteブックを新しく開き、マイグレーションを実行します。
260    pub async fn open_book(&mut self, path: impl AsRef<Path> + Debug) -> Result<(), DatabaseError> {
261        let pool_result = SqlitePoolOptions::new()
262            .connect_with(Self::connect_option(path))
263            .await;
264
265        let mut pool = pool_result?;
266
267        migrate(&mut pool, DBType::PrehniteBook).await?;
268
269        self.prehnite_book_db_pool
270            .write()
271            .await
272            .set_pool(Some(pool));
273        IS_PREHNITE_BOOK_OPENED.write().unwrap_or_log().set(true);
274        Ok(())
275    }
276
277    /// データベース接続を取得します。
278    pub async fn acquire(
279        &self,
280        mode: DBType,
281    ) -> Result<Option<PoolConnection<Sqlite>>, DatabaseError> {
282        Ok(match mode {
283            DBType::PrehniteBook => {
284                match self.prehnite_book_db_pool.clone().read().await.get_pool() {
285                    None => None,
286                    Some(v) => Some(v.acquire().await?),
287                }
288            }
289            DBType::AppGlobal => match self.app_global_db_pool.clone().read().await.get_pool() {
290                None => None,
291                Some(v) => Some(v.acquire().await?),
292            },
293        })
294    }
295
296    /// Prehniteブックが開かれているか否か。
297    pub fn is_book_opened() -> bool {
298        IS_PREHNITE_BOOK_OPENED
299            .clone()
300            .read()
301            .map(|v| v.0)
302            .unwrap_or_default()
303    }
304}