Skip to main content

vct_core/quota/
cache.rs

1//! On-disk caches for the latest per-provider quota snapshots
2//! (`~/.vct/{claude,codex}_usage.json`).
3//!
4//! Each is a single last-known-good file (not dated like the pricing cache,
5//! since we always want the latest value). A fresh `vct usage` launch seeds
6//! the panels from here instantly while the background workers refresh them.
7
8use crate::models::{
9    ClaudeQuotaSnapshot, CodexQuotaSnapshot, CopilotQuotaSnapshot, CursorQuotaSnapshot,
10};
11use crate::utils::{
12    get_claude_usage_cache_path, get_codex_usage_cache_path, get_copilot_usage_cache_path,
13    get_cursor_usage_cache_path, write_json_atomic,
14};
15use anyhow::Result;
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use std::path::PathBuf;
19
20/// Loads and parses a cache file, returning `None` on any error.
21fn load_cache<T: DeserializeOwned>(path: Result<PathBuf>) -> Option<T> {
22    let body = std::fs::read_to_string(path.ok()?).ok()?;
23    serde_json::from_str(&body).ok()
24}
25
26/// Persists a snapshot atomically to `path`.
27fn save_cache<T: Serialize>(path: Result<PathBuf>, snap: &T) -> Result<()> {
28    write_json_atomic(path?, snap)
29}
30
31/// Loads the last-known Claude quota snapshot, or `None` if absent/corrupt.
32pub fn load_claude_cache() -> Option<ClaudeQuotaSnapshot> {
33    load_cache(get_claude_usage_cache_path())
34}
35
36/// Persists the Claude quota snapshot atomically.
37pub fn save_claude_cache(snap: &ClaudeQuotaSnapshot) -> Result<()> {
38    save_cache(get_claude_usage_cache_path(), snap)
39}
40
41/// Loads the last-known Codex quota snapshot, or `None` if absent/corrupt.
42pub fn load_codex_cache() -> Option<CodexQuotaSnapshot> {
43    load_cache(get_codex_usage_cache_path())
44}
45
46/// Persists the Codex quota snapshot atomically.
47pub fn save_codex_cache(snap: &CodexQuotaSnapshot) -> Result<()> {
48    save_cache(get_codex_usage_cache_path(), snap)
49}
50
51/// Loads the last-known Copilot quota snapshot, or `None` if absent/corrupt.
52pub fn load_copilot_cache() -> Option<CopilotQuotaSnapshot> {
53    load_cache(get_copilot_usage_cache_path())
54}
55
56/// Persists the Copilot quota snapshot atomically.
57pub fn save_copilot_cache(snap: &CopilotQuotaSnapshot) -> Result<()> {
58    save_cache(get_copilot_usage_cache_path(), snap)
59}
60
61/// Loads the last-known Cursor quota snapshot, or `None` if absent/corrupt.
62pub fn load_cursor_cache() -> Option<CursorQuotaSnapshot> {
63    load_cache(get_cursor_usage_cache_path())
64}
65
66/// Persists the Cursor quota snapshot atomically.
67pub fn save_cursor_cache(snap: &CursorQuotaSnapshot) -> Result<()> {
68    save_cache(get_cursor_usage_cache_path(), snap)
69}