use super::namespace::home_dir_short_hash;
use super::{CACHE_DIR_ENV, COLOCATE_ENV};
use crate::core::NormalizedPath;
use std::ffi::OsString;
use std::path::Path;
#[must_use]
pub fn default_cache_dir() -> NormalizedPath {
default_cache_dir_from_env_value(std::env::var_os(CACHE_DIR_ENV))
}
pub(super) fn default_cache_dir_from_env_value(value: Option<OsString>) -> NormalizedPath {
resolve_cache_root_from_env_value(value).0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheRootSource {
Env,
Colocated,
Default,
}
impl CacheRootSource {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Env => "env:ZCCACHE_CACHE_DIR",
Self::Colocated => "colocate:cross_volume",
Self::Default => "default:platform_dirs",
}
}
}
impl std::fmt::Display for CacheRootSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[must_use]
pub fn resolve_cache_root() -> (NormalizedPath, CacheRootSource) {
resolve_cache_root_from_env_value(std::env::var_os(CACHE_DIR_ENV))
}
pub(super) fn resolve_cache_root_from_env_value(
value: Option<OsString>,
) -> (NormalizedPath, CacheRootSource) {
let (root, source) = resolve_cache_root_top_level_from_env_value(value);
(effective_cache_root_from_top_level(&root), source)
}
#[must_use]
pub fn resolve_cache_root_top_level() -> (NormalizedPath, CacheRootSource) {
resolve_cache_root_top_level_from_env_value(std::env::var_os(CACHE_DIR_ENV))
}
pub(super) fn resolve_cache_root_top_level_from_env_value(
value: Option<OsString>,
) -> (NormalizedPath, CacheRootSource) {
if let Some(p) = cache_dir_from_env_value(value) {
return (p, CacheRootSource::Env);
}
let home = dirs_fallback();
if colocate_enabled() {
if let Some(p) = colocated_cache_dir(&home) {
return (p, CacheRootSource::Colocated);
}
}
(home.join(".zccache"), CacheRootSource::Default)
}
#[must_use]
pub fn versioned_subdir() -> String {
format!("v{}", crate::core::VERSION)
}
#[must_use]
pub fn effective_cache_root_from_top_level(cache_root: &NormalizedPath) -> NormalizedPath {
let version = versioned_subdir();
if cache_root
.file_name()
.and_then(|segment| segment.to_str())
.is_some_and(|segment| segment == version)
{
return cache_root.clone();
}
cache_root.join(version)
}
pub(super) fn colocate_enabled() -> bool {
std::env::var(COLOCATE_ENV)
.ok()
.is_some_and(|v| !v.is_empty() && v != "0")
}
fn colocated_cache_dir(home: &NormalizedPath) -> Option<NormalizedPath> {
let cwd = std::env::current_dir().ok()?;
let home_path: &Path = home.as_path();
let home_vol = volume_root(home_path)?;
let cwd_vol = volume_root(&cwd)?;
if same_volume_root(&home_vol, &cwd_vol) {
return None;
}
let basename = home_path
.file_name()
.and_then(|n| n.to_str())
.map(sanitize_path_component)
.unwrap_or_default();
let stem = if basename.is_empty() {
format!(".zccache-{}", home_dir_short_hash(home_path))
} else {
format!(".zccache-{}-{}", basename, home_dir_short_hash(home_path))
};
Some(NormalizedPath::from(cwd_vol.join(stem)))
}
pub(super) fn volume_root(path: &Path) -> Option<std::path::PathBuf> {
use std::path::Component;
let mut root = std::path::PathBuf::new();
let mut saw_anchor = false;
for c in path.components() {
match c {
Component::Prefix(p) => {
root.push(p.as_os_str());
saw_anchor = true;
}
Component::RootDir => {
root.push(c);
saw_anchor = true;
break;
}
_ => break,
}
}
if saw_anchor {
Some(root)
} else {
None
}
}
pub(super) fn same_volume_root(a: &Path, b: &Path) -> bool {
let a_str = a.to_string_lossy();
let b_str = b.to_string_lossy();
if cfg!(windows) {
a_str.eq_ignore_ascii_case(&b_str)
} else {
a_str == b_str
}
}
pub(super) fn sanitize_path_component(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else {
'_'
}
})
.take(32)
.collect()
}
#[must_use]
pub fn cache_dir_override() -> Option<NormalizedPath> {
cache_dir_from_env_value(std::env::var_os(CACHE_DIR_ENV))
}
fn dirs_fallback() -> NormalizedPath {
std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.map(NormalizedPath::from)
.unwrap_or_else(|_| ".".into())
}
pub(super) fn cache_dir_from_env_value(value: Option<OsString>) -> Option<NormalizedPath> {
let value = value?;
if value.is_empty() {
return None;
}
Some(normalize_cache_dir_override(std::path::Path::new(&value)))
}
fn normalize_cache_dir_override(path: &std::path::Path) -> NormalizedPath {
if path.is_absolute() {
path.into()
} else {
std::env::current_dir()
.unwrap_or_default()
.join(path)
.into()
}
}