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}