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:
@@ -258,6 +258,14 @@ pub async fn switch_provider(
|
|||||||
.map_err(|e| format!("创建 Codex 目录失败: {}", e))?;
|
.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(必需)
|
// 写 auth.json(必需)
|
||||||
let auth = provider
|
let auth = provider
|
||||||
.settings_config
|
.settings_config
|
||||||
@@ -303,6 +311,13 @@ pub async fn switch_provider(
|
|||||||
if let Some(parent) = settings_path.parent() {
|
if let Some(parent) = settings_path.parent() {
|
||||||
std::fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
|
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)?;
|
write_json_file(&settings_path, &provider.settings_config)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,55 @@ pub fn get_app_config_path() -> PathBuf {
|
|||||||
get_app_config_dir().join("config.json")
|
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 {
|
pub fn sanitize_provider_name(name: &str) -> String {
|
||||||
name.chars()
|
name.chars()
|
||||||
|
|||||||
Reference in New Issue
Block a user