Page MenuHomeDevCentral

db.rs
No OneTemporary

// -------------------------------------------------------------
// Alkane :: Database
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Project: Nasqueron
// License: BSD-2-Clause
// -------------------------------------------------------------
use std::io::Error as IOError;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use log::warn;
use tokio::fs::{self, OpenOptions};
use crate::config::AlkaneConfig;
use crate::services::validation::is_valid_site_name;
pub struct Database {
root: String,
}
impl Database {
pub fn new<S>(root: S) -> Self
where
S: AsRef<str>,
{
Self {
root: root.as_ref().to_string(),
}
}
pub fn from_config(config: &AlkaneConfig) -> Option<Self> {
config.get_root("db").map(Self::new)
}
pub fn is_initialized(&self, site_name: &str) -> bool {
self.get_initialized_path(site_name)
.is_some_and(|path| path.exists())
}
pub async fn set_initialized(&self, site_name: &str) -> bool {
let Some(path) = self.get_initialized_path(site_name) else {
warn!(
"Can't mark invalid site name {:?} as initialized",
site_name
);
return false;
};
match fs::try_exists(&path).await {
Ok(false) => match ensure_parent_directory_exists(&path).await {
Ok(_) => match touch(&path).await {
Ok(_) => true,
Err(error) => {
warn!("Can't mark site {} as initialized: {:?}", site_name, error);
false
}
},
Err(error) => {
warn!("Can't create parent directory for {:?}: {:?}", &path, error);
false
}
},
Ok(true) => true,
Err(error) => {
warn!(
"Can't check initialization state for {}: {:?}",
site_name, error
);
false
}
}
}
fn get_initialized_path(&self, site_name: &str) -> Option<PathBuf> {
is_valid_site_name(site_name)
.then(|| Path::new(&self.root).join("initialized").join(site_name))
}
}
/// Creates an empty file, similar to the touch command
/// Ignores existing files.
async fn touch(path: &Path) -> Result<(), IOError> {
let mut options = OpenOptions::new();
options.create(true).write(true);
options.open(path).await.map(|_| ())
}
async fn ensure_parent_directory_exists(path: &Path) -> Result<(), IOError> {
let parent = path
.parent()
.ok_or_else(|| IOError::new(ErrorKind::InvalidInput, "Invalid path"))?;
fs::create_dir_all(parent).await?;
Ok(())
}
// -------------------------------------------------------------
// Tests
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_get_initialized_path_rejects_traversal() {
let database = Database::new("tests/data/db");
assert_eq!(None, database.get_initialized_path("../example.org"));
}
#[tokio::test]
pub async fn test_touch() {
let path = Path::new("tmp-touch.empty");
assert!(
!path.exists(),
"Temporary file tmp-touch.empty shouldn't exist when test starts"
);
touch(path).await.expect("File can't be created");
assert!(
path.exists(),
"Function touch returned Ok but temporary file does NOT exist."
);
fs::remove_file(path)
.await
.expect("Can't remove file after having created it.")
}
}

File Metadata

Mime Type
text/plain
Expires
Mon, Sep 14, 16:23 (12 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4037863
Default Alt Text
db.rs (3 KB)

Event Timeline