minecraft_assets/api/
provider.rs

1use std::{
2    fs, io,
3    path::{Path, PathBuf},
4};
5
6use crate::api::{ResourceKind, ResourceLocation, ResourcePath};
7
8/*
9 dMMMMMMP dMMMMb  .aMMMb  dMP dMMMMMMP .dMMMb
10   dMP   dMP.dMP dMP"dMP amr    dMP   dMP" VP
11  dMP   dMMMMK" dMMMMMP dMP    dMP    VMMMb
12 dMP   dMP"AMF dMP dMP dMP    dMP   dP .dMP
13dMP   dMP dMP dMP dMP dMP    dMP    VMMMP"
14*/
15
16/// Indicates that a type can enumerate available resources.
17pub trait EnumerateResources {
18    /// Enumerates the available resources of the given [`ResourceKind`] in the
19    /// given namespace.
20    fn enumerate_resources(
21        &self,
22        namespace: &str,
23        kind: ResourceKind,
24    ) -> Result<Vec<ResourceLocation<'static>>, io::Error>;
25}
26
27/// Indicates that a type can load provide the raw data of resources.
28pub trait LoadResource {
29    /// Returns the raw bytes of the resource referenced by the given
30    /// [`ResourceLocation`].
31    fn load_resource(&self, location: &ResourceLocation) -> Result<Vec<u8>, io::Error>;
32}
33
34/// Marker trait for types that are [`EnumerateResources`] and [`LoadResource`].
35pub trait ResourceProvider: EnumerateResources + LoadResource {}
36
37impl<T: EnumerateResources + LoadResource> ResourceProvider for T {}
38
39/*
40    dMMMMMP dMP dMP     dMMMMMP        .dMMMb  dMP dMP .dMMMb dMMMMMMP dMMMMMP dMMMMMMMMb
41   dMP     amr dMP     dMP            dMP" VP dMP.dMP dMP" VP   dMP   dMP     dMP"dMP"dMP
42  dMMMP   dMP dMP     dMMMP           VMMMb   VMMMMP  VMMMb    dMP   dMMMP   dMP dMP dMP
43 dMP     dMP dMP     dMP            dP .dMP dA .dMP dP .dMP   dMP   dMP     dMP dMP dMP
44dMP     dMP dMMMMMP dMMMMMP         VMMMP"  VMMMP"  VMMMP"   dMP   dMMMMMP dMP dMP dMP
45
46    dMMMMb  dMMMMb  .aMMMb  dMP dMP dMP dMMMMb  dMMMMMP dMMMMb
47   dMP.dMP dMP.dMP dMP"dMP dMP dMP amr dMP VMP dMP     dMP.dMP
48  dMMMMP" dMMMMK" dMP dMP dMP dMP dMP dMP dMP dMMMP   dMMMMK"
49 dMP     dMP"AMF dMP.aMP  YMvAP" dMP dMP.aMP dMP     dMP"AMF
50dMP     dMP dMP  VMMMP"    VP"  dMP dMMMMP" dMMMMMP dMP dMP
51
52*/
53
54/// A [`ResourceProvider`] that provides resources from the local file system.
55pub struct FileSystemResourceProvider {
56    root: PathBuf,
57}
58
59impl FileSystemResourceProvider {
60    /// Returns a new provider that provides resources from the given root directory.
61    ///
62    /// The root directory should be the directory that contains the `assets/`
63    /// and (optionally) `data/` directory.
64    pub fn new(root: impl AsRef<Path>) -> Self {
65        Self {
66            root: PathBuf::from(root.as_ref()),
67        }
68    }
69}
70
71impl EnumerateResources for FileSystemResourceProvider {
72    fn enumerate_resources(
73        &self,
74        namespace: &str,
75        kind: ResourceKind,
76    ) -> Result<Vec<ResourceLocation<'static>>, io::Error> {
77        let directory = ResourcePath::for_kind(&self.root, namespace, kind);
78        Ok(ResourceIter::new(directory, kind)?.collect())
79    }
80}
81
82impl LoadResource for FileSystemResourceProvider {
83    fn load_resource(&self, location: &ResourceLocation) -> Result<Vec<u8>, io::Error> {
84        let path = ResourcePath::for_resource(&self.root, location);
85        fs::read(path)
86    }
87}
88
89/*
90    dMP dMMMMMMP dMMMMMP dMMMMb
91   amr    dMP   dMP     dMP.dMP
92  dMP    dMP   dMMMP   dMMMMK"
93 dMP    dMP   dMP     dMP"AMF
94dMP    dMP   dMMMMMP dMP dMP
95
96*/
97
98/// An iterator over a directory that yields [`ResourceLocation`]s for every
99/// file of a certain [`ResourceKind`].
100pub struct ResourceIter {
101    // Stack of directory iterators.
102    dir_iters: Vec<fs::ReadDir>,
103    kind: ResourceKind,
104}
105
106enum DirOrResource {
107    Dir(fs::ReadDir),
108    Resource(ResourceLocation<'static>),
109}
110
111impl ResourceIter {
112    pub fn new(directory: impl AsRef<Path>, kind: ResourceKind) -> Result<Self, io::Error> {
113        let dir_iter = fs::read_dir(directory)?;
114
115        Ok(Self {
116            dir_iters: vec![dir_iter],
117            kind,
118        })
119    }
120
121    #[inline]
122    fn next_dir_or_resource(&mut self) -> Option<DirOrResource> {
123        // Continue iteration in the childmost directory.
124        let dir_iter = self.dir_iters.last_mut().unwrap();
125
126        dir_iter
127            .filter_map(|dir_entry| {
128                dir_entry
129                    // Skip over errorneous entries.
130                    .ok()
131                    // Get file type of entry and skip over fs errors.
132                    .and_then(|dir_entry| {
133                        dir_entry
134                            .file_type()
135                            .ok()
136                            .map(|file_type| (dir_entry, file_type))
137                    })
138                    .and_then(|(dir_entry, file_type)| {
139                        if file_type.is_dir() {
140                            // Start new ReadDir in subdirectory.
141                            fs::read_dir(dir_entry.path())
142                                // Skip over fs errors.
143                                .ok()
144                                .map(DirOrResource::Dir)
145                        } else {
146                            // Get file name and skip over UTF-8 errors.
147                            dir_entry.file_name().to_str().and_then(|file_name| {
148                                (
149                                    // Skip over files starting with '_'.
150                                    !file_name.starts_with('_') &&
151                                    // Skip over resources of the wrong kind (check the extension).
152                                    file_name.ends_with(self.kind.extension())
153                                )
154                                .then(|| {
155                                    // Cut the extension off the file name to
156                                    // get the resource name.
157                                    let dot_index =
158                                        file_name.len() - self.kind.extension().len() - 1;
159                                    let location = ResourceLocation::new_owned(
160                                        self.kind,
161                                        String::from(&file_name[..dot_index]),
162                                    );
163                                    DirOrResource::Resource(location)
164                                })
165                            })
166                        }
167                    })
168            })
169            .next()
170    }
171}
172
173impl Iterator for ResourceIter {
174    type Item = ResourceLocation<'static>;
175
176    fn next(&mut self) -> Option<Self::Item> {
177        loop {
178            // Get the next directory or resource location.
179            let next_dir_or_resource = self.next_dir_or_resource();
180
181            // A value of `None` here indicates that the childmost directory has no
182            // more entries, so pop the child or return the final `None`.
183            if next_dir_or_resource.is_none() {
184                if self.dir_iters.len() > 1 {
185                    self.dir_iters.pop();
186                    continue;
187                } else {
188                    return None;
189                }
190            }
191
192            match next_dir_or_resource.unwrap() {
193                // If the next entry is a directory, push a new child and continue
194                // iterating inside the subdirectory.
195                DirOrResource::Dir(dir_iter) => {
196                    self.dir_iters.push(dir_iter);
197                    continue;
198                }
199                DirOrResource::Resource(location) => return Some(location),
200            }
201        }
202    }
203}