use tokio_rusqlite::Connection;
use super::*;
mod instruction;
#[cfg(test)] mod tests;
pub use self::instruction::{
add::Add,
query::{OrderField, Query, QueryCriteria},
remove::Remove,
DatabaseInstruction,
};
#[derive(Debug, Clone)]
pub struct Database {
pub conn: Connection,
}
impl Database {
pub async fn open(path: impl AsRef<Path>) -> Result<Self> {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
let conn = Connection::open(path.as_ref()).await?;
conn
.call(|conn| {
Ok(conn.execute_batch(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/migrations/init.sql"
)))?)
})
.await?;
let db = Self { conn };
if db.get_storage_path().await.is_err() {
db.set_storage_path(Self::default_storage_path()).await?;
}
Ok(db)
}
pub async fn get_storage_path(&self) -> Result<PathBuf> {
Ok(
self
.conn
.call(|conn| {
Ok(
conn
.prepare_cached("SELECT value FROM config WHERE key = 'storage_path'")?
.query_row([], |row| Ok(PathBuf::from(row.get::<_, String>(0)?)))?,
)
})
.await?,
)
}
pub async fn set_storage_path(&self, path: impl AsRef<Path>) -> Result<()> {
let original_path_result = self.get_storage_path().await;
let path = path.as_ref();
let absolute_path =
if !path.is_absolute() { std::env::current_dir()?.join(path) } else { path.to_path_buf() };
let test_file = absolute_path.join(".learner_write_test");
match std::fs::create_dir_all(&absolute_path) {
Ok(_) => {
match std::fs::write(&test_file, b"test") {
Ok(_) => {
let _ = std::fs::remove_file(&test_file);
},
Err(e) => {
return Err(match e.kind() {
std::io::ErrorKind::PermissionDenied => LearnerError::Path(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Insufficient permissions to write to storage directory",
)),
std::io::ErrorKind::ReadOnlyFilesystem => LearnerError::Path(std::io::Error::new(
std::io::ErrorKind::ReadOnlyFilesystem,
"Storage location is on a read-only filesystem",
)),
_ => LearnerError::Path(e),
});
},
}
},
Err(e) => {
return Err(LearnerError::Path(std::io::Error::new(
e.kind(),
format!("Failed to create storage directory: {}", e),
)));
},
}
let path_str = absolute_path.to_string_lossy().to_string();
self
.conn
.call(move |conn| {
Ok(
conn
.execute("INSERT OR REPLACE INTO config (key, value) VALUES ('storage_path', ?1)", [
path_str,
])?,
)
})
.await?;
if let Ok(original_path) = original_path_result {
warn!(
"Original storage path was {:?}, set a new path to {:?}. Please be careful to check that \
your documents have been moved or that you intended to do this operation!",
original_path, absolute_path
);
}
Ok(())
}
pub fn default_path() -> PathBuf {
dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")).join("learner").join("learner.db")
}
pub fn default_storage_path() -> PathBuf {
let base_path = dirs::document_dir().unwrap_or_else(|| PathBuf::from("."));
if !base_path.is_absolute() {
std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")).join(base_path)
} else {
base_path
}
.join("learner")
.join("papers")
}
}