veloren_common_assets/
fs.rs1use 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#[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 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 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}