Skip to main content

bevy_cache/
config.rs

1use bevy::prelude::*;
2use std::path::{Path, PathBuf};
3use std::time::Duration;
4
5/// Configuration for the file cache system.
6///
7/// The cache directory defaults to the operating system's cache folder
8/// (e.g. `%LOCALAPPDATA%` on Windows, `~/Library/Caches` on macOS,
9/// `$XDG_CACHE_HOME` on Linux) joined with the application name.
10/// On platforms where no cache directory can be determined (including
11/// some Android configurations), [`std::env::temp_dir`] is used as
12/// a fallback.
13#[derive(Debug, Clone, Resource)]
14pub struct CacheConfig {
15    /// Application name used to derive the manifest filename
16    /// (`"{app_name}.cache_manifest"`).
17    pub app_name: String,
18
19    /// Filesystem directory for cache files and the manifest.
20    pub cache_dir: PathBuf,
21
22    /// Maximum age of an entry before it becomes eligible for cleanup
23    /// at application exit.
24    pub max_age: Duration,
25
26    /// Maximum number of cache entries. `None` means unlimited.
27    /// Enforced at application exit — new entries are never rejected.
28    pub max_entries: Option<usize>,
29}
30
31impl CacheConfig {
32    /// Create a new config using the OS cache directory for the given app name.
33    pub fn new(app_name: &str) -> Self {
34        Self {
35            app_name: app_name.to_owned(),
36            cache_dir: resolve_cache_dir(app_name),
37            max_age: Duration::from_secs(604_800), // 7 days
38            max_entries: None,
39        }
40}
41
42    /// Returns the manifest filename, e.g. `"my_game.cache_manifest"`.
43    pub fn manifest_file_name(&self) -> String {
44        format!("{}.cache_manifest", self.app_name)
45    }
46
47    /// Filesystem path for a cached file.
48    pub fn file_path(&self, file_name: &str) -> PathBuf {
49        self.cache_dir.join(file_name)
50    }
51
52    /// Filesystem path for the manifest, e.g.
53    /// `<cache_dir>/my_game.cache_manifest`.
54    pub fn manifest_fs_path(&self) -> PathBuf {
55        self.cache_dir.join(self.manifest_file_name())
56    }
57
58    /// Ensure the cache directory exists on disk.
59    pub fn ensure_cache_dir(&self) -> Result<(), std::io::Error> {
60        std::fs::create_dir_all(&self.cache_dir)
61    }
62
63    /// Validate that a cache key is safe (no path separators, not empty, no
64    /// null bytes, and does not collide with the manifest file).
65    pub fn validate_key(&self, key: &str) -> Result<(), crate::CacheError> {
66        if key.is_empty()
67            || key.contains('/')
68            || key.contains('\\')
69            || key.contains("..")
70            || key.contains('\0')
71            || key == self.manifest_file_name()
72        {
73            return Err(crate::CacheError::InvalidKey(key.to_owned()));
74        }
75        let joined = Path::new(key);
76        if joined.components().count() != 1 {
77            return Err(crate::CacheError::InvalidKey(key.to_owned()));
78        }
79        Ok(())
80    }
81}
82
83impl Default for CacheConfig {
84    fn default() -> Self {
85        Self::new("bevy_cache")
86    }
87}
88
89/// Resolve the platform-appropriate cache directory. Falls back to the OS temp
90/// dir when no user-specific cache folder is available (e.g. on Android).
91fn resolve_cache_dir(app_name: &str) -> PathBuf {
92    sysdirs::cache_dir()
93        .unwrap_or_else(std::env::temp_dir)
94        .join(app_name)
95}