android_ndk/
asset.rs

1//! Assets
2//!
3//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/asset)
4
5use std::ffi::{CStr, CString};
6use std::io;
7use std::ptr::NonNull;
8
9/// A native `AAssetManager *`.
10#[derive(Debug)]
11pub 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
17unsafe impl Send for AssetManager {}
18unsafe impl Sync for AssetManager {}
19
20impl 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`.
25    pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetManager>) -> Self {
26        Self { ptr }
27    }
28
29    /// Returns the pointer to the native `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.
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.
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 android_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)]
83pub 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
90impl Drop for AssetDir {
91    fn drop(&mut self) {
92        unsafe { ffi::AAssetDir_close(self.ptr.as_ptr()) }
93    }
94}
95
96impl 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`.
102    pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetDir>) -> Self {
103        Self { ptr }
104    }
105
106    /// The corresponding native `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`
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
127    pub fn rewind(&mut self) {
128        unsafe {
129            ffi::AAssetDir_rewind(self.ptr.as_ptr());
130        }
131    }
132}
133
134impl Iterator for AssetDir {
135    type Item = CString;
136
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 android_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)]
159pub 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
166impl Drop for Asset {
167    fn drop(&mut self) {
168        unsafe { ffi::AAsset_close(self.ptr.as_ptr()) }
169    }
170}
171
172impl 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.
179    pub unsafe fn from_ptr(ptr: NonNull<ffi::AAsset>) -> Self {
180        Self { ptr }
181    }
182
183    /// The corresponding native `AAsset *`
184    pub fn ptr(&self) -> NonNull<ffi::AAsset> {
185        self.ptr
186    }
187
188    /// Returns the total length of the asset, in bytes
189    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
194    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
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
219impl io::Read for Asset {
220    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
221        unsafe {
222            let res = ffi::AAsset_read(self.ptr.as_ptr(), buf.as_mut_ptr() as *mut _, buf.len());
223            if res >= 0 {
224                Ok(res as usize)
225            } else {
226                Err(io::Error::new(
227                    io::ErrorKind::Other,
228                    "Android Asset read error",
229                ))
230            }
231        }
232    }
233}
234
235impl io::Seek for Asset {
236    fn seek(&mut self, seek: io::SeekFrom) -> io::Result<u64> {
237        unsafe {
238            let res = match seek {
239                io::SeekFrom::Start(x) => {
240                    ffi::AAsset_seek64(self.ptr.as_ptr(), x as i64, ffi::SEEK_SET as i32)
241                }
242                io::SeekFrom::Current(x) => {
243                    ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_CUR as i32)
244                }
245                io::SeekFrom::End(x) => {
246                    ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_END as i32)
247                }
248            };
249            if res < 0 {
250                Err(io::Error::new(
251                    io::ErrorKind::Other,
252                    "Android Asset seek error",
253                ))
254            } else {
255                Ok(res as u64)
256            }
257        }
258    }
259}