connectors/src/directory_cache.rs
112 lines
use std::path::PathBuf;use codex_app_server_protocol::AppInfo;use serde::Deserialize;use serde::Serialize;use sha1::Digest;use sha1::Sha1;use tracing::warn;use crate::ConnectorDirectoryCacheKey;pub(crate) const CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION: u8 = 1;const CONNECTOR_DIRECTORY_DISK_CACHE_DIR: &str = "cache/codex_app_directory";#[derive(Clone)]pub struct ConnectorDirectoryCacheContext { pub(crate) codex_home: PathBuf, pub(crate) cache_key: ConnectorDirectoryCacheKey,}impl ConnectorDirectoryCacheContext { pub fn new(codex_home: PathBuf, cache_key: ConnectorDirectoryCacheKey) -> Self { Self { codex_home, cache_key, } } pub(crate) fn cache_path(&self) -> PathBuf { let cache_key_json = serde_json::to_string(&self.cache_key).unwrap_or_default(); let cache_key_hash = sha1_hex(&cache_key_json); self.codex_home .join(CONNECTOR_DIRECTORY_DISK_CACHE_DIR) .join(format!("{cache_key_hash}.json")) }}pub(crate) enum CachedConnectorDirectoryDiskLoad { Hit { connectors: Vec<AppInfo> }, Missing, Invalid,}pub(crate) fn load_cached_directory_connectors_from_disk( cache_context: &ConnectorDirectoryCacheContext,) -> CachedConnectorDirectoryDiskLoad { let cache_path = cache_context.cache_path(); let bytes = match std::fs::read(&cache_path) { Ok(bytes) => bytes, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { return CachedConnectorDirectoryDiskLoad::Missing; } Err(err) => { warn!( cache_path = %cache_path.display(), "failed to read connector directory disk cache: {err}" ); return CachedConnectorDirectoryDiskLoad::Invalid; } }; let cache: ConnectorDirectoryDiskCache = match serde_json::from_slice(&bytes) { Ok(cache) => cache, Err(err) => { warn!( cache_path = %cache_path.display(), "failed to parse connector directory disk cache: {err}" ); let _ = std::fs::remove_file(cache_path); return CachedConnectorDirectoryDiskLoad::Invalid; } }; if cache.schema_version != CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION { let _ = std::fs::remove_file(cache_path); return CachedConnectorDirectoryDiskLoad::Invalid; } CachedConnectorDirectoryDiskLoad::Hit { connectors: cache.connectors, }}pub(crate) fn write_cached_directory_connectors_to_disk( cache_context: &ConnectorDirectoryCacheContext, connectors: &[AppInfo],) { let cache_path = cache_context.cache_path(); if let Some(parent) = cache_path.parent() && std::fs::create_dir_all(parent).is_err() { return; } let Ok(bytes) = serde_json::to_vec_pretty(&ConnectorDirectoryDiskCache { schema_version: CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION, connectors: connectors.to_vec(), }) else { return; }; let _ = std::fs::write(cache_path, bytes);}#[derive(Debug, Clone, Serialize, Deserialize)]struct ConnectorDirectoryDiskCache { schema_version: u8, connectors: Vec<AppInfo>,}fn sha1_hex(value: &str) -> String { let mut hasher = Sha1::new(); hasher.update(value.as_bytes()); let sha1 = hasher.finalize(); format!("{sha1:x}")}