use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{anyhow, Result};
use serde_json::Value;
pub(crate) fn path_exists(path: &Path) -> bool {
fs::symlink_metadata(path).is_ok()
}
pub(crate) fn read_json_if_present(path: &Path) -> Option<Value> {
let contents = fs::read_to_string(path).ok()?;
serde_json::from_str(&contents).ok()
}
pub(crate) fn profile_file_if_present(profile_dir: &Path, name: &str) -> Option<PathBuf> {
let path = profile_dir.join(name);
path_exists(&path).then_some(path)
}
pub(crate) fn make_temp_dir(prefix: &str) -> Result<PathBuf> {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let parent = std::env::temp_dir();
loop {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|elapsed| elapsed.as_nanos())
.unwrap_or_default();
let count = COUNTER.fetch_add(1, Ordering::Relaxed);
let candidate = parent.join(format!("{prefix}{}-{nanos:x}-{count}", std::process::id()));
match fs::create_dir(&candidate) {
Ok(()) => return Ok(candidate),
Err(error) if error.kind() == ErrorKind::AlreadyExists => continue,
Err(error) => {
return Err(anyhow!(error).context(format!(
"Could not create a temporary directory in {}",
parent.display()
)))
}
}
}
}
pub(crate) fn remove_dir_quietly(path: &Path) {
let _ = fs::remove_dir_all(path);
}
pub(crate) fn copy_dir_recursive(source: &Path, target: &Path) -> Result<()> {
fs::create_dir_all(target).map_err(|error| {
anyhow!(error).context(format!("Could not create {}", target.display()))
})?;
for entry in fs::read_dir(source)
.map_err(|error| anyhow!(error).context(format!("Could not read {}", source.display())))?
{
let entry = entry?;
let destination = target.join(entry.file_name());
let file_type = entry.file_type()?;
if file_type.is_dir() {
copy_dir_recursive(&entry.path(), &destination)?;
} else {
fs::copy(entry.path(), &destination).map_err(|error| {
anyhow!(error).context(format!("Could not copy {}", entry.path().display()))
})?;
}
}
Ok(())
}
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct OrderedEntries(pub Vec<(String, Value)>);
impl<'de> serde::Deserialize<'de> for OrderedEntries {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct EntriesVisitor;
impl<'de> serde::de::Visitor<'de> for EntriesVisitor {
type Value = OrderedEntries;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a JSON object")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> Result<Self::Value, A::Error> {
let mut entries: Vec<(String, Value)> = Vec::new();
while let Some((key, value)) = map.next_entry::<String, Value>()? {
match entries.iter_mut().find(|(existing, _)| *existing == key) {
Some(entry) => entry.1 = value,
None => entries.push((key, value)),
}
}
Ok(OrderedEntries(entries))
}
}
deserializer.deserialize_map(EntriesVisitor)
}
}