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}