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