tauri_plugin_android_fs/api/
android_fs.rs

1#[allow(unused)]
2use std::io::{Read as _, Write as _};
3use crate::*;
4
5
6/// ***Root API***  
7/// 
8/// # Examples
9/// ```
10/// fn example(app: &tauri::AppHandle) {
11///     use tauri_plugin_android_fs::AndroidFsExt as _;
12/// 
13///     let api = app.android_fs();
14/// }
15/// ```
16pub struct AndroidFs<R: tauri::Runtime> {
17    #[allow(unused)]
18    pub(crate) app: tauri::AppHandle<R>, 
19
20    #[cfg(target_os = "android")]
21    pub(crate) api: tauri::plugin::PluginHandle<R>, 
22
23    #[cfg(target_os = "android")]
24    pub(crate) intent_lock: std::sync::Mutex<()>,
25}
26
27impl<R: tauri::Runtime> AndroidFs<R> {
28
29    pub(crate) fn new<C: serde::de::DeserializeOwned>(
30        app: tauri::AppHandle<R>,
31        api: tauri::plugin::PluginApi<R, C>,
32    ) -> crate::Result<Self> {
33
34        #[cfg(target_os = "android")] {
35            Ok(Self {
36                api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?, 
37                app,
38                intent_lock: std::sync::Mutex::new(())
39            })
40        }
41        
42        #[cfg(not(target_os = "android"))] {
43            Ok(Self { app })
44        }
45    }
46}
47
48impl<R: tauri::Runtime> AndroidFs<R> {
49
50    /// API of file storage intended for the app's use only.
51    pub fn private_storage(&self) -> PrivateStorage<'_, R> {
52        PrivateStorage(self)
53    }
54
55    /// API of file storage that is available to other applications and users.
56    pub fn public_storage(&self) -> PublicStorage<'_, R> {
57        PublicStorage(self)
58    }
59
60    /// API of file/dir picker.
61    pub fn file_picker(&self) -> FilePicker<'_, R> {
62        FilePicker(self)
63    }
64
65    /// API of sharing files with other apps.
66    pub fn file_sender(&self) -> FileSender<'_, R> {
67        FileSender(self)
68    }
69
70    /// Get the file or directory name.  
71    /// 
72    /// # Args
73    /// - ***uri*** :  
74    /// Target URI.  
75    /// Must be **readable**.
76    /// 
77    /// # Support
78    /// All Android version.
79    pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
80        on_android!({
81            impl_se!(struct Req<'a> { uri: &'a FileUri });
82            impl_de!(struct Res { name: String });
83
84            self.api
85                .run_mobile_plugin::<Res>("getName", Req { uri })
86                .map(|v| v.name)
87                .map_err(Into::into)
88        })
89    }
90
91    /// Queries the provider to get the MIME type.
92    ///
93    /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.  
94    /// In most other cases, it uses the MIME type that was associated with the file when it was created.  
95    /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.  
96    /// 
97    /// If the target is a directory, an error will occur.  
98    /// To check whether the target is a file or a directory, use [`AndroidFs::get_type`].  
99    /// 
100    /// # Args
101    /// - ***uri*** :  
102    /// Target file URI.  
103    /// Must be **readable**.
104    /// 
105    /// # Support
106    /// All Android version.
107    pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<String> {
108        on_android!({
109            let mime_type = self.get_type(uri)?
110                .into_mime_type()
111                .ok_or_else(|| Error { msg: "This is not file".into() })?;
112
113            Ok(mime_type)
114        })
115    }
116
117    /// Gets the entry type.
118    ///
119    /// If the target is a directory, returns [`EntryType::Dir`].
120    ///
121    /// If the target is a file, returns [`EntryType::File { mime_type }`](EntryType::File).  
122    /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.  
123    /// In most other cases, it uses the MIME type that was associated with the file when it was created.  
124    /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.  
125    /// 
126    /// # Args
127    /// - ***uri*** :  
128    /// Target URI.  
129    /// Must be **readable**.
130    /// 
131    /// # Support
132    /// All Android version.
133    pub fn get_type(&self, uri: &FileUri) -> crate::Result<EntryType> {
134        on_android!({
135            impl_se!(struct Req<'a> { uri: &'a FileUri });
136            impl_de!(struct Res { value: Option<String> });
137
138            self.api
139                .run_mobile_plugin::<Res>("getMimeType", Req { uri })
140                .map(|v| match v.value {
141                    Some(mime_type) => EntryType::File { mime_type },
142                    None => EntryType::Dir,
143                })
144                .map_err(Into::into)
145        })
146    }
147
148    /// Queries the file system to get information about a file, directory.
149    /// 
150    /// # Args
151    /// - ***uri*** :  
152    /// Target URI.  
153    /// Must be **readable**.
154    /// 
155    /// # Note
156    /// This uses [`AndroidFs::open_file`] internally.
157    /// 
158    /// # Support
159    /// All Android version.
160    pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
161        on_android!({
162            let file = self.open_file_readable(uri)?;
163            Ok(file.metadata()?)
164        })
165    }
166
167    /// Open the file in **readable** mode.  
168    /// 
169    /// # Note
170    /// If the target is a file on cloud storage or otherwise not physically present on the device,
171    /// the system creates a temporary file, downloads the data into it,
172    /// and then opens it. As a result, this processing may take longer than with regular local files.
173    /// 
174    /// # Args
175    /// - ***uri*** :  
176    /// Target file URI.  
177    /// This need to be **readable**.
178    /// 
179    /// # Support
180    /// All Android version.
181    pub fn open_file_readable(&self, uri: &FileUri) -> Result<std::fs::File> {
182        self.open_file(uri, FileAccessMode::Read)
183    }
184
185    /// Open the file in **writable** mode.  
186    /// This truncates the existing contents.  
187    /// 
188    /// If you only need a [`std::io::Write`] instead of a [`std::fs::File`], or if applicable, 
189    /// please use [`AndroidFs::open_writable_stream`] instead.
190    /// 
191    /// # Note
192    /// If the target is a file on cloud storage or otherwise not actually present on the device, 
193    /// writing with such files may not correctly reflect the changes.  
194    /// If you need write with such files, use [`AndroidFs::open_writable_stream`].
195    ///
196    /// # Args
197    /// - ***uri*** :  
198    /// Target file URI.  
199    /// This need to be **writable**.
200    /// 
201    /// # Support
202    /// All Android version.
203    pub fn open_file_writable(
204        &self, 
205        uri: &FileUri, 
206    ) -> crate::Result<std::fs::File> {
207
208        on_android!(#[allow(deprecated)] {
209            // Android 9 以下の場合、w は既存コンテンツを切り捨てる
210            if self.api_level()? <= api_level::ANDROID_9 {
211                self.open_file(uri, FileAccessMode::Write)
212            }
213            // Android 10 以上の場合、w は既存コンテンツの切り捨てを保証しない。
214            // そのため切り捨ててファイルを開くには wt を用いる必要があるが、
215            // wt は全ての file provider が対応しているとは限らないため、
216            // フォールバックを用いてなるべく多くの状況に対応する。
217            // https://issuetracker.google.com/issues/180526528?pli=1
218            else {
219                let (file, mode) = self.open_file_with_fallback(uri, [
220                    FileAccessMode::WriteTruncate, 
221                    FileAccessMode::ReadWriteTruncate,
222                    FileAccessMode::Write
223                ])?;
224
225                if mode == FileAccessMode::Write {
226                    // file provider が既存コンテンツを切り捨てず、
227                    // かつ書き込むデータ量が元のそれより少ない場合にファイルが壊れる可能性がある。
228                    // これを避けるため強制的にデータを切り捨てる。
229                    // file provider の実装によっては set_len は失敗することがあるので最終手段。
230                    file.set_len(0)?;
231                }
232
233                Ok(file)
234            }
235        })
236    }
237
238    /// Open the file in the specified mode.  
239    /// 
240    /// # Note
241    /// If the target is a file on cloud storage or otherwise not physically present on the device,
242    /// the system creates a temporary file, downloads the data into it,
243    /// and then opens it. As a result, this processing may take longer than with regular local files.
244    /// 
245    /// When writing to a file with this function,
246    /// pay attention to the following points:
247    /// 
248    /// 1. **Files that do not exist on the device**:  
249    /// Writing to files that are not physically present on the device may not correctly
250    /// reflect changes. If you need to write to such files, use [`AndroidFs::open_writable_stream`].
251    ///
252    /// 2. **File mode restrictions**:  
253    /// Files provided by third-party apps may not support writable modes other than
254    /// [`FileAccessMode::Write`]. However, [`FileAccessMode::Write`] does not guarantee
255    /// that existing contents will always be truncated.  
256    /// As a result, if the new contents are shorter than the original, the file may
257    /// become corrupted. To avoid this, consider using
258    /// [`AndroidFs::open_file_writable`] or [`AndroidFs::open_writable_stream`], which
259    /// ensure that existing contents are truncated and also automatically apply the
260    /// maximum possible fallbacks.  
261    /// <https://issuetracker.google.com/issues/180526528>
262    /// 
263    /// # Args
264    /// - ***uri*** :  
265    /// Target file URI.  
266    /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
267    /// 
268    /// - ***mode*** :  
269    /// Indicates how the file is opened and the permissions granted.  
270    /// Note that files hosted by third-party apps may not support following:
271    ///     - [`FileAccessMode::ReadWrite`]
272    ///     - [`FileAccessMode::ReadWriteTruncate`]  
273    ///     - [`FileAccessMode::WriteAppend`]  
274    ///     - [`FileAccessMode::WriteTruncate`]
275    ///
276    /// # Support
277    /// All Android version.
278    pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
279        on_android!({
280            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
281            impl_de!(struct Res { fd: std::os::fd::RawFd });
282    
283            let mode = mode.to_mode();
284
285            self.api
286                .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
287                .map(|v| {
288                    use std::os::fd::FromRawFd;
289                    unsafe { std::fs::File::from_raw_fd(v.fd) }
290                })
291                .map_err(Into::into)
292        })
293    }
294 
295    /// For detailed documentation and notes, see [`AndroidFs::open_file`].  
296    ///
297    /// The modes specified in ***candidate_modes*** are tried in order.  
298    /// If the file can be opened, this returns the file along with the mode used.  
299    /// If all attempts fail, an error is returned.  
300    pub fn open_file_with_fallback(
301        &self, 
302        uri: &FileUri, 
303        candidate_modes: impl IntoIterator<Item = FileAccessMode>
304    ) -> crate::Result<(std::fs::File, FileAccessMode)> {
305
306        on_android!({
307            impl_se!(struct Req<'a> { uri: &'a FileUri, modes: Vec<&'a str> });
308            impl_de!(struct Res { fd: std::os::fd::RawFd, mode: String });
309    
310            let modes = candidate_modes.into_iter().map(|m| m.to_mode()).collect::<Vec<_>>();
311
312            if modes.is_empty() {
313                return Err(Error::with("candidate_modes must not be empty"));
314            }
315
316            self.api
317                .run_mobile_plugin::<Res>("getFileDescriptorWithFallback", Req { uri, modes })
318                .map_err(Into::into)
319                .and_then(|v| FileAccessMode::from_mode(&v.mode).map(|m| (v.fd, m)))
320                .map(|(fd, mode)| {
321                    let file = {
322                        use std::os::fd::FromRawFd;
323                        unsafe { std::fs::File::from_raw_fd(fd) }
324                    };
325                    (file, mode)
326                })
327        })
328    }
329
330    /// Opens a stream for writing to the specified file.  
331    /// This truncates the existing contents.  
332    /// 
333    /// # Usage
334    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
335    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
336    ///
337    /// After writing, call [`WritableStream::reflect`].  
338    /// 
339    /// # Note
340    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
341    /// If it is `false`, this behaves like [`AndroidFs::open_file_writable`].  
342    /// If it is `true`, this behaves like [`AndroidFs::open_writable_stream_via_kotlin`].  
343    /// 
344    /// # Args
345    /// - ***uri*** :  
346    /// Target file URI.  
347    /// This need to be **writable**.
348    /// 
349    /// # Support
350    /// All Android version.
351    pub fn open_writable_stream(
352        &self,
353        uri: &FileUri
354    ) -> Result<WritableStream<R>> {
355
356        on_android!({
357            let need_write_via_kotlin = self.need_write_via_kotlin(uri)?;
358            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
359        })
360    }
361
362    /// Opens a writable stream to the specified file.  
363    /// This truncates the existing contents.  
364    /// 
365    /// This function always writes content via the Kotlin API, 
366    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
367    /// But this takes several times longer compared to [`AndroidFs::open_writable_stream`].
368    ///
369    /// [`AndroidFs::open_writable_stream`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
370    /// For performance reasons, it is strongly recommended to use [`AndroidFs::open_writable_stream`] 
371    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
372    /// 
373    /// # Usage
374    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
375    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
376    ///
377    /// After writing, call [`WritableStream::reflect`].
378    /// 
379    /// # Args
380    /// - ***uri*** :  
381    /// Target file URI.  
382    /// This need to be **writable**.
383    /// 
384    /// # Support
385    /// All Android version.
386    pub fn open_writable_stream_via_kotlin(
387        &self,
388        uri: &FileUri
389    ) -> Result<WritableStream<R>> {
390
391        on_android!({
392            let need_write_via_kotlin = true;
393            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
394        })
395    }
396
397    /// Reads the entire contents of a file into a bytes vector.  
398    /// 
399    /// # Args
400    /// - ***uri*** :  
401    /// Target file URI.    
402    /// Must be **readable**.
403    /// 
404    /// # Support
405    /// All Android version.
406    pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
407        on_android!({
408            let mut file = self.open_file_readable(uri)?;
409            let mut buf = file.metadata().ok()
410                .map(|m| m.len() as usize)
411                .map(Vec::with_capacity)
412                .unwrap_or_else(Vec::new);
413
414            file.read_to_end(&mut buf)?;
415            Ok(buf)
416        })
417    }
418
419    /// Reads the entire contents of a file into a string.  
420    /// 
421    /// # Args
422    /// - ***uri*** :  
423    /// Target file URI.  
424    /// Must be **readable**.
425    /// 
426    /// # Support
427    /// All Android version.
428    pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
429        on_android!({
430            let mut file = self.open_file_readable(uri)?;
431            let mut buf = file.metadata().ok()
432                .map(|m| m.len() as usize)
433                .map(String::with_capacity)
434                .unwrap_or_else(String::new);
435    
436            file.read_to_string(&mut buf)?;
437            Ok(buf)
438        })
439    }
440
441    /// Writes a slice as the entire contents of a file.  
442    /// This function will entirely replace its contents if it does exist.    
443    /// 
444    /// # Note
445    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
446    /// If it is `false`, this uses [`std::fs::File::write_all`].  
447    /// If it is `true`, this uses [`AndroidFs::write_via_kotlin`].  
448    /// 
449    /// # Args
450    /// - ***uri*** :  
451    /// Target file URI.  
452    /// Must be **writable**.
453    /// 
454    /// # Support
455    /// All Android version.
456    pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
457        on_android!({
458            let mut stream = self.open_writable_stream(uri)?;
459            stream.write_all(contents.as_ref())?;
460            stream.reflect()?;
461            Ok(())
462        })
463    }
464
465    /// Writes a slice as the entire contents of a file.  
466    /// This function will entirely replace its contents if it does exist.    
467    /// 
468    /// This function always writes content via the Kotlin API, 
469    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
470    /// But this takes several times longer compared to [`AndroidFs::write`].
471    ///
472    /// [`AndroidFs::write`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
473    /// For performance reasons, it is strongly recommended to use [`AndroidFs::write`] 
474    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
475    /// 
476    /// # Support
477    /// All Android version.
478    pub fn write_via_kotlin(
479        &self, 
480        uri: &FileUri,
481        contents: impl AsRef<[u8]>
482    ) -> crate::Result<()> {
483
484        on_android!({
485            let mut stream = self.open_writable_stream_via_kotlin(uri)?;
486            stream.write_all(contents.as_ref())?;
487            stream.reflect()?;
488            Ok(())
489        })
490    }
491
492    /// Copies the contents of the source file to the destination.  
493    /// If the destination already has contents, they are truncated before writing the source contents.  
494    /// 
495    /// # Note
496    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
497    /// If it is `false`, this uses [`std::io::copy`] with [`std::fs::File`].  
498    /// If it is `true`, this uses [`AndroidFs::copy_via_kotlin`].  
499    /// 
500    /// # Args
501    /// - ***src*** :  
502    /// The URI of source file.   
503    /// Must be **readable**.
504    /// 
505    /// - ***dest*** :  
506    /// The URI of destination file.  
507    /// Must be **writable**.
508    /// 
509    /// # Support
510    /// All Android version.
511    pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
512        on_android!({
513            if self.need_write_via_kotlin(dest)? {
514                self.copy_via_kotlin(src, dest, None)?;
515            }
516            else {
517                // std::io::copy は std::fs::File 同士のコピーの場合、最適化が働く可能性がある。
518                // そのため self.open_writable_stream は用いない。
519                let src = &mut self.open_file_readable(src)?;
520                let dest = &mut self.open_file_writable(dest)?;
521                std::io::copy(src, dest)?;
522            }
523            Ok(())
524        })
525    }
526
527    /// Copies the contents of src file to dest.  
528    /// If dest already has contents, it is truncated before write src contents.  
529    /// 
530    /// This function always writes content via the Kotlin API, 
531    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
532    /// 
533    /// [`AndroidFs::copy`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].   
534    /// 
535    /// # Args
536    /// - ***src*** :  
537    /// The URI of source file.   
538    /// Must be **readable**.
539    /// 
540    /// - ***dest*** :  
541    /// The URI of destination file.  
542    /// Must be **writable**.
543    /// 
544    /// - ***buffer_size***:  
545    /// The size of the buffer, in bytes, to use during the copy process on Kotlin.  
546    /// If `None`, [`DEFAULT_BUFFER_SIZE`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/-d-e-f-a-u-l-t_-b-u-f-f-e-r_-s-i-z-e.html) is used. 
547    /// At least, when I checked, it was 8 KB.  
548    /// If zero, this causes error.
549    /// 
550    /// # Support
551    /// All Android version.
552    pub fn copy_via_kotlin(
553        &self, 
554        src: &FileUri, 
555        dest: &FileUri,
556        buffer_size: Option<u32>,
557    ) -> crate::Result<()> {
558
559        on_android!({
560            impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri, buffer_size: Option<u32> });
561            impl_de!(struct Res;);
562
563            if buffer_size.is_some_and(|s| s <= 0) {
564                return Err(Error { msg: "buffer_size must be non zero".into() })
565            }
566
567            self.api
568                .run_mobile_plugin::<Res>("copyFile", Req { src, dest, buffer_size })
569                .map(|_| ())
570                .map_err(Into::into)
571        })
572    }
573
574    /// Determines whether the file must be written via the Kotlin API rather than through a file descriptor.
575    /// 
576    /// **This may not cover all cases.**
577    /// 
578    /// # Why
579    /// For files that do not physically exist on the device, such as those stored in cloud storage,  
580    /// writing data through [`std::fs::File`] may not be properly reflected.  
581    /// In such cases, the write operation must be performed via the Kotlin API.   
582    /// <https://community.latenode.com/t/csv-export-to-google-drive-results-in-empty-file-but-local-storage-works-fine>   
583    ///
584    /// # Support
585    /// All Android version.
586    pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
587        on_android!({
588            // Note:
589            // Android 15 の Dropbox (ver. 440.2.4) では fd 経由で書き込んで反映された
590
591            const URI_PREFIXES: &'static [&'static str] = &[
592                "content://com.google.android.apps.docs", // Google drive
593            ];
594
595            Ok(URI_PREFIXES.iter().any(|prefix| uri.uri.starts_with(prefix)))
596        })
597    }
598
599    /// Renames a file or directory to a new name, and return new URI.  
600    /// Even if the names conflict, the existing file will not be overwritten.  
601    /// 
602    /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
603    /// In other words, this function returns a new URI without any permissions.
604    /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
605    /// In this, this function returns the same URI as original URI.
606    ///
607    /// # Args
608    /// - ***uri*** :  
609    /// URI of target entry.  
610    /// 
611    /// - ***new_name*** :  
612    /// New name of target entry. 
613    /// This include extension if use.  
614    /// The behaviour in the same name already exists depends on the file provider.  
615    /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.  
616    /// In the case of files hosted by other applications, errors may occur.  
617    /// But at least, the existing file will not be overwritten.  
618    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
619    /// 
620    /// # Support
621    /// All Android version.
622    pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
623        on_android!({
624            impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
625
626            let new_name = new_name.as_ref();
627
628            self.api
629                .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
630                .map_err(Into::into)
631        })
632    }
633
634    /// Remove the file.
635    /// 
636    /// # Args
637    /// - ***uri*** :  
638    /// Target file URI.  
639    /// Must be **writable**, at least. But even if it is, 
640    /// removing may not be possible in some cases. 
641    /// For details, refer to the documentation of the function that provided the URI.  
642    /// If not file, an error will occur.
643    /// 
644    /// # Support
645    /// All Android version.
646    pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
647        on_android!({
648            impl_se!(struct Req<'a> { uri: &'a FileUri });
649            impl_de!(struct Res;);
650    
651            self.api
652                .run_mobile_plugin::<Res>("deleteFile", Req { uri })
653                .map(|_| ())
654                .map_err(Into::into)
655        })
656    }
657
658    /// Remove the **empty** directory.
659    /// 
660    /// # Args
661    /// - ***uri*** :  
662    /// Target directory URI.  
663    /// Must be **writable**.  
664    /// If not empty directory, an error will occur.
665    /// 
666    /// # Support
667    /// All Android version.
668    pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
669        on_android!({
670            impl_se!(struct Req<'a> { uri: &'a FileUri });
671            impl_de!(struct Res;);
672        
673            self.api
674                .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
675                .map(|_| ())
676                .map_err(Into::into)
677        })
678    }
679
680    /// Removes a directory and all its contents. Use carefully!
681    /// 
682    /// # Args
683    /// - ***uri*** :  
684    /// Target directory URI.  
685    /// Must be **writable**.  
686    /// If not directory, an error will occur.
687    /// 
688    /// # Support
689    /// All Android version.
690    pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
691        on_android!({
692            impl_se!(struct Req<'a> { uri: &'a FileUri });
693            impl_de!(struct Res;);
694        
695            self.api
696                .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
697                .map(|_| ())
698                .map_err(Into::into)
699        })
700    }
701
702    /// Build a URI of an **existing** file located at the relative path from the specified directory.   
703    /// Error occurs, if the file does not exist.  
704    /// 
705    /// The permissions and validity period of the returned URI depend on the origin directory 
706    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
707    /// 
708    /// # Note
709    /// For [`AndroidFs::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
710    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
711    /// 
712    /// # Support
713    /// All Android version.
714    pub fn try_resolve_file_uri(
715        &self, 
716        dir: &FileUri, 
717        relative_path: impl AsRef<std::path::Path>
718    ) -> crate::Result<FileUri> {
719
720        on_android!({
721            #[allow(deprecated)]
722            let uri = self.resolve_uri(dir, relative_path)?;         
723
724            if !self.get_type(&uri)?.is_file() {
725                return Err(crate::Error::with(format!("This is not a file: {uri:?}")))
726            }
727            Ok(uri)
728        })
729    }
730
731    /// Build a URI of an **existing** directory located at the relative path from the specified directory.   
732    /// Error occurs, if the directory does not exist.  
733    /// 
734    /// The permissions and validity period of the returned URI depend on the origin directory 
735    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
736    /// 
737    /// # Note
738    /// For [`AndroidFs::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
739    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
740    /// 
741    /// # Support
742    /// All Android version.
743    pub fn try_resolve_dir_uri(
744        &self,
745        dir: &FileUri, 
746        relative_path: impl AsRef<std::path::Path>
747    ) -> crate::Result<FileUri> {
748
749        on_android!({
750            #[allow(deprecated)]
751            let uri = self.resolve_uri(dir, relative_path)?;
752            
753            if !self.get_type(&uri)?.is_dir() {
754                return Err(crate::Error::with(format!("This is not a directory: {uri:?}")))
755            }
756            Ok(uri)
757        })
758    }
759
760    /// Build a URI of an entry located at the relative path from the specified directory.   
761    /// 
762    /// This function does **not** create any entries; it only constructs the URI.
763    /// 
764    /// This function does not perform checks on the arguments or the returned URI.  
765    /// Even if the dir argument refers to a file, no error occurs (and no panic either).
766    /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.  
767    /// 
768    /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead. 
769    /// Or use this with [`AndroidFs::get_mime_type`].
770    /// 
771    /// The permissions and validity period of the returned URI depend on the origin directory 
772    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
773    /// 
774    /// # Note
775    /// For [`PublicStorage::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
776    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
777    /// 
778    /// # Support
779    /// All Android version.
780    #[deprecated = "Use AndroidFs::try_resolve_file_uri or AndroidFs::try_resolve_dir_uri instead"]
781    pub fn resolve_uri(
782        &self, 
783        dir: &FileUri, 
784        relative_path: impl AsRef<std::path::Path>
785    ) -> crate::Result<FileUri> {
786
787        on_android!({
788            let base_dir = &dir.uri;
789            let relative_path = validate_relative_path(relative_path.as_ref())?;
790            let relative_path = relative_path.to_string_lossy();
791
792            if relative_path.is_empty() {
793                return Ok(dir.clone())
794            }
795
796            Ok(FileUri {
797                document_top_tree_uri: dir.document_top_tree_uri.clone(),
798                uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
799            })
800        })
801    }
802
803    /// See [`AndroidFs::get_thumbnail_to`] for descriptions.  
804    /// 
805    /// If thumbnail does not wrote to dest, return false.
806    pub fn get_thumbnail_to(
807        &self, 
808        src: &FileUri,
809        dest: &FileUri,
810        preferred_size: Size,
811        format: ImageFormat,
812    ) -> crate::Result<bool> {
813
814        on_android!({
815            impl_se!(struct Req<'a> {
816                src: &'a FileUri, 
817                dest: &'a FileUri,
818                format: &'a str,
819                quality: u8,
820                width: u32,
821                height: u32,
822            });
823            impl_de!(struct Res { value: bool });
824
825            let (quality, format) = match format {
826                ImageFormat::Png => (1.0, "Png"),
827                ImageFormat::Jpeg => (0.75, "Jpeg"),
828                ImageFormat::Webp => (0.7, "Webp"),
829                ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
830                ImageFormat::WebpWith { quality } => (quality, "Webp"),
831            };
832            let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
833            let Size { width, height } = preferred_size;
834        
835            self.api
836                .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
837                .map(|v| v.value)
838                .map_err(Into::into)
839        })
840    }
841
842    /// Query the provider to get a file thumbnail.  
843    /// If thumbnail does not exist it, return None.
844    /// 
845    /// Note this does not cache. Please do it in your part if need.  
846    /// 
847    /// # Args
848    /// - ***uri*** :  
849    /// Targe file uri.  
850    /// Thumbnail availablty depends on the file provider.  
851    /// In general, images and videos are available.  
852    /// For files in [`PrivateStorage`], 
853    /// the file type must match the filename extension.  
854    /// 
855    /// - ***preferred_size*** :  
856    /// Optimal thumbnail size desired.  
857    /// This may return a thumbnail of a different size, 
858    /// but never more than double the requested size. 
859    /// In any case, the aspect ratio is maintained.
860    /// 
861    /// - ***format*** :  
862    /// Thumbnail image format.  
863    /// [`ImageFormat::Jpeg`] is recommended. 
864    /// If you need transparency, use others.
865    /// 
866    /// # Support
867    /// All Android version.
868    pub fn get_thumbnail(
869        &self,
870        uri: &FileUri,
871        preferred_size: Size,
872        format: ImageFormat,
873    ) -> crate::Result<Option<Vec<u8>>> {
874
875        on_android!({
876            let (tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
877            std::mem::drop(tmp_file);
878
879            let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
880                .and_then(|ok| {
881                    if ok {
882                        std::fs::read(&tmp_file_path)
883                            .map(Some)
884                            .map_err(Into::into)
885                    }
886                    else {
887                        Ok(None)
888                    }
889                });
890
891            let _ = std::fs::remove_file(&tmp_file_path);
892
893            result
894        })
895    }
896
897    /// Creates a new empty file in the specified location and returns a URI.   
898    /// 
899    /// The permissions and validity period of the returned URIs depend on the origin directory 
900    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
901    /// 
902    /// # Args  
903    /// - ***dir*** :  
904    /// The URI of the base directory.  
905    /// Must be **read-write**.
906    ///  
907    /// - ***relative_path*** :  
908    /// The file path relative to the base directory.  
909    /// Any missing subdirectories in the specified path will be created automatically.  
910    /// If a file with the same name already exists, 
911    /// the system append a sequential number to ensure uniqueness.  
912    /// If no extension is present, 
913    /// the system may infer one from ***mime_type*** and may append it to the file name. 
914    /// But this append-extension operation depends on the model and version.  
915    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
916    ///  
917    /// - ***mime_type*** :  
918    /// The MIME type of the file to be created.  
919    /// If this is None, MIME type is inferred from the extension of ***relative_path***
920    /// and if that fails, `application/octet-stream` is used.  
921    ///  
922    /// # Support
923    /// All Android version.
924    pub fn create_new_file(
925        &self,
926        dir: &FileUri, 
927        relative_path: impl AsRef<std::path::Path>, 
928        mime_type: Option<&str>
929    ) -> crate::Result<FileUri> {
930
931        on_android!({
932            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
933        
934            let relative_path = validate_relative_path(relative_path.as_ref())?;
935            let relative_path = relative_path.to_string_lossy();
936                
937            self.api
938                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path: relative_path.as_ref() })
939                .map_err(Into::into)
940        })
941    }
942
943    /// Recursively create a directory and all of its parent components if they are missing,
944    /// then return the URI.  
945    /// If it already exists, do nothing and just return the direcotry uri.
946    /// 
947    /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
948    /// 
949    /// # Args  
950    /// - ***dir*** :  
951    /// The URI of the base directory.  
952    /// Must be **read-write**.
953    ///  
954    /// - ***relative_path*** :  
955    /// The directory path relative to the base directory.    
956    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
957    ///  
958    /// # Support
959    /// All Android version.
960    pub fn create_dir_all(
961        &self,
962        dir: &FileUri, 
963        relative_path: impl AsRef<std::path::Path>, 
964    ) -> Result<FileUri> {
965
966        on_android!({
967            impl_se!(struct Req<'a> { dir: &'a FileUri,relative_path: &'a str });
968        
969            let relative_path = validate_relative_path(relative_path.as_ref())?;
970            let relative_path = relative_path.to_string_lossy();
971                
972            self.api
973                .run_mobile_plugin::<FileUri>("createDirAll", Req { dir, relative_path: relative_path.as_ref() })
974                .map_err(Into::into)
975        })
976    }
977
978    /// Returns the child files and directories of the specified directory.  
979    /// The order of the entries is not guaranteed.  
980    /// 
981    /// The permissions and validity period of the returned URIs depend on the origin directory 
982    /// (e.g., the top directory selected by [`FilePicker::pick_dir`])  
983    /// 
984    /// # Note
985    /// The returned type is an iterator, but the file system call is not executed lazily.  
986    /// Instead, all data is retrieved at once.  
987    /// For directories containing thousands or even tens of thousands of entries,  
988    /// this function may take several seconds to complete.  
989    /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
990    /// 
991    /// # Args
992    /// - ***uri*** :  
993    /// Target directory URI.  
994    /// Must be **readable**.
995    /// 
996    /// # Support
997    /// All Android version.
998    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
999        on_android!(std::iter::Empty::<_>, {
1000            impl_se!(struct Req<'a> { uri: &'a FileUri });
1001            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
1002            impl_de!(struct Res { entries: Vec<Obj> });
1003
1004            use std::time::{UNIX_EPOCH, Duration};
1005    
1006            self.api
1007                .run_mobile_plugin::<Res>("readDir", Req { uri })
1008                .map(|v| v.entries.into_iter())
1009                .map(|v| v.map(|v| match v.mime_type {
1010                    Some(mime_type) => Entry::File {
1011                        name: v.name,
1012                        last_modified: UNIX_EPOCH + Duration::from_millis(v.last_modified as u64),
1013                        len: v.byte_size as u64,
1014                        mime_type,
1015                        uri: v.uri,
1016                    },
1017                    None => Entry::Dir {
1018                        name: v.name,
1019                        last_modified: UNIX_EPOCH + Duration::from_millis(v.last_modified as u64),
1020                        uri: v.uri,
1021                    }
1022                }))
1023                .map_err(Into::into)
1024        })
1025    }
1026
1027    /// Take persistent permission to access the file, directory and its descendants.  
1028    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1029    /// 
1030    /// This works by just calling, without displaying any confirmation to the user.
1031    /// 
1032    /// Note that [there is a limit to the total number of URI that can be made persistent by this function.](https://stackoverflow.com/questions/71099575/should-i-release-persistableuripermission-when-a-new-storage-location-is-chosen/71100621#71100621)  
1033    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1034    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1035    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1036    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1037    /// 
1038    /// # Args
1039    /// - **uri** :  
1040    /// URI of the target file or directory.   
1041    /// This must be a URI taken from following :  
1042    ///     - [`FilePicker::pick_files`]  
1043    ///     - [`FilePicker::pick_file`]  
1044    ///     - [`FilePicker::pick_visual_medias`]  
1045    ///     - [`FilePicker::pick_visual_media`]  
1046    ///     - [`FilePicker::pick_dir`]  
1047    ///     - [`FilePicker::save_file`]  
1048    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :  
1049    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function. 
1050    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1051    /// 
1052    /// # Support
1053    /// All Android version. 
1054    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1055        on_android!({
1056            impl_se!(struct Req<'a> { uri: &'a FileUri });
1057            impl_de!(struct Res;);
1058
1059            self.api
1060                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1061                .map(|_| ())
1062                .map_err(Into::into)
1063        })
1064    }
1065
1066    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1067    /// Returns false if there are only non-persistent permissions or no permissions.
1068    /// 
1069    /// # Args
1070    /// - **uri** :  
1071    /// URI of the target file or directory.  
1072    /// This must be a URI taken from following :  
1073    ///     - [`FilePicker::pick_files`]  
1074    ///     - [`FilePicker::pick_file`]  
1075    ///     - [`FilePicker::pick_visual_medias`]  
1076    ///     - [`FilePicker::pick_visual_media`]  
1077    ///     - [`FilePicker::pick_dir`]  
1078    ///     - [`FilePicker::save_file`]  
1079    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :  
1080    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is checked, not a entry iteself by this function. 
1081    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1082    /// 
1083    /// - **mode** :  
1084    /// The mode of permission you want to check.  
1085    /// 
1086    /// # Support
1087    /// All Android version.
1088    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1089        on_android!({
1090            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1091            impl_de!(struct Res { value: bool });
1092
1093            self.api
1094                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1095                .map(|v| v.value)
1096                .map_err(Into::into)
1097        })
1098    }
1099
1100    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1101    /// 
1102    /// # Support
1103    /// All Android version.
1104    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1105        on_android!(std::iter::Empty::<_>, {
1106            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1107            impl_de!(struct Res { items: Vec<Obj> });
1108    
1109            self.api
1110                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1111                .map(|v| v.items.into_iter())
1112                .map(|v| v.map(|v| {
1113                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1114                    match v.d {
1115                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1116                        false => PersistedUriPermission::File { uri, can_read, can_write }
1117                    }
1118                }))
1119                .map_err(Into::into)
1120        })
1121    }
1122
1123    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1124    /// 
1125    /// # Args
1126    /// - ***uri*** :  
1127    /// URI of the target file or directory.  
1128    ///
1129    /// # Support
1130    /// All Android version.
1131    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1132        on_android!({
1133            impl_se!(struct Req<'a> { uri: &'a FileUri });
1134            impl_de!(struct Res;);
1135
1136            self.api
1137                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1138                .map(|_| ())
1139                .map_err(Into::into)
1140        })
1141    }
1142
1143    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1144    /// 
1145    /// # Support
1146    /// All Android version.
1147    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1148        on_android!({
1149            impl_de!(struct Res);
1150
1151            self.api
1152                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1153                .map(|_| ())
1154                .map_err(Into::into)
1155        })
1156    }
1157
1158    /// Verify whether this plugin is available.  
1159    /// 
1160    /// On Android, this returns true.  
1161    /// On other platforms, this returns false.  
1162    pub fn is_available(&self) -> bool {
1163        cfg!(target_os = "android")
1164    }
1165
1166    /// Get the api level of this Android device.
1167    /// 
1168    /// The correspondence table between API levels and Android versions can be found following.  
1169    /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1170    /// 
1171    /// If you want the constant value of the API level from an Android version, there is the [`api_level`] module.
1172    /// 
1173    /// # Table
1174    /// | Android version  | API Level |
1175    /// |------------------|-----------|
1176    /// | 16.0             | 36        |
1177    /// | 15.0             | 35        |
1178    /// | 14.0             | 34        |
1179    /// | 13.0             | 33        |
1180    /// | 12L              | 32        |
1181    /// | 12.0             | 31        |
1182    /// | 11.0             | 30        |
1183    /// | 10.0             | 29        |
1184    /// | 9.0              | 28        |
1185    /// | 8.1              | 27        |
1186    /// | 8.0              | 26        |
1187    /// | 7.1 - 7.1.2      | 25        |
1188    /// | 7.0              | 24        |
1189    /// 
1190    /// Tauri does not support Android versions below 7.
1191    pub fn api_level(&self) -> Result<i32> {
1192        Ok(self.consts()?.build_version_sdk_int)
1193    }
1194}
1195
1196
1197#[allow(unused)]
1198impl<R: tauri::Runtime> AndroidFs<R> {
1199
1200    pub(crate) fn check_media_store_volume_name_available(
1201        &self,
1202        media_store_volume_name: impl AsRef<str>,
1203    ) -> Result<bool> {
1204
1205        on_android!({
1206            impl_se!(struct Req<'a> { media_store_volume_name: &'a str });
1207            impl_de!(struct Res { value: bool });
1208            
1209            let media_store_volume_name = media_store_volume_name.as_ref();
1210            
1211            self.api
1212                .run_mobile_plugin::<Res>("checkMediaStoreVolumeNameAvailable", Req { media_store_volume_name })
1213                .map(|v| v.value)
1214                .map_err(Into::into)
1215        })
1216    }
1217
1218    pub(crate) fn check_storage_volume_available_by_path(
1219        &self,
1220        path: impl AsRef<std::path::Path>,
1221    ) -> Result<bool> {
1222
1223        on_android!({
1224            impl_se!(struct Req<'a> { path: &'a std::path::Path });
1225            impl_de!(struct Res { value: bool });
1226
1227            let path = path.as_ref();
1228
1229            self.api
1230                .run_mobile_plugin::<Res>("checkStorageVolumeAvailableByPath", Req { path })
1231                .map(|v| v.value)
1232                .map_err(Into::into)
1233        })
1234    }
1235
1236    pub(crate) fn get_available_storage_volumes(&self) -> Result<Vec<StorageVolume>> {
1237        on_android!({
1238            impl_de!(struct Res { volumes: Vec<StorageVolume> });
1239
1240            let mut volumes = self.api
1241                .run_mobile_plugin::<Res>("getAvailableStorageVolumes", "")
1242                .map(|v| v.volumes)?;
1243
1244            // primary volume を先頭にする。他はそのままの順序
1245            volumes.sort_by(|a, b| b.is_primary.cmp(&a.is_primary));
1246
1247            Ok(volumes)
1248        })
1249    }
1250
1251    pub(crate) fn get_primary_storage_volume_if_available(&self) -> Result<Option<StorageVolume>> {
1252        on_android!({
1253            impl_de!(struct Res { volume: Option<StorageVolume> });
1254
1255            self.api
1256                .run_mobile_plugin::<Res>("getPrimaryStorageVolumeIfAvailable", "")
1257                .map(|v| v.volume)
1258                .map_err(Into::into)
1259        })
1260    }
1261
1262    pub(crate) fn consts(&self) -> Result<&Consts> {
1263        on_android!({
1264            static CONSTS: std::sync::OnceLock<Consts> = std::sync::OnceLock::new();
1265
1266            if CONSTS.get().is_none() {
1267                let _ = CONSTS.set(
1268                    self.api.run_mobile_plugin::<Consts>("getConsts", "")?
1269                );
1270            }
1271            let consts = CONSTS.get().expect("Should call 'set' before 'get'");
1272
1273            Ok(consts)
1274        })
1275    }
1276}
1277
1278/// アプリ起動中に変更されることのない値
1279#[derive(serde::Deserialize)]
1280#[serde(rename_all = "camelCase")]
1281#[allow(unused)]
1282pub(crate) struct Consts {
1283    pub build_version_sdk_int: i32,
1284
1285    /// Android 10 (API level 29) 以上で有効
1286    pub media_store_primary_volume_name: Option<String>,
1287
1288    pub env_dir_pictures: String,
1289    pub env_dir_dcim: String,
1290    pub env_dir_movies: String,
1291    pub env_dir_music: String,
1292    pub env_dir_alarms: String,
1293    pub env_dir_notifications: String,
1294    pub env_dir_podcasts: String,
1295    pub env_dir_ringtones: String,
1296    pub env_dir_documents: String,
1297    pub env_dir_download: String,
1298
1299    /// Android 10 (API level 29) 以上で有効
1300    pub env_dir_audiobooks: Option<String>,
1301
1302    /// Android 12 (API level 31) 以上で有効
1303    pub env_dir_recordings: Option<String>,
1304}
1305
1306#[allow(unused)]
1307impl Consts {
1308
1309    pub(crate) fn public_dir_name(&self, dir: impl Into<PublicDir>) -> Result<&str> {
1310        Ok(match dir.into() {
1311            PublicDir::Image(dir) => match dir {
1312                PublicImageDir::Pictures => &self.env_dir_pictures,
1313                PublicImageDir::DCIM => &self.env_dir_dcim,
1314            },
1315            PublicDir::Video(dir) => match dir {
1316                PublicVideoDir::Movies => &self.env_dir_movies,
1317                PublicVideoDir::DCIM => &self.env_dir_dcim,
1318            },
1319            PublicDir::Audio(dir) => match dir  {
1320                PublicAudioDir::Music => &self.env_dir_music,
1321                PublicAudioDir::Alarms => &self.env_dir_alarms,
1322                PublicAudioDir::Notifications => &self.env_dir_notifications,
1323                PublicAudioDir::Podcasts => &self.env_dir_podcasts,
1324                PublicAudioDir::Ringtones => &self.env_dir_ringtones,
1325                PublicAudioDir::Recordings => self.env_dir_recordings.as_ref().ok_or_else(|| Error { msg: "requires API level 31 or higher".into() })?,
1326                PublicAudioDir::Audiobooks => self.env_dir_audiobooks.as_ref().ok_or_else(|| Error { msg: "requires API level 29 or higher".into() })?,
1327            },
1328            PublicDir::GeneralPurpose(dir) => match dir {
1329                PublicGeneralPurposeDir::Documents => &self.env_dir_documents,
1330                PublicGeneralPurposeDir::Download => &self.env_dir_download,
1331            }
1332        })
1333    }
1334}