feat(backup): archive current live config before switching\n\n- Archive Claude settings.json and Codex auth.json/config.toml to ~/.cc-switch/archive/<ts>\n- Preserve user edits while centralizing SSOT in cc-switch config.json\n- Uses atomic writes for all subsequent updates

This commit is contained in:
Jason
2025-09-04 16:07:38 +08:00
parent 3c32f12152
commit 79370dd8a1
2 changed files with 64 additions and 0 deletions

View File

@@ -258,6 +258,14 @@ pub async fn switch_provider(
.map_err(|e| format!("创建 Codex 目录失败: {}", e))?;
}
// 备份当前 live 文件到归档(单一数据源:以 cc-switch 配置为主,但保护用户手改历史)
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let _ = crate::config::archive_file(ts, "codex", &auth_path);
let _ = crate::config::archive_file(ts, "codex", &config_path);
// 写 auth.json必需
let auth = provider
.settings_config
@@ -303,6 +311,13 @@ pub async fn switch_provider(
if let Some(parent) = settings_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
}
// 备份当前 live 文件到归档
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let _ = crate::config::archive_file(ts, "claude", &settings_path);
write_json_file(&settings_path, &provider.settings_config)?;
}
}

View File

@@ -39,6 +39,55 @@ pub fn get_app_config_path() -> PathBuf {
get_app_config_dir().join("config.json")
}
/// 归档根目录 ~/.cc-switch/archive
pub fn get_archive_root() -> PathBuf {
get_app_config_dir().join("archive")
}
fn ensure_unique_path(mut dest: PathBuf) -> PathBuf {
if !dest.exists() {
return dest;
}
let file_name = dest
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "file".into());
let ext = dest
.extension()
.map(|s| format!(".{}", s.to_string_lossy()))
.unwrap_or_default();
let parent = dest.parent().map(|p| p.to_path_buf()).unwrap_or_default();
for i in 2..1000 {
let mut candidate = parent.clone();
candidate.push(format!("{}-{}{}", file_name, i, ext));
if !candidate.exists() {
return candidate;
}
}
dest
}
/// 将现有文件归档到 `~/.cc-switch/archive/<ts>/<category>/` 下,返回归档路径
pub fn archive_file(ts: u64, category: &str, src: &Path) -> Result<Option<PathBuf>, String> {
if !src.exists() {
return Ok(None);
}
let mut dest_dir = get_archive_root();
dest_dir.push(ts.to_string());
dest_dir.push(category);
fs::create_dir_all(&dest_dir).map_err(|e| format!("创建归档目录失败: {}", e))?;
let file_name = src
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "file".into());
let mut dest = dest_dir.join(file_name);
dest = ensure_unique_path(dest);
copy_file(src, &dest)?;
Ok(Some(dest))
}
/// 清理供应商名称,确保文件名安全
pub fn sanitize_provider_name(name: &str) -> String {
name.chars()