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);