54 lines
1.7 KiB
Rust
54 lines
1.7 KiB
Rust
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{Context, Result};
|
|
use chrono::Utc;
|
|
|
|
/// Write a file through a sibling temporary file and then rename it into place.
|
|
/// This keeps auth/config files from being left half-written after failures.
|
|
pub fn write_atomic(path: &Path, content: &str) -> Result<()> {
|
|
if let Some(parent) = path.parent() {
|
|
fs::create_dir_all(parent)
|
|
.with_context(|| format!("创建目录失败: {}", parent.display()))?;
|
|
}
|
|
|
|
let tmp = temp_path(path);
|
|
fs::write(&tmp, content).with_context(|| format!("写入临时文件失败: {}", tmp.display()))?;
|
|
fs::rename(&tmp, path).with_context(|| {
|
|
format!(
|
|
"替换目标文件失败: tmp={}, target={}",
|
|
tmp.display(),
|
|
path.display()
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Backup an existing file into `<codex_home>/cdxs-backups/`.
|
|
pub fn backup_if_exists(path: &Path, codex_home: &Path, label: &str) -> Result<Option<PathBuf>> {
|
|
if !path.exists() {
|
|
return Ok(None);
|
|
}
|
|
let backup_dir = codex_home.join("cdxs-backups");
|
|
fs::create_dir_all(&backup_dir)
|
|
.with_context(|| format!("创建备份目录失败: {}", backup_dir.display()))?;
|
|
let stamp = Utc::now().format("%Y%m%d-%H%M%S%.3f");
|
|
let backup = backup_dir.join(format!("{label}-{stamp}.bak"));
|
|
fs::copy(path, &backup).with_context(|| {
|
|
format!(
|
|
"备份文件失败: source={}, backup={}",
|
|
path.display(),
|
|
backup.display()
|
|
)
|
|
})?;
|
|
Ok(Some(backup))
|
|
}
|
|
|
|
fn temp_path(path: &Path) -> PathBuf {
|
|
let file_name = path
|
|
.file_name()
|
|
.and_then(|value| value.to_str())
|
|
.unwrap_or("cdxs.tmp");
|
|
path.with_file_name(format!(".{file_name}.tmp"))
|
|
}
|