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}