use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use super::dirs::{cache_dir, db_dir, global_config_toml, last_applied_config_path};
use super::names::{UBLX_NAMES, path_to_hex, sanitize_name_for_fs};
#[derive(Clone, Debug)]
pub struct UblxPaths {
pub dir_to_ublx_abs: PathBuf,
}
impl UblxPaths {
#[must_use]
pub fn new(dir_to_ublx: &Path) -> Self {
Self {
dir_to_ublx_abs: dir_to_ublx.to_path_buf(),
}
}
fn db_stem(&self) -> String {
let dir_name = self
.dir_to_ublx_abs
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("root");
let safe_name = sanitize_name_for_fs(dir_name);
let hash = path_to_hex(&self.dir_to_ublx_abs);
format!("{safe_name}_{hash}")
}
#[must_use]
fn db_filename(&self) -> String {
format!("{}{}", self.db_stem(), UBLX_NAMES.index_db_file_ext)
}
#[must_use]
pub fn db_dir(&self) -> Option<PathBuf> {
db_dir()
}
pub fn ensure_db_dir(&self) -> Result<PathBuf> {
let dir = self
.db_dir()
.ok_or_else(|| anyhow::anyhow!("could not resolve user cache directory"))?;
fs::create_dir_all(&dir)?;
Ok(dir)
}
#[must_use]
pub fn log_path(&self) -> PathBuf {
self.dir_to_ublx_abs
.join(format!("{}.log", UBLX_NAMES.pkg_name))
}
#[must_use]
pub fn hidden_toml(&self) -> PathBuf {
self.dir_to_ublx_abs
.join(UBLX_NAMES.local_config_hidden_toml)
}
#[must_use]
pub fn visible_toml(&self) -> PathBuf {
self.dir_to_ublx_abs
.join(UBLX_NAMES.local_config_visible_toml)
}
#[must_use]
pub fn is_config_file(&self, path: &Path) -> bool {
let Some(name) = path.file_name() else {
return false;
};
name == OsStr::new(UBLX_NAMES.local_config_visible_toml)
|| name == OsStr::new(UBLX_NAMES.local_config_hidden_toml)
}
#[must_use]
pub fn toml_path(&self) -> Option<PathBuf> {
let hidden = self.hidden_toml();
let visible = self.visible_toml();
if hidden.exists() {
Some(hidden)
} else if visible.exists() {
Some(visible)
} else {
None
}
}
#[must_use]
pub fn local_config_path_for_write(&self) -> PathBuf {
self.toml_path().unwrap_or_else(|| self.hidden_toml())
}
#[must_use]
pub fn db(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(self.db_filename())
}
#[must_use]
pub fn nefax_db(&self) -> PathBuf {
self.dir_to_ublx_abs.join(UBLX_NAMES.nefax_db)
}
#[must_use]
pub fn tmp(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(format!(
"{}_tmp{}",
self.db_stem(),
UBLX_NAMES.index_db_file_ext
))
}
#[must_use]
pub fn tmp_wal(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(format!(
"{}_tmp{}-wal",
self.db_stem(),
UBLX_NAMES.index_db_file_ext
))
}
#[must_use]
pub fn tmp_shm(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(format!(
"{}_tmp{}-shm",
self.db_stem(),
UBLX_NAMES.index_db_file_ext
))
}
#[must_use]
pub fn wal(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(format!(
"{}{}-wal",
self.db_stem(),
UBLX_NAMES.index_db_file_ext
))
}
#[must_use]
pub fn shm(&self) -> PathBuf {
self.db_dir()
.unwrap_or_else(|| self.dir_to_ublx_abs.clone())
.join(format!(
"{}{}-shm",
self.db_stem(),
UBLX_NAMES.index_db_file_ext
))
}
#[must_use]
pub fn exclude(&self) -> Vec<String> {
vec![
UBLX_NAMES.nefax_db.to_string(),
UBLX_NAMES.local_config_visible_toml.to_string(),
UBLX_NAMES.local_config_hidden_toml.to_string(),
UBLX_NAMES.zahir_export_dir_name.to_string(),
]
}
pub fn remove_aux_files(&self) -> Result<(), anyhow::Error> {
for p in [
self.tmp(),
self.tmp_wal(),
self.tmp_shm(),
self.wal(),
self.shm(),
] {
if p.exists() {
fs::remove_file(&p)?;
}
}
Ok(())
}
#[must_use]
pub fn global_config(&self) -> Option<PathBuf> {
global_config_toml()
}
#[allow(dead_code)]
#[must_use]
pub fn cache_dir(&self) -> Option<PathBuf> {
cache_dir()
}
#[must_use]
pub fn last_applied_config_path(&self) -> Option<PathBuf> {
last_applied_config_path(&self.dir_to_ublx_abs)
}
}
#[must_use]
pub fn get_log_path(dir_to_ublx: &Path) -> PathBuf {
UblxPaths::new(dir_to_ublx).log_path()
}
#[must_use]
pub fn normalize_rel_path_for_policy(s: &str) -> String {
let s = s.replace('\\', "/");
let s = s.trim_start_matches("./");
s.trim_end_matches('/').to_string()
}
#[must_use]
pub fn path_is_under_or_equal(rel: &str, prefix: &str) -> bool {
rel == prefix || (rel.starts_with(prefix) && rel.as_bytes().get(prefix.len()) == Some(&b'/'))
}