tauri_plugin_android_fs/api/
public_storage.rs

1use crate::*;
2
3
4/// API of file storage that is available to other applications and users.  
5/// 
6/// # Examples
7/// ```
8/// fn example(app: &tauri::AppHandle) {
9///     use tauri_plugin_android_fs::AndroidFsExt as _;
10/// 
11///     let api = app.android_fs();
12///     let public_storage = api.public_storage();
13/// }
14/// ```
15pub struct PublicStorage<'a, R: tauri::Runtime>(
16    #[allow(unused)]
17    pub(crate) &'a AndroidFs<R>
18);
19
20impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
21
22    /// Creates a new empty file in the app dir of specified public directory
23    /// and returns a **persistent read-write** URI.  
24    ///  
25    /// The created file has following features :   
26    /// - Will be registered with the corresponding MediaStore as needed.  
27    /// - Always supports remove and rename by this app until the app uninstalled.
28    /// - Not removed when the app is uninstalled.
29    /// 
30    /// This is the same as following: 
31    /// ```ignore
32    /// let app_name = public_storage.app_dir_name()?;
33    /// public_storage.create_file(
34    ///     dir,
35    ///     format!("{app_name}/{relative_path}"),
36    ///     mime_type
37    /// )?;
38    /// ```
39    /// 
40    /// # Args
41    /// - ***dir*** :  
42    /// The base directory.  
43    /// When using [`PublicImageDir`], use only image MIME types for ***mime_type***, which is discussed below.; using other types may cause errors.
44    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
45    /// Only [`PublicGeneralPurposeDir`] supports all MIME types. 
46    ///  
47    /// - ***relative_path*** :  
48    /// The file path relative to the base directory.  
49    /// Any missing subdirectories in the specified path will be created automatically.  
50    /// If a file with the same name already exists, 
51    /// the system append a sequential number to ensure uniqueness.  
52    /// If no extension is present, 
53    /// the system may infer one from ***mime_type*** and may append it to the file name. 
54    /// But this append-extension operation depends on the model and version.
55    ///  
56    /// - ***mime_type*** :  
57    /// The MIME type of the file to be created.  
58    /// If this is None, MIME type is inferred from the extension of ***relative_path***
59    /// and if that fails, `application/octet-stream` is used.  
60    /// 
61    /// # Support
62    /// Android 10 (API level 29) or higher.  
63    ///
64    /// Note :  
65    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
66    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
67    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
68    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
69    /// - Others dirs are available in all Android versions.
70    pub fn create_file_in_app_dir(
71        &self,
72        dir: impl Into<PublicDir>,
73        relative_path: impl AsRef<str>, 
74        mime_type: Option<&str>
75    ) -> crate::Result<FileUri> {
76
77        on_android!({
78            let app_dir_name = self.app_dir_name()?;
79            let relative_path = relative_path.as_ref().trim_matches('/');
80            let relative_path_with_subdir = format!("{app_dir_name}/{relative_path}");
81
82            self.create_file(dir, relative_path_with_subdir, mime_type)
83        })
84    }
85
86    /// Creates a new empty file in the specified public directory
87    /// and returns a **persistent read-write** URI.  
88    ///  
89    /// The created file has following features :   
90    /// - Will be registered with the corresponding MediaStore as needed.  
91    /// - Always supports remove and rename by this app until the app uninstalled.
92    /// - Not removed when the app is uninstalled.
93    ///
94    /// # Args
95    /// - ***dir*** :  
96    /// The base directory.  
97    /// When using [`PublicImageDir`], use only image MIME types for ***mime_type***, which is discussed below.; using other types may cause errors.
98    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
99    /// Only [`PublicGeneralPurposeDir`] supports all MIME types. 
100    ///  
101    /// - ***relative_path_with_subdir*** :  
102    /// The file path relative to the base directory.  
103    /// Please specify a subdirectory in this, 
104    /// such as `MyApp/file.txt` or `MyApp/2025-2-11/file.txt`. Do not use `file.txt`.  
105    /// As shown above, it is customary to specify the app name at the beginning of the subdirectory, 
106    /// and in this case, using [`PublicStorage::create_file_in_app_dir`] is recommended.  
107    /// Any missing subdirectories in the specified path will be created automatically.  
108    /// If a file with the same name already exists, 
109    /// the system append a sequential number to ensure uniqueness.   
110    /// If no extension is present, 
111    /// the system may infer one from ***mime_type*** and may append it to the file name. 
112    /// But this append-extension operation depends on the model and version.
113    ///  
114    /// - ***mime_type*** :  
115    /// The MIME type of the file to be created.  
116    /// If this is None, MIME type is inferred from the extension of ***relative_path_with_subdir***
117    /// and if that fails, `application/octet-stream` is used.  
118    /// 
119    /// # Support
120    /// Android 10 (API level 29) or higher.  
121    ///
122    /// Note :  
123    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
124    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
125    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
126    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
127    /// - Others dirs are available in all Android versions.
128    pub fn create_file(
129        &self,
130        dir: impl Into<PublicDir>,
131        relative_path_with_subdir: impl AsRef<str>, 
132        mime_type: Option<&str>
133    ) -> crate::Result<FileUri> {
134
135        on_android!({
136            impl_se!(struct Req<'a> { dir: PublicDir, dir_type: &'a str });
137            impl_de!(struct Res { name: String, uri: String });
138
139            let dir = dir.into();
140            let dir_type = match dir {
141                PublicDir::Image(_) => "Image",
142                PublicDir::Video(_) => "Video",
143                PublicDir::Audio(_) => "Audio",
144                PublicDir::GeneralPurpose(_) => "GeneralPurpose",
145            };
146
147            let (dir_name, dir_parent_uri) = self.0.api
148                .run_mobile_plugin::<Res>("getPublicDirInfo", Req { dir, dir_type })
149                .map(|v| (v.name, v.uri))?;
150        
151            let relative_path = relative_path_with_subdir.as_ref().trim_matches('/');
152            let relative_path = format!("{dir_name}/{relative_path}");
153
154            let dir_parent_uri = FileUri {
155                uri: dir_parent_uri,
156                document_top_tree_uri: None
157            };
158
159            self.0.create_file(&dir_parent_uri, relative_path, mime_type)
160        })
161    }
162
163    /// Recursively create a directory and all of its parent components if they are missing.  
164    /// If it already exists, do nothing.
165    /// 
166    /// [`PublicStorage::create_file`] does this automatically, so there is no need to use it together.
167    /// 
168    /// # Args  
169    /// - ***dir*** :  
170    /// The base directory.  
171    ///  
172    /// - ***relative_path*** :  
173    /// The directory path relative to the base directory.    
174    ///  
175    /// # Support
176    /// Android 10 (API level 29) or higher.  
177    ///
178    /// Note :  
179    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
180    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
181    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
182    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
183    /// - Others dirs are available in all Android versions.
184    pub fn create_dir_all(
185        &self,
186        dir: impl Into<PublicDir>,
187        relative_path: impl AsRef<str>, 
188    ) -> Result<()> {
189
190        on_android!({
191            let relative_path = relative_path.as_ref().trim_matches('/');
192            if relative_path.is_empty() {
193                return Ok(())
194            }
195
196            // TODO:
197            // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出す
198            let dir = dir.into();
199            let tmp_file_uri = self.create_file(
200                dir, 
201                format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
202                Some(match dir {
203                    PublicDir::Image(_) => "image/png",
204                    PublicDir::Audio(_) => "audio/mp3",
205                    PublicDir::Video(_) => "video/mp4",
206                    PublicDir::GeneralPurpose(_) => "application/octet-stream"
207                })
208            )?;
209            let _ = self.0.remove_file(&tmp_file_uri);
210
211            Ok(())
212        })
213    }
214
215    /// Recursively create a directory and all of its parent components if they are missing.  
216    /// If it already exists, do nothing.
217    /// 
218    /// [`PublicStorage::create_file_in_app_dir`] does this automatically, so there is no need to use it together.  
219    /// 
220    /// This is the same as following: 
221    /// ```ignore
222    /// let app_name = public_storage.app_dir_name()?;
223    /// public_storage.create_dir_all(
224    ///     dir,
225    ///     format!("{app_name}/{relative_path}"),
226    /// )?;
227    /// ```
228    /// # Args  
229    /// - ***dir*** :  
230    /// The base directory.  
231    ///  
232    /// - ***relative_path*** :  
233    /// The directory path relative to the base directory.    
234    ///  
235    /// # Support
236    /// Android 10 (API level 29) or higher.  
237    ///
238    /// Note :  
239    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
240    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
241    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
242    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
243    /// - Others dirs are available in all Android versions.
244    pub fn create_dir_all_in_app_dir(
245        &self,
246        dir: impl Into<PublicDir>,
247        relative_path: impl AsRef<str>, 
248    ) -> Result<()> {
249
250        on_android!({
251            let app_dir_name = self.app_dir_name()?;
252            let relative_path = relative_path.as_ref().trim_start_matches('/');
253            let relative_path_with_subdir = format!("{app_dir_name}/{relative_path}");
254
255            self.create_dir_all(dir, relative_path_with_subdir)
256        })
257    }
258
259    /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.   
260    /// If on Android 9 (API level 28) and lower, this returns false.
261    /// 
262    /// # Support
263    /// All.
264    pub fn is_audiobooks_dir_available(&self) -> crate::Result<bool> {
265        on_android!({
266            impl_de!(struct Res { value: bool });
267
268            self.0.api
269                .run_mobile_plugin::<Res>("isAudiobooksDirAvailable", "")
270                .map(|v| v.value)
271                .map_err(Into::into)
272        })
273    }
274
275    /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.   
276    /// If on Android 11 (API level 30) and lower, this returns false.
277    /// 
278    /// # Support
279    /// All.
280    pub fn is_recordings_dir_available(&self) -> crate::Result<bool> {
281        on_android!({
282            impl_de!(struct Res { value: bool });
283
284            self.0.api
285                .run_mobile_plugin::<Res>("isRecordingsDirAvailable", "")
286                .map(|v| v.value)
287                .map_err(Into::into)
288        })
289    }
290
291    /// Resolve the app dir name from Tauri's config.  
292    /// 
293    /// # Support
294    /// All.
295    pub fn app_dir_name(&self) -> crate::Result<&str> {
296        on_android!({
297            use std::sync::OnceLock;
298            
299            static APP_DIR_NAME: OnceLock<String> = OnceLock::new();
300
301            if APP_DIR_NAME.get().is_none() {
302                let config = self.0.app.config();
303                let app_name = config.product_name
304                    .as_deref()
305                    .filter(|s| !s.is_empty())
306                    .unwrap_or(&config.identifier)
307                    .replace('/', " ");
308                
309                // The cell is guaranteed to contain a value when set returns
310                let _ = APP_DIR_NAME.set(app_name);
311            }
312
313            Ok(&APP_DIR_NAME.get().unwrap())
314        })
315    }
316}