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
45pub mod migrate {
47 use crate::db::DBType;
48 use sqlx::SqlitePool;
49
50 pub mod prehnite_book {
52 use sqlx::migrate::Migrator;
53 use sqlx::sqlx_macros::migrate;
54
55 pub static MIGRATOR: Migrator = migrate!("../migrations/prehnite_book");
57 }
58
59 pub mod app_global {
61 use sqlx::migrate::Migrator;
62 use sqlx::sqlx_macros::migrate;
63
64 pub static MIGRATOR: Migrator = migrate!("../migrations/app_global");
66 }
67
68 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
104pub 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
124pub 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]
130pub 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
141pub async fn acquire_book_or_alert() -> PoolConnection<Sqlite> {
143 acquire_or_log(DBType::PrehniteBook).await.unwrap_or_alert()
144}
145
146#[tracing::instrument]
147pub 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
172pub 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)]
207pub struct Database {
209 app_global_db_pool: Arc<RwLock<Pool>>,
210 prehnite_book_db_pool: Arc<RwLock<Pool>>,
211}
212
213#[derive(Error, Debug)]
214pub 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 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 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 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 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}