mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 11:01:36 +08:00
feat(db): rebuild Codex usage on upgrade and via maintenance action
Schema v16 wipes codex_session detail rows, _codex_session rollups and Codex rollout cursors inside the migration savepoint (cursor deletion uses pure shape matching so CODEX_HOME drift cannot orphan cursors); the next session sync re-imports history from source JSONL under the corrected importer. Fresh installs traverse the same branch as a no-op. Add a manual "Rebuild Codex usage" maintenance action (single-flight, hard-fail backup before reset, unconditional refresh notification even when reimport is empty or fails after reset) with a destructive confirm dialog, result toast and four-locale strings. Historical proxy-side duplicate rows are intentionally left untouched; history whose source JSONL was already deleted cannot be reconstructed.
This commit is contained in:
@@ -289,6 +289,35 @@ pub async fn sync_session_usage(
|
||||
.map_err(|error| AppError::Message(format!("会话用量同步任务失败: {error}")))
|
||||
}
|
||||
|
||||
/// Codex reset 成功后,无论重导是否导入新行或返回错误,都必须通知前端刷新。
|
||||
/// 调用方应只在 reset 成功后调用,避免把未发生的数据变更误报为重建完成。
|
||||
fn finish_codex_rebuild(
|
||||
result: Result<crate::services::session_usage::SessionSyncResult, AppError>,
|
||||
) -> Result<crate::services::session_usage::SessionSyncResult, AppError> {
|
||||
crate::usage_events::notify_log_recorded();
|
||||
result
|
||||
}
|
||||
|
||||
/// 备份数据库后,仅重建 Codex session 用量。锁覆盖 backup → reset → import
|
||||
/// 整个序列,避免后台同步在清理和重导之间插入数据。
|
||||
#[tauri::command]
|
||||
pub async fn rebuild_codex_usage(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<crate::services::session_usage::SessionSyncResult, AppError> {
|
||||
let db = state.db.clone();
|
||||
let _guard = crate::services::session_usage::session_sync_mutex()
|
||||
.lock()
|
||||
.await;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
db.backup_database_file()?;
|
||||
db.reset_codex_usage()?;
|
||||
let result = crate::services::session_usage_codex::sync_codex_usage(&db);
|
||||
finish_codex_rebuild(result)
|
||||
})
|
||||
.await
|
||||
.map_err(|error| AppError::Message(format!("Codex 用量重建任务失败: {error}")))?
|
||||
}
|
||||
|
||||
/// 获取数据来源分布
|
||||
#[tauri::command]
|
||||
pub fn get_usage_data_sources(
|
||||
@@ -308,3 +337,33 @@ pub struct ModelPricingInfo {
|
||||
pub cache_read_cost_per_million: String,
|
||||
pub cache_creation_cost_per_million: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn codex_rebuild_notifies_when_reimport_is_empty() {
|
||||
crate::usage_events::take_test_notify_count();
|
||||
|
||||
let result = finish_codex_rebuild(Ok(
|
||||
crate::services::session_usage::SessionSyncResult::default(),
|
||||
))
|
||||
.expect("空重导应成功");
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(crate::usage_events::take_test_notify_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_rebuild_notifies_when_reimport_fails_after_reset() {
|
||||
crate::usage_events::take_test_notify_count();
|
||||
|
||||
let result = finish_codex_rebuild(Err(AppError::Message(
|
||||
"synthetic reimport failure".to_string(),
|
||||
)));
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(crate::usage_events::take_test_notify_count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user