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}