tauri_plugin_android_fs/
lib.rs

1//! Overview and usage is [here](https://crates.io/crates/tauri-plugin-android-fs)
2
3mod models;
4mod impls;
5mod error;
6
7use std::io::{Read, Write};
8
9pub use models::*;
10pub use error::{Error, Result};
11pub use impls::{AndroidFsExt, init};
12
13/// API
14pub trait AndroidFs<R: tauri::Runtime> {
15
16    /// Verify whether this plugin is available.  
17    /// 
18    /// On Android, this returns true.  
19    /// On other platforms, this returns false.  
20    fn is_available(&self) -> bool {
21        #[cfg(not(target_os = "android"))] {
22            false
23        }
24        #[cfg(target_os = "android")] {
25            true
26        }
27    }
28
29    /// Get the file or directory name.  
30    /// 
31    /// # Args
32    /// - **uri** :  
33    /// Target uri.  
34    /// This needs to be **readable**.
35    /// 
36    /// # Support
37    /// All Android version.
38    fn get_name(&self, uri: &FileUri) -> crate::Result<String>;
39
40    /// Query the provider to get mime type.  
41    /// If the directory, this returns `None`.  
42    /// If the file, this returns no `None`.  
43    /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.  
44    ///
45    /// # Args
46    /// - **uri** :  
47    /// Target uri.  
48    /// This needs to be **readable**.
49    /// 
50    /// # Support
51    /// All Android version.
52    fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>>;
53
54    /// Queries the file system to get information about a file, directory.
55    /// 
56    /// # Args
57    /// - **uri** :  
58    /// Target uri.  
59    /// This needs to be **readable**.
60    /// 
61    /// # Note
62    /// This uses [`AndroidFs::open_file`] internally.
63    /// 
64    /// # Support
65    /// All Android version.
66    fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
67        let file = self.open_file(uri, FileAccessMode::Read)?;
68        Ok(file.metadata()?)
69    }
70
71    /// Open a file in the specified mode.
72    /// 
73    /// # Note
74    /// This method uses a FileDescriptor internally. 
75    /// However, if the target file does not physically exist on the device, such as cloud-based files, 
76    /// the write operation using a FileDescriptor may not be reflected properly.
77    /// In such cases, consider using [AndroidFs::write_via_kotlin], 
78    /// which writes using a standard method that does not rely on FileDescriptor, 
79    /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
80    /// If you specifically need to write using std::fs::File not entire contents, see [AndroidFs::write_via_kotlin_in] or [AndroidFs::copy_via_kotlin].  
81    /// 
82    /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues, 
83    /// but files on Dropbox can be written to correctly using a FileDescriptor.
84    /// 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). 
85    /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.  
86    /// 
87    /// There are no problems with file reading.
88    /// 
89    /// # Support
90    /// All Android version.
91    fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File>;
92
93    /// Reads the entire contents of a file into a bytes vector.  
94    /// 
95    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
96    /// 
97    /// # Args
98    /// - **uri** :  
99    /// Target file uri.  
100    /// This needs to be **readable**.
101    /// 
102    /// # Support
103    /// All Android version.
104    fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
105        let mut file = self.open_file(uri, FileAccessMode::Read)?;
106        let mut buf = file.metadata().ok()
107            .map(|m| m.len() as usize)
108            .map(Vec::with_capacity)
109            .unwrap_or_else(Vec::new);
110
111        file.read_to_end(&mut buf)?;
112        Ok(buf)
113    }
114
115    /// Reads the entire contents of a file into a string.  
116    /// 
117    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
118    /// 
119    /// # Args
120    /// - **uri** :  
121    /// Target file uri.  
122    /// This needs to be **readable**.
123    /// 
124    /// # Support
125    /// All Android version.
126    fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
127        let mut file = self.open_file(uri, FileAccessMode::Read)?;
128        let mut buf = file.metadata().ok()
129            .map(|m| m.len() as usize)
130            .map(String::with_capacity)
131            .unwrap_or_else(String::new);
132    
133        file.read_to_string(&mut buf)?;
134        Ok(buf)
135    }
136
137    /// Writes a slice as the entire contents of a file.  
138    /// This function will entirely replace its contents if it does exist.    
139    /// 
140    /// If you want to operate the file, use [`AndroidFs::open_file`] instead.  
141    /// 
142    /// # Args
143    /// - **uri** :  
144    /// Target file uri.  
145    /// This needs to be **writable**.
146    /// 
147    /// # Support
148    /// All Android version.
149    fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
150        if self.need_write_via_kotlin(uri)? {
151            self.write_via_kotlin(uri, contents)?;
152            return Ok(())
153        }
154
155        let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
156        file.write_all(contents.as_ref())?;
157        Ok(())
158    }
159
160    /// Writes a slice as the entire contents of a file.  
161    /// This function will entirely replace its contents if it does exist.    
162    /// 
163    /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.  
164    /// See [`AndroidFs::open_file`] for why this function exists.
165    /// 
166    /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`], 
167    /// so there should be few opportunities to use this.
168    /// 
169    /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
170    /// 
171    /// # Inner process
172    /// The contents is written to a temporary file by Rust side 
173    /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].  
174    /// 
175    /// # Support
176    /// All Android version.
177    fn write_via_kotlin(
178        &self, 
179        uri: &FileUri,
180        contents: impl AsRef<[u8]>
181    ) -> crate::Result<()> {
182
183        self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
184    }
185
186    /// See [`AndroidFs::write_via_kotlin`] for information.  
187    /// Use this if you want to write using `std::fs::File`, not entire contents.
188    /// 
189    /// If you want to retain the file outside the closure, 
190    /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`]. 
191    /// For details, please refer to the internal implementation of this function.
192    /// 
193    /// # Args
194    /// - **uri** :  
195    /// Target file uri to write.
196    /// 
197    /// - **contetns_writer** :  
198    /// A closure that accepts a mutable reference to a `std::fs::File`
199    /// and performs the actual write operations. Note that this represents a temporary file.
200    fn write_via_kotlin_in<T>(
201        &self, 
202        uri: &FileUri,
203        contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
204    ) -> crate::Result<T> {
205
206        static TMP_FILE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
207
208        // 一時ファイルの排他アクセスを保証
209        let _guard = TMP_FILE_LOCK.lock();
210
211        // 一時ファイルのパスを取得
212        let tmp_file_path = self.app_handle()
213            .android_fs()
214            .private_storage()
215            .resolve_path_with(PrivateDir::Cache, "tauri-plugin-android-fs-tempfile-write-via-kotlin")?;
216
217        // 一時ファイルに内容を書き込む
218        // エラー処理は一時ファイルを削除するまで保留
219        let result = {
220            let ref mut file = std::fs::File::create(&tmp_file_path)?;
221            contents_writer(file)
222        };
223
224        // 上記処理が成功していれば、一時ファイルの内容をkotlin側で指定されたファイルにコピーする
225        // エラー処理は一時ファイルを削除するまで保留
226        let result = result
227            .map_err(crate::Error::from)
228            .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
229
230        // 一時ファイルを削除
231        let _ = std::fs::remove_file(&tmp_file_path);
232
233        result
234    }
235
236    /// Determines if the file needs to be written via Kotlin side instead of Rust side.  
237    /// Currently, this returns true only if the file is on GoogleDrive.  
238    /// 
239    /// # Support
240    /// All Android version.
241    fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
242        Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
243    }
244
245    /// Copies the contents of src file to dest. 
246    /// 
247    /// This copy process is done on Kotlin side, not on Rust.  
248    /// Large files in GB units are also supported.  
249    ///  
250    /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
251    /// 
252    /// # Args
253    /// - **src** :  
254    /// The uri of source file.   
255    /// This needs to be **readable**.
256    /// 
257    /// - **dest** :  
258    /// The uri of destination file.  
259    /// This needs to be **writable**.
260    /// 
261    /// # Support
262    /// All Android version.
263    fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()>;
264
265    /// Remove the file.
266    /// 
267    /// # Args
268    /// - **uri** :  
269    /// Target file uri.  
270    /// This needs to be **removable**.
271    /// 
272    /// # Support
273    /// All Android version.
274    fn remove_file(&self, uri: &FileUri) -> crate::Result<()>;
275
276    /// Remove the **empty** directory.
277    /// 
278    /// # Args
279    /// - **uri** :  
280    /// Target directory uri.  
281    /// This needs to be **removable**.
282    /// 
283    /// # Support
284    /// All Android version.
285    fn remove_dir(&self, uri: &FileUri) -> crate::Result<()>;
286
287    /// Creates a new empty file in the specified location and returns a uri.  
288    /// 
289    /// The permissions and validity period of the returned uris depend on the origin directory 
290    /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`]) 
291    ///  
292    /// # Args  
293    /// - ***dir*** :  
294    /// The uri of the base directory.  
295    /// This needs to be **read-writable**.
296    ///  
297    /// - ***relative_path*** :  
298    /// The file path relative to the base directory.  
299    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
300    /// Any missing subdirectories in the specified path will be created automatically.  
301    ///  
302    /// - ***mime_type*** :  
303    /// The MIME type of the file to be created.  
304    /// Specifying this is recommended whenever possible.  
305    /// If not provided, `application/octet-stream` will be used, as generic, unknown, or undefined file types.  
306    ///  
307    /// # Support
308    /// All Android version.
309    fn create_file(
310        &self,
311        dir: &FileUri, 
312        relative_path: impl AsRef<str>, 
313        mime_type: Option<&str>
314    ) -> crate::Result<FileUri>;
315
316    /// Returns the child files and directories of the specified directory.  
317    /// The order of the entries is not guaranteed.  
318    /// 
319    /// The permissions and validity period of the returned uris depend on the origin directory 
320    /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`])  
321    /// 
322    /// # Args
323    /// - **uri** :  
324    /// Target directory uri.  
325    /// This needs to be **readable**.
326    ///  
327    /// # Note  
328    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily. 
329    /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.  
330    /// 
331    /// # Support
332    /// All Android version.
333    fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>>;
334
335    /// Opens a system file picker and returns a **read-writable** uris.  
336    /// If no file is selected or the user cancels, an empty vec is returned.  
337    /// 
338    /// By default, returned uri is valid until the app is terminated. 
339    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
340    ///
341    /// Removing the returned files is also supported in most cases, 
342    /// but note that files provided by third-party apps may not be removable.  
343    ///  
344    /// Just to read images and videos, consider using [`AndroidFs::show_open_visual_media_dialog`] instead. 
345    ///  
346    /// # Args  
347    /// - ***initial_location*** :  
348    /// Indicate the initial location of dialog.  
349    /// System will do its best to launch the dialog in the specified entry 
350    /// if it's a directory, or the directory that contains the specified file if not.  
351    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
352    /// 
353    /// - ***mime_types*** :  
354    /// The MIME types of the file to be selected.  
355    /// However, there is no guarantee that the returned file will match the specified types.  
356    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
357    ///  
358    /// - ***multiple*** :  
359    /// Indicates whether multiple file selection is allowed.  
360    /// 
361    /// # Issue
362    /// This dialog has known issues. See the following for details and workarounds
363    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
364    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
365    /// 
366    /// # Support
367    /// All Android version.
368    /// 
369    /// # References
370    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
371    fn show_open_file_dialog(
372        &self,
373        initial_location: Option<&FileUri>,
374        mime_types: &[&str],
375        multiple: bool,
376    ) -> crate::Result<Vec<FileUri>>;
377
378    /// Opens a media picker and returns a **readonly** uris.  
379    /// If no file is selected or the user cancels, an empty vec is returned.  
380    ///  
381    /// This media picker provides a browsable interface that presents the user with their media library, 
382    /// sorted by date from newest to oldest. 
383    /// 
384    /// By default, returned uri is valid until the app is terminated. 
385    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
386    ///  
387    /// # Args  
388    /// - ***target*** :  
389    /// The media type of the file to be selected.  
390    /// Images or videos, or both.  
391    ///  
392    /// - ***multiple*** :  
393    /// Indicates whether multiple file selection is allowed.  
394    ///  
395    /// # Issue
396    /// This dialog has known issues. See the following for details and workarounds
397    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
398    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
399    ///  
400    /// # Note
401    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
402    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
403    /// And this is marked intended behavior, not a bug.
404    /// - <https://issuetracker.google.com/issues/268079113>  
405    ///  
406    /// # Support
407    /// This feature is available on devices that meet the following criteria:  
408    /// - Running Android 11 (API level 30) or higher  
409    /// - Receive changes to Modular System Components through Google System Updates  
410    ///  
411    /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].  
412    /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].  
413    /// 
414    /// # References
415    /// <https://developer.android.com/training/data-storage/shared/photopicker>
416    fn show_open_visual_media_dialog(
417        &self,
418        target: VisualMediaTarget,
419        multiple: bool,
420    ) -> crate::Result<Vec<FileUri>>;
421
422    /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one, 
423    /// and returns a **read-write-removable** directory uri. 
424    /// App can fully manage entries within the returned directory.  
425    /// If no directory is selected or the user cancels, `None` is returned. 
426    /// 
427    /// By default, returned uri is valid until the app is terminated. 
428    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
429    /// 
430    /// # Args  
431    /// - ***initial_location*** :  
432    /// Indicate the initial location of dialog.  
433    /// System will do its best to launch the dialog in the specified entry 
434    /// if it's a directory, or the directory that contains the specified file if not.  
435    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
436    /// 
437    /// # Issue
438    /// This dialog has known issues. See the following for details and workarounds
439    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
440    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
441    ///  
442    /// # Support
443    /// All Android version.
444    /// 
445    /// # References
446    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
447    fn show_manage_dir_dialog(
448        &self,
449        initial_location: Option<&FileUri>,
450    ) -> crate::Result<Option<FileUri>>;
451
452    /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
453    #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
454    #[warn(deprecated)]
455    fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
456        self.show_manage_dir_dialog(None)
457    }
458
459    /// Opens a dialog to save a file and returns a **writeonly** uri.  
460    /// The returned file may be a newly created file with no content,
461    /// or it may be an existing file with the requested MIME type.  
462    /// If the user cancels, `None` is returned. 
463    /// 
464    /// By default, returned uri is valid until the app is terminated. 
465    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
466    /// 
467    /// Removing and reading the returned files is also supported in most cases, 
468    /// but note that files provided by third-party apps may not be removable.  
469    ///  
470    /// # Args  
471    /// - ***initial_location*** :  
472    /// Indicate the initial location of dialog.  
473    /// System will do its best to launch the dialog in the specified entry 
474    /// if it's a directory, or the directory that contains the specified file if not.  
475    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
476    /// 
477    /// - ***initial_file_name*** :  
478    /// An initial file name, but the user may change this value before creating the file.  
479    /// 
480    /// - ***mime_type*** :  
481    /// The MIME type of the file to be saved.  
482    /// Specifying this is recommended whenever possible.  
483    /// If not provided, `application/octet-stream` will be used, as generic, unknown, or undefined file types.  
484    ///  
485    /// # Issue
486    /// This dialog has known issues. See the following for details and workarounds
487    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
488    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
489    /// 
490    /// # Support
491    /// All Android version.
492    /// 
493    /// # References
494    /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
495    fn show_save_file_dialog(
496        &self,
497        initial_location: Option<&FileUri>,
498        initial_file_name: impl AsRef<str>,
499        mime_type: Option<&str>,
500    ) -> crate::Result<Option<FileUri>>;
501
502    /// Take persistent permission to access the file, directory and its descendants.  
503    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
504    /// 
505    /// This works by just calling, without displaying any confirmation to the user.
506    /// 
507    /// 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)  
508    /// Therefore, it is recommended to relinquish the unnecessary persisted uri by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
509    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
510    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
511    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
512    /// 
513    /// # Args
514    /// - **uri** :  
515    /// Uri of the target file or directory. This must be a uri taken from following :  
516    ///     - [`AndroidFs::show_open_file_dialog`]
517    ///     - [`AndroidFs::show_open_visual_media_dialog`]
518    ///     - [`AndroidFs::show_save_file_dialog`]
519    ///     - [`AndroidFs::show_manage_dir_dialog`]  
520    ///     - [`AndroidFs::read_dir`] :  
521    ///         If this, the permissions of the origin directory uri is persisted, not a entry iteself. 
522    ///         Because the permissions and validity period of the entry uris depend on the origin directory.
523    /// 
524    /// # Support
525    /// All Android version. 
526    fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
527
528    /// Check a persisted uri permission grant by [`AndroidFs::take_persistable_uri_permission`].   
529    /// Returns false if there are only non-persistent permissions or no permissions.
530    /// 
531    /// # Args
532    /// - **uri** :  
533    /// Uri of the target file or directory.  
534    ///
535    /// - **mode** :  
536    /// The mode of permission you want to check.  
537    /// 
538    /// # Support
539    /// All Android version.
540    fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool>;
541
542    /// Return list of all uri permission grants that have been persisted by [`AndroidFs::take_persistable_uri_permission`].   
543    /// 
544    /// # Support
545    /// All Android version.
546    fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>>;
547
548    /// Relinquish a persisted uri permission grant by [`AndroidFs::take_persistable_uri_permission`].   
549    /// 
550    /// # Args
551    /// - **uri** :  
552    /// Uri of the target file or directory.  
553    ///
554    /// # Support
555    /// All Android version.
556    fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
557
558    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
559    /// 
560    /// # Support
561    /// All Android version.
562    fn release_all_persisted_uri_permissions(&self) -> crate::Result<()>;
563
564    /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
565    /// 
566    /// # Support
567    /// All Android version.
568    fn is_visual_media_dialog_available(&self) -> crate::Result<bool>;
569
570    /// File storage intended for the app’s use only.
571    fn private_storage(&self) -> &impl PrivateStorage<R>;
572
573    /// File storage that is available to other applications and users.
574    fn public_storage(&self) -> &impl PublicStorage<R>;
575
576    fn app_handle(&self) -> &tauri::AppHandle<R>;
577}
578
579/// File storage intended for the app’s use only.  
580pub trait PublicStorage<R: tauri::Runtime> {
581
582    /// Creates a new empty file in the specified public app directory and returns a **persistent read-write-removable** URI.  
583    ///  
584    /// The created file will be registered with the corresponding MediaStore as needed.  
585    /// 
586    /// # Args
587    /// - ***dir*** :  
588    /// The base directory.  
589    ///  
590    /// - ***relative_path*** :  
591    /// The file path relative to the base directory.  
592    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
593    /// Any missing subdirectories in the specified path will be created automatically.  
594    ///  
595    /// - ***mime_type*** :  
596    /// The MIME type of the file to be created.  
597    /// Specifying this is recommended whenever possible.  
598    /// If not provided, `application/octet-stream` will be used, as generic, unknown, or undefined file types.  
599    /// When using [`PublicImageDir`], please use only image MIME types; using other types may cause errors.
600    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
601    /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
602    /// 
603    /// # Support
604    /// Android 10 (API level 29) or higher.  
605    /// Lower are need runtime request of `WRITE_EXTERNAL_STORAGE`. (This option will be made available in the future)
606    ///
607    /// [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
608    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
609    /// [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
610    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
611    /// Others are available in all Android versions.
612    fn create_file_in_public_app_dir(
613        &self,
614        dir: impl Into<PublicDir>,
615        relative_path: impl AsRef<str>, 
616        mime_type: Option<&str>
617    ) -> crate::Result<FileUri> {
618
619        let config = self.app_handle().config();
620        let app_name = config.product_name.as_deref().unwrap_or("");
621        let app_name = match app_name.is_empty() {
622            true => &config.identifier,
623            false => app_name
624        };
625        let app_name = app_name.replace('/', " ");
626        let relative_path = relative_path.as_ref().trim_start_matches('/');
627        let relative_path_with_subdir = format!("{app_name}/{relative_path}");
628
629        self.create_file_in_public_dir(dir, relative_path_with_subdir, mime_type)
630    }
631
632    /// Creates a new empty file in the specified public directory and returns a **persistent read-write-removable** URI.  
633    ///  
634    /// The created file will be registered with the corresponding MediaStore as needed.  
635    /// 
636    /// # Args
637    /// - ***dir*** :  
638    /// The base directory.  
639    ///  
640    /// - ***relative_path_with_subdir*** :  
641    /// The file path relative to the base directory.  
642    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
643    /// Any missing subdirectories in the specified path will be created automatically.  
644    /// Please specify a subdirectory in this, 
645    /// such as `MyApp/file.txt` or `MyApp/2025-2-11/file.txt`. Do not use `file.txt`.  
646    /// It is customary to specify the app name at the beginning of the subdirectory, 
647    /// and in this case, using [`PublicStorage::create_file_in_public_app_dir`] is recommended.
648    ///  
649    /// - ***mime_type*** :  
650    /// The MIME type of the file to be created.  
651    /// Specifying this is recommended whenever possible.  
652    /// If not provided, `application/octet-stream` will be used, as generic, unknown, or undefined file types.  
653    /// When using [`PublicImageDir`], please use only image MIME types; using other types may cause errors.
654    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
655    /// Only [`PublicGeneralPurposeDir`] supports all MIME types. 
656    /// 
657    /// # Support
658    /// Android 10 (API level 29) or higher.  
659    /// Lower are need runtime request of `WRITE_EXTERNAL_STORAGE`. (This option will be made available in the future)
660    ///
661    /// [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
662    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
663    /// [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
664    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
665    /// Others are available in all Android versions.
666    fn create_file_in_public_dir(
667        &self,
668        dir: impl Into<PublicDir>,
669        relative_path_with_subdir: impl AsRef<str>, 
670        mime_type: Option<&str>
671    ) -> crate::Result<FileUri>;
672
673    /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
674    /// 
675    /// # Support
676    /// All Android version.
677    fn is_audiobooks_dir_available(&self) -> crate::Result<bool>;
678
679    /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
680    /// 
681    /// # Support
682    /// All Android version.
683    fn is_recordings_dir_available(&self) -> crate::Result<bool>;
684
685    fn app_handle(&self) -> &tauri::AppHandle<R>;
686}
687
688/// File storage intended for the app’s use only.  
689pub trait PrivateStorage<R: tauri::Runtime> {
690
691    /// Get the absolute path of the specified directory.  
692    /// App can fully manage entries within this directory without any permission via std::fs.   
693    ///
694    /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s initialising request.  
695    /// When using [`PrivateDir::Cache`], the system will automatically delete files in this directory as disk space is needed elsewhere on the device.   
696    /// 
697    /// The returned path may change over time if the calling app is moved to an adopted storage device, 
698    /// so only relative paths should be persisted.   
699    /// 
700    /// # Examples
701    /// ```no_run
702    /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, PrivateDir, PrivateStorage};
703    /// 
704    /// fn example(app: tauri::AppHandle) {
705    ///     let api = app.android_fs().private_storage();
706    /// 
707    ///     let dir_path = api.resolve_path(PrivateDir::Data).unwrap();
708    ///     let file_path = dir_path.join("2025-2-12/data.txt");
709    ///     
710    ///     // Write file
711    ///     std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
712    ///     std::fs::write(&file_path, "aaaa").unwrap();
713    /// 
714    ///     // Read file
715    ///     let _ = std::fs::read_to_string(&file_path).unwrap();
716    /// 
717    ///     // Remove file
718    ///     std::fs::remove_file(&file_path).unwrap();
719    /// }
720    /// ```
721    /// 
722    /// # Support
723    /// All Android version.
724    fn resolve_path(&self, dir: PrivateDir) -> crate::Result<std::path::PathBuf>;
725
726    /// Get the absolute path of the specified relative path and base directory.  
727    /// App can fully manage entries of this path without any permission via std::fs.   
728    ///
729    /// See [`PrivateStorage::resolve_path`] for details.  
730    /// 
731    /// # Support
732    /// All Android version.
733    fn resolve_path_with(
734        &self,
735        dir: PrivateDir,
736        relative_path: impl AsRef<str>
737    ) -> crate::Result<std::path::PathBuf> {
738
739        let relative_path = relative_path.as_ref().trim_start_matches('/');
740        let path = self.resolve_path(dir)?.join(relative_path);
741        Ok(path)
742    }
743
744    fn resolve_uri(&self, dir: PrivateDir) -> crate::Result<FileUri> {
745        self.resolve_path(dir).map(Into::into)
746    }
747
748    fn resolve_uri_with(&self, dir: PrivateDir, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
749        self.resolve_path_with(dir, relative_path).map(Into::into)
750    }
751
752    /// Writes a slice as the entire contents of a file.  
753    /// 
754    /// This function will create a file if it does not exist, and will entirely replace its contents if it does.  
755    /// Recursively create parent directories if they are missing.  
756    /// 
757    /// This internally uses [`PrivateStorage::resolve_path`] , [`std::fs::create_dir_all`], and [`std::fs::write`].  
758    /// See [`PrivateStorage::resolve_path`] for details.  
759    /// 
760    /// # Support
761    /// All Android version.
762    fn write(
763        &self, 
764        base_dir: PrivateDir, 
765        relative_path: impl AsRef<str>, 
766        contents: impl AsRef<[u8]>
767    ) -> crate::Result<()> {
768
769        let path = self.resolve_path_with(base_dir, relative_path)?;
770
771        if let Some(parent_dir) = path.parent() {
772            std::fs::create_dir_all(parent_dir)?;
773        }
774
775        std::fs::write(path, contents)?;
776
777        Ok(())
778    }
779
780    /// Open a file in read-only mode.  
781    /// 
782    /// If you only need to read the entire file contents, consider using [`PrivateStorage::read`]  or [`PrivateStorage::read_to_string`] instead.  
783    /// 
784    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::open`].  
785    /// See [`PrivateStorage::resolve_path`] for details.  
786    /// 
787    /// # Support
788    /// All Android version.
789    fn open_file(
790        &self,
791        base_dir: PrivateDir, 
792        relative_path: impl AsRef<str>, 
793    ) -> crate::Result<std::fs::File> {
794
795        let path = self.resolve_path_with(base_dir, relative_path)?;
796        Ok(std::fs::File::open(path)?)
797    }
798
799    /// Opens a file in write-only mode.  
800    /// This function will create a file if it does not exist, and will truncate it if it does.
801    /// 
802    /// If you only need to write the contents, consider using [`PrivateStorage::write`]  instead.  
803    /// 
804    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create`].  
805    /// See [`PrivateStorage::resolve_path`] for details.  
806    /// 
807    /// # Support
808    /// All Android version.
809    fn create_file(
810        &self,
811        base_dir: PrivateDir, 
812        relative_path: impl AsRef<str>, 
813    ) -> crate::Result<std::fs::File> {
814
815        let path = self.resolve_path_with(base_dir, relative_path)?;
816        Ok(std::fs::File::create(path)?)
817    }
818
819    /// Creates a new file in read-write mode; error if the file exists. 
820    /// 
821    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create_new`].  
822    /// See [`PrivateStorage::resolve_path`] for details.  
823    /// 
824    /// # Support
825    /// All Android version.
826    fn create_new_file(
827        &self,
828        base_dir: PrivateDir, 
829        relative_path: impl AsRef<str>, 
830    ) -> crate::Result<std::fs::File> {
831
832        let path = self.resolve_path_with(base_dir, relative_path)?;
833        Ok(std::fs::File::create_new(path)?)
834    }
835
836    /// Reads the entire contents of a file into a bytes vector.  
837    /// 
838    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
839    /// 
840    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read`].  
841    /// See [`PrivateStorage::resolve_path`] for details.  
842    /// 
843    /// # Support
844    /// All Android version.
845    fn read(
846        &self,
847        base_dir: PrivateDir, 
848        relative_path: impl AsRef<str>, 
849    ) -> crate::Result<Vec<u8>> {
850
851        let path = self.resolve_path_with(base_dir, relative_path)?;
852        Ok(std::fs::read(path)?)
853    }
854
855    /// Reads the entire contents of a file into a string.  
856    /// 
857    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
858    /// 
859    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_to_string`].  
860    /// See [`PrivateStorage::resolve_path`] for details.  
861    /// 
862    /// # Support
863    /// All Android version.
864    fn read_to_string(
865        &self,
866        base_dir: PrivateDir,
867        relative_path: impl AsRef<str>, 
868    ) -> crate::Result<String> {
869
870        let path = self.resolve_path_with(base_dir, relative_path)?;
871        Ok(std::fs::read_to_string(path)?)
872    }
873
874    /// Returns an iterator over the entries within a directory.
875    /// 
876    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_dir`].  
877    /// See [`PrivateStorage::resolve_path`] for details.  
878    /// 
879    /// # Support
880    /// All Android version.
881    fn read_dir(
882        &self,
883        base_dir: PrivateDir,
884        relative_path: Option<&str>,
885    ) -> crate::Result<std::fs::ReadDir> {
886
887        let path = match relative_path {
888            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
889            None => self.resolve_path(base_dir)?,
890        };
891
892        Ok(std::fs::read_dir(path)?)
893    }
894
895    /// Removes a file from the filesystem.  
896    /// 
897    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_file`].  
898    /// See [`PrivateStorage::resolve_path`] for details.  
899    /// 
900    /// # Support
901    /// All Android version.
902    fn remove_file(
903        &self,
904        base_dir: PrivateDir,
905        relative_path: impl AsRef<str>,
906    ) -> crate::Result<()> {
907
908        let path = self.resolve_path_with(base_dir, relative_path)?;
909        Ok(std::fs::remove_file(path)?)
910    }
911
912    /// Removes an empty directory.  
913    /// If you want to remove a directory that is not empty, as well as all of its contents recursively, consider using [`PrivateStorage::remove_dir_all`] instead.  
914    /// 
915    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir`].  
916    /// See [`PrivateStorage::resolve_path`] for details.  
917    /// 
918    /// # Support
919    /// All Android version.
920    fn remove_dir(
921        &self,
922        base_dir: PrivateDir,
923        relative_path: Option<&str>,
924    ) -> crate::Result<()> {
925
926        let path = match relative_path {
927            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
928            None => self.resolve_path(base_dir)?,
929        };
930
931        std::fs::remove_dir(path)?;
932        Ok(())
933    }
934
935    /// Removes a directory at this path, after removing all its contents. Use carefully!  
936    /// 
937    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir_all`].  
938    /// See [`PrivateStorage::resolve_path`] for details.  
939    /// 
940    /// # Support
941    /// All Android version.
942    fn remove_dir_all(
943        &self,
944        base_dir: PrivateDir,
945        relative_path: Option<&str>,
946    ) -> crate::Result<()> {
947
948        let path = match relative_path {
949            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
950            None => self.resolve_path(base_dir)?,
951        };
952
953        std::fs::remove_dir_all(path)?;
954        Ok(())
955    }
956
957    /// Returns Ok(true) if the path points at an existing entity.  
958    /// 
959    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::exists`].  
960    /// See [`PrivateStorage::resolve_path`] for details.  
961    /// 
962    /// # Support
963    /// All Android version.
964    fn exists(
965        &self,
966        base_dir: PrivateDir,
967        relative_path: impl AsRef<str>
968    ) -> crate::Result<bool> {
969
970        let path = self.resolve_path_with(base_dir, relative_path)?;
971        Ok(std::fs::exists(path)?)
972    }
973
974    /// Queries the file system to get information about a file, directory.  
975    /// 
976    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::metadata`].  
977    /// See [`PrivateStorage::resolve_path`] for details.  
978    /// 
979    /// # Support
980    /// All Android version.
981    fn metadata(
982        &self,
983        base_dir: PrivateDir,
984        relative_path: Option<&str>,
985    ) -> crate::Result<std::fs::Metadata> {
986
987        let path = match relative_path {
988            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
989            None => self.resolve_path(base_dir)?,
990        };
991
992        Ok(std::fs::metadata(path)?)
993    }
994
995    fn app_handle(&self) -> &tauri::AppHandle<R>;
996}