tauri_plugin_android_fs/api/
public_storage.rs

1use crate::*;
2
3
4/// API of file storage that is available to other applications and users.  
5/// 
6/// # Examples
7/// ```
8/// fn example(app: &tauri::AppHandle) {
9///     use tauri_plugin_android_fs::AndroidFsExt as _;
10/// 
11///     let api = app.android_fs();
12///     let public_storage = api.public_storage();
13/// }
14/// ```
15pub struct PublicStorage<'a, R: tauri::Runtime>(
16    #[allow(unused)]
17    pub(crate) &'a AndroidFs<R>
18);
19
20impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
21
22    /// Gets a list of currently available storage volumes (internal storage, SD card, USB drive, etc.).
23    /// Be aware of TOCTOU.
24    /// 
25    /// This typically includes [`primary storage volume`](PublicStorage::get_primary_volume),
26    /// but it may occasionally be absent if the primary volume is inaccessible 
27    /// (e.g., mounted on a computer, removed, or another issue).
28    ///
29    /// Primary storage volume is always listed first, if included. 
30    /// But the order of the others is not guaranteed.  
31    ///
32    /// # Note
33    /// The volume represents the logical view of a storage volume for an individual user:
34    /// each user may have a different view for the same physical volume
35    /// (e.g. when the volume is a built-in emulated storage).
36    /// 
37    /// # Support
38    /// Android 10 (API level 29) or higher.  
39    pub fn get_available_volumes(&self) -> Result<Vec<PublicStorageVolume>> {
40        on_android!({
41            impl_de!(struct Obj { volume_name: String, description: Option<String>, is_primary: bool });
42            impl_de!(struct Res { volumes: Vec<Obj> });
43
44            if self.0.api_level()? < api_level::ANDROID_10 {
45                return Err(Error { msg: "requires Android 10 (API level 29) or higher".into() })
46            }
47
48            let mut volumes = self.0.api
49                .run_mobile_plugin::<Res>("getStorageVolumes", "")
50                .map(|v| v.volumes.into_iter())
51                .map(|v| v.map(|v| 
52                    PublicStorageVolume {
53                        description: v.description.unwrap_or_else(|| v.volume_name.clone()),
54                        id: PublicStorageVolumeId(v.volume_name),
55                        is_primary: v.is_primary,
56                    }
57                ))
58                .map(|v| v.collect::<Vec<_>>())?;
59
60            // primary volume を先頭にする。他はそのままの順序
61            volumes.sort_by(|a, b| b.is_primary.cmp(&a.is_primary));
62
63            Ok(volumes)
64        })
65    }
66
67    /// Gets a primary storage volume.  
68    /// This is the most common and recommended storage volume for placing files that can be accessed by other apps or user.
69    /// 
70    /// A device always has one (and one only) primary storage volume.  
71    /// 
72    /// The returned primary volume may not currently be accessible 
73    /// if it has been mounted by the user on their computer, 
74    /// has been removed from the device, or some other problem has happened.  
75    /// 
76    /// You can find a list of all currently available volumes using [`PublicStorage::get_available_volumes`].
77    /// 
78    /// # Note
79    /// The volume represents the logical view of a storage volume for an individual user:
80    /// each user may have a different view for the same physical volume
81    /// (e.g. when the volume is a built-in emulated storage).
82    /// 
83    /// The primary volume provides a separate area for each user in a multi-user environment.
84    /// 
85    /// # Support
86    /// Android 10 (API level 29) or higher.   
87    /// 
88    /// # References
89    /// <https://developer.android.com/reference/android/provider/MediaStore#VOLUME_EXTERNAL_PRIMARY>
90    pub fn get_primary_volume(&self) -> Result<PublicStorageVolume> {
91        on_android!({
92            impl_de!(struct Res { volume_name: String, description: Option<String>, is_primary: bool });
93
94            if self.0.api_level()? < api_level::ANDROID_10 {
95                return Err(Error { msg: "requires Android 10 (API level 29) or higher".into() })
96            }
97
98            self.0.api
99                .run_mobile_plugin::<Res>("getPrimaryStorageVolume", "")
100                .map(|v| 
101                    PublicStorageVolume {
102                        description: v.description.unwrap_or_else(|| v.volume_name.clone()),
103                        id: PublicStorageVolumeId(v.volume_name),
104                        is_primary: v.is_primary,
105                    }
106                )
107                .map_err(Into::into)
108        })
109    }
110
111    /// Creates a new empty file in the specified public directory of the storage volume.  
112    /// This returns a **persistent read-write** URI.
113    ///
114    /// The created file has the following features:  
115    /// - It is registered with the appropriate MediaStore as needed.  
116    /// - The app can fully manage it until the app is uninstalled.  
117    /// - It is **not** removed when the app itself is uninstalled.  
118    /// 
119    /// # Args
120    /// - ***volume*** :  
121    /// The storage volume, such as internal storage, SD card, etc.  
122    /// Usually, you don't need to specify this unless there is a special reason.  
123    /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.  
124    /// 
125    /// - ***base_dir*** :  
126    /// The base directory.  
127    /// When using [`PublicImageDir`], use only image MIME types for ***mime_type***, which is discussed below.; using other types may cause errors.
128    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
129    /// Only [`PublicGeneralPurposeDir`] supports all MIME types. 
130    /// 
131    /// - ***use_app_dir*** :   
132    /// Determines whether to insert a directory named after the application name 
133    /// specified in Tauri's configuration between ***base_dir*** and ***relative_path***.
134    /// It is recommended to enable this unless there is a special reason not to.   
135    /// See [`PublicStorage::app_dir_name`]
136    /// 
137    /// - ***relative_path*** :  
138    /// The file path relative to the base directory.  
139    /// Any missing subdirectories in the specified path will be created automatically.  
140    /// If a file with the same name already exists, 
141    /// the system append a sequential number to ensure uniqueness.  
142    /// If no extension is present, 
143    /// the system may infer one from ***mime_type*** and may append it to the file name. 
144    /// But this append-extension operation depends on the model and version.  
145    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
146    ///  
147    /// - ***mime_type*** :  
148    /// The MIME type of the file to be created.  
149    /// If this is None, MIME type is inferred from the extension of ***relative_path***
150    /// and if that fails, `application/octet-stream` is used.  
151    /// 
152    /// # Support
153    /// Android 10 (API level 29) or higher.  
154    ///
155    /// Note :  
156    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
157    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
158    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
159    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
160    /// - Others dirs are available in all Android versions.
161    pub fn create_new_file(
162        &self,
163        volume: Option<&PublicStorageVolumeId>,
164        base_dir: impl Into<PublicDir>,
165        use_app_dir: bool,
166        relative_path: impl AsRef<std::path::Path>, 
167        mime_type: Option<&str>
168    ) -> crate::Result<FileUri> {
169
170        on_android!({
171            if self.0.api_level()? < api_level::ANDROID_10 {
172                return Err(Error { msg: "requires Android 10 (API level 29) or higher".into() })
173            }
174
175            let base_dir = base_dir.into();
176            let relative_path = validate_relative_path(relative_path.as_ref())?;
177            let base_dir_uri = self.get_dir_uri(volume, base_dir)?;
178         
179            let relative_path = {
180                let mut p = std::path::PathBuf::new();
181                p.push(self.get_dir_name(base_dir)?);
182                if use_app_dir {
183                    p.push(self.app_dir_name()?);
184                }
185                p.push(relative_path);
186                p
187            };
188
189            self.0.create_new_file(&base_dir_uri, relative_path, mime_type)
190        })
191    }
192
193    /// Recursively create a directory and all of its parent components if they are missing.  
194    /// If it already exists, do nothing.
195    /// 
196    /// [`PublicStorage::create_new_file`] does this automatically, so there is no need to use it together.
197    /// 
198    /// # Args  
199    /// - ***volume*** :  
200    /// The storage volume, such as internal storage, SD card, etc.  
201    /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.  
202    /// 
203    /// - ***base_dir*** :  
204    /// The base directory.  
205    ///  
206    /// - ***use_app_dir*** :   
207    /// Determines whether to insert a directory named after the application name 
208    /// specified in Tauri's configuration between ***base_dir*** and ***relative_path***.
209    /// 
210    /// - ***relative_path*** :  
211    /// The directory path relative to the base directory.    
212    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
213    ///  
214    /// # Support
215    /// Android 10 (API level 29) or higher.  
216    ///
217    /// Note :  
218    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
219    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
220    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
221    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
222    /// - Others dirs are available in all Android versions.
223    pub fn create_dir_all(
224        &self,
225        volume: Option<&PublicStorageVolumeId>,
226        base_dir: impl Into<PublicDir>,
227        use_app_dir: bool,
228        relative_path: impl AsRef<std::path::Path>, 
229    ) -> Result<()> {
230
231        on_android!({
232            if self.0.api_level()? < api_level::ANDROID_10 {
233                return Err(Error { msg: "requires Android 10 (API level 29) or higher".into() })
234            }
235
236            let relative_path = validate_relative_path(relative_path.as_ref())?;
237            let base_dir = base_dir.into();
238
239            let tmp_file_uri = self.create_new_file(
240                volume,
241                base_dir, 
242                use_app_dir,
243                relative_path.join("TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
244                Some(match base_dir {
245                    PublicDir::Image(_) => "image/png",
246                    PublicDir::Audio(_) => "audio/mp3",
247                    PublicDir::Video(_) => "video/mp4",
248                    PublicDir::GeneralPurpose(_) => "application/octet-stream"
249                })
250            )?;
251            let _ = self.0.remove_file(&tmp_file_uri);
252
253            Ok(())
254        })
255    }
256
257    /// Retrieves the absolute path for a specified public directory within the given storage volume.   
258    /// This function does **not** create any directories; it only constructs the path.
259    /// 
260    /// You can create files and folders under this directory and read or write only them. 
261    /// 
262    /// **Please avoid using this whenever possible.**    
263    /// Use it only in cases that cannot be handled by [`PublicStorage::create_new_file`] or [`PrivateStorage::resolve_path`], 
264    /// such as when you need to pass the absolute path of a user-accessible file as an argument to any database library.  
265    ///
266    /// # Note
267    /// Filesystem access via this path may be heavily impacted by emulation overhead.
268    /// And those files will not be registered in MediaStore. 
269    /// It might eventually be registered over time, but this should not be expected.
270    /// As a result, it may not appear in gallery apps or photo picker tools.
271    /// 
272    /// You cannot access files created by other apps. 
273    /// Additionally, if the app is uninstalled, 
274    /// you will no longer be able to access the files you created, 
275    /// even if the app is reinstalled.  
276    /// Android tends to restrict public file access using paths, so this may stop working in the future.
277    /// 
278    /// # Args
279    /// - ***volume*** :  
280    /// The storage volume, such as internal storage, SD card, etc.  
281    /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.  
282    /// 
283    /// - ***base_dir*** :  
284    /// The base directory.  
285    ///  
286    /// - ***use_app_dir*** :   
287    /// Determines whether to insert a directory named after the application name 
288    /// specified in Tauri's configuration under ***base_dir***.  
289    /// 
290    /// # Support
291    /// Android 10 (API level 29) or higher.  
292    /// 
293    /// Note :  
294    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
295    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
296    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
297    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
298    /// - Others dirs are available in all Android versions.
299    pub fn resolve_path(
300        &self,
301        volume: Option<&PublicStorageVolumeId>,
302        base_dir: impl Into<PublicDir>,
303        use_app_dir: bool,
304    ) -> Result<std::path::PathBuf> {
305
306        on_android!({
307            if self.0.api_level()? < api_level::ANDROID_10 {
308                return Err(Error::with("requires Android 10 (API level 29) or higher"))
309            }
310
311            let mut path = self.get_volume_path(volume)?;
312            path.push(self.get_dir_name(base_dir)?);
313            if use_app_dir {
314                path.push(self.app_dir_name()?);
315            }
316            Ok(path)
317        })
318    }
319
320    /// Create the specified directory URI that has **no permissions**.  
321    /// 
322    /// This should only be used as `initial_location` in the file picker. 
323    /// It must not be used for any other purpose.  
324    /// 
325    /// This is useful when selecting save location, 
326    /// but when selecting existing entries, `initial_location` is often better with None.
327    /// 
328    /// # Args  
329    /// - ***volume*** :  
330    /// The storage volume, such as internal storage, SD card, etc.  
331    /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.  
332    /// 
333    /// - ***base_dir*** :  
334    /// The base directory.  
335    ///  
336    /// - ***use_app_dir*** :   
337    /// Determines whether to insert a directory named after the application name 
338    /// specified in Tauri's configuration between ***base_dir*** and ***relative_path***.
339    /// 
340    /// - ***relative_path*** :  
341    /// The directory path relative to the base directory.    
342    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
343    ///  
344    /// # Support
345    /// If use `None` to ***volume***, all Android version: 
346    /// otherwise: Android 10 (API level 29) or higher
347    ///
348    /// Note :  
349    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
350    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
351    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
352    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
353    /// - Others dirs are available in all Android versions.
354    pub fn resolve_initial_location(
355        &self,
356        volume: Option<&PublicStorageVolumeId>,
357        base_dir: impl Into<PublicDir>,
358        use_app_dir: bool,
359        relative_path: impl AsRef<std::path::Path>,
360        create_dir_all: bool
361    ) -> Result<FileUri> {
362
363        on_android!({
364            let base_dir = base_dir.into();
365            
366            let mut uri = self.resolve_initial_location_top(volume)?;
367            uri.uri.push_str(self.get_dir_name(base_dir)?);
368
369            let relative_path = validate_relative_path(relative_path.as_ref())?;
370            let relative_path = relative_path.to_string_lossy();
371            if !relative_path.is_empty() {
372                uri.uri.push_str("%2F");
373                uri.uri.push_str(&match use_app_dir {
374                    false => encode_document_id(relative_path.as_ref()),
375                    true => {
376                        let mut r = std::path::PathBuf::new();
377                        r.push(self.app_dir_name()?);
378                        r.push(relative_path.as_ref());
379                        encode_document_id(r.to_string_lossy().as_ref())
380                    },
381                });
382            }
383
384            if create_dir_all {
385                let _ = self.create_dir_all(volume, base_dir, use_app_dir, relative_path.as_ref());
386            }
387
388            Ok(uri)
389        })
390    }
391
392    /// Create the specified directory URI that has **no permissions**.  
393    /// 
394    /// This should only be used as `initial_location` in the file picker. 
395    /// It must not be used for any other purpose.  
396    /// 
397    /// This is useful when selecting save location, 
398    /// but when selecting existing entries, `initial_location` is often better with None.
399    /// 
400    /// # Args  
401    /// - ***volume*** :  
402    /// The storage volume, such as internal storage, SD card, etc.  
403    /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.  
404    /// 
405    /// # Support
406    /// If use `None` to ***volume***, all Android version: 
407    /// otherwise: Android 10 (API level 29) or higher
408    ///
409    /// Note :  
410    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
411    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
412    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
413    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
414    /// - Others dirs are available in all Android versions.
415    pub fn resolve_initial_location_top(
416        &self,
417        volume: Option<&PublicStorageVolumeId>
418    ) -> Result<FileUri> {
419
420        on_android!({
421            let volume_uid = match volume {
422                None => None,
423                Some(volume) => {
424                    if self.0.api_level()? < api_level::ANDROID_10 {
425                        return Err(Error { msg: "requires Android 10 (API level 29) or higher".into() })
426                    }
427                    self.get_volume_uuid(volume)?
428                }
429            };
430
431            let uri = match volume_uid {
432                None => "content://com.android.externalstorage.documents/document/primary%3A".to_string(),
433                Some(uid) => format!("content://com.android.externalstorage.documents/document/{uid}%3A")
434            };
435
436            Ok(FileUri { uri, document_top_tree_uri: None })
437        })
438    }
439
440    /// Verify whether the basic functions of PublicStorage 
441    /// (such as [`PublicStorage::create_new_file`]) can be performed.
442    /// 
443    /// If on Android 9 (API level 28) and lower, this returns false.  
444    /// If on Android 10 (API level 29) or higher, this returns true.  
445    /// 
446    /// # Support
447    /// All Android version.
448    pub fn is_available(&self) -> crate::Result<bool> {
449        on_android!({
450            Ok(api_level::ANDROID_10 <= self.0.api_level()?)
451        })
452    }
453
454    /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.   
455    /// 
456    /// If on Android 9 (API level 28) and lower, this returns false.  
457    /// If on Android 10 (API level 29) or higher, this returns true.  
458    /// 
459    /// # Support
460    /// All Android version.
461    pub fn is_audiobooks_dir_available(&self) -> crate::Result<bool> {
462        on_android!({
463            Ok(api_level::ANDROID_10 <= self.0.api_level()?)
464        })
465    }
466
467    /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.   
468    /// 
469    /// If on Android 11 (API level 30) and lower, this returns false.  
470    /// If on Android 12 (API level 31) or higher, this returns true.  
471    /// 
472    /// # Support
473    /// All Android version.
474    pub fn is_recordings_dir_available(&self) -> crate::Result<bool> {
475        on_android!({
476            Ok(api_level::ANDROID_12 <= self.0.api_level()?)
477        })
478    }
479
480    /// Resolve the app dir name from Tauri's config.  
481    /// 
482    /// This uses "productName" in `src-tauri/tauri.conf.json`
483    /// 
484    /// # Support
485    /// All Android version.
486    pub fn app_dir_name(&self) -> crate::Result<&str> {
487        on_android!({
488            use std::sync::OnceLock;
489            
490            static APP_DIR_NAME: OnceLock<String> = OnceLock::new();
491
492            if APP_DIR_NAME.get().is_none() {
493                let config = self.0.app.config();
494                let app_name = config.product_name
495                    .as_deref()
496                    .filter(|s| !s.is_empty())
497                    .unwrap_or(&config.identifier)
498                    .replace('/', " ");
499
500                let _ = APP_DIR_NAME.set(app_name);
501            }
502
503            Ok(&APP_DIR_NAME.get().expect("Should call 'set' before 'get'"))
504        })
505    }
506}
507
508
509#[allow(unused)]
510impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
511
512    fn get_volume_uuid(&self, volume: &PublicStorageVolumeId) -> Result<Option<String>> {
513        on_android!({
514            impl_se!(struct Req<'a> { volume_name: &'a str });
515            impl_de!(struct Res { value: Option<String> });
516
517            let volume_name = &volume.0;
518
519            self.0.api
520                .run_mobile_plugin::<Res>("getStorageVolumeUuid", Req { volume_name })
521                .map(|v| v.value)
522                .map_err(Into::into)
523        })
524    }
525
526    fn get_volume_path(&self,
527        volume: Option<&PublicStorageVolumeId>, 
528    ) -> Result<std::path::PathBuf> {
529
530        on_android!({
531            impl_se!(struct Req<'a> { volume_name: Option<&'a str> });
532            impl_de!(struct Res { path: String });
533
534            let volume_name = volume.map(|v| v.0.as_str());
535
536            self.0.api
537                .run_mobile_plugin::<Res>("getStorageVolumePath", Req { volume_name })
538                .map(|v| {
539                    use std::str::FromStr;
540                    
541                    let r = std::path::PathBuf::from_str(&v.path);
542                    std::result::Result::<_, std::convert::Infallible>::unwrap(r)
543                })
544                .map_err(Into::into)
545        })
546    }
547
548    fn get_dir_uri(
549        &self, 
550        volume: Option<&PublicStorageVolumeId>, 
551        dir: impl Into<PublicDir>,
552    ) -> Result<FileUri> {
553
554        on_android!({
555            impl_se!(struct Req<'a> { dir_type: &'a str, volume_name: Option<&'a str> });
556            impl_de!(struct Res { uri: String });
557
558            let volume_name = volume.map(|v| v.0.as_str());
559            let dir_type = match dir.into() {
560                PublicDir::Image(_) => "Image",
561                PublicDir::Video(_) => "Video",
562                PublicDir::Audio(_) => "Audio",
563                PublicDir::GeneralPurpose(_) => "GeneralPurpose",
564            };
565
566            self.0.api
567                .run_mobile_plugin::<Res>("getPublicDirUri", Req { dir_type, volume_name })
568                .map(|v| FileUri { uri: v.uri, document_top_tree_uri: None })
569                .map_err(Into::into)
570        })
571    }
572
573    fn get_dir_name(&self, dir: impl Into<PublicDir>) -> Result<&str> {
574        on_android!({
575            impl_de!(struct EnvironmentDirs {
576                pictures: String,
577                dcim: String,
578                movies: String,
579                music: String,
580                alarms: String,
581                notifications: String,
582                podcasts: String,
583                ringtones: String,
584                documents: String,
585                download: String,
586                audiobooks: Option<String>,
587                recordings: Option<String>,
588            });
589            
590            static DIRS: std::sync::OnceLock<EnvironmentDirs> = std::sync::OnceLock::new();
591
592            if DIRS.get().is_none() {
593                let _ = DIRS.set(
594                    self.0.api.run_mobile_plugin::<EnvironmentDirs>("getEnvironmentDirs", "")?
595                );
596            }
597            let e = DIRS.get().expect("Should call 'set' before 'get'");
598
599
600            return Ok(match dir.into() {
601                PublicDir::Image(dir) => match dir {
602                    PublicImageDir::Pictures => &e.pictures,
603                    PublicImageDir::DCIM => &e.dcim,
604                },
605                PublicDir::Video(dir) => match dir {
606                    PublicVideoDir::Movies => &e.movies,
607                    PublicVideoDir::DCIM => &e.dcim,
608                },
609                PublicDir::Audio(dir) => match dir  {
610                    PublicAudioDir::Music => &e.music,
611                    PublicAudioDir::Alarms => &e.alarms,
612                    PublicAudioDir::Notifications => &e.notifications,
613                    PublicAudioDir::Podcasts => &e.podcasts,
614                    PublicAudioDir::Ringtones => &e.ringtones,
615                    PublicAudioDir::Recordings => e.recordings.as_ref().ok_or_else(|| Error { msg: "requires API level 31 or higher".into() })?,
616                    PublicAudioDir::Audiobooks => e.audiobooks.as_ref().ok_or_else(|| Error { msg: "requires API level 29 or higher".into() })?,
617                },
618                PublicDir::GeneralPurpose(dir) => match dir {
619                    PublicGeneralPurposeDir::Documents => &e.documents,
620                    PublicGeneralPurposeDir::Download => &e.download,
621                }
622            })
623        })
624    }
625}