Codex Handbook
memories/write/src/extensions/prune.rs 100 lines
use crate::memory_extensions_root;use chrono::DateTime;use chrono::Duration;use chrono::NaiveDateTime;use chrono::Utc;use std::path::Path;use tracing::warn;pub async fn prune_old_extension_resources(memory_root: &Path) {    prune_old_extension_resources_with_now(memory_root, Utc::now()).await}async fn prune_old_extension_resources_with_now(memory_root: &Path, now: DateTime<Utc>) {    let cutoff = now - Duration::days(crate::extension_resources::RETENTION_DAYS);    let extensions_root = memory_extensions_root(memory_root);    let mut extensions = match tokio::fs::read_dir(&extensions_root).await {        Ok(extensions) => extensions,        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return,        Err(err) => {            warn!(                "failed reading memory extensions root {}: {err}",                extensions_root.display()            );            return;        }    };    while let Ok(Some(extension_entry)) = extensions.next_entry().await {        let extension_path = extension_entry.path();        let Ok(file_type) = extension_entry.file_type().await else {            continue;        };        if !file_type.is_dir()            || !tokio::fs::try_exists(extension_path.join("instructions.md"))                .await                .unwrap_or(false)        {            continue;        }        let resources_path = extension_path.join("resources");        let mut resources = match tokio::fs::read_dir(&resources_path).await {            Ok(resources) => resources,            Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue,            Err(err) => {                warn!(                    "failed reading memory extension resources {}: {err}",                    resources_path.display()                );                continue;            }        };        while let Ok(Some(resource_entry)) = resources.next_entry().await {            let resource_file_path = resource_entry.path();            let Ok(file_type) = resource_entry.file_type().await else {                continue;            };            if !file_type.is_file() {                continue;            }            let Some(file_name) = resource_file_path                .file_name()                .and_then(|name| name.to_str())            else {                continue;            };            if !file_name.ends_with(".md") {                continue;            }            let Some(resource_timestamp) = resource_timestamp(file_name) else {                continue;            };            if resource_timestamp > cutoff {                continue;            }            if let Err(err) = tokio::fs::remove_file(&resource_file_path).await                && err.kind() != std::io::ErrorKind::NotFound            {                warn!(                    "failed pruning old memory extension resource {}: {err}",                    resource_file_path.display()                );            }        }    }}fn resource_timestamp(file_name: &str) -> Option<DateTime<Utc>> {    let timestamp = file_name.get(..19)?;    let naive =        NaiveDateTime::parse_from_str(timestamp, crate::extension_resources::FILENAME_TS_FORMAT)            .ok()?;    Some(DateTime::from_naive_utc_and_offset(naive, Utc))}#[cfg(test)]#[path = "prune_tests.rs"]mod tests;