use std::path::PathBuf;
use crate::ensure::Endpoint;
#[must_use]
pub fn resolve_state_dir() -> PathBuf {
if let Some(p) = std::env::var("TC_DATA").ok().filter(|s| !s.is_empty()) {
return PathBuf::from(p);
}
#[cfg(windows)]
{
if let Ok(p) = std::env::var("LOCALAPPDATA") {
return PathBuf::from(p).join("terminal-commanderd").join("state");
}
}
#[cfg(unix)]
{
if let Ok(p) = std::env::var("HOME") {
return PathBuf::from(p)
.join(".local")
.join("share")
.join("terminal-commanderd");
}
}
std::env::temp_dir()
.join("terminal-commanderd")
.join("state")
}
#[must_use]
pub fn resolve_socket_path() -> PathBuf {
if let Some(p) = std::env::var("TC_SOCKET").ok().filter(|s| !s.is_empty()) {
return PathBuf::from(p);
}
#[cfg(windows)]
{
let user = std::env::var("USERNAME")
.or_else(|_| std::env::var("USER"))
.unwrap_or_else(|_| "default".to_owned());
return PathBuf::from(format!(r"\\.\pipe\terminal-commander-{user}"));
}
#[cfg(unix)]
{
return resolve_state_dir().join("terminal-commanderd.sock");
}
#[allow(unreachable_code)]
resolve_state_dir().join("terminal-commanderd.sock")
}
#[must_use]
pub fn endpoint_from_socket_path(p: &std::path::Path) -> Endpoint {
let s = p.to_string_lossy();
if s.starts_with(r"\\.\pipe\") {
Endpoint::WindowsPipe {
name: s.into_owned(),
}
} else {
Endpoint::UnixSocket {
path: p.to_path_buf(),
}
}
}
#[must_use]
pub fn resolve_log_path() -> PathBuf {
resolve_state_dir()
.join("logs")
.join("terminal-commanderd.log")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tc_data_env_overrides_everything() {
unsafe {
std::env::set_var("TC_DATA", "/custom/path");
}
let result = resolve_state_dir();
unsafe {
std::env::remove_var("TC_DATA");
}
assert_eq!(result, PathBuf::from("/custom/path"));
}
#[test]
fn endpoint_from_windows_pipe_path() {
let p = PathBuf::from(r"\\.\pipe\terminal-commander-poslj");
match endpoint_from_socket_path(&p) {
Endpoint::WindowsPipe { name } => {
assert_eq!(name, r"\\.\pipe\terminal-commander-poslj");
}
other => panic!("expected WindowsPipe, got {other:?}"),
}
}
#[test]
fn endpoint_from_unix_socket_path() {
let p = PathBuf::from("/tmp/foo/terminal-commanderd.sock");
match endpoint_from_socket_path(&p) {
Endpoint::UnixSocket { path } => assert_eq!(path, p),
other => panic!("expected UnixSocket, got {other:?}"),
}
}
#[cfg(windows)]
#[test]
fn windows_socket_path_has_no_default_suffix() {
unsafe { std::env::remove_var("TC_SOCKET") };
let p = resolve_socket_path();
let s = p.to_string_lossy();
assert!(s.starts_with(r"\\.\pipe\terminal-commander-"));
assert!(
!s.ends_with("-default"),
"got {s} — pipe must match daemon DaemonConfig::pipe_name() which has no -default suffix"
);
}
#[test]
fn empty_tc_data_is_ignored() {
let prev = std::env::var("TC_DATA").ok();
unsafe { std::env::set_var("TC_DATA", "") };
let result = resolve_state_dir();
match prev {
Some(p) => unsafe { std::env::set_var("TC_DATA", p) },
None => unsafe { std::env::remove_var("TC_DATA") },
}
assert!(
!result.as_os_str().is_empty(),
"empty TC_DATA must fall through to platform default, got {result:?}"
);
}
#[test]
fn empty_tc_socket_is_ignored() {
let prev = std::env::var("TC_SOCKET").ok();
unsafe { std::env::set_var("TC_SOCKET", "") };
let result = resolve_socket_path();
match prev {
Some(p) => unsafe { std::env::set_var("TC_SOCKET", p) },
None => unsafe { std::env::remove_var("TC_SOCKET") },
}
assert!(
!result.as_os_str().is_empty(),
"empty TC_SOCKET must fall through to platform default, got {result:?}"
);
}
#[cfg(unix)]
#[test]
fn xdg_state_home_is_ignored_on_unix() {
let prev_xdg = std::env::var("XDG_STATE_HOME").ok();
let prev_tc = std::env::var("TC_DATA").ok();
let prev_home = std::env::var("HOME").ok();
unsafe {
std::env::set_var("XDG_STATE_HOME", "/should-be-ignored");
std::env::remove_var("TC_DATA");
std::env::set_var("HOME", "/test-home");
}
let result = resolve_state_dir();
unsafe {
match prev_xdg {
Some(p) => std::env::set_var("XDG_STATE_HOME", p),
None => std::env::remove_var("XDG_STATE_HOME"),
}
match prev_tc {
Some(p) => std::env::set_var("TC_DATA", p),
None => std::env::remove_var("TC_DATA"),
}
match prev_home {
Some(p) => std::env::set_var("HOME", p),
None => std::env::remove_var("HOME"),
}
}
assert_eq!(
result,
std::path::PathBuf::from("/test-home/.local/share/terminal-commanderd"),
"XDG_STATE_HOME must NOT be consulted (daemon ignores it)"
);
}
}