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}