use std::path::{Path, PathBuf};
use std::sync::LazyLock;
#[cfg(not(test))]
static CONF_DIR: LazyLock<Option<PathBuf>> = LazyLock::new(config_dir);
#[cfg(test)]
static CONF_DIR: LazyLock<Option<PathBuf>> =
LazyLock::new(|| Some(std::env::temp_dir().join("termscp")));
#[cfg(not(test))]
static CACHE_DIR: LazyLock<Option<PathBuf>> =
LazyLock::new(|| dirs::cache_dir().map(|dir| dir.join("termscp")));
#[cfg(test)]
static CACHE_DIR: LazyLock<Option<PathBuf>> =
LazyLock::new(|| Some(std::env::temp_dir().join("termscp")));
#[cfg(not(test))]
fn config_dir() -> Option<PathBuf> {
#[cfg(macos)]
{
dirs::home_dir().map(|home| home.join(".config").join("termscp"))
}
#[cfg(win)]
{
dirs::home_dir().map(|home| home.join(".termscp"))
}
#[cfg(not(any(macos, win)))]
{
dirs::config_dir().map(|dir| dir.join("termscp"))
}
}
#[cfg(not(test))]
fn legacy_config_dir() -> Option<PathBuf> {
#[cfg(any(macos, win))]
{
dirs::config_dir().map(|dir| dir.join("termscp"))
}
#[cfg(not(any(macos, win)))]
{
None
}
}
#[cfg(test)]
fn legacy_config_dir() -> Option<PathBuf> {
None
}
pub fn init_config_dir() -> Result<Option<PathBuf>, String> {
let Some(dir) = CONF_DIR.as_deref() else {
return Ok(None);
};
migrate_config_dir(legacy_config_dir().as_deref(), dir)?;
init_dir(dir).map(Option::Some)
}
pub fn init_cache_dir() -> Result<Option<PathBuf>, String> {
if let Some(dir) = CACHE_DIR.as_deref() {
init_dir(dir).map(Option::Some)
} else {
Ok(None)
}
}
fn migrate_config_dir(legacy: Option<&Path>, new_dir: &Path) -> Result<(), String> {
let Some(legacy) = legacy else {
return Ok(());
};
if new_dir.exists() || !legacy.exists() {
return Ok(());
}
if let Some(parent) = new_dir.parent() {
std::fs::create_dir_all(parent).map_err(|err| err.to_string())?;
}
std::fs::rename(legacy, new_dir).map_err(|err| err.to_string())
}
fn init_dir(p: &Path) -> Result<PathBuf, String> {
if p.is_dir() {
return Ok(p.to_path_buf());
}
std::fs::create_dir_all(p).map_err(|err| err.to_string())?;
Ok(p.to_path_buf())
}
pub fn get_bookmarks_paths(config_dir: &Path) -> PathBuf {
let mut bookmarks_file: PathBuf = PathBuf::from(config_dir);
bookmarks_file.push("bookmarks.toml");
bookmarks_file
}
pub fn get_config_paths(config_dir: &Path) -> (PathBuf, PathBuf) {
let mut bookmarks_file: PathBuf = PathBuf::from(config_dir);
bookmarks_file.push("config.toml");
let mut keys_dir: PathBuf = PathBuf::from(config_dir);
keys_dir.push(".ssh/"); (bookmarks_file, keys_dir)
}
pub fn get_log_paths(cache_dir: &Path) -> PathBuf {
let mut log_file: PathBuf = PathBuf::from(cache_dir);
log_file.push("termscp.log");
log_file
}
pub fn get_theme_path(config_dir: &Path) -> PathBuf {
let mut theme_file: PathBuf = PathBuf::from(config_dir);
theme_file.push("theme.toml");
theme_file
}
#[cfg(test)]
mod tests {
use std::fs::{File, OpenOptions};
use std::io::Write;
use pretty_assertions::assert_eq;
use serial_test::serial;
use super::*;
#[test]
#[serial]
fn test_system_environment_get_config_dir() {
let conf_dir: PathBuf = init_config_dir().ok().unwrap().unwrap();
assert!(std::fs::remove_dir_all(conf_dir.as_path()).is_ok());
}
#[test]
#[serial]
fn should_get_cache_dir() {
let cache_dir: PathBuf = init_cache_dir().ok().unwrap().unwrap();
assert!(std::fs::remove_dir_all(cache_dir.as_path()).is_ok());
}
#[test]
#[serial]
fn test_system_environment_get_config_dir_err() {
let mut conf_dir: PathBuf = std::env::temp_dir();
conf_dir.push("termscp");
let mut f: File = OpenOptions::new()
.create(true)
.write(true)
.open(conf_dir.as_path())
.ok()
.unwrap();
assert!(writeln!(f, "Hello world!").is_ok());
drop(f);
assert!(init_config_dir().is_err());
assert!(std::fs::remove_file(conf_dir.as_path()).is_ok());
}
#[test]
#[serial]
fn should_migrate_legacy_config_dir() {
let base = std::env::temp_dir().join("termscp-migrate-test-move");
let legacy = base.join("legacy");
let new_dir = base.join("new");
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&legacy).unwrap();
let legacy_file = legacy.join("config.toml");
std::fs::write(&legacy_file, b"hello").unwrap();
assert!(migrate_config_dir(Some(legacy.as_path()), new_dir.as_path()).is_ok());
assert!(!legacy.exists());
assert!(new_dir.join("config.toml").exists());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
#[serial]
fn should_not_migrate_when_new_dir_exists() {
let base = std::env::temp_dir().join("termscp-migrate-test-keep");
let legacy = base.join("legacy");
let new_dir = base.join("new");
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&legacy).unwrap();
std::fs::create_dir_all(&new_dir).unwrap();
assert!(migrate_config_dir(Some(legacy.as_path()), new_dir.as_path()).is_ok());
assert!(legacy.exists());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
#[serial]
fn should_not_migrate_without_legacy_dir() {
let base = std::env::temp_dir().join("termscp-migrate-test-none");
let new_dir = base.join("new");
let _ = std::fs::remove_dir_all(&base);
assert!(migrate_config_dir(None, new_dir.as_path()).is_ok());
assert!(!new_dir.exists());
let missing = base.join("missing");
assert!(migrate_config_dir(Some(missing.as_path()), new_dir.as_path()).is_ok());
assert!(!new_dir.exists());
let _ = std::fs::remove_dir_all(&base);
}
#[test]
#[serial]
fn test_system_environment_get_bookmarks_paths() {
assert_eq!(
get_bookmarks_paths(Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
);
}
#[test]
#[serial]
fn test_system_environment_get_config_paths() {
assert_eq!(
get_config_paths(Path::new("/home/omar/.config/termscp/")),
(
PathBuf::from("/home/omar/.config/termscp/config.toml"),
PathBuf::from("/home/omar/.config/termscp/.ssh/")
)
);
}
#[test]
#[serial]
fn test_system_environment_get_log_paths() {
assert_eq!(
get_log_paths(Path::new("/home/omar/.cache/termscp/")),
PathBuf::from("/home/omar/.cache/termscp/termscp.log"),
);
}
#[test]
#[serial]
fn test_system_environment_get_theme_path() {
assert_eq!(
get_theme_path(Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/theme.toml"),
);
}
}