tauri_plugin_android_fs/api/android_fs.rs
1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6/// ***Root API***
7///
8/// # Examples
9/// ```no_run
10/// fn example(app: &tauri::AppHandle) {
11/// use tauri_plugin_android_fs::AndroidFsExt as _;
12///
13/// let api = app.android_fs();
14/// }
15/// ```
16
17#[sync_async]
18pub struct AndroidFs<R: tauri::Runtime> {
19 #[cfg(target_os = "android")]
20 pub(crate) handle: tauri::plugin::PluginHandle<R>,
21
22 #[cfg(not(target_os = "android"))]
23 #[allow(unused)]
24 pub(crate) handle: std::marker::PhantomData<fn() -> R>
25}
26
27#[cfg(target_os = "android")]
28#[sync_async(
29 use(if_sync) impls::SyncImpls as Impls;
30 use(if_async) impls::AsyncImpls as Impls;
31)]
32impl<R: tauri::Runtime> AndroidFs<R> {
33
34 #[always_sync]
35 pub(crate) fn impls(&self) -> Impls<'_, R> {
36 Impls { handle: &self.handle }
37 }
38}
39
40#[sync_async(
41 use(if_async) api_async::{FileOpener, FilePicker, AppStorage, PrivateStorage, PublicStorage, WritableStream};
42 use(if_sync) api_sync::{FileOpener, FilePicker, AppStorage, PrivateStorage, PublicStorage, WritableStream};
43)]
44impl<R: tauri::Runtime> AndroidFs<R> {
45
46 /// API of file storage that is available to other applications and users.
47 #[always_sync]
48 pub fn public_storage(&self) -> PublicStorage<'_, R> {
49 PublicStorage { handle: &self.handle }
50 }
51
52 /// API of file storage intended for the app's use only.
53 #[always_sync]
54 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
55 PrivateStorage { handle: &self.handle }
56 }
57
58 /// API of file storage intended for the app's use.
59 #[always_sync]
60 pub fn app_storage(&self) -> AppStorage<'_, R> {
61 AppStorage { handle: &self.handle }
62 }
63
64 /// API of file/dir picker.
65 #[always_sync]
66 pub fn file_picker(&self) -> FilePicker<'_, R> {
67 FilePicker { handle: &self.handle }
68 }
69
70 /// API of opening file/dir with other apps.
71 #[always_sync]
72 pub fn file_opener(&self) -> FileOpener<'_, R> {
73 FileOpener { handle: &self.handle }
74 }
75
76 /// Get the file or directory name.
77 ///
78 /// # Args
79 /// - ***uri*** :
80 /// Target URI.
81 /// Must be **readable**.
82 ///
83 /// # Support
84 /// All Android version.
85 #[maybe_async]
86 pub fn get_name(&self, uri: &FileUri) -> Result<String> {
87 #[cfg(not(target_os = "android"))] {
88 Err(Error::NOT_ANDROID)
89 }
90 #[cfg(target_os = "android")] {
91 self.impls().get_entry_name(uri).await
92 }
93 }
94
95 /// Queries the provider to get the MIME type.
96 ///
97 /// For file URIs via [`FileUri::from_path`], the MIME type is determined from the file extension.
98 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
99 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
100 ///
101 /// If the target is a directory, an error will occur.
102 /// To check whether the target is a file or a directory, use [`AndroidFs::get_type`].
103 ///
104 /// # Args
105 /// - ***uri*** :
106 /// Target file URI.
107 /// Must be **readable**.
108 ///
109 /// # Support
110 /// All Android version.
111 #[maybe_async]
112 pub fn get_mime_type(&self, uri: &FileUri) -> Result<String> {
113 #[cfg(not(target_os = "android"))] {
114 Err(Error::NOT_ANDROID)
115 }
116 #[cfg(target_os = "android")] {
117 self.impls().get_file_mime_type(uri).await
118 }
119 }
120
121 /// Gets the entry type.
122 ///
123 /// If the target is a directory, returns [`EntryType::Dir`].
124 ///
125 /// If the target is a file, returns [`EntryType::File { mime_type }`](EntryType::File).
126 /// For file URIs via [`FileUri::from_path`], the MIME type is determined from the file extension.
127 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
128 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
129 ///
130 /// # Args
131 /// - ***uri*** :
132 /// Target URI.
133 /// Must be **readable**.
134 ///
135 /// # Support
136 /// All Android version.
137 #[maybe_async]
138 pub fn get_type(&self, uri: &FileUri) -> Result<EntryType> {
139 #[cfg(not(target_os = "android"))] {
140 Err(Error::NOT_ANDROID)
141 }
142 #[cfg(target_os = "android")] {
143 self.impls().get_entry_type(uri).await
144 }
145 }
146
147 /// Queries the file system to get information about a file, directory.
148 ///
149 /// # Args
150 /// - ***uri*** :
151 /// Target URI.
152 /// Must be **readable**.
153 ///
154 /// # Note
155 /// This uses [`AndroidFs::open_file`] internally.
156 ///
157 /// # Support
158 /// All Android version.
159 #[maybe_async]
160 pub fn get_metadata(&self, uri: &FileUri) -> Result<std::fs::Metadata> {
161 #[cfg(not(target_os = "android"))] {
162 Err(Error::NOT_ANDROID)
163 }
164 #[cfg(target_os = "android")] {
165 self.impls().get_entry_metadata(uri).await
166 }
167 }
168
169 /// Open the file in **readable** mode.
170 ///
171 /// # Note
172 /// If the target is a file on cloud storage or otherwise not physically present on the device,
173 /// the file provider may downloads the entire contents, and then opens it.
174 /// As a result, this processing may take longer than with regular local files.
175 /// And files might be a pair of pipe or socket for streaming data.
176 ///
177 /// # Args
178 /// - ***uri*** :
179 /// Target file URI.
180 /// This need to be **readable**.
181 ///
182 /// # Support
183 /// All Android version.
184 #[maybe_async]
185 pub fn open_file_readable(&self, uri: &FileUri) -> Result<std::fs::File> {
186 #[cfg(not(target_os = "android"))] {
187 Err(Error::NOT_ANDROID)
188 }
189 #[cfg(target_os = "android")] {
190 self.impls().open_file_readable(uri).await
191 }
192 }
193
194 /// Open the file in **writable** mode.
195 /// This truncates the existing contents.
196 ///
197 /// # Note
198 /// For file provider of some cloud storage,
199 /// writing by file descriptor like std::fs may not correctoly notify and reflect changes.
200 /// If you need to write to such files, use [`AndroidFs::open_writable_stream`].
201 /// It will fall back to Kotlin API as needed.
202 /// And you can check by [`AndroidFs::need_write_via_kotlin`].
203 ///
204 /// # Args
205 /// - ***uri*** :
206 /// Target file URI.
207 /// This need to be **writable**.
208 ///
209 /// # Support
210 /// All Android version.
211 #[maybe_async]
212 pub fn open_file_writable(
213 &self,
214 uri: &FileUri,
215 ) -> Result<std::fs::File> {
216
217 #[cfg(not(target_os = "android"))] {
218 Err(Error::NOT_ANDROID)
219 }
220 #[cfg(target_os = "android")] {
221 self.impls().open_file_writable(uri).await
222 }
223 }
224
225 /// Open the file in the specified mode.
226 ///
227 /// # Note
228 /// If the target is a file on cloud storage or otherwise not physically present on the device,
229 /// the file provider may downloads the entire contents, and then opens it.
230 /// As a result, this processing may take longer than with regular local files.
231 /// And files might be a pair of pipe or socket for streaming data.
232 ///
233 /// When writing to a file with this function,
234 /// pay attention to the following points:
235 ///
236 /// 1. **File reflection**:
237 /// For file provider of some cloud storage,
238 /// writing by file descriptor like std::fs may not correctoly notify and reflect changes.
239 /// If you need to write to such files, use [`AndroidFs::open_writable_stream`].
240 /// It will fall back to Kotlin API as needed.
241 /// And you can check by [`AndroidFs::need_write_via_kotlin`].
242 ///
243 /// 2. **File mode restrictions**:
244 /// Files provided by third-party apps may not support writable modes other than
245 /// [`FileAccessMode::Write`]. However, [`FileAccessMode::Write`] does not guarantee
246 /// that existing contents will always be truncated.
247 /// As a result, if the new contents are shorter than the original, the file may
248 /// become corrupted. To avoid this, consider using
249 /// [`AndroidFs::open_file_writable`] or [`AndroidFs::open_writable_stream`], which
250 /// ensure that existing contents are truncated and also automatically apply the
251 /// maximum possible fallbacks.
252 /// <https://issuetracker.google.com/issues/180526528>
253 ///
254 /// # Args
255 /// - ***uri*** :
256 /// Target file URI.
257 /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
258 ///
259 /// - ***mode*** :
260 /// Indicates how the file is opened and the permissions granted.
261 /// The only ones that can be expected to work in all cases are [`FileAccessMode::Write`] and [`FileAccessMode::Read`].
262 /// Because files hosted by third-party apps may not support others.
263 ///
264 /// # Support
265 /// All Android version.
266 #[maybe_async]
267 pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> Result<std::fs::File> {
268 #[cfg(not(target_os = "android"))] {
269 Err(Error::NOT_ANDROID)
270 }
271 #[cfg(target_os = "android")] {
272 self.impls().open_file(uri, mode).await
273 }
274 }
275
276 /// For detailed documentation and notes, see [`AndroidFs::open_file`].
277 ///
278 /// The modes specified in ***candidate_modes*** are tried in order.
279 /// If the file can be opened, this returns the file along with the mode used.
280 /// If all attempts fail, an error is returned.
281 #[maybe_async]
282 pub fn open_file_with_fallback(
283 &self,
284 uri: &FileUri,
285 candidate_modes: impl IntoIterator<Item = FileAccessMode>
286 ) -> Result<(std::fs::File, FileAccessMode)> {
287
288 #[cfg(not(target_os = "android"))] {
289 Err(Error::NOT_ANDROID)
290 }
291 #[cfg(target_os = "android")] {
292 self.impls().open_file_with_fallback(uri, candidate_modes).await
293 }
294 }
295
296 /// Opens a stream for writing to the specified file.
297 /// This truncates the existing contents.
298 ///
299 /// # Usage
300 /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.
301 /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.
302 ///
303 /// After writing, call [`WritableStream::reflect`].
304 ///
305 /// # Note
306 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
307 /// If it is `false`, this behaves like [`AndroidFs::open_file_writable`].
308 /// If it is `true`, this behaves like [`AndroidFs::open_writable_stream_via_kotlin`].
309 ///
310 /// # Args
311 /// - ***uri*** :
312 /// Target file URI.
313 /// This need to be **writable**.
314 ///
315 /// # Support
316 /// All Android version.
317 #[maybe_async]
318 pub fn open_writable_stream(
319 &self,
320 uri: &FileUri
321 ) -> Result<WritableStream<R>> {
322
323 #[cfg(not(target_os = "android"))] {
324 Err(Error::NOT_ANDROID)
325 }
326 #[cfg(target_os = "android")] {
327 let impls = self.impls().create_writable_stream_auto(uri).await?;
328 Ok(WritableStream { impls })
329 }
330 }
331
332 /// Opens a writable stream to the specified file.
333 /// This truncates the existing contents.
334 ///
335 /// This function always writes content via the Kotlin API.
336 /// But this takes several times longer compared.
337 /// [`AndroidFs::open_writable_stream`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
338 ///
339 /// # Usage
340 /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.
341 /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.
342 ///
343 /// After writing, call [`WritableStream::reflect`].
344 ///
345 /// # Args
346 /// - ***uri*** :
347 /// Target file URI.
348 /// This need to be **writable**.
349 ///
350 /// # Support
351 /// All Android version.
352 #[maybe_async]
353 pub fn open_writable_stream_via_kotlin(
354 &self,
355 uri: &FileUri
356 ) -> Result<WritableStream<R>> {
357
358 #[cfg(not(target_os = "android"))] {
359 Err(Error::NOT_ANDROID)
360 }
361 #[cfg(target_os = "android")] {
362 let impls = self.impls().create_writable_stream_via_kotlin(uri).await?;
363 Ok(WritableStream { impls })
364 }
365 }
366
367 /// Reads the entire contents of a file into a bytes vector.
368 ///
369 /// # Args
370 /// - ***uri*** :
371 /// Target file URI.
372 /// Must be **readable**.
373 ///
374 /// # Support
375 /// All Android version.
376 #[maybe_async]
377 pub fn read(&self, uri: &FileUri) -> Result<Vec<u8>> {
378 #[cfg(not(target_os = "android"))] {
379 Err(Error::NOT_ANDROID)
380 }
381 #[cfg(target_os = "android")] {
382 self.impls().read_file(uri).await
383 }
384 }
385
386 /// Reads the entire contents of a file into a string.
387 ///
388 /// # Args
389 /// - ***uri*** :
390 /// Target file URI.
391 /// Must be **readable**.
392 ///
393 /// # Support
394 /// All Android version.
395 #[maybe_async]
396 pub fn read_to_string(&self, uri: &FileUri) -> Result<String> {
397 #[cfg(not(target_os = "android"))] {
398 Err(Error::NOT_ANDROID)
399 }
400 #[cfg(target_os = "android")] {
401 self.impls().read_file_to_string(uri).await
402 }
403 }
404
405 /// Writes a slice as the entire contents of a file.
406 /// This function will entirely replace its contents if it does exist.
407 ///
408 /// # Note
409 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
410 /// If it is `false`, this uses [`std::fs::File`].
411 /// If it is `true`, this uses [`AndroidFs::write_via_kotlin`].
412 ///
413 /// # Args
414 /// - ***uri*** :
415 /// Target file URI.
416 /// Must be **writable**.
417 ///
418 /// # Support
419 /// All Android version.
420 #[maybe_async]
421 pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> Result<()> {
422 #[cfg(not(target_os = "android"))] {
423 Err(Error::NOT_ANDROID)
424 }
425 #[cfg(target_os = "android")] {
426 self.impls().write_file_auto(uri, contents).await
427 }
428 }
429
430 /// Writes a slice as the entire contents of a file.
431 /// This function will entirely replace its contents if it does exist.
432 ///
433 /// This function always writes content via the Kotlin API.
434 /// But this takes several times longer compared.
435 /// [`AndroidFs::write`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
436 ///
437 /// # Support
438 /// All Android version.
439 #[maybe_async]
440 pub fn write_via_kotlin(
441 &self,
442 uri: &FileUri,
443 contents: impl AsRef<[u8]>
444 ) -> Result<()> {
445
446 #[cfg(not(target_os = "android"))] {
447 Err(Error::NOT_ANDROID)
448 }
449 #[cfg(target_os = "android")] {
450 self.impls().write_file_via_kotlin(uri, contents).await
451 }
452 }
453
454 /// Copies the contents of the source file to the destination.
455 /// If the destination already has contents, they are truncated before writing the source contents.
456 ///
457 /// # Note
458 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
459 /// If it is `false`, this uses [`std::io::copy`] with [`std::fs::File`].
460 /// If it is `true`, this uses [`AndroidFs::copy_via_kotlin`].
461 ///
462 /// # Args
463 /// - ***src*** :
464 /// The URI of source file.
465 /// Must be **readable**.
466 ///
467 /// - ***dest*** :
468 /// The URI of destination file.
469 /// Must be **writable**.
470 ///
471 /// # Support
472 /// All Android version.
473 #[maybe_async]
474 pub fn copy(&self, src: &FileUri, dest: &FileUri) -> Result<()> {
475 #[cfg(not(target_os = "android"))] {
476 Err(Error::NOT_ANDROID)
477 }
478 #[cfg(target_os = "android")] {
479 self.impls().copy_file(src, dest).await
480 }
481 }
482
483 /// Copies the contents of src file to dest.
484 /// If dest already has contents, it is truncated before write src contents.
485 ///
486 /// This function always writes content via the Kotlin API.
487 /// [`AndroidFs::copy`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
488 ///
489 /// # Args
490 /// - ***src*** :
491 /// The URI of source file.
492 /// Must be **readable**.
493 ///
494 /// - ***dest*** :
495 /// The URI of destination file.
496 /// Must be **writable**.
497 ///
498 /// - ***buffer_size***:
499 /// The size of the buffer, in bytes, to use during the copy process on Kotlin.
500 /// 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.
501 /// At least, when I checked, it was 8 KB.
502 /// If zero, this causes error.
503 ///
504 /// # Support
505 /// All Android version.
506 #[maybe_async]
507 pub fn copy_via_kotlin(
508 &self,
509 src: &FileUri,
510 dest: &FileUri,
511 buffer_size: Option<u32>,
512 ) -> Result<()> {
513
514 #[cfg(not(target_os = "android"))] {
515 Err(Error::NOT_ANDROID)
516 }
517 #[cfg(target_os = "android")] {
518 self.impls().copy_file_via_kotlin(src, dest, buffer_size).await
519 }
520 }
521
522 /// Determines whether the file must be written via the Kotlin API rather than through a file descriptor.
523 ///
524 /// In the case of a file that physically exists on the device, this will always return false.
525 /// This is intended for special cases, such as some cloud storage.
526 ///
527 /// # Support
528 /// All Android version.
529 #[maybe_async]
530 pub fn need_write_via_kotlin(&self, uri: &FileUri) -> Result<bool> {
531 #[cfg(not(target_os = "android"))] {
532 Err(Error::NOT_ANDROID)
533 }
534 #[cfg(target_os = "android")] {
535 self.impls().need_write_file_via_kotlin(uri).await
536 }
537 }
538
539 /// Renames a file or directory to a new name, and return new URI.
540 /// Even if the names conflict, the existing file will not be overwritten.
541 ///
542 /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
543 /// In other words, this function returns a new URI without any permissions.
544 /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
545 /// In this, this function returns the same URI as original URI.
546 ///
547 /// # Args
548 /// - ***uri*** :
549 /// URI of target entry.
550 ///
551 /// - ***new_name*** :
552 /// New name of target entry.
553 /// This include extension if use.
554 /// The behaviour in the same name already exists depends on the file provider.
555 /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.
556 /// In the case of files hosted by other applications, errors may occur.
557 /// But at least, the existing file will not be overwritten.
558 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
559 ///
560 /// # Support
561 /// All Android version.
562 #[maybe_async]
563 pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> Result<FileUri> {
564 #[cfg(not(target_os = "android"))] {
565 Err(Error::NOT_ANDROID)
566 }
567 #[cfg(target_os = "android")] {
568 self.impls().rename_entry(uri, new_name).await
569 }
570 }
571
572 /// Remove the file.
573 ///
574 /// # Args
575 /// - ***uri*** :
576 /// Target file URI.
577 /// Must be **read-writable**.
578 /// If not file, an error will occur.
579 ///
580 /// # Support
581 /// All Android version.
582 #[maybe_async]
583 pub fn remove_file(&self, uri: &FileUri) -> Result<()> {
584 #[cfg(not(target_os = "android"))] {
585 Err(Error::NOT_ANDROID)
586 }
587 #[cfg(target_os = "android")] {
588 self.impls().remove_file(uri).await
589 }
590 }
591
592 /// Remove the **empty** directory.
593 ///
594 /// # Args
595 /// - ***uri*** :
596 /// Target directory URI.
597 /// Must be **read-writable**.
598 /// If not empty directory, an error will occur.
599 ///
600 /// # Support
601 /// All Android version.
602 #[maybe_async]
603 pub fn remove_dir(&self, uri: &FileUri) -> Result<()> {
604 #[cfg(not(target_os = "android"))] {
605 Err(Error::NOT_ANDROID)
606 }
607 #[cfg(target_os = "android")] {
608 self.impls().remove_dir_if_empty(uri).await
609 }
610 }
611
612 /// Removes a directory and all its contents. Use carefully!
613 ///
614 /// # Args
615 /// - ***uri*** :
616 /// Target directory URI.
617 /// Must be **read-writable**.
618 /// If not directory, an error will occur.
619 ///
620 /// # Support
621 /// All Android version.
622 #[maybe_async]
623 pub fn remove_dir_all(&self, uri: &FileUri) -> Result<()> {
624 #[cfg(not(target_os = "android"))] {
625 Err(Error::NOT_ANDROID)
626 }
627 #[cfg(target_os = "android")] {
628 self.impls().remove_dir_all(uri).await
629 }
630 }
631
632 /// Build a URI of an **existing** file located at the relative path from the specified directory.
633 /// Error occurs, if the file does not exist.
634 ///
635 /// The permissions and validity period of the returned URI depend on the origin directory
636 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
637 ///
638 /// # Note
639 /// 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.
640 /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
641 /// So consider using [`AndroidFs::create_new_file_and_return_relative_path`].
642 ///
643 /// # Args
644 /// - ***uri*** :
645 /// Base directory URI.
646 /// Must be **readable**.
647 ///
648 /// - ***relative_path*** :
649 /// Relative path from base directory.
650 ///
651 /// # Support
652 /// All Android version.
653 #[maybe_async]
654 pub fn resolve_file_uri(
655 &self,
656 dir: &FileUri,
657 relative_path: impl AsRef<std::path::Path>
658 ) -> Result<FileUri> {
659
660 #[cfg(not(target_os = "android"))] {
661 Err(Error::NOT_ANDROID)
662 }
663 #[cfg(target_os = "android")] {
664 self.impls().resolve_file_uri(dir, relative_path).await
665 }
666 }
667
668 /// Build a URI of an **existing** directory located at the relative path from the specified directory.
669 /// Error occurs, if the directory does not exist.
670 ///
671 /// The permissions and validity period of the returned URI depend on the origin directory
672 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
673 ///
674 /// # Note
675 /// For [`AndroidFs::create_dir_all`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
676 /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
677 /// So consider using [`AndroidFs::create_dir_all_and_return_relative_path`].
678 ///
679 /// # Args
680 /// - ***uri*** :
681 /// Base directory URI.
682 /// Must be **readable**.
683 ///
684 /// - ***relative_path*** :
685 /// Relative path from base directory.
686 ///
687 /// # Support
688 /// All Android version.
689 #[maybe_async]
690 pub fn resolve_dir_uri(
691 &self,
692 dir: &FileUri,
693 relative_path: impl AsRef<std::path::Path>
694 ) -> Result<FileUri> {
695
696 #[cfg(not(target_os = "android"))] {
697 Err(Error::NOT_ANDROID)
698 }
699 #[cfg(target_os = "android")] {
700 self.impls().resolve_dir_uri(dir, relative_path).await
701 }
702 }
703
704 /// See [`AndroidFs::get_thumbnail`] for descriptions.
705 ///
706 /// If thumbnail does not wrote to dest, return false.
707 #[maybe_async]
708 pub fn get_thumbnail_to(
709 &self,
710 src: &FileUri,
711 dest: &FileUri,
712 preferred_size: Size,
713 format: ImageFormat,
714 ) -> Result<bool> {
715
716 #[cfg(not(target_os = "android"))] {
717 Err(Error::NOT_ANDROID)
718 }
719 #[cfg(target_os = "android")] {
720 self.impls().get_file_thumbnail_to_file(src, dest, preferred_size, format).await
721 }
722 }
723
724 /// Get a file thumbnail.
725 /// If thumbnail does not exist it, return None.
726 ///
727 /// Note this does not cache. Please do it in your part if need.
728 ///
729 /// # Args
730 /// - ***uri*** :
731 /// Targe file uri.
732 /// Thumbnail availablty depends on the file provider.
733 /// In general, images and videos are available.
734 /// For file URIs via [`FileUri::from_path`],
735 /// the file type must match the filename extension.
736 /// In this case, the type is determined by the extension and generate thumbnails.
737 /// Otherwise, thumbnails are provided through MediaStore, file provider, and etc.
738 ///
739 /// - ***preferred_size*** :
740 /// Optimal thumbnail size desired.
741 /// This may return a thumbnail of a different size,
742 /// but never more than about double the requested size.
743 /// In any case, the aspect ratio is maintained.
744 ///
745 /// - ***format*** :
746 /// Thumbnail image format.
747 /// If you’re not sure which one to use, [`ImageFormat::Jpeg`] is recommended.
748 /// If you need transparency, use others.
749 ///
750 /// # Support
751 /// All Android version.
752 #[maybe_async]
753 pub fn get_thumbnail(
754 &self,
755 uri: &FileUri,
756 preferred_size: Size,
757 format: ImageFormat,
758 ) -> Result<Option<Vec<u8>>> {
759
760 #[cfg(not(target_os = "android"))] {
761 Err(Error::NOT_ANDROID)
762 }
763 #[cfg(target_os = "android")] {
764 self.impls().get_file_thumbnail(uri, preferred_size, format).await
765 }
766 }
767
768 /// Get a file thumbnail that encoded to base64 string.
769 /// If thumbnail does not exist it, return None.
770 ///
771 /// Note this does not cache. Please do it in your part if need.
772 ///
773 /// # Inner
774 /// This uses Kotlin's [`android.util.Base64.encodeToString(.., android.util.Base64.NO_WRAP)`](https://developer.android.com/reference/android/util/Base64#encodeToString(byte[],%20int)) internally.
775 /// It is the same as [`base64::engine::general_purpose::STANDARD`](https://docs.rs/base64/0.22.1/base64/engine/general_purpose/constant.STANDARD.html) in `base64` crate.
776 ///
777 /// # Args
778 /// - ***uri*** :
779 /// Targe file uri.
780 /// Thumbnail availablty depends on the file provider.
781 /// In general, images and videos are available.
782 /// For file URIs via [`FileUri::from_path`],
783 /// the file type must match the filename extension.
784 /// In this case, the type is determined by the extension and generate thumbnails.
785 /// Otherwise, thumbnails are provided through MediaStore, file provider, and etc.
786 ///
787 /// - ***preferred_size*** :
788 /// Optimal thumbnail size desired.
789 /// This may return a thumbnail of a different size,
790 /// but never more than about double the requested size.
791 /// In any case, the aspect ratio is maintained.
792 ///
793 /// - ***format*** :
794 /// Thumbnail image format.
795 /// If you’re not sure which one to use, [`ImageFormat::Jpeg`] is recommended.
796 /// If you need transparency, use others.
797 ///
798 /// # Support
799 /// All Android version.
800 #[maybe_async]
801 pub fn get_thumbnail_base64(
802 &self,
803 uri: &FileUri,
804 preferred_size: Size,
805 format: ImageFormat,
806 ) -> Result<Option<String>> {
807
808 #[cfg(not(target_os = "android"))] {
809 Err(Error::NOT_ANDROID)
810 }
811 #[cfg(target_os = "android")] {
812 self.impls().get_file_thumbnail_base64(uri, preferred_size, format).await
813 }
814 }
815
816 /// Creates a new empty file in the specified location and returns a URI.
817 ///
818 /// The permissions and validity period of the returned URIs depend on the origin directory
819 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
820 ///
821 /// # Args
822 /// - ***dir*** :
823 /// The URI of the base directory.
824 /// Must be **read-write**.
825 ///
826 /// - ***relative_path*** :
827 /// The file path relative to the base directory.
828 /// Any missing parent directories will be created automatically.
829 /// If a file with the same name already exists, a sequential number may be appended to ensure uniqueness.
830 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
831 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
832 /// Note those operation may vary depending on the file provider.
833 ///
834 /// - ***mime_type*** :
835 /// The MIME type of the file to be created.
836 /// If this is None, MIME type is inferred from the extension of ***relative_path***
837 /// and if that fails, `application/octet-stream` is used.
838 ///
839 /// # Support
840 /// All Android version.
841 #[maybe_async]
842 pub fn create_new_file(
843 &self,
844 dir: &FileUri,
845 relative_path: impl AsRef<std::path::Path>,
846 mime_type: Option<&str>
847 ) -> Result<FileUri> {
848
849 #[cfg(not(target_os = "android"))] {
850 Err(Error::NOT_ANDROID)
851 }
852 #[cfg(target_os = "android")] {
853 self.impls().create_new_file(dir, relative_path, mime_type).await
854 }
855 }
856
857 /// Creates a new empty file in the specified location and returns a URI and relative path.
858 ///
859 /// The returned relative path may be sanitized and have a suffix appended to the file name,
860 /// so it may differ from the input relative path.
861 /// And it is a logical path within the file provider and
862 /// available for [`AndroidFs::resolve_file_uri`].
863 ///
864 /// The permissions and validity period of the returned URIs depend on the origin directory
865 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
866 ///
867 /// # Args
868 /// - ***dir*** :
869 /// The URI of the base directory.
870 /// Must be **read-write**.
871 ///
872 /// - ***relative_path*** :
873 /// The file path relative to the base directory.
874 /// Any missing parent directories will be created automatically.
875 /// If a file with the same name already exists, a sequential number may be appended to ensure uniqueness.
876 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
877 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
878 /// Note those operation may vary depending on the file provider.
879 ///
880 /// - ***mime_type*** :
881 /// The MIME type of the file to be created.
882 /// If this is None, MIME type is inferred from the extension of ***relative_path***
883 /// and if that fails, `application/octet-stream` is used.
884 ///
885 /// # Support
886 /// All Android version.
887 #[maybe_async]
888 pub fn create_new_file_and_return_relative_path(
889 &self,
890 dir: &FileUri,
891 relative_path: impl AsRef<std::path::Path>,
892 mime_type: Option<&str>
893 ) -> Result<(FileUri, std::path::PathBuf)> {
894
895 #[cfg(not(target_os = "android"))] {
896 Err(Error::NOT_ANDROID)
897 }
898 #[cfg(target_os = "android")] {
899 self.impls().create_new_file_and_retrun_relative_path(dir, relative_path, mime_type).await
900 }
901 }
902
903 /// Recursively create a directory and all of its parent components if they are missing,
904 /// then return the URI.
905 /// If it already exists, do nothing and just return the direcotry uri.
906 ///
907 /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
908 ///
909 /// # Args
910 /// - ***dir*** :
911 /// The URI of the base directory.
912 /// Must be **read-write**.
913 ///
914 /// - ***relative_path*** :
915 /// The directory path relative to the base directory.
916 /// Any missing parent directories will be created automatically.
917 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
918 /// Note this sanitization may vary depending on the file provider.
919 ///
920 /// # Support
921 /// All Android version.
922 #[maybe_async]
923 pub fn create_dir_all(
924 &self,
925 dir: &FileUri,
926 relative_path: impl AsRef<std::path::Path>,
927 ) -> Result<FileUri> {
928
929 #[cfg(not(target_os = "android"))] {
930 Err(Error::NOT_ANDROID)
931 }
932 #[cfg(target_os = "android")] {
933 self.impls().create_dir_all(dir, relative_path).await
934 }
935 }
936
937 /// Recursively create a directory and all of its parent components if they are missing,
938 /// then return the URI and relative path.
939 ///
940 /// The returned relative path may be sanitized,
941 /// so it may differ from the input relative path.
942 /// And it is a logical path within the file provider and
943 /// available for [`AndroidFs::resolve_dir_uri`].
944 ///
945 /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
946 ///
947 /// # Args
948 /// - ***dir*** :
949 /// The URI of the base directory.
950 /// Must be **read-write**.
951 ///
952 /// - ***relative_path*** :
953 /// The directory path relative to the base directory.
954 /// Any missing parent directories will be created automatically.
955 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
956 /// Note this sanitization may vary depending on the file provider.
957 ///
958 /// # Support
959 /// All Android version.
960 #[maybe_async]
961 pub fn create_dir_all_and_return_relative_path(
962 &self,
963 dir: &FileUri,
964 relative_path: impl AsRef<std::path::Path>,
965 ) -> Result<(FileUri, std::path::PathBuf)> {
966
967 #[cfg(not(target_os = "android"))] {
968 Err(Error::NOT_ANDROID)
969 }
970 #[cfg(target_os = "android")] {
971 self.impls().create_dir_all_and_return_relative_path(dir, relative_path).await
972 }
973 }
974
975 /// Returns the child files and directories of the specified directory.
976 /// The order of the entries is not guaranteed.
977 ///
978 /// The permissions and validity period of the returned URIs depend on the origin directory
979 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
980 ///
981 /// This retrieves all metadata including `uri`, `name`, `last_modified`, `len`, and `mime_type`.
982 /// If only specific information is needed,
983 /// using [`AndroidFs::read_dir_with_options`] will improve performance.
984 ///
985 /// # Note
986 /// The returned type is an iterator, but the file system call is not executed lazily.
987 /// Instead, all data is retrieved at once.
988 /// For directories containing thousands or even tens of thousands of entries,
989 /// this function may take several seconds to complete.
990 /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
991 ///
992 /// # Args
993 /// - ***uri*** :
994 /// Target directory URI.
995 /// Must be **readable**.
996 ///
997 /// # Support
998 /// All Android version.
999 #[maybe_async]
1000 pub fn read_dir(&self, uri: &FileUri) -> Result<impl Iterator<Item = Entry>> {
1001 let entries = self.read_dir_with_options(uri, EntryOptions::ALL).await?
1002 .map(Entry::try_from)
1003 .filter_map(Result::ok);
1004
1005 Ok(entries)
1006 }
1007
1008 /// Returns the child files and directories of the specified directory.
1009 /// The order of the entries is not guaranteed.
1010 ///
1011 /// The permissions and validity period of the returned URIs depend on the origin directory
1012 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
1013 ///
1014 /// # Note
1015 /// The returned type is an iterator, but the file system call is not executed lazily.
1016 /// Instead, all data is retrieved at once.
1017 /// For directories containing thousands or even tens of thousands of entries,
1018 /// this function may take several seconds to complete.
1019 /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
1020 ///
1021 /// # Args
1022 /// - ***uri*** :
1023 /// Target directory URI.
1024 /// Must be **readable**.
1025 ///
1026 /// # Support
1027 /// All Android version.
1028 #[maybe_async]
1029 pub fn read_dir_with_options(
1030 &self,
1031 uri: &FileUri,
1032 options: EntryOptions
1033 ) -> Result<impl Iterator<Item = OptionalEntry>> {
1034
1035 #[cfg(not(target_os = "android"))] {
1036 Err::<std::iter::Empty<_>, _>(Error::NOT_ANDROID)
1037 }
1038 #[cfg(target_os = "android")] {
1039 self.impls().read_dir_with_options(uri, options).await
1040 }
1041 }
1042
1043 /// Take persistent permission to access the file, directory and its descendants.
1044 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
1045 ///
1046 /// This works by just calling, without displaying any confirmation to the user.
1047 ///
1048 /// 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)
1049 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
1050 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1051 /// So check by [`AndroidFs::check_persisted_uri_permission`].
1052 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1053 ///
1054 /// # Args
1055 /// - **uri** :
1056 /// URI of the target file or directory.
1057 /// This must be a URI taken from following :
1058 /// - [`FilePicker::pick_files`]
1059 /// - [`FilePicker::pick_file`]
1060 /// - [`FilePicker::pick_visual_medias`]
1061 /// - [`FilePicker::pick_visual_media`]
1062 /// - [`FilePicker::pick_dir`]
1063 /// - [`FilePicker::save_file`]
1064 /// - [`AndroidFs::resolve_file_uri`], [`AndroidFs::resolve_dir_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :
1065 /// If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function.
1066 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
1067 ///
1068 /// # Support
1069 /// All Android version.
1070 #[maybe_async]
1071 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> Result<()> {
1072 #[cfg(not(target_os = "android"))] {
1073 Err(Error::NOT_ANDROID)
1074 }
1075 #[cfg(target_os = "android")] {
1076 self.impls().take_persistable_uri_permission(uri).await
1077 }
1078 }
1079
1080 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1081 /// Returns false if there are only non-persistent permissions or no permissions.
1082 ///
1083 /// # Args
1084 /// - **uri** :
1085 /// URI of the target file or directory.
1086 /// This must be a URI taken from following :
1087 /// - [`FilePicker::pick_files`]
1088 /// - [`FilePicker::pick_file`]
1089 /// - [`FilePicker::pick_visual_medias`]
1090 /// - [`FilePicker::pick_visual_media`]
1091 /// - [`FilePicker::pick_dir`]
1092 /// - [`FilePicker::save_file`]
1093 /// - [`AndroidFs::resolve_file_uri`], [`AndroidFs::resolve_dir_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :
1094 /// If use URI from thoese fucntions, the permissions of the origin directory URI is checked, not a entry iteself by this function.
1095 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
1096 ///
1097 /// - **mode** :
1098 /// The mode of permission you want to check.
1099 ///
1100 /// # Support
1101 /// All Android version.
1102 #[maybe_async]
1103 pub fn check_persisted_uri_permission(
1104 &self,
1105 uri: &FileUri,
1106 mode: PersistableAccessMode
1107 ) -> Result<bool> {
1108
1109 #[cfg(not(target_os = "android"))] {
1110 Err(Error::NOT_ANDROID)
1111 }
1112 #[cfg(target_os = "android")] {
1113 self.impls().check_persisted_uri_permission(uri, mode).await
1114 }
1115 }
1116
1117 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
1118 ///
1119 /// # Support
1120 /// All Android version.
1121 #[maybe_async]
1122 pub fn get_all_persisted_uri_permissions(&self) -> Result<impl Iterator<Item = PersistedUriPermission>> {
1123 #[cfg(not(target_os = "android"))] {
1124 Err::<std::iter::Empty<_>, _>(Error::NOT_ANDROID)
1125 }
1126 #[cfg(target_os = "android")] {
1127 self.impls().get_all_persisted_uri_permissions().await
1128 }
1129 }
1130
1131 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1132 ///
1133 /// # Args
1134 /// - ***uri*** :
1135 /// URI of the target file or directory.
1136 ///
1137 /// # Support
1138 /// All Android version.
1139 #[maybe_async]
1140 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> Result<()> {
1141 #[cfg(not(target_os = "android"))] {
1142 Err(Error::NOT_ANDROID)
1143 }
1144 #[cfg(target_os = "android")] {
1145 self.impls().release_persisted_uri_permission(uri).await
1146 }
1147 }
1148
1149 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1150 ///
1151 /// # Support
1152 /// All Android version.
1153 #[maybe_async]
1154 pub fn release_all_persisted_uri_permissions(&self) -> Result<()> {
1155 #[cfg(not(target_os = "android"))] {
1156 Err(Error::NOT_ANDROID)
1157 }
1158 #[cfg(target_os = "android")] {
1159 self.impls().release_all_persisted_uri_permissions().await
1160 }
1161 }
1162
1163 /// See [`AppStorage::get_volumes`] or [`PublicStorage::get_volumes`] for details.
1164 ///
1165 /// The difference is that this does not perform any filtering.
1166 /// You can it by [`StorageVolume { is_available_for_app_storage, is_available_for_public_storage, .. } `](StorageVolume).
1167 #[maybe_async]
1168 pub fn get_volumes(&self) -> Result<Vec<StorageVolume>> {
1169 #[cfg(not(target_os = "android"))] {
1170 Err(Error::NOT_ANDROID)
1171 }
1172 #[cfg(target_os = "android")] {
1173 self.impls().get_available_storage_volumes().await
1174 }
1175 }
1176
1177 /// See [`AppStorage::get_primary_volume`] or [`PublicStorage::get_primary_volume`] for details.
1178 ///
1179 /// The difference is that this does not perform any filtering.
1180 /// You can it by [`StorageVolume { is_available_for_app_storage, is_available_for_public_storage, .. } `](StorageVolume).
1181 #[maybe_async]
1182 pub fn get_primary_volume(&self) -> Result<Option<StorageVolume>> {
1183 #[cfg(not(target_os = "android"))] {
1184 Err(Error::NOT_ANDROID)
1185 }
1186 #[cfg(target_os = "android")] {
1187 self.impls().get_primary_storage_volume_if_available().await
1188 }
1189 }
1190
1191 /// Builds the storage volume root URI.
1192 ///
1193 /// This should only be used as `initial_location` in the file picker, such as [`FilePicker::pick_files`].
1194 /// It must not be used for any other purpose.
1195 ///
1196 /// This is useful when selecting save location,
1197 /// but when selecting existing entries, `initial_location` is often better with None.
1198 ///
1199 /// # Args
1200 /// - ***volume_id*** :
1201 /// ID of the storage volume, such as internal storage, SD card, etc.
1202 /// If `None` is provided, [`the primary storage volume`](AndroidFs::get_primary_volume) will be used.
1203 ///
1204 /// # Support
1205 /// All Android version.
1206 #[maybe_async]
1207 pub fn resolve_root_initial_location(&self, volume_id: Option<&StorageVolumeId>) -> Result<FileUri> {
1208 #[cfg(not(target_os = "android"))] {
1209 Err(Error::NOT_ANDROID)
1210 }
1211 #[cfg(target_os = "android")] {
1212 self.impls().resolve_root_initial_location(volume_id).await
1213 }
1214 }
1215
1216 /// Verify whether this plugin is available.
1217 ///
1218 /// On Android, this returns true.
1219 /// On other platforms, this returns false.
1220 #[always_sync]
1221 pub fn is_available(&self) -> bool {
1222 cfg!(target_os = "android")
1223 }
1224
1225 /// Get the api level of this Android device.
1226 ///
1227 /// The correspondence table between API levels and Android versions can be found following.
1228 /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1229 ///
1230 /// If you want the constant value of the API level from an Android version, there is the [`api_level`] module.
1231 ///
1232 /// # Table
1233 /// | Android version | API Level |
1234 /// |------------------|-----------|
1235 /// | 16.0 | 36 |
1236 /// | 15.0 | 35 |
1237 /// | 14.0 | 34 |
1238 /// | 13.0 | 33 |
1239 /// | 12L | 32 |
1240 /// | 12.0 | 31 |
1241 /// | 11.0 | 30 |
1242 /// | 10.0 | 29 |
1243 /// | 9.0 | 28 |
1244 /// | 8.1 | 27 |
1245 /// | 8.0 | 26 |
1246 /// | 7.1 - 7.1.2 | 25 |
1247 /// | 7.0 | 24 |
1248 ///
1249 /// Tauri does not support Android versions below 7.
1250 #[always_sync]
1251 pub fn api_level(&self) -> Result<i32> {
1252 #[cfg(not(target_os = "android"))] {
1253 Err(Error::NOT_ANDROID)
1254 }
1255 #[cfg(target_os = "android")] {
1256 self.impls().api_level()
1257 }
1258 }
1259}