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| io::Error::other(format!("failed to load canary asset: {}", e)))?;
28
29        if !canary.starts_with("VELOREN_CANARY_MAGIC") {
30            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.");
31        }
32
33        Ok(Self {
34            default,
35            override_dir,
36        })
37    }
38}
39
40impl Source for FileSystem {
41    fn read(&self, id: &str, ext: &str) -> io::Result<FileContent<'_>> {
42        if let Some(dir) = &self.override_dir {
43            match dir.read(id, ext) {
44                Ok(content) => return Ok(content),
45                Err(err) => {
46                    if err.kind() != io::ErrorKind::NotFound {
47                        let path = dir.path_of(DirEntry::File(id, ext));
48                        tracing::warn!(
49                            "Error reading \"{}\": {}. Falling back to default",
50                            path.display(),
51                            err
52                        );
53                    }
54                },
55            }
56        }
57
58        // If not found in override path, try load from main asset path
59        self.default.read(id, ext)
60    }
61
62    fn read_dir(&self, id: &str, f: &mut dyn FnMut(DirEntry)) -> io::Result<()> {
63        if let Some(dir) = &self.override_dir {
64            match dir.read_dir(id, f) {
65                Ok(()) => return Ok(()),
66                Err(err) => {
67                    if err.kind() != io::ErrorKind::NotFound {
68                        let path = dir.path_of(DirEntry::Directory(id));
69                        tracing::warn!(
70                            "Error reading \"{}\": {}. Falling back to default",
71                            path.display(),
72                            err
73                        );
74                    }
75                },
76            }
77        }
78
79        // If not found in override path, try load from main asset path
80        self.default.read_dir(id, f)
81    }
82
83    fn exists(&self, entry: DirEntry) -> bool {
84        self.override_dir
85            .as_ref()
86            .is_some_and(|dir| dir.exists(entry))
87            || self.default.exists(entry)
88    }
89
90    fn configure_hot_reloading(&self, events: EventSender) -> Result<(), BoxedError> {
91        let mut builder = FsWatcherBuilder::new()?;
92
93        if let Some(dir) = &self.override_dir {
94            builder.watch(dir.root().to_owned())?;
95        }
96        builder.watch(self.default.root().to_owned())?;
97
98        builder.build(events);
99        Ok(())
100    }
101}