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