use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::fs;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TokenCache {
pub token: String,
#[serde(rename = "expires_at")]
pub expires_at: u64,
}
impl TokenCache {
pub fn cache_path() -> PathBuf {
crate::config::paths::bamboo_dir().join("copilot_token.json")
}
pub async fn load() -> Option<Self> {
let path = Self::cache_path();
if !path.exists() {
return None;
}
let content = fs::read_to_string(&path).await.ok()?;
let cache: TokenCache = serde_json::from_str(&content).ok()?;
Some(cache)
}
pub async fn save(&self) -> Result<(), String> {
let path = Self::cache_path();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.await
.map_err(|e| format!("Failed to create cache directory: {}", e))?;
}
let content = serde_json::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize token cache: {}", e))?;
fs::write(&path, content)
.await
.map_err(|e| format!("Failed to write token cache: {}", e))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o600);
let _ = std::fs::set_permissions(&path, perms);
}
Ok(())
}
pub async fn delete() -> Result<(), String> {
let path = Self::cache_path();
if path.exists() {
fs::remove_file(&path)
.await
.map_err(|e| format!("Failed to delete token cache: {}", e))?;
}
Ok(())
}
pub fn is_valid(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
self.expires_at > now + 300
}
pub fn remaining_seconds(&self) -> i64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
self.expires_at as i64 - now
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_cache_valid() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let cache = TokenCache {
token: "test".to_string(),
expires_at: now + 3600,
};
assert!(cache.is_valid());
let cache = TokenCache {
token: "test".to_string(),
expires_at: now + 60,
};
assert!(!cache.is_valid());
}
}