veloren_common_assets/
fs.rs

1use std::{fs, io};
2
3use assets_manager::{
4    BoxedError,
5    hot_reloading::{EventSender, FsWatcherBuilder},
6    source::{DirEntry, FileContent, FileSystem as RawFs, Source},
7};
8
9/// Loads assets from the default path or `VELOREN_ASSETS_OVERRIDE` env if it is
10/// set.
11#[derive(Debug, Clone)]
12pub struct FileSystem {
13    default: RawFs,
14    override_dir: Option<RawFs>,
15}
16
17impl FileSystem {
18    pub fn new() -> io::Result<Self> {
19        let default = RawFs::new(&*super::ASSETS_PATH)?;
20        let override_dir = std::env::var_os("VELOREN_ASSETS_OVERRIDE").and_then(|path| {
21            RawFs::new(path)
22                .map_err(|err| tracing::error!("Error setting override assets directory: {}", err))
23                .ok()
24        });
25
26        let canary = fs::read_to_string(super::ASSETS_PATH.join("common").join("canary.canary"))
27            .map_err(|e| {
28                io::Error::new(
29                    io::ErrorKind::Other,
30                    format!("failed to load canary asset: {}", e),
31                )
32            })?;
33
34        if !canary.starts_with("VELOREN_CANARY_MAGIC") {
35            panic!("Canary asset `canary.canary` was present but did not contain the expected data. This *heavily* implies that you've not correctly set up Git LFS (Large File Storage). Visit `https://book.veloren.net/contributors/development-tools.html#git-lfs` for more information about setting up Git LFS.");
36        }
37
38        Ok(Self {
39            default,
40            override_dir,
41        })
42    }
43}
44
45impl Source for FileSystem {
46    fn read(&self, id: &str, ext: &str) -> io::Result<FileContent> {
47        if let Some(dir) = &self.override_dir {
48            match dir.read(id, ext) {
49                Ok(content) => return Ok(content),
50                Err(err) => {
51                    if err.kind() != io::ErrorKind::NotFound {
52                        let path = dir.path_of(DirEntry::File(id, ext));
53                        tracing::warn!(
54                            "Error reading \"{}\": {}. Falling back to default",
55                            path.display(),
56                            err
57                        );
58                    }
59                },
60            }
61        }
62
63        // If not found in override path, try load from main asset path
64        self.default.read(id, ext)
65    }
66
67    fn read_dir(&self, id: &str, f: &mut dyn FnMut(DirEntry)) -> io::Result<()> {
68        if let Some(dir) = &self.override_dir {
69            match dir.read_dir(id, f) {
70                Ok(()) => return Ok(()),
71                Err(err) => {
72                    if err.kind() != io::ErrorKind::NotFound {
73                        let path = dir.path_of(DirEntry::Directory(id));
74                        tracing::warn!(
75                            "Error reading \"{}\": {}. Falling back to default",
76                            path.display(),
77                            err
78                        );
79                    }
80                },
81            }
82        }
83
84        // If not found in override path, try load from main asset path
85        self.default.read_dir(id, f)
86    }
87
88    fn exists(&self, entry: DirEntry) -> bool {
89        self.override_dir
90            .as_ref()
91            .is_some_and(|dir| dir.exists(entry))
92            || self.default.exists(entry)
93    }
94
95    fn make_source(&self) -> Option<Box<dyn Source + Send>> { Some(Box::new(self.clone())) }
96
97    fn configure_hot_reloading(&self, events: EventSender) -> Result<(), BoxedError> {
98        let mut builder = FsWatcherBuilder::new()?;
99
100        if let Some(dir) = &self.override_dir {
101            builder.watch(dir.root().to_owned())?;
102        }
103        builder.watch(self.default.root().to_owned())?;
104
105        builder.build(events);
106        Ok(())
107    }
108}