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::*;
10#[allow(deprecated)]
11pub use error::{Error, Result, PathError};
12pub use impls::{AndroidFsExt, init};
13
14/// API
15pub trait AndroidFs {
16
17    /// Verify whether this plugin is available.  
18    /// 
19    /// On Android, this returns true.  
20    /// On other platforms, this returns false.  
21    fn is_available(&self) -> bool {
22        #[cfg(not(target_os = "android"))] {
23            false
24        }
25        #[cfg(target_os = "android")] {
26            true
27        }
28    }
29
30    /// Get the file or directory name.  
31    /// 
32    /// # Support
33    /// All Android version.
34    fn get_name(&self, uri: &FileUri) -> crate::Result<String>;
35
36    /// Query the provider to get mime type.  
37    /// If the directory, this returns `None`.  
38    /// If the file, this returns no `None`.  
39    /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.  
40    ///
41    /// # Support
42    /// All Android version.
43    fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>>;
44
45    /// Queries the file system to get information about a file, directory.
46    /// 
47    /// # Note
48    /// This uses [`AndroidFs::open_file`] internally.
49    /// 
50    /// # Support
51    /// All Android version.
52    fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
53        let file = self.open_file(uri, FileAccessMode::Read)?;
54        Ok(file.metadata()?)
55    }
56
57    /// Open a file in the specified mode.
58    /// 
59    /// # Note
60    /// Other than [`FileAccessMode::Read`] is only for **writable** uri.
61    /// 
62    /// # Support
63    /// All Android version.
64    fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File>;
65
66    /// Reads the entire contents of a file into a bytes vector.  
67    /// 
68    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
69    /// 
70    /// # Support
71    /// All Android version.
72    fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
73        let mut file = self.open_file(uri, FileAccessMode::Read)?;
74        let mut buf = file.metadata().ok()
75            .map(|m| m.len() as usize)
76            .map(Vec::with_capacity)
77            .unwrap_or_else(Vec::new);
78
79        file.read_to_end(&mut buf)?;
80        Ok(buf)
81    }
82
83    /// Reads the entire contents of a file into a string.  
84    /// 
85    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
86    /// 
87    /// # Support
88    /// All Android version.
89    fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
90        let mut file = self.open_file(uri, FileAccessMode::Read)?;
91        let mut buf = file.metadata().ok()
92            .map(|m| m.len() as usize)
93            .map(String::with_capacity)
94            .unwrap_or_else(String::new);
95    
96        file.read_to_string(&mut buf)?;
97        Ok(buf)
98    }
99
100    /// Writes a slice as the entire contents of a file.  
101    /// This function will entirely replace its contents if it does exist.    
102    /// 
103    /// If you want to operate the file, use [`AndroidFs::open_file`] instead.  
104    /// 
105    /// # Note
106    /// This is only for **writable** file uri.
107    /// 
108    /// # Support
109    /// All Android version.
110    fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
111        let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
112        file.write_all(contents.as_ref())?;
113        Ok(())
114    }
115
116    /// Remove the file.
117    /// 
118    /// # Note
119    /// This is only for **removeable** uri.
120    /// 
121    /// # Support
122    /// All Android version.
123    fn remove_file(&self, uri: &FileUri) -> crate::Result<()>;
124
125    /// Remove the **empty** directory.
126    /// 
127    /// # Note
128    /// This is only for **removeable** uri.
129    /// 
130    /// # Support
131    /// All Android version.
132    fn remove_dir(&self, uri: &FileUri) -> crate::Result<()>;
133
134    /// Creates a new file at the specified directory, and returns **read-write-removeable** uri.    
135    /// If the same file name already exists, a sequential number is added to the name. And recursively create sub directories if they are missing.  
136    /// 
137    /// If you want to create a new file without a `FileUri`, use [`AndroidFs::create_file_in_public_location`].
138    /// 
139    /// # Note
140    /// `mime_type`  specify the type of the file to be created. 
141    /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.  
142    /// 
143    /// # Support
144    /// All Android version.
145    fn create_file(
146        &self,
147        dir: &FileUri, 
148        relative_path: impl AsRef<str>, 
149        mime_type: Option<&str>
150    ) -> crate::Result<FileUri>;
151
152    /// Creates a new file at the specified public location, and returns **read-write-removeable** uri.    
153    /// If the same file name already exists, a sequential number is added to the name. And recursively create sub directories if they are missing.  
154    /// 
155    /// # Note
156    /// `mime_type`  specify the type of the file to be created. 
157    /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.  
158    /// 
159    /// # Support
160    /// All Android version.
161    fn create_file_in_public_location(
162        &self,
163        dir: impl Into<PublicDir>,
164        relative_path: impl AsRef<str>, 
165        mime_type: Option<&str>
166    ) -> crate::Result<FileUri>;
167
168    /// Returns the unordered child entries of the specified directory.  
169    /// Returned [`Entry`](crate::Entry) contains file or directory uri.
170    ///
171    /// # Note
172    /// By default, children are valid until the app is terminated.  
173    /// To persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`]. 
174    /// However, if you have obtained persistent permissions for the origin directory (e.g. parent, grand parents), it is unnecessary.
175    /// 
176    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
177    /// 
178    /// # Support
179    /// All Android version.
180    fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>>;
181
182    /// Take persistent permission to access the file, directory and its descendants.  
183    /// 
184    /// Preserve access across app and device restarts. 
185    /// If you only need it until the end of the app session, you do not need to use this.  
186    /// 
187    /// This works by just calling, without displaying any confirmation to the user.  
188    /// 
189    /// # Note
190    /// Even after calling this, the app doesn't retain access to the entry if it is moved or deleted.  
191    /// 
192    /// # Support
193    /// All Android version.
194    fn take_persistable_uri_permission(&self, uri: FileUri, mode: PersistableAccessMode) -> crate::Result<()>;
195
196    /// Open a dialog for file selection.  
197    /// This returns a **read-only** uris. If no file is selected or canceled by user, it is an empty.  
198    /// 
199    /// For images and videos, consider using [`AndroidFs::show_open_visual_media_dialog`] instead.  
200    /// 
201    /// # Issue
202    /// **Dialog has an issue. Details and resolution are following.**  
203    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
204    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
205    /// 
206    /// # Note
207    /// `mime_types` represents the types of files that should be selected. 
208    /// However, there is no guarantee that the returned file will match the specified types.    
209    /// If this is empty, all file types will be available for selection. 
210    /// This is equivalent to `["*/*"]`, and it will invoke the standard file picker in most cases.  
211    /// 
212    /// By default, returned uri is valid until the app is terminated. 
213    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
214    /// 
215    /// # Support
216    /// All Android version.
217    fn show_open_file_dialog(
218        &self,
219        mime_types: &[&str],
220        multiple: bool
221    ) -> crate::Result<Vec<FileUri>>;
222
223    /// Opens a dialog for media file selection, such as images and videos.  
224    /// This returns a **read-only** uris. If no file is selected or canceled by user, it is an empty.  
225    /// 
226    /// This is more user-friendly than [`AndroidFs::show_open_file_dialog`].  
227    ///
228    /// # Issue
229    /// **Dialog has an issue. Details and resolution are following.**  
230    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
231    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
232    /// 
233    /// # Note
234    /// By default, returned uri is valid until the app is terminated. 
235    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].  
236    /// 
237    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
238    /// Instead, it will be assigned a sequential number, such as `1000091523.png`.  
239    /// <https://issuetracker.google.com/issues/268079113>  
240    ///
241    /// # Support
242    /// This is available on devices that meet the following criteria:
243    ///  - Run Android 11 (API level 30) or higher
244    ///  - Receive changes to Modular System Components through Google System Updates
245    ///  
246    /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].  
247    /// If not supported, this functions the same as [`AndroidFs::show_open_file_dialog`].
248    fn show_open_visual_media_dialog(
249        &self,
250        target: VisualMediaTarget,
251        multiple: bool
252    ) -> crate::Result<Vec<FileUri>>;
253
254    /// Open a dialog for directory selection,
255    /// allowing the app to read and write any file in the selected directory and its subdirectories.  
256    /// If canceled by the user, return None.    
257    /// 
258    /// # Issue
259    /// **Dialog has an issue. Details and resolution are following.**  
260    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
261    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
262    /// 
263    /// # Note
264    /// By default, retruned uri is valid until the app is terminated. 
265    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
266    /// If you take permission for a directory, you can recursively obtain it for its descendants.
267    /// 
268    /// # Support
269    /// All Android version.
270    fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>>;
271
272    /// Open a dialog for file saving, and return the selected path.  
273    /// This returns a **read-write-removeable** uri. If canceled by the user, return None.    
274    /// 
275    /// When storing media files such as images, videos, and audio, consider using [`AndroidFs::create_file_in_public_location`].  
276    /// When a file does not need to be accessed by other applications and users, consider using  [`PrivateStorage::write`].  
277    /// These are easier because the destination does not need to be selected in a dialog.  
278    /// 
279    /// # Issue
280    /// **Dialog has an issue. Details and resolution are following.**  
281    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
282    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
283    /// 
284    /// # Note
285    /// `mime_type` specify the type of the target file to be saved. 
286    /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.  
287    /// 
288    /// By default, returned uri is valid until the app is terminated. 
289    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
290    /// 
291    /// # Support
292    /// All Android version.
293    fn show_save_file_dialog(
294        &self,
295        default_file_name: impl AsRef<str>,
296        mime_type: Option<&str>,
297    ) -> crate::Result<Option<FileUri>>;
298
299    /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
300    /// 
301    /// # Support
302    /// All Android version.
303    fn is_visual_media_dialog_available(&self) -> crate::Result<bool>;
304
305    /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
306    /// 
307    /// # Support
308    /// All Android version.
309    fn is_public_audiobooks_dir_available(&self) -> crate::Result<bool>;
310
311    /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
312    /// 
313    /// # Support
314    /// All Android version.
315    fn is_public_recordings_dir_available(&self) -> crate::Result<bool>;
316
317    /// File storage API intended for the app’s use only.
318    fn private_storage(&self) -> &impl PrivateStorage;
319}
320
321/// File storage intended for the app’s use only.  
322pub trait PrivateStorage {
323
324    /// Get the absolute path of the specified directory.  
325    /// Apps require no permissions to read or write to the returned path, since this path lives in their private storage.  
326    ///
327    /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request. 
328    /// When using [`PrivateDir::Cache`], the system will automatically delete files in this directory as disk space is needed elsewhere on the device.   
329    /// 
330    /// The returned path may change over time if the calling app is moved to an adopted storage device, so only relative paths should be persisted.   
331    /// 
332    /// # Examples
333    /// ```no_run
334    /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, PrivateDir, PrivateStorage};
335    /// 
336    /// fn example(app: tauri::AppHandle) {
337    ///     let api = app.android_fs().private_storage();
338    /// 
339    ///     let dir_path = api.resolve_path(PrivateDir::Data).unwrap();
340    ///     let file_path = dir_path.join("2025-2-12/data.txt");
341    ///     
342    ///     // Write file
343    ///     std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
344    ///     std::fs::write(&file_path, "aaaa").unwrap();
345    /// 
346    ///     // Read file
347    ///     let _ = std::fs::read_to_string(&file_path).unwrap();
348    /// 
349    ///     // Remove file
350    ///     std::fs::remove_file(&file_path).unwrap();
351    /// }
352    /// ```
353    /// 
354    /// # Support
355    /// All Android version.
356    fn resolve_path(&self, dir: PrivateDir) -> crate::Result<std::path::PathBuf>;
357
358    /// Get the absolute path of the specified relative path and base directory.  
359    /// Apps require no extra permissions to read or write to the returned path, since this path lives in their private storage.  
360    ///
361    /// See [`PrivateStorage::resolve_path`] for details.  
362    /// 
363    /// # Support
364    /// All Android version.
365    fn resolve_path_with(
366        &self,
367        dir: PrivateDir,
368        relative_path: impl AsRef<str>
369    ) -> crate::Result<std::path::PathBuf> {
370
371        let relative_path = relative_path.as_ref().trim_start_matches('/');
372        let path = self.resolve_path(dir)?.join(relative_path);
373        Ok(path)
374    }
375
376    fn resolve_uri(&self, dir: PrivateDir) -> crate::Result<FileUri> {
377        Ok(FileUri::from(tauri_plugin_fs::FilePath::Path(self.resolve_path(dir)?)))
378    }
379
380    fn resolve_uri_with(&self, dir: PrivateDir, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
381        Ok(FileUri::from(tauri_plugin_fs::FilePath::Path(self.resolve_path_with(dir, relative_path)?)))
382    }
383
384    /// Writes a slice as the entire contents of a file.  
385    /// 
386    /// This function will create a file if it does not exist, and will entirely replace its contents if it does.  
387    /// Recursively create parent directories if they are missing.  
388    /// 
389    /// This internally uses [`PrivateStorage::resolve_path`] , [`std::fs::create_dir_all`], and [`std::fs::write`].  
390    /// See [`PrivateStorage::resolve_path`] for details.  
391    /// 
392    /// # Support
393    /// All Android version.
394    fn write(
395        &self, 
396        base_dir: PrivateDir, 
397        relative_path: impl AsRef<str>, 
398        contents: impl AsRef<[u8]>
399    ) -> crate::Result<()> {
400
401        let path = self.resolve_path_with(base_dir, relative_path)?;
402
403        if let Some(parent_dir) = path.parent() {
404            std::fs::create_dir_all(parent_dir)?;
405        }
406
407        std::fs::write(path, contents)?;
408
409        Ok(())
410    }
411
412    /// Open a file in read-only mode.  
413    /// 
414    /// If you only need to read the entire file contents, consider using [`PrivateStorage::read`]  or [`PrivateStorage::read_to_string`] instead.  
415    /// 
416    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::open`].  
417    /// See [`PrivateStorage::resolve_path`] for details.  
418    /// 
419    /// # Support
420    /// All Android version.
421    fn open_file(
422        &self,
423        base_dir: PrivateDir, 
424        relative_path: impl AsRef<str>, 
425    ) -> crate::Result<std::fs::File> {
426
427        let path = self.resolve_path_with(base_dir, relative_path)?;
428        Ok(std::fs::File::open(path)?)
429    }
430
431    /// Opens a file in write-only mode.  
432    /// This function will create a file if it does not exist, and will truncate it if it does.
433    /// 
434    /// If you only need to write the contents, consider using [`PrivateStorage::write`]  instead.  
435    /// 
436    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create`].  
437    /// See [`PrivateStorage::resolve_path`] for details.  
438    /// 
439    /// # Support
440    /// All Android version.
441    fn create_file(
442        &self,
443        base_dir: PrivateDir, 
444        relative_path: impl AsRef<str>, 
445    ) -> crate::Result<std::fs::File> {
446
447        let path = self.resolve_path_with(base_dir, relative_path)?;
448        Ok(std::fs::File::create(path)?)
449    }
450
451    /// Reads the entire contents of a file into a bytes vector.  
452    /// 
453    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
454    /// 
455    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read`].  
456    /// See [`PrivateStorage::resolve_path`] for details.  
457    /// 
458    /// # Support
459    /// All Android version.
460    fn read(
461        &self,
462        base_dir: PrivateDir, 
463        relative_path: impl AsRef<str>, 
464    ) -> crate::Result<Vec<u8>> {
465
466        let path = self.resolve_path_with(base_dir, relative_path)?;
467        Ok(std::fs::read(path)?)
468    }
469
470    /// Reads the entire contents of a file into a string.  
471    /// 
472    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
473    /// 
474    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_to_string`].  
475    /// See [`PrivateStorage::resolve_path`] for details.  
476    /// 
477    /// # Support
478    /// All Android version.
479    fn read_to_string(
480        &self,
481        base_dir: PrivateDir,
482        relative_path: impl AsRef<str>, 
483    ) -> crate::Result<String> {
484
485        let path = self.resolve_path_with(base_dir, relative_path)?;
486        Ok(std::fs::read_to_string(path)?)
487    }
488
489    /// Returns an iterator over the entries within a directory.
490    /// 
491    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_dir`].  
492    /// See [`PrivateStorage::resolve_path`] for details.  
493    /// 
494    /// # Support
495    /// All Android version.
496    fn read_dir(
497        &self,
498        base_dir: PrivateDir,
499        relative_path: Option<&str>,
500    ) -> crate::Result<std::fs::ReadDir> {
501
502        let path = match relative_path {
503            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
504            None => self.resolve_path(base_dir)?,
505        };
506
507        Ok(std::fs::read_dir(path)?)
508    }
509
510    /// Removes a file from the filesystem.  
511    /// 
512    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_file`].  
513    /// See [`PrivateStorage::resolve_path`] for details.  
514    /// 
515    /// # Support
516    /// All Android version.
517    fn remove_file(
518        &self,
519        base_dir: PrivateDir,
520        relative_path: impl AsRef<str>,
521    ) -> crate::Result<()> {
522
523        let path = self.resolve_path_with(base_dir, relative_path)?;
524        Ok(std::fs::remove_file(path)?)
525    }
526
527    /// Removes an empty directory.  
528    /// 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.  
529    /// 
530    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir`].  
531    /// See [`PrivateStorage::resolve_path`] for details.  
532    /// 
533    /// # Support
534    /// All Android version.
535    fn remove_dir(
536        &self,
537        base_dir: PrivateDir,
538        relative_path: Option<&str>,
539    ) -> crate::Result<()> {
540
541        let path = match relative_path {
542            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
543            None => self.resolve_path(base_dir)?,
544        };
545
546        std::fs::remove_dir(path)?;
547        Ok(())
548    }
549
550    /// Removes a directory at this path, after removing all its contents. Use carefully!  
551    /// 
552    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir_all`].  
553    /// See [`PrivateStorage::resolve_path`] for details.  
554    /// 
555    /// # Support
556    /// All Android version.
557    fn remove_dir_all(
558        &self,
559        base_dir: PrivateDir,
560        relative_path: Option<&str>,
561    ) -> crate::Result<()> {
562
563        let path = match relative_path {
564            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
565            None => self.resolve_path(base_dir)?,
566        };
567
568        std::fs::remove_dir_all(path)?;
569        Ok(())
570    }
571
572    /// Returns Ok(true) if the path points at an existing entity.  
573    /// 
574    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::exists`].  
575    /// See [`PrivateStorage::resolve_path`] for details.  
576    /// 
577    /// # Support
578    /// All Android version.
579    fn exists(
580        &self,
581        base_dir: PrivateDir,
582        relative_path: impl AsRef<str>
583    ) -> crate::Result<bool> {
584
585        let path = self.resolve_path_with(base_dir, relative_path)?;
586        Ok(std::fs::exists(path)?)
587    }
588
589    /// Queries the file system to get information about a file, directory.  
590    /// 
591    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::metadata`].  
592    /// See [`PrivateStorage::resolve_path`] for details.  
593    /// 
594    /// # Support
595    /// All Android version.
596    fn metadata(
597        &self,
598        base_dir: PrivateDir,
599        relative_path: Option<&str>,
600    ) -> crate::Result<std::fs::Metadata> {
601
602        let path = match relative_path {
603            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
604            None => self.resolve_path(base_dir)?,
605        };
606
607        Ok(std::fs::metadata(path)?)
608    }
609}