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}