ggez_assets_manager/
source.rs

1use std::{io, sync::Arc};
2
3use assets_manager::{
4    hot_reloading::{EventSender, FsWatcherBuilder},
5    source::{self, DirEntry, FileContent, Source},
6};
7
8/// A [`Source`] using `ggez`' paths to read from the filesystem.
9///
10/// See [`ggez::filesystem`] for more details.
11///
12/// When hot-reloading is activated, changes to `"resources.zip"` are ignored.
13#[derive(Debug, Clone)]
14pub struct GgezFileSystem {
15    resources: Option<source::FileSystem>,
16    zip: Option<Arc<source::Zip>>,
17    local: Option<source::FileSystem>,
18    config: Option<source::FileSystem>,
19}
20
21fn no_valid_source_error() -> io::Error {
22    io::Error::new(io::ErrorKind::Other, "Cannot find a valid source")
23}
24
25impl GgezFileSystem {
26    /// Creates a new `FileSystem` from `ggez` context.
27    ///
28    /// Note that additionnal
29    #[inline]
30    pub fn from_context(fs: &impl ggez::context::Has<ggez::filesystem::Filesystem>) -> Self {
31        fn inner(fs: &ggez::filesystem::Filesystem) -> GgezFileSystem {
32            let resources = source::FileSystem::new(fs.resources_dir()).ok();
33            let zip = source::Zip::open(fs.zip_dir()).ok().map(Arc::new);
34            let local = source::FileSystem::new(fs.user_data_dir()).ok();
35            let config = source::FileSystem::new(fs.user_config_dir()).ok();
36
37            GgezFileSystem {
38                resources,
39                zip,
40                local,
41                config,
42            }
43        }
44
45        inner(fs.retrieve())
46    }
47
48    /// Creates a new `FileSystem`.
49    ///
50    /// `game_id` and `author` parameters should be the same as thoses given to
51    /// [`ggez::ContextBuilder::new`].
52    #[deprecated = "use `GgezFileSystem::from_context` instead"]
53    pub fn new(game_id: &str, author: &str) -> Self {
54        let resources = source::FileSystem::new("resources").ok();
55        let zip = source::Zip::open("resources.zip").ok().map(Arc::new);
56
57        let (local, config) = match directories::ProjectDirs::from("", author, game_id) {
58            Some(project_dir) => (
59                source::FileSystem::new(project_dir.data_local_dir()).ok(),
60                source::FileSystem::new(project_dir.config_dir()).ok(),
61            ),
62            None => (None, None),
63        };
64
65        Self {
66            resources,
67            zip,
68            local,
69            config,
70        }
71    }
72}
73
74impl Source for GgezFileSystem {
75    fn read(&self, id: &str, ext: &str) -> io::Result<FileContent> {
76        let mut err = None;
77
78        if let Some(source) = &self.resources {
79            match source.read(id, ext) {
80                Err(e) => err = Some(e),
81                content => return content,
82            };
83        }
84        if let Some(source) = &self.zip {
85            match source.read(id, ext) {
86                Err(e) => err = Some(e),
87                content => return content,
88            }
89        }
90        if let Some(source) = &self.local {
91            match source.read(id, ext) {
92                Err(e) => err = Some(e),
93                content => return content,
94            }
95        }
96        if let Some(source) = &self.config {
97            match source.read(id, ext) {
98                Err(e) => err = Some(e),
99                content => return content,
100            }
101        }
102
103        Err(err.unwrap_or_else(no_valid_source_error))
104    }
105
106    fn read_dir(&self, id: &str, f: &mut dyn FnMut(DirEntry)) -> io::Result<()> {
107        let mut err = None;
108
109        if let Some(source) = &self.resources {
110            match source.read_dir(id, f) {
111                Err(e) => err = Some(e),
112                content => return content,
113            };
114        }
115        if let Some(source) = &self.zip {
116            match source.read_dir(id, f) {
117                Err(e) => err = Some(e),
118                content => return content,
119            }
120        }
121        if let Some(source) = &self.local {
122            match source.read_dir(id, f) {
123                Err(e) => err = Some(e),
124                content => return content,
125            }
126        }
127        if let Some(source) = &self.config {
128            match source.read_dir(id, f) {
129                Err(e) => err = Some(e),
130                content => return content,
131            }
132        }
133
134        Err(err.unwrap_or_else(no_valid_source_error))
135    }
136
137    fn exists(&self, entry: DirEntry) -> bool {
138        fn exists<S: Source>(s: &Option<S>, entry: DirEntry) -> bool {
139            s.as_ref().map_or(false, |s| s.exists(entry))
140        }
141
142        exists(&self.resources, entry)
143            || exists(&self.zip, entry)
144            || exists(&self.local, entry)
145            || exists(&self.config, entry)
146    }
147
148    fn configure_hot_reloading(
149        &self,
150        events: EventSender,
151    ) -> Result<(), assets_manager::BoxedError> {
152        let mut watcher = FsWatcherBuilder::new()?;
153        if let Some(res) = &self.resources {
154            watcher.watch(res.root().to_owned())?;
155        }
156        if let Some(res) = &self.local {
157            watcher.watch(res.root().to_owned())?;
158        }
159        if let Some(res) = &self.config {
160            watcher.watch(res.root().to_owned())?;
161        }
162        watcher.build(events);
163        Ok(())
164    }
165}