tauri_plugin_android_fs/
models.rs

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