tauri_plugin_android_fs/models.rs
1use serde::{Deserialize, Serialize};
2use crate::{Error, Result};
3
4/// Path to represent a file or directory.
5///
6/// # Note
7/// For compatibility, an interconversion to [`tauri_plugin_fs::FilePath`] is implemented, such as follwing.
8/// This is lossy and also not guaranteed to work properly with other plugins.
9/// However, reading and writing files by official [`tauri_plugin_fs`] etc. should work well.
10/// ```no_run
11/// use tauri_plugin_android_fs::FileUri;
12/// use tauri_plugin_fs::FilePath;
13///
14/// let uri: FileUri = unimplemented!();
15/// let path: FilePath = uri.into();
16/// let uri: FileUri = path.into();
17/// ```
18///
19/// # Typescript type
20/// ```typescript
21/// type FileUri = {
22/// uri: string, // This can use as path for official tauri_plugin_fs
23/// documentTopTreeUri: string | null
24/// }
25/// ```
26#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
27#[serde(rename_all = "camelCase")]
28pub struct FileUri {
29 /// `file://` or `content://` URI of file or directory.
30 pub uri: String,
31
32 /// Only files/directories under the directory obtained by `FilePicker::pick_dir` will own this.
33 pub document_top_tree_uri: Option<String>,
34}
35
36impl FileUri {
37
38 pub fn to_string(&self) -> crate::Result<String> {
39 serde_json::to_string(self).map_err(Into::into)
40 }
41
42 pub fn from_str(s: &str) -> crate::Result<Self> {
43 serde_json::from_str(s).map_err(Into::into)
44 }
45
46 pub fn from_path(path: impl AsRef<std::path::Path>) -> Self {
47 Self { uri: format!("file://{}", path.as_ref().to_string_lossy()), document_top_tree_uri: None }
48 }
49
50 #[allow(unused)]
51 pub(crate) fn as_path(&self) -> Option<&std::path::Path> {
52 if self.uri.starts_with("file://") {
53 return Some(std::path::Path::new(self.uri.trim_start_matches("file://")))
54 }
55 None
56 }
57}
58
59impl From<&std::path::Path> for FileUri {
60
61 fn from(path: &std::path::Path) -> Self {
62 Self::from_path(path)
63 }
64}
65
66impl From<&std::path::PathBuf> for FileUri {
67
68 fn from(path: &std::path::PathBuf) -> Self {
69 Self::from_path(path)
70 }
71}
72
73impl From<std::path::PathBuf> for FileUri {
74
75 fn from(path: std::path::PathBuf) -> Self {
76 Self::from_path(path)
77 }
78}
79
80impl From<tauri_plugin_fs::FilePath> for FileUri {
81
82 fn from(value: tauri_plugin_fs::FilePath) -> Self {
83 match value {
84 tauri_plugin_fs::FilePath::Url(url) => Self { uri: url.to_string(), document_top_tree_uri: None },
85 tauri_plugin_fs::FilePath::Path(path_buf) => path_buf.into(),
86 }
87 }
88}
89
90impl From<FileUri> for tauri_plugin_fs::FilePath {
91
92 fn from(value: FileUri) -> Self {
93 type NeverErr<T> = std::result::Result::<T, std::convert::Infallible>;
94 NeverErr::unwrap(value.uri.parse())
95 }
96}
97
98#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
99#[serde(rename_all = "camelCase")]
100pub struct StorageVolume {
101
102 /// A user-visible description of the volume.
103 /// This can be determined by the manufacturer and is often localized according to the user’s language.
104 ///
105 /// e.g.
106 /// - `Internal shared storage`
107 /// - `SDCARD`
108 /// - `SD card`
109 /// - `Virtual SD card`
110 pub description: String,
111
112 /// Indicates whether this is primary storage volume.
113 /// A device always has one (and one only) primary storage volume.
114 pub is_primary: bool,
115
116 /// Indicates whether this is physically removable.
117 /// If `false`, this is device's built-in storage.
118 pub is_removable: bool,
119
120 /// Indicates whether thit is stable part of the device.
121 ///
122 /// For example, a device’s built-in storage and physical media slots under protective covers are considered stable,
123 /// while USB flash drives connected to handheld devices are not.
124 pub is_stable: bool,
125
126 /// Indicates whether this is backed by private user data partition,
127 /// either internal storage or [adopted storage](https://source.android.com/docs/core/storage/adoptable).
128 pub is_emulated: bool,
129
130 /// Indicates whether this is readonly storage volume.
131 ///
132 /// e.g. SD card with readonly mode.
133 ///
134 /// # Remark
135 /// As far as I understand, this should never be `true`
136 /// when either `is_primary` or `is_emulated` is true,
137 /// or when `is_removable` is false,
138 /// but it might not be the case due to any issues or rare cases.
139 pub is_readonly: bool,
140
141 pub id: StorageVolumeId
142}
143
144#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
145#[serde(rename_all = "camelCase")]
146pub struct StorageVolumeId {
147 pub(crate) top_directory_path: Option<std::path::PathBuf>,
148 pub(crate) media_store_volume_name: Option<String>,
149 pub(crate) private_data_dir_path: Option<std::path::PathBuf>,
150 pub(crate) private_cache_dir_path: Option<std::path::PathBuf>,
151 pub(crate) uuid: Option<String>,
152}
153
154#[allow(unused)]
155impl StorageVolumeId {
156
157 pub(crate) fn outside_private_dir_path(&self, dir: OutsidePrivateDir) -> Option<&std::path::PathBuf> {
158 match dir {
159 OutsidePrivateDir::Data => self.private_data_dir_path.as_ref(),
160 OutsidePrivateDir::Cache => self.private_cache_dir_path.as_ref(),
161 }
162 }
163}
164
165#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
166#[serde(rename_all = "camelCase")]
167pub enum Entry {
168
169 #[non_exhaustive]
170 File {
171 uri: FileUri,
172 name: String,
173 last_modified: std::time::SystemTime,
174 len: u64,
175 mime_type: String,
176 },
177
178 #[non_exhaustive]
179 Dir {
180 uri: FileUri,
181 name: String,
182 last_modified: std::time::SystemTime,
183 }
184}
185
186#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
187#[serde(rename_all = "camelCase")]
188pub enum EntryType {
189 File {
190 mime_type: String
191 },
192 Dir,
193}
194
195impl EntryType {
196
197 pub fn is_file(&self) -> bool {
198 matches!(self, Self::File { .. })
199 }
200
201 pub fn is_dir(&self) -> bool {
202 matches!(self, Self::Dir)
203 }
204
205 /// If file, this is no None.
206 /// If directory, this is None.
207 pub fn mime_type(&self) -> Option<&str> {
208 match self {
209 EntryType::File { mime_type } => Some(&mime_type),
210 EntryType::Dir => None,
211 }
212 }
213
214 /// If file, this is no None.
215 /// If directory, this is None.
216 pub fn into_mime_type(self) -> Option<String> {
217 match self {
218 EntryType::File { mime_type } => Some(mime_type),
219 EntryType::Dir => None,
220 }
221 }
222}
223
224/// Access mode
225#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
226pub enum PersistableAccessMode {
227
228 /// Read access.
229 Read,
230
231 /// Write access.
232 Write,
233
234 /// Read-write access.
235 ReadAndWrite,
236}
237
238#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
239pub enum PersistedUriPermission {
240 File {
241 uri: FileUri,
242 can_read: bool,
243 can_write: bool,
244 },
245 Dir {
246 uri: FileUri,
247 can_read: bool,
248 can_write: bool,
249 }
250}
251
252impl PersistedUriPermission {
253
254 pub fn uri(&self) -> &FileUri {
255 match self {
256 PersistedUriPermission::File { uri, .. } => uri,
257 PersistedUriPermission::Dir { uri, .. } => uri,
258 }
259 }
260
261 pub fn into_uri(self) -> FileUri {
262 match self {
263 PersistedUriPermission::File { uri, .. } => uri,
264 PersistedUriPermission::Dir { uri, .. } => uri,
265 }
266 }
267
268 pub fn can_read(&self) -> bool {
269 match self {
270 PersistedUriPermission::File { can_read, .. } => *can_read,
271 PersistedUriPermission::Dir { can_read, .. } => *can_read,
272 }
273 }
274
275 pub fn can_write(&self) -> bool {
276 match self {
277 PersistedUriPermission::File { can_write, .. } => *can_write,
278 PersistedUriPermission::Dir { can_write, .. } => *can_write,
279 }
280 }
281
282 pub fn is_file(&self) -> bool {
283 matches!(self, PersistedUriPermission::File { .. })
284 }
285
286 pub fn is_dir(&self) -> bool {
287 matches!(self, PersistedUriPermission::Dir { .. })
288 }
289}
290
291#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
292pub struct Size {
293 pub width: u32,
294 pub height: u32
295}
296
297#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
298#[non_exhaustive]
299pub enum ImageFormat {
300
301 /// - Loss less
302 /// - Support transparency
303 Png,
304
305 /// - Lossy
306 /// - Unsupport transparency
307 Jpeg,
308
309 /// - Lossy (**Not loss less**)
310 /// - Support transparency
311 Webp,
312
313 /// - Lossy
314 /// - Unsupport transparency
315 JpegWith {
316
317 /// Range is `0.0 ~ 1.0`
318 /// 0.0 means compress for the smallest size.
319 /// 1.0 means compress for max visual quality.
320 quality: f32
321 },
322
323 /// - Lossy
324 /// - Support transparency
325 WebpWith {
326
327 /// Range is `0.0 ~ 1.0`
328 /// 0.0 means compress for the smallest size.
329 /// 1.0 means compress for max visual quality.
330 quality: f32
331 }
332}
333
334/// Access mode
335#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
336#[non_exhaustive]
337pub enum FileAccessMode {
338
339 /// Opens the file in read-only mode.
340 ///
341 /// FileDescriptor mode: "r"
342 Read,
343
344 /// Opens the file in write-only mode.
345 ///
346 /// Since Android 10, this may or may not truncate existing contents.
347 /// If the new file is smaller than the old one, **this may cause the file to become corrupted**.
348 /// <https://issuetracker.google.com/issues/180526528>
349 ///
350 /// The reason this is marked as deprecated is because of that behavior,
351 /// and it is not scheduled to be removed in the future.
352 ///
353 /// FileDescriptor mode: "w"
354 #[deprecated(note = "This may or may not truncate existing contents. If the new file is smaller than the old one, this may cause the file to become corrupted.")]
355 Write,
356
357 /// Opens the file in write-only mode.
358 /// The existing content is truncated (deleted), and new data is written from the beginning.
359 ///
360 /// FileDescriptor mode: "wt"
361 WriteTruncate,
362
363 /// Opens the file in write-only mode.
364 /// The existing content is preserved, and new data is appended to the end of the file.
365 ///
366 /// FileDescriptor mode: "wa"
367 WriteAppend,
368
369 /// Opens the file in read-write mode.
370 ///
371 /// FileDescriptor mode: "rw"
372 ReadWrite,
373
374 /// Opens the file in read-write mode.
375 /// The existing content is truncated (deleted), and new data is written from the beginning.
376 ///
377 /// FileDescriptor mode: "rwt"
378 ReadWriteTruncate,
379}
380
381#[allow(unused)]
382#[allow(deprecated)]
383impl FileAccessMode {
384
385 pub(crate) fn to_mode(&self) -> &'static str {
386 match self {
387 FileAccessMode::Read => "r",
388 FileAccessMode::Write => "w",
389 FileAccessMode::WriteTruncate => "wt",
390 FileAccessMode::WriteAppend => "wa",
391 FileAccessMode::ReadWriteTruncate => "rwt",
392 FileAccessMode::ReadWrite => "rw",
393 }
394 }
395
396 pub(crate) fn from_mode(mode: &str) -> Result<Self> {
397 match mode {
398 "r" => Ok(Self::Read),
399 "w" => Ok(Self::Write),
400 "wt" => Ok(Self::WriteTruncate),
401 "wa" => Ok(Self::WriteAppend),
402 "rwt" => Ok(Self::ReadWriteTruncate),
403 "rw" => Ok(Self::ReadWrite),
404 mode => Err(Error { msg: format!("Illegal mode: {mode}").into() })
405 }
406 }
407}
408
409/// Filters for VisualMediaPicker.
410#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
411#[non_exhaustive]
412pub enum VisualMediaTarget<'a> {
413
414 /// Allow only images to be selected.
415 ImageOnly,
416
417 /// Allow only videos to be selected.
418 VideoOnly,
419
420 /// Allow only images and videos to be selected.
421 ImageAndVideo,
422
423 /// Allow only images or videos of specified single Mime type to be selected.
424 ImageOrVideo {
425 mime_type: &'a str
426 }
427}
428
429/// The application specific directory.
430#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
431#[non_exhaustive]
432pub enum PrivateDir {
433
434 /// The application specific persistent-data directory.
435 ///
436 /// Files stored in this directory are included in [Android Auto Backup](https://developer.android.com/identity/data/autobackup).
437 ///
438 /// The system prevents other apps and user from accessing these locations.
439 /// In cases where the device is rooted or the user has special permissions, the user may be able to access this.
440 ///
441 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
442 ///
443 /// e.g. `/data/user/0/{app-package-name}/files`
444 ///
445 /// <https://developer.android.com/reference/android/content/Context#getFilesDir()>
446 Data,
447
448 /// The application specific cache directory.
449 ///
450 /// Files stored in this directory are **not** included in [Android Auto Backup](https://developer.android.com/identity/data/autobackup).
451 ///
452 /// The system prevents other apps and user from accessing these locations.
453 /// In cases where the device is rooted or the user has special permissions, the user may be able to access this.
454 ///
455 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
456 /// In addition, the system will automatically delete files in this directory as disk space is needed elsewhere on the device.
457 ///
458 /// e.g. `/data/user/0/{app-package-name}/cache`
459 ///
460 /// <https://developer.android.com/reference/android/content/Context#getCacheDir()>
461 Cache,
462
463 /// The application specific persistent-data directory.
464 ///
465 /// This is similar to [`PrivateDir::Data`].
466 /// But files stored in this directory are **not** included in [Android Auto Backup](https://developer.android.com/identity/data/autobackup).
467 ///
468 /// The system prevents other apps and user from accessing these locations.
469 /// In cases where the device is rooted or the user has special permissions, the user may be able to access this.
470 ///
471 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
472 ///
473 /// e.g. `/data/user/0/{app-package-name}/no_backup`
474 ///
475 /// <https://developer.android.com/reference/android/content/Context#getNoBackupFilesDir()>
476 NoBackupData,
477}
478
479/// The application specific directory.
480#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
481#[non_exhaustive]
482pub enum OutsidePrivateDir {
483
484 /// The application specific persistent-data directory.
485 ///
486 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
487 Data,
488
489 /// The application specific cache directory.
490 ///
491 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
492 Cache,
493}
494
495#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
496#[non_exhaustive]
497pub enum PublicDir {
498
499 #[serde(untagged)]
500 Image(PublicImageDir),
501
502 #[serde(untagged)]
503 Video(PublicVideoDir),
504
505 #[serde(untagged)]
506 Audio(PublicAudioDir),
507
508 #[serde(untagged)]
509 GeneralPurpose(PublicGeneralPurposeDir),
510}
511
512/// Directory in which to place images that are available to other applications and users.
513#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
514#[non_exhaustive]
515pub enum PublicImageDir {
516
517 /// Standard directory in which to place pictures that are available to the user.
518 ///
519 /// e.g. `~/Pictures`
520 Pictures,
521
522 /// The traditional location for pictures and videos when mounting the device as a camera.
523 ///
524 /// e.g. `~/DCIM`
525 DCIM,
526}
527
528/// Directory in which to place videos that are available to other applications and users.
529#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
530#[non_exhaustive]
531pub enum PublicVideoDir {
532
533 /// Standard directory in which to place movies that are available to the user.
534 ///
535 /// e.g. `~/Movies`
536 Movies,
537
538 /// The traditional location for pictures and videos when mounting the device as a camera.
539 ///
540 /// e.g. `~/DCIM`
541 DCIM,
542}
543
544/// Directory in which to place audios that are available to other applications and users.
545#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
546#[non_exhaustive]
547pub enum PublicAudioDir {
548
549 /// Standard directory in which to place movies that are available to the user.
550 ///
551 /// e.g. `~/Music`
552 Music,
553
554 /// Standard directory in which to place any audio files that should be in the list of alarms that the user can select (not as regular music).
555 ///
556 /// e.g. `~/Alarms`
557 Alarms,
558
559 /// Standard directory in which to place any audio files that should be in the list of audiobooks that the user can select (not as regular music).
560 ///
561 /// This is not available on Android 9 (API level 28) and lower.
562 ///
563 /// e.g. `~/Audiobooks`
564 Audiobooks,
565
566 /// Standard directory in which to place any audio files that should be in the list of notifications that the user can select (not as regular music).
567 ///
568 /// e.g. `~/Notifications`
569 Notifications,
570
571 /// Standard directory in which to place any audio files that should be in the list of podcasts that the user can select (not as regular music).
572 ///
573 /// e.g. `~/Podcasts`
574 Podcasts,
575
576 /// Standard directory in which to place any audio files that should be in the list of ringtones that the user can select (not as regular music).
577 ///
578 /// e.g. `~/Ringtones`
579 Ringtones,
580
581 /// Standard directory in which to place any audio files that should be in the list of voice recordings recorded by voice recorder apps that the user can select (not as regular music).
582 ///
583 /// This is not available on Android 11 (API level 30) and lower.
584 ///
585 /// e.g. `~/Recordings`
586 Recordings,
587}
588
589/// Directory in which to place files that are available to other applications and users.
590#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Deserialize, Serialize)]
591#[non_exhaustive]
592pub enum PublicGeneralPurposeDir {
593
594 /// Standard directory in which to place documents that have been created by the user.
595 ///
596 /// e.g. `~/Documents`
597 Documents,
598
599 /// Standard directory in which to place files that have been downloaded by the user.
600 ///
601 /// e.g. `~/Download`
602 ///
603 /// This is not the plural "Downloads", but the singular "Download".
604 /// <https://developer.android.com/reference/android/os/Environment#DIRECTORY_DOWNLOADS>
605 Download,
606}
607
608impl std::fmt::Display for PublicImageDir {
609 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610 match self {
611 PublicImageDir::Pictures => write!(f, "Pictures"),
612 PublicImageDir::DCIM => write!(f, "DCIM"),
613 }
614 }
615}
616
617impl std::fmt::Display for PublicVideoDir {
618 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619 match self {
620 PublicVideoDir::Movies => write!(f, "Movies"),
621 PublicVideoDir::DCIM => write!(f, "DCIM"),
622 }
623 }
624}
625
626impl std::fmt::Display for PublicAudioDir {
627 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628 match self {
629 PublicAudioDir::Music => write!(f, "Music"),
630 PublicAudioDir::Alarms => write!(f, "Alarms"),
631 PublicAudioDir::Audiobooks => write!(f, "Audiobooks"),
632 PublicAudioDir::Notifications => write!(f, "Notifications"),
633 PublicAudioDir::Podcasts => write!(f, "Podcasts"),
634 PublicAudioDir::Ringtones => write!(f, "Ringtones"),
635 PublicAudioDir::Recordings => write!(f, "Recordings"),
636 }
637 }
638}
639
640impl std::fmt::Display for PublicGeneralPurposeDir {
641 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
642 match self {
643 PublicGeneralPurposeDir::Documents => write!(f, "Documents"),
644 PublicGeneralPurposeDir::Download => write!(f, "Download"),
645 }
646 }
647}
648
649impl std::fmt::Display for PublicDir {
650 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
651 match self {
652 PublicDir::Image(p) => p.fmt(f),
653 PublicDir::Video(p) => p.fmt(f),
654 PublicDir::Audio(p) => p.fmt(f),
655 PublicDir::GeneralPurpose(p) => p.fmt(f),
656 }
657 }
658}
659
660macro_rules! impl_into_pubdir {
661 ($target: ident, $wrapper: ident) => {
662 impl From<$target> for PublicDir {
663 fn from(value: $target) -> Self {
664 Self::$wrapper(value)
665 }
666 }
667 };
668}
669impl_into_pubdir!(PublicImageDir, Image);
670impl_into_pubdir!(PublicVideoDir, Video);
671impl_into_pubdir!(PublicAudioDir, Audio);
672impl_into_pubdir!(PublicGeneralPurposeDir, GeneralPurpose);