tauri_plugin_android_fs/api/android_fs.rs
1use std::io::{Read as _, Write as _};
2use crate::*;
3
4
5/// ***Root API***
6///
7/// # Examples
8/// ```
9/// fn example(app: &tauri::AppHandle) {
10/// use tauri_plugin_android_fs::AndroidFsExt as _;
11///
12/// let api = app.android_fs();
13/// }
14/// ```
15pub struct AndroidFs<R: tauri::Runtime> {
16 #[cfg(target_os = "android")]
17 pub(crate) app: tauri::AppHandle<R>,
18
19 #[cfg(target_os = "android")]
20 pub(crate) api: tauri::plugin::PluginHandle<R>,
21
22 #[cfg(target_os = "android")]
23 pub(crate) intent_lock: std::sync::Mutex<()>,
24
25 #[cfg(not(target_os = "android"))]
26 _marker: std::marker::PhantomData<fn() -> R>
27}
28
29impl<R: tauri::Runtime> AndroidFs<R> {
30
31 pub(crate) fn new<C: serde::de::DeserializeOwned>(
32 app: tauri::AppHandle<R>,
33 api: tauri::plugin::PluginApi<R, C>,
34 ) -> crate::Result<Self> {
35
36 #[cfg(target_os = "android")] {
37 Ok(Self {
38 api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?,
39 app,
40 intent_lock: std::sync::Mutex::new(())
41 })
42 }
43
44 #[cfg(not(target_os = "android"))] {
45 Ok(Self { _marker: Default::default() })
46 }
47 }
48}
49
50impl<R: tauri::Runtime> AndroidFs<R> {
51
52 /// Verify whether this plugin is available.
53 ///
54 /// On Android, this returns true.
55 /// On other platforms, this returns false.
56 pub fn is_available(&self) -> bool {
57 cfg!(target_os = "android")
58 }
59
60 /// API of file storage intended for the app's use only.
61 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
62 PrivateStorage(self)
63 }
64
65 /// API of file storage that is available to other applications and users.
66 pub fn public_storage(&self) -> PublicStorage<'_, R> {
67 PublicStorage(self)
68 }
69
70 /// API of file/dir picker.
71 pub fn file_picker(&self) -> FilePicker<'_, R> {
72 FilePicker(self)
73 }
74
75 /// API of sharing files with other apps.
76 pub fn file_sender(&self) -> FileSender<'_, R> {
77 FileSender(self)
78 }
79
80 /// Get the file or directory name.
81 ///
82 /// # Args
83 /// - ***uri*** :
84 /// Target URI.
85 /// This needs to be **readable**.
86 ///
87 /// # Support
88 /// All.
89 pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
90 on_android!({
91 impl_se!(struct Req<'a> { uri: &'a FileUri });
92 impl_de!(struct Res { name: String });
93
94 self.api
95 .run_mobile_plugin::<Res>("getName", Req { uri })
96 .map(|v| v.name)
97 .map_err(Into::into)
98 })
99 }
100
101 /// Query the provider to get mime type.
102 /// If the directory, this returns `None`.
103 /// If the file, this returns no `None`.
104 /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.
105 ///
106 /// In the case of files in [`PrivateStorage`], this is determined from the extension.
107 ///
108 /// # Args
109 /// - ***uri*** :
110 /// Target URI.
111 /// This needs to be **readable**.
112 ///
113 /// # Support
114 /// All.
115 pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>> {
116 on_android!({
117 impl_se!(struct Req<'a> { uri: &'a FileUri });
118 impl_de!(struct Res { value: Option<String> });
119
120 self.api
121 .run_mobile_plugin::<Res>("getMimeType", Req { uri })
122 .map(|v| v.value)
123 .map_err(Into::into)
124 })
125 }
126
127 /// Queries the file system to get information about a file, directory.
128 ///
129 /// # Args
130 /// - ***uri*** :
131 /// Target URI.
132 /// This needs to be **readable**.
133 ///
134 /// # Note
135 /// This uses [`AndroidFs::open_file`] internally.
136 ///
137 /// # Support
138 /// All.
139 pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
140 on_android!({
141 let file = self.open_file(uri, FileAccessMode::Read)?;
142 Ok(file.metadata()?)
143 })
144 }
145
146 /// Open a file in the specified mode.
147 ///
148 /// # Args
149 /// - ***uri*** :
150 /// Target file URI.
151 /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
152 ///
153 /// - ***mode*** :
154 /// Indicates how the file is opened and the permissions granted.
155 /// Note that files hosted by third-party apps may not support following:
156 /// - [`FileAccessMode::ReadWrite`]
157 /// - [`FileAccessMode::ReadWriteTruncate`]
158 /// - [`FileAccessMode::WriteAppend`]
159 ///
160 /// # Note
161 /// For files that do not physically exist on the device, such as those stored in cloud storage,
162 /// the system first downloads the data to a temporary file before opening it.
163 /// As a result, this function may take some time to complete.
164 /// And write operations on such files may not always be applied correctly.
165 /// To ensure reliable writes, use [`AndroidFs::write_via_kotlin`] directly,
166 /// or [`AndroidFs::write`], which automatically falls back to this method when necessary.
167 /// If you need to write data via a stream rather than the entire file,
168 /// it is recommended to use [`AndroidFs::write_via_kotlin_in`] or, via a temporary file, [`AndroidFs::copy_via_kotlin`].
169 /// This issue does not occur on all cloud storage platforms.
170 /// For example, files on Google Drive may have problems,
171 /// whereas files on Dropbox can be written correctly using this function, at least on my device.
172 /// If you encounter issues with files on app other than Google Drive, please let me know on [GitHub](https://github.com/aiueo13/tauri-plugin-android-fs/issues/new).
173 /// This information is used by [`AndroidFs::need_write_via_kotlin`], which is utilized by [`AndroidFs::write`].
174 ///
175 /// # Support
176 /// All.
177 pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
178 on_android!({
179 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
180 impl_de!(struct Res { fd: std::os::fd::RawFd });
181
182 let mode = match mode {
183 FileAccessMode::Read => "r",
184 FileAccessMode::Write => "w",
185 FileAccessMode::WriteTruncate => "wt",
186 FileAccessMode::WriteAppend => "wa",
187 FileAccessMode::ReadWriteTruncate => "rwt",
188 FileAccessMode::ReadWrite => "rw",
189 };
190
191 self.api
192 .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
193 .map(|v| {
194 use std::os::fd::FromRawFd;
195 unsafe { std::fs::File::from_raw_fd(v.fd) }
196 })
197 .map_err(Into::into)
198 })
199 }
200
201 /// Reads the entire contents of a file into a bytes vector.
202 ///
203 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
204 ///
205 /// # Args
206 /// - ***uri*** :
207 /// Target file URI.
208 /// This needs to be **readable**.
209 ///
210 /// # Support
211 /// All.
212 pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
213 on_android!({
214 let mut file = self.open_file(uri, FileAccessMode::Read)?;
215 let mut buf = file.metadata().ok()
216 .map(|m| m.len() as usize)
217 .map(Vec::with_capacity)
218 .unwrap_or_else(Vec::new);
219
220 file.read_to_end(&mut buf)?;
221 Ok(buf)
222 })
223 }
224
225 /// Reads the entire contents of a file into a string.
226 ///
227 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
228 ///
229 /// # Args
230 /// - ***uri*** :
231 /// Target file URI.
232 /// This needs to be **readable**.
233 ///
234 /// # Support
235 /// All.
236 pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
237 on_android!({
238 let mut file = self.open_file(uri, FileAccessMode::Read)?;
239 let mut buf = file.metadata().ok()
240 .map(|m| m.len() as usize)
241 .map(String::with_capacity)
242 .unwrap_or_else(String::new);
243
244 file.read_to_string(&mut buf)?;
245 Ok(buf)
246 })
247 }
248
249 /// Writes a slice as the entire contents of a file.
250 /// This function will entirely replace its contents if it does exist.
251 ///
252 /// If you want to operate the file, use [`AndroidFs::open_file`] instead.
253 ///
254 /// # Args
255 /// - ***uri*** :
256 /// Target file URI.
257 /// This needs to be **writable**.
258 ///
259 /// # Support
260 /// All.
261 pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
262 on_android!({
263 if self.need_write_via_kotlin(uri)? {
264 self.write_via_kotlin(uri, contents)?;
265 }
266 else {
267 let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
268 file.write_all(contents.as_ref())?;
269 }
270 Ok(())
271 })
272 }
273
274 /// Writes a slice as the entire contents of a file.
275 /// This function will entirely replace its contents if it does exist.
276 ///
277 /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.
278 /// See [`AndroidFs::open_file`] for why this function exists.
279 ///
280 /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`],
281 /// so there should be few opportunities to use this.
282 ///
283 /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
284 ///
285 /// # Inner process
286 /// The contents is written to a temporary file by Rust side
287 /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].
288 ///
289 /// # Support
290 /// All.
291 pub fn write_via_kotlin(
292 &self,
293 uri: &FileUri,
294 contents: impl AsRef<[u8]>
295 ) -> crate::Result<()> {
296
297 on_android!({
298 self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
299 })
300 }
301
302 /// See [`AndroidFs::write_via_kotlin`] for information.
303 /// Use this if you want to write using `std::fs::File`, not entire contents.
304 ///
305 /// If you want to retain the file outside the closure,
306 /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`].
307 /// For details, please refer to the internal implementation of this function.
308 ///
309 /// # Args
310 /// - ***uri*** :
311 /// Target file URI to write.
312 ///
313 /// - **contetns_writer** :
314 /// A closure that accepts a mutable reference to a `std::fs::File`
315 /// and performs the actual write operations. Note that this represents a temporary file.
316 pub fn write_via_kotlin_in<T>(
317 &self,
318 uri: &FileUri,
319 contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
320 ) -> crate::Result<T> {
321
322 on_android!({
323 let tmp_file_path = {
324 use std::sync::atomic::{AtomicUsize, Ordering};
325
326 static COUNTER: AtomicUsize = AtomicUsize::new(0);
327 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
328
329 self.private_storage().resolve_path_with(
330 PrivateDir::Cache,
331 format!("{TMP_DIR_RELATIVE_PATH}/write_via_kotlin_in {id}")
332 )?
333 };
334
335 if let Some(parent) = tmp_file_path.parent() {
336 let _ = std::fs::create_dir_all(parent);
337 }
338
339 let result = {
340 let ref mut file = std::fs::File::create(&tmp_file_path)?;
341 contents_writer(file)
342 };
343
344 let result = result
345 .map_err(crate::Error::from)
346 .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
347
348 let _ = std::fs::remove_file(&tmp_file_path);
349
350 result
351 })
352 }
353
354 /// Determines if the file needs to be written via Kotlin side instead of Rust side.
355 /// Currently, this returns true only if the file is on GoogleDrive.
356 ///
357 /// # Support
358 /// All.
359 pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
360 on_android!({
361 Ok(uri.uri.starts_with("content://com.google.android.apps.docs"))
362 })
363 }
364
365 /// Copies the contents of src file to dest.
366 /// If dest already has contents, it is truncated before write src contents.
367 ///
368 /// This copy process is done on Kotlin side, not on Rust.
369 /// Large files in GB units are also supported.
370 /// Note that [`AndroidFs::copy`] and [`std::io::copy`] are faster.
371 ///
372 /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
373 ///
374 /// # Args
375 /// - ***src*** :
376 /// The URI of source file.
377 /// This needs to be **readable**.
378 ///
379 /// - ***dest*** :
380 /// The URI of destination file.
381 /// This needs to be **writable**.
382 ///
383 /// # Support
384 /// All.
385 pub fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
386 on_android!({
387 impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri });
388 impl_de!(struct Res;);
389
390 self.api
391 .run_mobile_plugin::<Res>("copyFile", Req { src, dest })
392 .map(|_| ())
393 .map_err(Into::into)
394 })
395 }
396
397 /// Copies the contents of src file to dest.
398 /// If dest already has contents, it is truncated before write src contents.
399 ///
400 /// # Args
401 /// - ***src*** :
402 /// The URI of source file.
403 /// This needs to be **readable**.
404 ///
405 /// - ***dest*** :
406 /// The URI of destination file.
407 /// This needs to be **writable**.
408 ///
409 /// # Support
410 /// All.
411 pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
412 on_android!({
413 let src = &mut self.open_file(src, FileAccessMode::Read)?;
414 let dest = &mut self.open_file(dest, FileAccessMode::WriteTruncate)?;
415 std::io::copy(src, dest)?;
416 Ok(())
417 })
418 }
419
420 /// Renames a file or directory to a new name, and return new URI.
421 /// Even if the names conflict, the existing file will not be overwritten.
422 ///
423 /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
424 /// In other words, this function returns a new URI without any permissions.
425 /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
426 /// In this, this function returns the same URI as original URI.
427 ///
428 /// # Args
429 /// - ***uri*** :
430 /// URI of target entry.
431 ///
432 /// - ***new_name*** :
433 /// New name of target entry.
434 /// This include extension if use.
435 /// The behaviour in the same name already exists depends on the file provider.
436 /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.
437 /// In the case of files hosted by other applications, errors may occur.
438 /// But at least, the existing file will not be overwritten.
439 ///
440 /// # Support
441 /// All.
442 pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
443 on_android!({
444 impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
445
446 let new_name = new_name.as_ref();
447
448 self.api
449 .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
450 .map_err(Into::into)
451 })
452 }
453
454 /// Remove the file.
455 ///
456 /// # Args
457 /// - ***uri*** :
458 /// Target file URI.
459 /// This needs to be **writable**, at least. But even if it is,
460 /// removing may not be possible in some cases.
461 /// For details, refer to the documentation of the function that provided the URI.
462 /// If not file, an error will occur.
463 ///
464 /// # Support
465 /// All.
466 pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
467 on_android!({
468 impl_se!(struct Req<'a> { uri: &'a FileUri });
469 impl_de!(struct Res;);
470
471 self.api
472 .run_mobile_plugin::<Res>("deleteFile", Req { uri })
473 .map(|_| ())
474 .map_err(Into::into)
475 })
476 }
477
478 /// Remove the **empty** directory.
479 ///
480 /// # Args
481 /// - ***uri*** :
482 /// Target directory URI.
483 /// This needs to be **writable**.
484 /// If not empty directory, an error will occur.
485 ///
486 /// # Support
487 /// All.
488 pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
489 on_android!({
490 impl_se!(struct Req<'a> { uri: &'a FileUri });
491 impl_de!(struct Res;);
492
493 self.api
494 .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
495 .map(|_| ())
496 .map_err(Into::into)
497 })
498 }
499
500 /// Removes a directory and all its contents. Use carefully!
501 ///
502 /// # Args
503 /// - ***uri*** :
504 /// Target directory URI.
505 /// This needs to be **writable**.
506 /// If not directory, an error will occur.
507 ///
508 /// # Support
509 /// All.
510 pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
511 on_android!({
512 impl_se!(struct Req<'a> { uri: &'a FileUri });
513 impl_de!(struct Res;);
514
515 self.api
516 .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
517 .map(|_| ())
518 .map_err(Into::into)
519 })
520 }
521
522 /// Build a URI of an **existing** file located at the relative path from the specified directory.
523 /// Error occurs, if the file does not exist.
524 ///
525 /// The permissions and validity period of the returned URI depend on the origin directory
526 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
527 ///
528 /// # Support
529 /// All.
530 pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
531 on_android!({
532 let uri = self.resolve_uri(dir, relative_path)?;
533 if self.get_mime_type(&uri)?.is_none() {
534 return Err(crate::Error { msg: format!("This is a directory, not a file: {uri:?}").into() })
535 }
536 Ok(uri)
537 })
538 }
539
540 /// Build a URI of an **existing** directory located at the relative path from the specified directory.
541 /// Error occurs, if the directory does not exist.
542 ///
543 /// The permissions and validity period of the returned URI depend on the origin directory
544 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
545 ///
546 /// # Support
547 /// All.
548 pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
549 on_android!({
550 let uri = self.resolve_uri(dir, relative_path)?;
551 if self.get_mime_type(&uri)?.is_some() {
552 return Err(crate::Error { msg: format!("This is a file, not a directory: {uri:?}").into() })
553 }
554 Ok(uri)
555 })
556 }
557
558 /// Build a URI of an entry located at the relative path from the specified directory.
559 ///
560 /// This function does not perform checks on the arguments or the returned URI.
561 /// Even if the dir argument refers to a file, no error occurs (and no panic either).
562 /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.
563 ///
564 /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead.
565 /// Or use this with [`AndroidFs::get_mime_type`].
566 ///
567 /// The permissions and validity period of the returned URI depend on the origin directory
568 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
569 ///
570 /// # Performance
571 /// This operation is relatively fast
572 /// because it does not call Kotlin API and only involves operating strings on Rust side.
573 ///
574 /// # Support
575 /// All.
576 pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
577 on_android!({
578 let base_dir = &dir.uri;
579 let relative_path = relative_path.as_ref().trim_matches('/');
580
581 if relative_path.is_empty() {
582 return Ok(dir.clone())
583 }
584
585 Ok(FileUri {
586 document_top_tree_uri: dir.document_top_tree_uri.clone(),
587 uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
588 })
589 })
590 }
591
592 /// See [`AndroidFs::get_thumbnail_to`] for descriptions.
593 ///
594 /// If thumbnail does not wrote to dest, return false.
595 pub fn get_thumbnail_to(
596 &self,
597 src: &FileUri,
598 dest: &FileUri,
599 preferred_size: Size,
600 format: ImageFormat,
601 ) -> crate::Result<bool> {
602
603 on_android!({
604 impl_se!(struct Req<'a> {
605 src: &'a FileUri,
606 dest: &'a FileUri,
607 format: &'a str,
608 quality: u8,
609 width: u32,
610 height: u32,
611 });
612 impl_de!(struct Res { value: bool });
613
614 let (quality, format) = match format {
615 ImageFormat::Png => (1.0, "Png"),
616 ImageFormat::Jpeg => (0.75, "Jpeg"),
617 ImageFormat::Webp => (0.7, "Webp"),
618 ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
619 ImageFormat::WebpWith { quality } => (quality, "Webp"),
620 };
621 let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
622 let Size { width, height } = preferred_size;
623
624 self.api
625 .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
626 .map(|v| v.value)
627 .map_err(Into::into)
628 })
629 }
630
631 /// Query the provider to get a file thumbnail.
632 /// If thumbnail does not exist it, return None.
633 ///
634 /// Note this does not cache. Please do it in your part if need.
635 ///
636 /// # Args
637 /// - ***uri*** :
638 /// Targe file uri.
639 /// Thumbnail availablty depends on the file provider.
640 /// In general, images and videos are available.
641 /// For files in [`PrivateStorage`],
642 /// the file type must match the filename extension.
643 ///
644 /// - ***preferred_size*** :
645 /// Optimal thumbnail size desired.
646 /// This may return a thumbnail of a different size,
647 /// but never more than double the requested size.
648 /// In any case, the aspect ratio is maintained.
649 ///
650 /// - ***format*** :
651 /// Thumbnail image format.
652 /// [`ImageFormat::Jpeg`] is recommended.
653 /// If you need transparency, use others.
654 ///
655 /// # Support
656 /// All.
657 pub fn get_thumbnail(
658 &self,
659 uri: &FileUri,
660 preferred_size: Size,
661 format: ImageFormat,
662 ) -> crate::Result<Option<Vec<u8>>> {
663
664 on_android!({
665 let tmp_file_path = {
666 use std::sync::atomic::{AtomicUsize, Ordering};
667
668 static COUNTER: AtomicUsize = AtomicUsize::new(0);
669 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
670
671 self.private_storage().resolve_path_with(
672 PrivateDir::Cache,
673 format!("{TMP_DIR_RELATIVE_PATH}/get_thumbnail {id}")
674 )?
675 };
676
677 if let Some(parent) = tmp_file_path.parent() {
678 let _ = std::fs::create_dir_all(parent);
679 }
680
681 std::fs::File::create(&tmp_file_path)?;
682
683 let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
684 .and_then(|ok| {
685 if (ok) {
686 std::fs::read(&tmp_file_path)
687 .map(Some)
688 .map_err(Into::into)
689 }
690 else {
691 Ok(None)
692 }
693 });
694
695 let _ = std::fs::remove_file(&tmp_file_path);
696
697 result
698 })
699 }
700
701 /// Creates a new empty file in the specified location and returns a URI.
702 ///
703 /// The permissions and validity period of the returned URIs depend on the origin directory
704 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
705 ///
706 /// Please note that this has a different meaning from `std::fs::create` that open the file in write mod.
707 /// If you need it, use [`AndroidFs::open_file`] with [`FileAccessMode::WriteTruncate`].
708 ///
709 /// # Args
710 /// - ***dir*** :
711 /// The URI of the base directory.
712 /// This needs to be **read-write**.
713 ///
714 /// - ***relative_path*** :
715 /// The file path relative to the base directory.
716 /// Any missing subdirectories in the specified path will be created automatically.
717 /// If a file with the same name already exists,
718 /// the system append a sequential number to ensure uniqueness.
719 /// If no extension is present,
720 /// the system may infer one from ***mime_type*** and may append it to the file name.
721 /// But this append-extension operation depends on the model and version.
722 ///
723 /// - ***mime_type*** :
724 /// The MIME type of the file to be created.
725 /// If this is None, MIME type is inferred from the extension of ***relative_path***
726 /// and if that fails, `application/octet-stream` is used.
727 ///
728 /// # Support
729 /// All.
730 pub fn create_file(
731 &self,
732 dir: &FileUri,
733 relative_path: impl AsRef<str>,
734 mime_type: Option<&str>
735 ) -> crate::Result<FileUri> {
736
737 on_android!({
738 impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
739
740 let relative_path = relative_path.as_ref();
741
742 self.api
743 .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
744 .map_err(Into::into)
745 })
746 }
747
748 /// Recursively create a directory and all of its parent components if they are missing,
749 /// then return the URI.
750 /// If it already exists, do nothing and just return the direcotry uri.
751 ///
752 /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
753 ///
754 /// # Args
755 /// - ***dir*** :
756 /// The URI of the base directory.
757 /// This needs to be **read-write**.
758 ///
759 /// - ***relative_path*** :
760 /// The directory path relative to the base directory.
761 ///
762 /// # Support
763 /// All.
764 pub fn create_dir_all(
765 &self,
766 dir: &FileUri,
767 relative_path: impl AsRef<str>,
768 ) -> Result<FileUri> {
769
770 on_android!({
771 let relative_path = relative_path.as_ref().trim_matches('/');
772 if relative_path.is_empty() {
773 return Ok(dir.clone())
774 }
775
776 // TODO:
777 // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
778 let tmp_file_uri = self.create_file(
779 dir,
780 format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"),
781 Some("application/octet-stream")
782 )?;
783 let _ = self.remove_file(&tmp_file_uri);
784 let uri = self.resolve_uri(dir, relative_path)?;
785
786 Ok(uri)
787 })
788 }
789
790 /// Returns the child files and directories of the specified directory.
791 /// The order of the entries is not guaranteed.
792 ///
793 /// The permissions and validity period of the returned URIs depend on the origin directory
794 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
795 ///
796 /// # Args
797 /// - ***uri*** :
798 /// Target directory URI.
799 /// This needs to be **readable**.
800 ///
801 /// # Note
802 /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
803 /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.
804 ///
805 /// # Support
806 /// All.
807 pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
808 on_android!(std::iter::Empty::<_>, {
809 impl_se!(struct Req<'a> { uri: &'a FileUri });
810 impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
811 impl_de!(struct Res { entries: Vec<Obj> });
812
813 self.api
814 .run_mobile_plugin::<Res>("readDir", Req { uri })
815 .map(|v| v.entries.into_iter())
816 .map(|v| v.map(|v| match v.mime_type {
817 Some(mime_type) => Entry::File {
818 name: v.name,
819 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
820 len: v.byte_size as u64,
821 mime_type,
822 uri: v.uri,
823 },
824 None => Entry::Dir {
825 name: v.name,
826 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
827 uri: v.uri,
828 }
829 }))
830 .map_err(Into::into)
831 })
832 }
833
834 /// Use [`FilePicker::pick_files`] instead.
835 #[deprecated = "Use FilePicker::pick_files instead"]
836 pub fn show_open_file_dialog(
837 &self,
838 initial_location: Option<&FileUri>,
839 mime_types: &[&str],
840 multiple: bool,
841 ) -> crate::Result<Vec<FileUri>> {
842
843 self.file_picker().pick_files(initial_location, mime_types, multiple)
844 }
845
846 /// Use [`FilePicker::pick_contents`] instead.
847 #[deprecated = "Use FilePicker::pick_contents instead"]
848 pub fn show_open_content_dialog(
849 &self,
850 mime_types: &[&str],
851 multiple: bool
852 ) -> crate::Result<Vec<FileUri>> {
853
854 self.file_picker().pick_contents(mime_types, multiple)
855 }
856
857 /// Use [`FilePicker::pick_visual_medias`] instead.
858 #[deprecated = "Use FilePicker::pick_visual_medias instead"]
859 pub fn show_open_visual_media_dialog(
860 &self,
861 target: VisualMediaTarget,
862 multiple: bool,
863 ) -> crate::Result<Vec<FileUri>> {
864
865 self.file_picker().pick_visual_medias(target, multiple)
866 }
867
868 /// Use [`FilePicker::pick_dir`] instead.
869 #[deprecated = "Use FilePicker::pick_dir instead"]
870 pub fn show_manage_dir_dialog(
871 &self,
872 initial_location: Option<&FileUri>,
873 ) -> crate::Result<Option<FileUri>> {
874
875 self.file_picker().pick_dir(initial_location)
876 }
877
878 /// Use [`FilePicker::pick_dir`] instead.
879 #[deprecated = "Use FilePicker::pick_dir instead."]
880 pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
881 self.file_picker().pick_dir(None)
882 }
883
884
885 /// Use [`FilePicker::save_file`] instead.
886 #[deprecated = "Use FilePicker::save_file instead."]
887 pub fn show_save_file_dialog(
888 &self,
889 initial_location: Option<&FileUri>,
890 initial_file_name: impl AsRef<str>,
891 mime_type: Option<&str>,
892 ) -> crate::Result<Option<FileUri>> {
893
894 self.file_picker().save_file(initial_location, initial_file_name, mime_type)
895 }
896
897 /// Create an **restricted** URI for the specified directory.
898 /// This should only be used as `initial_location` in the file picker.
899 /// It must not be used for any other purpose.
900 ///
901 /// This is useful when selecting (creating) new files and folders,
902 /// but when selecting existing entries, `initial_location` is often better with None.
903 ///
904 /// Note this is an informal method and is not guaranteed to work reliably.
905 /// But this URI does not cause the dialog to error.
906 /// So please use this with the mindset that it's better than doing nothing.
907 ///
908 /// # Examples
909 /// ```rust
910 /// use tauri_plugin_android_fs::{AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
911 ///
912 /// fn sample(app: tauri::AppHandle) {
913 /// let api = app.android_fs();
914 ///
915 /// // Get URI of the top public directory in primary volume
916 /// let initial_location = api.resolve_initial_location(
917 /// InitialLocation::TopPublicDir,
918 /// false,
919 /// ).expect("Should be on Android");
920 ///
921 /// // Get URI of ~/Pictures/
922 /// let initial_location = api.resolve_initial_location(
923 /// PublicImageDir::Pictures,
924 /// false
925 /// ).expect("Should be on Android");
926 ///
927 /// // Get URI of ~/Documents/sub_dir1/sub_dir2/
928 /// let initial_location = api.resolve_initial_location(
929 /// InitialLocation::DirInPublicDir {
930 /// base_dir: PublicGeneralPurposeDir::Documents.into(),
931 /// relative_path: "sub_dir1/sub_dir2"
932 /// },
933 /// true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
934 /// ).expect("Should be on Android");
935 ///
936 /// // Open dialog with initial_location
937 /// let _ = api.file_picker().save_file(Some(&initial_location), "", None);
938 /// let _ = api.file_picker().pick_file(Some(&initial_location), &[]);
939 /// let _ = api.file_picker().pick_dir(Some(&initial_location));
940 /// }
941 /// ```
942 ///
943 /// # Support
944 /// All.
945 pub fn resolve_initial_location<'a>(
946 &self,
947 dir: impl Into<InitialLocation<'a>>,
948 create_dirs: bool
949 ) -> crate::Result<FileUri> {
950
951 on_android!({
952 const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
953
954 let uri = match dir.into() {
955 InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
956 InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
957 InitialLocation::DirInPublicDir { base_dir, relative_path } => {
958 let relative_path = relative_path.trim_matches('/');
959
960 if relative_path.is_empty() {
961 format!("{TOP_DIR}%3A{base_dir}")
962 }
963 else {
964 if create_dirs {
965 let _ = self.public_storage().create_dir_all(base_dir, relative_path);
966 }
967 let sub_dirs = encode_document_id(relative_path);
968 format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
969 }
970 },
971 InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
972 let relative_path = &format!(
973 "{}/{}",
974 self.public_storage().app_dir_name()?,
975 relative_path.trim_matches('/'),
976 );
977
978 return self.resolve_initial_location(
979 InitialLocation::DirInPublicDir { base_dir, relative_path },
980 create_dirs
981 )
982 }
983 };
984
985 Ok(FileUri { uri, document_top_tree_uri: None })
986 })
987 }
988
989 /// Use [`FileSender::share_file`] instead
990 #[deprecated = "Use FileSender::share_file instead."]
991 pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
992 self.file_sender().share_file(uri)
993 }
994
995 /// Use [`FileSender::open_file`] instead
996 #[deprecated = "Use FileSender::open_file instead."]
997 pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
998 self.file_sender().open_file(uri)
999 }
1000
1001 /// Use [`FileSender::can_share_file`] instead
1002 #[deprecated = "Use FileSender::can_share_file instead"]
1003 pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1004 #[allow(deprecated)]
1005 self.file_sender().can_share_file(uri)
1006 }
1007
1008 /// Use [`FileSender::can_open_file`] instead
1009 #[deprecated = "Use FileSender::can_open_file instead"]
1010 pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1011 #[allow(deprecated)]
1012 self.file_sender().can_open_file(uri)
1013 }
1014
1015 /// Take persistent permission to access the file, directory and its descendants.
1016 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
1017 ///
1018 /// This works by just calling, without displaying any confirmation to the user.
1019 ///
1020 /// 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)
1021 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
1022 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1023 /// So check by [`AndroidFs::check_persisted_uri_permission`].
1024 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1025 ///
1026 /// # Args
1027 /// - **uri** :
1028 /// URI of the target file or directory. This must be a URI taken from following :
1029 /// - [`FilePicker::pick_files`]
1030 /// - [`FilePicker::pick_file`]
1031 /// - [`FilePicker::pick_visual_medias`]
1032 /// - [`FilePicker::pick_visual_media`]
1033 /// - [`FilePicker::pick_dir`]
1034 /// - [`FilePicker::save_file`]
1035 /// - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_file`], [`AndroidFs::create_dir_all`] :
1036 /// If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function.
1037 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
1038 ///
1039 /// # Support
1040 /// All.
1041 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1042 on_android!({
1043 impl_se!(struct Req<'a> { uri: &'a FileUri });
1044 impl_de!(struct Res;);
1045
1046 self.api
1047 .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1048 .map(|_| ())
1049 .map_err(Into::into)
1050 })
1051 }
1052
1053 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1054 /// Returns false if there are only non-persistent permissions or no permissions.
1055 ///
1056 /// # Args
1057 /// - **uri** :
1058 /// URI of the target file or directory.
1059 /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself.
1060 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1061 ///
1062 /// - **mode** :
1063 /// The mode of permission you want to check.
1064 ///
1065 /// # Support
1066 /// All.
1067 pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1068 on_android!({
1069 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1070 impl_de!(struct Res { value: bool });
1071
1072 self.api
1073 .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1074 .map(|v| v.value)
1075 .map_err(Into::into)
1076 })
1077 }
1078
1079 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
1080 ///
1081 /// # Support
1082 /// All.
1083 pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1084 on_android!(std::iter::Empty::<_>, {
1085 impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1086 impl_de!(struct Res { items: Vec<Obj> });
1087
1088 self.api
1089 .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1090 .map(|v| v.items.into_iter())
1091 .map(|v| v.map(|v| {
1092 let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1093 match v.d {
1094 true => PersistedUriPermission::Dir { uri, can_read, can_write },
1095 false => PersistedUriPermission::File { uri, can_read, can_write }
1096 }
1097 }))
1098 .map_err(Into::into)
1099 })
1100 }
1101
1102 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1103 ///
1104 /// # Args
1105 /// - ***uri*** :
1106 /// URI of the target file or directory.
1107 ///
1108 /// # Support
1109 /// All.
1110 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1111 on_android!({
1112 impl_se!(struct Req<'a> { uri: &'a FileUri });
1113 impl_de!(struct Res;);
1114
1115 self.api
1116 .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1117 .map(|_| ())
1118 .map_err(Into::into)
1119 })
1120 }
1121
1122 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1123 ///
1124 /// # Support
1125 /// All.
1126 pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1127 on_android!({
1128 impl_de!(struct Res);
1129
1130 self.api
1131 .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1132 .map(|_| ())
1133 .map_err(Into::into)
1134 })
1135 }
1136
1137 /// Use [`FilePicker::is_visual_media_picker_available`] instead.
1138 #[deprecated = "Use FilePicker::is_visual_media_picker_available instead"]
1139 pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1140 self.file_picker().is_visual_media_picker_available()
1141 }
1142}