1 //! Assets
2 //!
3 //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/asset)
4 
5 use std::ffi::{CStr, CString};
6 use std::io;
7 use std::ptr::NonNull;
8 
9 /// A native `AAssetManager *`.
10 #[derive(Debug)]
11 pub struct AssetManager {
12     ptr: NonNull<ffi::AAssetManager>,
13 }
14 
15 // AAssetManager is thread safe.
16 // See https://developer.android.com/ndk/reference/group/asset#aassetmanager
17 unsafe impl Send for AssetManager {}
18 unsafe impl Sync for AssetManager {}
19 
20 impl AssetManager {
21     /// Create an `AssetManager` from a pointer
22     ///
23     /// By calling this function, you assert that the pointer is a valid pointer to a native
24     /// `AAssetManager`.
from_ptr(ptr: NonNull<ffi::AAssetManager>) -> Self25     pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetManager>) -> Self {
26         Self { ptr }
27     }
28 
29     /// Returns the pointer to the native `AAssetManager`.
ptr(&self) -> NonNull<ffi::AAssetManager>30     pub fn ptr(&self) -> NonNull<ffi::AAssetManager> {
31         self.ptr
32     }
33 
34     /// Open the asset. Returns `None` if opening the asset fails.
35     ///
36     /// This currently always opens the asset in the streaming mode.
open(&self, filename: &CStr) -> Option<Asset>37     pub fn open(&self, filename: &CStr) -> Option<Asset> {
38         unsafe {
39             let ptr = ffi::AAssetManager_open(
40                 self.ptr.as_ptr(),
41                 filename.as_ptr(),
42                 ffi::AASSET_MODE_STREAMING as i32,
43             );
44             Some(Asset::from_ptr(NonNull::new(ptr)?))
45         }
46     }
47 
48     /// Open an asset directory. Returns `None` if opening the directory fails.
open_dir(&self, filename: &CStr) -> Option<AssetDir>49     pub fn open_dir(&self, filename: &CStr) -> Option<AssetDir> {
50         unsafe {
51             let ptr = ffi::AAssetManager_openDir(self.ptr.as_ptr(), filename.as_ptr());
52             Some(AssetDir::from_ptr(NonNull::new(ptr)?))
53         }
54     }
55 }
56 
57 /// A native `AAssetDir *`.
58 ///
59 /// ```no_run
60 /// # use std::ffi::CString;
61 /// # use ndk::asset::AssetManager;
62 /// # let asset_manager: AssetManager = unimplemented!();
63 /// use std::io::Read;
64 ///
65 /// let mut my_dir = asset_manager
66 ///     .open_dir(&CString::new("my_dir").unwrap())
67 ///     .expect("Could not open directory");
68 ///
69 /// // Use it as an iterator
70 /// let all_files = my_dir.collect::<Vec<CString>>();
71 ///
72 /// // Reset the iterator
73 /// my_dir.rewind();
74 ///
75 /// // Use .with_next() to iterate without allocating `CString`s
76 /// while let Some(asset) = my_dir.with_next(|cstr| asset_manager.open(cstr).unwrap()) {
77 ///     let mut text = String::new();
78 ///     asset.read_to_string(&mut text);
79 ///     // ...
80 /// }
81 /// ```
82 #[derive(Debug)]
83 pub struct AssetDir {
84     ptr: NonNull<ffi::AAssetDir>,
85 }
86 
87 // It's unclear if AAssetDir is thread safe.
88 // However, AAsset is not, so there's a good chance that AAssetDir is not either.
89 
90 impl Drop for AssetDir {
drop(&mut self)91     fn drop(&mut self) {
92         unsafe { ffi::AAssetDir_close(self.ptr.as_ptr()) }
93     }
94 }
95 
96 impl AssetDir {
97     /// Construct an `AssetDir` from the native `AAssetDir *`.  This gives ownership of the
98     /// `AAssetDir *` to the `AssetDir`, which will handle closing the asset.  Avoid using
99     /// the pointer after calling this function.
100     ///
101     /// By calling this function, you assert that it points to a valid native `AAssetDir`.
from_ptr(ptr: NonNull<ffi::AAssetDir>) -> Self102     pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetDir>) -> Self {
103         Self { ptr }
104     }
105 
106     /// The corresponding native `AAssetDir *`
ptr(&self) -> NonNull<ffi::AAssetDir>107     pub fn ptr(&self) -> NonNull<ffi::AAssetDir> {
108         self.ptr
109     }
110 
111     /// Get the next filename, if any, and process it.  Like `.next()`, but performs no additional
112     /// allocation.
113     ///
114     /// The filenames are in the correct format to be passed to `AssetManager::open`
with_next<T>(&mut self, f: impl for<'a> FnOnce(&'a CStr) -> T) -> Option<T>115     pub fn with_next<T>(&mut self, f: impl for<'a> FnOnce(&'a CStr) -> T) -> Option<T> {
116         unsafe {
117             let next_name = ffi::AAssetDir_getNextFileName(self.ptr.as_ptr());
118             if next_name.is_null() {
119                 None
120             } else {
121                 Some(f(CStr::from_ptr(next_name)))
122             }
123         }
124     }
125 
126     /// Reset the iteration state
rewind(&mut self)127     pub fn rewind(&mut self) {
128         unsafe {
129             ffi::AAssetDir_rewind(self.ptr.as_ptr());
130         }
131     }
132 }
133 
134 impl Iterator for AssetDir {
135     type Item = CString;
136 
next(&mut self) -> Option<CString>137     fn next(&mut self) -> Option<CString> {
138         self.with_next(|cstr| cstr.to_owned())
139     }
140 }
141 
142 /// A native `AAsset *`, open in the streaming mode
143 ///
144 /// ```no_run
145 /// # use std::ffi::CString;
146 /// # use ndk::asset::AssetManager;
147 /// # let asset_manager: AssetManager = unimplemented!();
148 /// use std::io::Read;
149 ///
150 /// let asset = asset_manager
151 ///     .open(&CString::new("path/to/asset").unwrap())
152 ///     .expect("Could not open asset");
153 ///
154 /// let mut data = vec![];
155 /// asset.read_to_end(&mut data);
156 /// // ... use data ...
157 /// ```
158 #[derive(Debug)]
159 pub struct Asset {
160     ptr: NonNull<ffi::AAsset>,
161 }
162 
163 // AAsset is *not* thread safe.
164 // See https://developer.android.com/ndk/reference/group/asset#aasset
165 
166 impl Drop for Asset {
drop(&mut self)167     fn drop(&mut self) {
168         unsafe { ffi::AAsset_close(self.ptr.as_ptr()) }
169     }
170 }
171 
172 impl Asset {
173     /// Construct an `Asset` from the native `AAsset *`.  This gives ownership of the `AAsset *` to
174     /// the `Asset`, which will handle closing the asset.  Avoid using the pointer after calling
175     /// this function.
176     ///
177     /// By calling this function, you assert that it points to a valid native `AAsset`, open
178     /// in the streaming mode.
from_ptr(ptr: NonNull<ffi::AAsset>) -> Self179     pub unsafe fn from_ptr(ptr: NonNull<ffi::AAsset>) -> Self {
180         Self { ptr }
181     }
182 
183     /// The corresponding native `AAsset *`
ptr(&self) -> NonNull<ffi::AAsset>184     pub fn ptr(&self) -> NonNull<ffi::AAsset> {
185         self.ptr
186     }
187 
188     /// Returns the total length of the asset, in bytes
get_length(&self) -> usize189     pub fn get_length(&self) -> usize {
190         unsafe { ffi::AAsset_getLength64(self.ptr.as_ptr()) as usize }
191     }
192 
193     /// Returns the remaining length of the asset, in bytes
get_remaining_length(&self) -> usize194     pub fn get_remaining_length(&self) -> usize {
195         unsafe { ffi::AAsset_getRemainingLength64(self.ptr.as_ptr()) as usize }
196     }
197 
198     /// Reads all data into a buffer and returns it
get_buffer(&mut self) -> io::Result<&[u8]>199     pub fn get_buffer(&mut self) -> io::Result<&[u8]> {
200         unsafe {
201             let buf_ptr = ffi::AAsset_getBuffer(self.ptr.as_ptr());
202             if buf_ptr.is_null() {
203                 Err(io::Error::new(
204                     io::ErrorKind::Other,
205                     "Android Asset error creating buffer",
206                 ))
207             } else {
208                 Ok(std::slice::from_raw_parts(
209                     buf_ptr as *const u8,
210                     self.get_remaining_length(),
211                 ))
212             }
213         }
214     }
215 
216     //pub fn open_file_descriptor(&self) -> TODO
217 }
218 
219 impl io::Read for Asset {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>220     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
221         unsafe {
222             let res = ffi::AAsset_read(
223                 self.ptr.as_ptr(),
224                 buf.as_mut_ptr() as *mut _,
225                 buf.len() as ffi::size_t,
226             );
227             if res >= 0 {
228                 Ok(res as usize)
229             } else {
230                 Err(io::Error::new(
231                     io::ErrorKind::Other,
232                     "Android Asset read error",
233                 ))
234             }
235         }
236     }
237 }
238 
239 impl io::Seek for Asset {
seek(&mut self, seek: io::SeekFrom) -> io::Result<u64>240     fn seek(&mut self, seek: io::SeekFrom) -> io::Result<u64> {
241         unsafe {
242             let res = match seek {
243                 io::SeekFrom::Start(x) => {
244                     ffi::AAsset_seek64(self.ptr.as_ptr(), x as i64, ffi::SEEK_SET as i32)
245                 }
246                 io::SeekFrom::Current(x) => {
247                     ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_CUR as i32)
248                 }
249                 io::SeekFrom::End(x) => {
250                     ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_END as i32)
251                 }
252             };
253             if res < 0 {
254                 Err(io::Error::new(
255                     io::ErrorKind::Other,
256                     "Android Asset seek error",
257                 ))
258             } else {
259                 Ok(res as u64)
260             }
261         }
262     }
263 }
264