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