use crate::{
CONFIG_UPDATE_FILENAME, CONFIG_UPDATE_PATH_ENV, SCREENSHOT_REQUEST_FILENAME,
SCREENSHOT_REQUEST_PATH_ENV, SCREENSHOT_RESPONSE_FILENAME, SCREENSHOT_RESPONSE_PATH_ENV,
SHADER_DIAGNOSTICS_REQUEST_FILENAME, SHADER_DIAGNOSTICS_REQUEST_PATH_ENV,
SHADER_DIAGNOSTICS_RESPONSE_FILENAME, SHADER_DIAGNOSTICS_RESPONSE_PATH_ENV,
};
use serde::Serialize;
use std::io::Write;
use std::path::{Path, PathBuf};
pub fn open_restricted_write(path: &Path) -> Result<std::fs::File, std::io::Error> {
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
opts.open(path)
}
#[allow(dead_code)]
pub fn set_ipc_file_permissions(path: &Path) -> Result<(), String> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o600);
std::fs::set_permissions(path, perms)
.map_err(|e| format!("Failed to set permissions on {}: {e}", path.display()))?;
}
#[cfg(not(unix))]
{
let _ = path; }
Ok(())
}
pub fn config_update_path() -> PathBuf {
resolve_ipc_path(CONFIG_UPDATE_PATH_ENV, CONFIG_UPDATE_FILENAME)
}
pub fn screenshot_request_path() -> PathBuf {
resolve_ipc_path(SCREENSHOT_REQUEST_PATH_ENV, SCREENSHOT_REQUEST_FILENAME)
}
pub fn screenshot_response_path() -> PathBuf {
resolve_ipc_path(SCREENSHOT_RESPONSE_PATH_ENV, SCREENSHOT_RESPONSE_FILENAME)
}
pub fn shader_diagnostics_request_path() -> PathBuf {
resolve_ipc_path(
SHADER_DIAGNOSTICS_REQUEST_PATH_ENV,
SHADER_DIAGNOSTICS_REQUEST_FILENAME,
)
}
pub fn shader_diagnostics_response_path() -> PathBuf {
resolve_ipc_path(
SHADER_DIAGNOSTICS_RESPONSE_PATH_ENV,
SHADER_DIAGNOSTICS_RESPONSE_FILENAME,
)
}
pub fn resolve_ipc_path(env_var: &str, default_filename: &str) -> PathBuf {
if let Ok(path) = std::env::var(env_var) {
return PathBuf::from(path);
}
#[cfg(target_os = "windows")]
let config_dir = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("par-term");
#[cfg(not(target_os = "windows"))]
let config_dir = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".config")
.join("par-term");
config_dir.join(default_filename)
}
pub fn write_json_atomic<T: Serialize>(payload: &T, path: &Path) -> Result<(), String> {
if let Some(parent) = path.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
return Err(format!(
"Failed to create parent directory {}: {e}",
parent.display()
));
}
let temp_path = path.with_extension("json.tmp");
let bytes = serde_json::to_vec_pretty(payload).map_err(|e| e.to_string())?;
open_restricted_write(&temp_path)
.and_then(|mut f| f.write_all(&bytes))
.map_err(|e| {
format!(
"Failed to write temp file {}: {e}",
temp_path.to_string_lossy()
)
})?;
std::fs::rename(&temp_path, path).map_err(|e| {
let _ = std::fs::remove_file(&temp_path);
format!(
"Failed to rename temp file to {}: {e}",
path.to_string_lossy()
)
})?;
Ok(())
}
pub fn try_read_screenshot_response(
path: &Path,
) -> Result<Option<crate::TerminalScreenshotResponse>, String> {
try_read_json_response(path)
}
pub fn try_read_shader_diagnostics_response(
path: &Path,
) -> Result<Option<crate::ShaderDiagnosticsResponse>, String> {
try_read_json_response(path)
}
fn try_read_json_response<T>(path: &Path) -> Result<Option<T>, String>
where
T: serde::de::DeserializeOwned,
{
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e.to_string()),
};
if content.trim().is_empty() {
return Ok(None);
}
let resp = serde_json::from_str::<T>(&content).map_err(|e| e.to_string())?;
Ok(Some(resp))
}