gossip/src/profile.rs
2023-07-31 17:37:53 +12:00

120 lines
3.7 KiB
Rust

use crate::error::Error;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;
lazy_static! {
static ref CURRENT: RwLock<Option<Profile>> = RwLock::new(None);
}
/// Storage paths
#[derive(Clone, Debug, PartialEq)]
pub struct Profile {
/// The base directory for all gossip data
pub base_dir: PathBuf,
/// The directory for cache
pub cache_dir: PathBuf,
/// 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 = fs::canonicalize(data_dir)?;
// 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
fs::canonicalize(PathBuf::from(dir))?
}
Err(_) => {
let mut base_dir = data_dir;
base_dir.push("gossip");
fs::canonicalize(base_dir)? // because gossip might be a link
}
};
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."));
}
// 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
}
Err(_) => base_dir.clone(),
};
let lmdb_dir = {
let mut lmdb_dir = base_dir.clone();
lmdb_dir.push("lmdb");
lmdb_dir
};
// Create all these directories if missing
fs::create_dir_all(&base_dir)?;
fs::create_dir_all(&cache_dir)?;
fs::create_dir_all(&profile_dir)?;
fs::create_dir_all(&lmdb_dir)?;
Ok(Profile {
base_dir,
profile_dir,
cache_dir,
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() {
return Ok(current.as_ref().unwrap().clone());
}
}
let created = Profile::new()?;
let mut w = CURRENT.write().unwrap();
*w = Some(created.clone());
Ok(created)
}
}