gossip/src/profile.rs

141 lines
4.3 KiB
Rust
Raw Normal View History

use crate::error::Error;
2023-05-25 22:00:55 +00:00
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
2023-05-25 22:00:55 +00:00
use std::sync::RwLock;
#[cfg(windows)]
use normpath::PathExt;
lazy_static! {
2023-05-25 21:29:13 +00:00
static ref CURRENT: RwLock<Option<Profile>> = RwLock::new(None);
}
2023-07-08 12:44:25 +00:00
/// Storage paths
#[derive(Clone, Debug, PartialEq)]
pub struct Profile {
2023-07-08 12:44:25 +00:00
/// The base directory for all gossip data
pub base_dir: PathBuf,
2023-07-08 12:44:25 +00:00
/// The directory for cache
pub cache_dir: PathBuf,
2023-07-08 12:44:25 +00:00
/// The profile directory (could be the same as the base_dir if default)
pub profile_dir: PathBuf,
/// The LMDB directory (within the profile directory)
pub lmdb_dir: PathBuf,
}
impl Profile {
fn new() -> Result<Profile, Error> {
// Get system standard directory for user data
let data_dir = dirs::data_dir()
.ok_or::<Error>("Cannot find a directory to store application data.".into())?;
// Canonicalize (follow symlinks, resolve ".." paths)
let data_dir = normalize(data_dir)?;
2023-07-08 12:44:25 +00:00
// Push "gossip" to data_dir, or override with GOSSIP_DIR
let base_dir = match env::var("GOSSIP_DIR") {
Ok(dir) => {
tracing::info!("Using GOSSIP_DIR: {}", dir);
// Note, this must pre-exist
normalize(dir)?
2023-05-25 22:00:55 +00:00
}
Err(_) => {
2023-07-01 21:55:20 +00:00
let mut base_dir = data_dir;
base_dir.push("gossip");
// We canonicalize here because gossip might be a link, but if it
// doesn't exist yet we have to just go with basedir
normalize(base_dir.as_path()).unwrap_or(base_dir)
}
};
2023-07-08 12:44:25 +00:00
let cache_dir = {
let mut cache_dir = base_dir.clone();
cache_dir.push("cache");
cache_dir
};
// optional profile name, if specified the the user data is stored in a subdirectory
let profile_dir = match env::var("GOSSIP_PROFILE") {
Ok(profile) => {
if "cache".eq_ignore_ascii_case(profile.as_str()) {
return Err(Error::from("Profile name 'cache' is reserved."));
}
2023-07-08 12:44:25 +00:00
// Check that it doesn't corrupt the expected path
let mut dir = base_dir.clone();
dir.push(&profile);
match dir.file_name() {
Some(filename) => {
if filename != OsStr::new(&profile) {
return Err(Error::from(format!(
"Profile is not a simple filename: {}",
profile
)));
}
}
None => {
return Err(Error::from(format!("Profile is invalid: {}", profile)));
}
};
dir
}
2023-05-25 22:00:55 +00:00
Err(_) => base_dir.clone(),
};
2023-07-08 12:44:25 +00:00
let lmdb_dir = {
2023-08-12 22:53:35 +00:00
let mut lmdb_dir = profile_dir.clone();
2023-07-08 12:44:25 +00:00
lmdb_dir.push("lmdb");
2023-08-10 17:51:42 +00:00
// Windows syntax not compatible with lmdb:
if lmdb_dir.starts_with(r#"\\?\"#) {
lmdb_dir = lmdb_dir.strip_prefix(r#"\\?\"#).unwrap().to_path_buf();
}
2023-07-08 12:44:25 +00:00
lmdb_dir
};
2023-07-08 12:44:25 +00:00
// Create all these directories if missing
fs::create_dir_all(&base_dir)?;
fs::create_dir_all(&cache_dir)?;
2023-07-08 12:44:25 +00:00
fs::create_dir_all(&profile_dir)?;
fs::create_dir_all(&lmdb_dir)?;
Ok(Profile {
base_dir,
profile_dir,
cache_dir,
2023-07-08 12:44:25 +00:00
lmdb_dir,
})
}
pub fn current() -> Result<Profile, Error> {
{
// create a new scope to drop the read lock before we try to create a new profile if it doesn't exist
let current = CURRENT.read().unwrap();
if current.is_some() {
2023-05-25 22:00:55 +00:00
return Ok(current.as_ref().unwrap().clone());
}
}
let created = Profile::new()?;
let mut w = CURRENT.write().unwrap();
*w = Some(created.clone());
Ok(created)
}
2023-05-25 22:00:55 +00:00
}
#[cfg(not(windows))]
fn normalize<P: AsRef<Path>>(path: P) -> Result<PathBuf, Error> {
Ok(fs::canonicalize(path)?)
}
#[cfg(windows)]
fn normalize<P: AsRef<Path>>(path: P) -> Result<PathBuf, Error> {
2023-08-10 18:43:23 +00:00
Ok(path.as_ref().normalize()?.into_path_buf())
}