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}