tauri_plugin_android_fs/api/android_fs.rs
1#[allow(unused)]
2use std::io::{Read as _, Write as _};
3use crate::*;
4
5
6/// ***Root API***
7///
8/// # Examples
9/// ```
10/// fn example(app: &tauri::AppHandle) {
11/// use tauri_plugin_android_fs::AndroidFsExt as _;
12///
13/// let api = app.android_fs();
14/// }
15/// ```
16pub struct AndroidFs<R: tauri::Runtime> {
17 #[cfg(target_os = "android")]
18 pub(crate) app: tauri::AppHandle<R>,
19
20 #[cfg(target_os = "android")]
21 pub(crate) api: tauri::plugin::PluginHandle<R>,
22
23 #[cfg(target_os = "android")]
24 pub(crate) intent_lock: std::sync::Mutex<()>,
25
26 #[cfg(not(target_os = "android"))]
27 _marker: std::marker::PhantomData<fn() -> R>
28}
29
30impl<R: tauri::Runtime> AndroidFs<R> {
31
32 pub(crate) fn new<C: serde::de::DeserializeOwned>(
33 app: tauri::AppHandle<R>,
34 api: tauri::plugin::PluginApi<R, C>,
35 ) -> crate::Result<Self> {
36
37 #[cfg(target_os = "android")] {
38 Ok(Self {
39 api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?,
40 app,
41 intent_lock: std::sync::Mutex::new(())
42 })
43 }
44
45 #[cfg(not(target_os = "android"))] {
46 Ok(Self { _marker: Default::default() })
47 }
48 }
49}
50
51impl<R: tauri::Runtime> AndroidFs<R> {
52
53 /// API of file storage intended for the app's use only.
54 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
55 PrivateStorage(self)
56 }
57
58 /// API of file storage that is available to other applications and users.
59 pub fn public_storage(&self) -> PublicStorage<'_, R> {
60 PublicStorage(self)
61 }
62
63 /// API of file/dir picker.
64 pub fn file_picker(&self) -> FilePicker<'_, R> {
65 FilePicker(self)
66 }
67
68 /// API of sharing files with other apps.
69 pub fn file_sender(&self) -> FileSender<'_, R> {
70 FileSender(self)
71 }
72
73 /// Get the file or directory name.
74 ///
75 /// # Args
76 /// - ***uri*** :
77 /// Target URI.
78 /// Must be **readable**.
79 ///
80 /// # Support
81 /// All.
82 pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
83 on_android!({
84 impl_se!(struct Req<'a> { uri: &'a FileUri });
85 impl_de!(struct Res { name: String });
86
87 self.api
88 .run_mobile_plugin::<Res>("getName", Req { uri })
89 .map(|v| v.name)
90 .map_err(Into::into)
91 })
92 }
93
94 /// Queries the provider to get the MIME type.
95 ///
96 /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.
97 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
98 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
99 ///
100 /// If the target is a directory, an error will occur.
101 /// To check whether the target is a file or a directory, use [`AndroidFs::get_type`].
102 ///
103 /// # Args
104 /// - ***uri*** :
105 /// Target file URI.
106 /// Must be **readable**.
107 ///
108 /// # Support
109 /// All.
110 pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<String> {
111 on_android!({
112 let mime_type = self.get_type(uri)?
113 .into_mime_type()
114 .ok_or_else(|| Error { msg: "This is not file".into() })?;
115
116 Ok(mime_type)
117 })
118 }
119
120 /// Gets the entry type.
121 ///
122 /// If the target is a directory, returns [`EntryType::Dir`].
123 ///
124 /// If the target is a file, returns [`EntryType::File { mime_type }`](EntryType::File).
125 /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.
126 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
127 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
128 ///
129 /// # Args
130 /// - ***uri*** :
131 /// Target URI.
132 /// Must be **readable**.
133 ///
134 /// # Support
135 /// All.
136 pub fn get_type(&self, uri: &FileUri) -> crate::Result<EntryType> {
137 on_android!({
138 impl_se!(struct Req<'a> { uri: &'a FileUri });
139 impl_de!(struct Res { value: Option<String> });
140
141 self.api
142 .run_mobile_plugin::<Res>("getMimeType", Req { uri })
143 .map(|v| match v.value {
144 Some(mime_type) => EntryType::File { mime_type },
145 None => EntryType::Dir,
146 })
147 .map_err(Into::into)
148 })
149 }
150
151 /// Queries the file system to get information about a file, directory.
152 ///
153 /// # Args
154 /// - ***uri*** :
155 /// Target URI.
156 /// Must be **readable**.
157 ///
158 /// # Note
159 /// This uses [`AndroidFs::open_file`] internally.
160 ///
161 /// # Support
162 /// All.
163 pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
164 on_android!({
165 let file = self.open_file_readable(uri)?;
166 Ok(file.metadata()?)
167 })
168 }
169
170 /// Open the file in **readable** mode.
171 ///
172 /// # Note
173 /// If the target is a file on cloud storage or otherwise not physically present on the device,
174 /// the system creates a temporary file, downloads the data into it,
175 /// and then opens it. As a result, this processing may take longer than with regular local files.
176 ///
177 /// # Args
178 /// - ***uri*** :
179 /// Target file URI.
180 /// This need to be **readable**.
181 ///
182 /// # Support
183 /// All.
184 pub fn open_file_readable(&self, uri: &FileUri) -> Result<std::fs::File> {
185 self.open_file(uri, FileAccessMode::Read)
186 }
187
188 /// Open the file in **writable** mode.
189 /// This truncates the existing contents.
190 ///
191 /// If you only need a [`std::io::Write`] instead of a [`std::fs::File`], or if applicable,
192 /// please use [`AndroidFs::open_writable_stream`] instead.
193 ///
194 /// # Note
195 /// If the target is a file on cloud storage or otherwise not actually present on the device,
196 /// writing with such files may not correctly reflect the changes.
197 /// In this case, this function may causes an error, or may not.
198 /// If you need write with such files, use [`AndroidFs::open_writable_stream`].
199 ///
200 /// # Args
201 /// - ***uri*** :
202 /// Target file URI.
203 /// This need to be **writable**.
204 ///
205 /// # Support
206 /// All.
207 pub fn open_file_writable(
208 &self,
209 uri: &FileUri,
210 ) -> crate::Result<std::fs::File> {
211
212 on_android!(#[allow(deprecated)] {
213 // サイレントエラーをなるべく避けるため強制的にエラーにする。
214 if self.need_write_via_kotlin(uri)? {
215 return Err(Error { msg: "This content does not support write via file descriptor. Please use AndroidFs::open_writable_stream instead.".into() })
216 }
217
218 // Android 9 以下の場合、w は既存コンテンツを切り捨てる
219 if self.api_level()? <= 28 {
220 self.open_file(uri, FileAccessMode::Write)
221 }
222 // Android 10 以上の場合、w は既存コンテンツの切り捨てを保証しない。
223 // そのため切り捨ててファイルを開くには wt を用いる必要があるが、
224 // wt は全ての file provider が対応しているとは限らないため、
225 // フォールバックを用いてなるべく多くの状況に対応する。
226 // https://issuetracker.google.com/issues/180526528?pli=1
227 else {
228 let (file, mode) = self.open_file_with_fallback(uri, [
229 FileAccessMode::WriteTruncate,
230 FileAccessMode::ReadWriteTruncate,
231 FileAccessMode::Write
232 ])?;
233
234 if mode == FileAccessMode::Write {
235 // file provider が既存コンテンツを切り捨てず、
236 // かつ書き込むデータ量が元のそれより少ない場合、ファイルが壊れる可能性がある。
237 // これを避けるため強制的にデータを切り捨てる。
238 // file provider の実装によっては set_len は失敗することがある。
239 file.set_len(0)?;
240 }
241
242 Ok(file)
243 }
244 })
245 }
246
247 /// Open the file in the specified mode.
248 ///
249 /// # Note
250 /// If the target is a file on cloud storage or otherwise not physically present on the device,
251 /// the system creates a temporary file, downloads the data into it,
252 /// and then opens it. As a result, this processing may take longer than with regular local files.
253 ///
254 /// When writing to a file with this function,
255 /// pay attention to the following points:
256 ///
257 /// 1. **Files that do not exist on the device**:
258 /// Writing to files that are not physically present on the device may not correctly
259 /// reflect changes. If you need to write to such files, use [`AndroidFs::open_writable_stream`].
260 ///
261 /// 2. **File mode restrictions**:
262 /// Files provided by third-party apps may not support writable modes other than
263 /// [`FileAccessMode::Write`]. However, [`FileAccessMode::Write`] does not guarantee
264 /// that existing contents will always be truncated.
265 /// As a result, if the new contents are shorter than the original, the file may
266 /// become corrupted. To avoid this, consider using
267 /// [`AndroidFs::open_file_writable`] or [`AndroidFs::open_writable_stream`], which
268 /// ensure that existing contents are truncated and also automatically apply the
269 /// maximum possible fallbacks.
270 /// <https://issuetracker.google.com/issues/180526528>
271 ///
272 /// # Args
273 /// - ***uri*** :
274 /// Target file URI.
275 /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
276 ///
277 /// - ***mode*** :
278 /// Indicates how the file is opened and the permissions granted.
279 /// Note that files hosted by third-party apps may not support following:
280 /// - [`FileAccessMode::ReadWrite`]
281 /// - [`FileAccessMode::ReadWriteTruncate`]
282 /// - [`FileAccessMode::WriteAppend`]
283 /// - [`FileAccessMode::WriteTruncate`]
284 ///
285 /// # Support
286 /// All.
287 pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
288 on_android!({
289 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
290 impl_de!(struct Res { fd: std::os::fd::RawFd });
291
292 let mode = mode.to_mode();
293
294 self.api
295 .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
296 .map(|v| {
297 use std::os::fd::FromRawFd;
298 unsafe { std::fs::File::from_raw_fd(v.fd) }
299 })
300 .map_err(Into::into)
301 })
302 }
303
304 /// For detailed documentation and notes, see [`AndroidFs::open_file`].
305 ///
306 /// The modes specified in ***candidate_modes*** are tried in order.
307 /// If the file can be opened, this returns the file along with the mode used.
308 /// If all attempts fail, an error is returned.
309 pub fn open_file_with_fallback(
310 &self,
311 uri: &FileUri,
312 candidate_modes: impl IntoIterator<Item = FileAccessMode>
313 ) -> crate::Result<(std::fs::File, FileAccessMode)> {
314
315 on_android!({
316 impl_se!(struct Req<'a> { uri: &'a FileUri, modes: Vec<&'a str> });
317 impl_de!(struct Res { fd: std::os::fd::RawFd, mode: String });
318
319 let modes = candidate_modes.into_iter().map(|m| m.to_mode()).collect::<Vec<_>>();
320
321 if modes.is_empty() {
322 return Err(Error { msg: "candidate_modes should not be empty".into() });
323 }
324
325 self.api
326 .run_mobile_plugin::<Res>("getFileDescriptorWithFallback", Req { uri, modes })
327 .map_err(Into::into)
328 .and_then(|v| FileAccessMode::from_mode(&v.mode).map(|m| (v.fd, m)))
329 .map(|(fd, mode)| {
330 let file = {
331 use std::os::fd::FromRawFd;
332 unsafe { std::fs::File::from_raw_fd(fd) }
333 };
334 (file, mode)
335 })
336 })
337 }
338
339 /// Opens a stream for writing to the specified file.
340 /// This truncates the existing contents.
341 ///
342 /// # Usage
343 /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.
344 /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.
345 ///
346 /// After writing, call [`WritableStream::reflect`].
347 ///
348 /// # Note
349 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
350 /// If it is `false`, this behaves like [`AndroidFs::open_file_writable`].
351 /// If it is `true`, this behaves like [`AndroidFs::open_writable_stream_via_kotlin`].
352 ///
353 /// # Args
354 /// - ***uri*** :
355 /// Target file URI.
356 /// This need to be **writable**.
357 ///
358 /// # Support
359 /// All.
360 pub fn open_writable_stream(
361 &self,
362 uri: &FileUri
363 ) -> Result<WritableStream<R>> {
364
365 on_android!({
366 let need_write_via_kotlin = self.need_write_via_kotlin(uri)?;
367 WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
368 })
369 }
370
371 /// Opens a writable stream to the specified file.
372 /// This truncates the existing contents.
373 ///
374 /// This function always writes content via the Kotlin API,
375 /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
376 /// But this takes several times longer compared to [`AndroidFs::open_writable_stream`].
377 ///
378 /// [`AndroidFs::open_writable_stream`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
379 /// For performance reasons, it is strongly recommended to use [`AndroidFs::open_writable_stream`]
380 /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.
381 ///
382 /// # Usage
383 /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.
384 /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.
385 ///
386 /// After writing, call [`WritableStream::reflect`].
387 ///
388 /// # Args
389 /// - ***uri*** :
390 /// Target file URI.
391 /// This need to be **writable**.
392 ///
393 /// # Support
394 /// All.
395 pub fn open_writable_stream_via_kotlin(
396 &self,
397 uri: &FileUri
398 ) -> Result<WritableStream<R>> {
399
400 on_android!({
401 let need_write_via_kotlin = true;
402 WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
403 })
404 }
405
406 /// Reads the entire contents of a file into a bytes vector.
407 ///
408 /// # Args
409 /// - ***uri*** :
410 /// Target file URI.
411 /// Must be **readable**.
412 ///
413 /// # Support
414 /// All.
415 pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
416 on_android!({
417 let mut file = self.open_file_readable(uri)?;
418 let mut buf = file.metadata().ok()
419 .map(|m| m.len() as usize)
420 .map(Vec::with_capacity)
421 .unwrap_or_else(Vec::new);
422
423 file.read_to_end(&mut buf)?;
424 Ok(buf)
425 })
426 }
427
428 /// Reads the entire contents of a file into a string.
429 ///
430 /// # Args
431 /// - ***uri*** :
432 /// Target file URI.
433 /// Must be **readable**.
434 ///
435 /// # Support
436 /// All.
437 pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
438 on_android!({
439 let mut file = self.open_file_readable(uri)?;
440 let mut buf = file.metadata().ok()
441 .map(|m| m.len() as usize)
442 .map(String::with_capacity)
443 .unwrap_or_else(String::new);
444
445 file.read_to_string(&mut buf)?;
446 Ok(buf)
447 })
448 }
449
450 /// Writes a slice as the entire contents of a file.
451 /// This function will entirely replace its contents if it does exist.
452 ///
453 /// # Note
454 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
455 /// If it is `false`, this uses [`std::fs::File::write_all`].
456 /// If it is `true`, this uses [`AndroidFs::write_via_kotlin`].
457 ///
458 /// # Args
459 /// - ***uri*** :
460 /// Target file URI.
461 /// Must be **writable**.
462 ///
463 /// # Support
464 /// All.
465 pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
466 on_android!({
467 let mut stream = self.open_writable_stream(uri)?;
468
469 match stream.write_all(contents.as_ref()) {
470 Ok(_) => stream.reflect(),
471 Err(e) => {
472 // これは drop 時にも必要に応じて行われるのでなくてもいい。
473 // drop 時に任せるよりこれを呼び出した方がコストが低いので呼び出す。
474 let _ = stream.close_without_reflect();
475 Err(e.into())
476 },
477 }
478 })
479 }
480
481 /// Writes a slice as the entire contents of a file.
482 /// This function will entirely replace its contents if it does exist.
483 ///
484 /// This function always writes content via the Kotlin API,
485 /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
486 /// But this takes several times longer compared to [`AndroidFs::write`].
487 ///
488 /// [`AndroidFs::write`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
489 /// For performance reasons, it is strongly recommended to use [`AndroidFs::write`]
490 /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.
491 ///
492 /// # Support
493 /// All.
494 pub fn write_via_kotlin(
495 &self,
496 uri: &FileUri,
497 contents: impl AsRef<[u8]>
498 ) -> crate::Result<()> {
499
500 on_android!({
501 let mut stream = self.open_writable_stream_via_kotlin(uri)?;
502
503 match stream.write_all(contents.as_ref()) {
504 Ok(_) => stream.reflect(),
505 Err(e) => {
506 // これは drop 時にも必要に応じて行われるのでなくてもいい。
507 // drop 時に任せるよりこれを呼び出した方がコストが低いので呼び出す。
508 let _ = stream.close_without_reflect();
509 Err(e.into())
510 },
511 }
512 })
513 }
514
515 /// Copies the contents of the source file to the destination.
516 /// If the destination already has contents, they are truncated before writing the source contents.
517 ///
518 /// # Note
519 /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].
520 /// If it is `false`, this uses [`std::io::copy`] with [`std::fs::File`].
521 /// If it is `true`, this uses [`AndroidFs::copy_via_kotlin`].
522 ///
523 /// # Args
524 /// - ***src*** :
525 /// The URI of source file.
526 /// Must be **readable**.
527 ///
528 /// - ***dest*** :
529 /// The URI of destination file.
530 /// Must be **writable**.
531 ///
532 /// # Support
533 /// All.
534 pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
535 on_android!({
536 // NOTE:
537 // std::io::copy は std::fs::File 同士のコピーの場合、最適化が働く可能性がある。
538 // そのため self.open_writable_stream は用いない。
539
540 if self.need_write_via_kotlin(dest)? {
541 self.copy_via_kotlin(src, dest, None)?;
542 }
543 else {
544 let src = &mut self.open_file_readable(src)?;
545 let dest = &mut self.open_file_writable(dest)?;
546 std::io::copy(src, dest)?;
547 }
548 Ok(())
549 })
550 }
551
552 /// Copies the contents of src file to dest.
553 /// If dest already has contents, it is truncated before write src contents.
554 ///
555 /// This function always writes content via the Kotlin API,
556 /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
557 ///
558 /// [`AndroidFs::copy`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].
559 /// For performance reasons, it is strongly recommended to use [`AndroidFs::copy`]
560 /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.
561 ///
562 /// # Args
563 /// - ***src*** :
564 /// The URI of source file.
565 /// Must be **readable**.
566 ///
567 /// - ***dest*** :
568 /// The URI of destination file.
569 /// Must be **writable**.
570 ///
571 /// - ***buffer_size***:
572 /// The size of the buffer, in bytes, to use during the copy process on Kotlin.
573 /// If `None`, [`DEFAULT_BUFFER_SIZE`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/-d-e-f-a-u-l-t_-b-u-f-f-e-r_-s-i-z-e.html) is used.
574 /// At least, when I checked, it was 8 KB.
575 /// If zero, this causes error.
576 ///
577 /// # Support
578 /// All.
579 pub fn copy_via_kotlin(
580 &self,
581 src: &FileUri,
582 dest: &FileUri,
583 buffer_size: Option<u32>,
584 ) -> crate::Result<()> {
585
586 on_android!({
587 impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri, buffer_size: Option<u32> });
588 impl_de!(struct Res;);
589
590 if buffer_size.is_some_and(|s| s <= 0) {
591 return Err(Error { msg: "buffer_size must be non zero".into() })
592 }
593
594 self.api
595 .run_mobile_plugin::<Res>("copyFile", Req { src, dest, buffer_size })
596 .map(|_| ())
597 .map_err(Into::into)
598 })
599 }
600
601 /// Determines whether the file must be written via the Kotlin API rather than through a file descriptor.
602 ///
603 /// **Even if this returns `false`, it does not guarantee that writing via the Kotlin API is never required.**
604 ///
605 /// # Note
606 /// Currently, this function returns `true` only for files hosted by the following apps:
607 /// - Google Drive
608 ///
609 /// # Why
610 /// For files that do not physically exist on the device, such as those stored in cloud storage,
611 /// writing data through [`std::fs::File`] may not be properly reflected.
612 /// In such cases, the write operation must be performed via the Kotlin API.
613 /// <https://community.latenode.com/t/csv-export-to-google-drive-results-in-empty-file-but-local-storage-works-fine>
614 ///
615 /// # Support
616 /// All.
617 pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
618 on_android!({
619 const URI_PREFIXES: &'static [&'static str] = &[
620 "content://com.google.android.apps.docs", // Google drive
621 ];
622
623 Ok(URI_PREFIXES.iter().any(|prefix| uri.uri.starts_with(prefix)))
624 })
625 }
626
627 /// Renames a file or directory to a new name, and return new URI.
628 /// Even if the names conflict, the existing file will not be overwritten.
629 ///
630 /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
631 /// In other words, this function returns a new URI without any permissions.
632 /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
633 /// In this, this function returns the same URI as original URI.
634 ///
635 /// # Args
636 /// - ***uri*** :
637 /// URI of target entry.
638 ///
639 /// - ***new_name*** :
640 /// New name of target entry.
641 /// This include extension if use.
642 /// The behaviour in the same name already exists depends on the file provider.
643 /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.
644 /// In the case of files hosted by other applications, errors may occur.
645 /// But at least, the existing file will not be overwritten.
646 ///
647 /// # Support
648 /// All.
649 pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
650 on_android!({
651 impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
652
653 let new_name = new_name.as_ref();
654
655 self.api
656 .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
657 .map_err(Into::into)
658 })
659 }
660
661 /// Remove the file.
662 ///
663 /// # Args
664 /// - ***uri*** :
665 /// Target file URI.
666 /// Must be **writable**, at least. But even if it is,
667 /// removing may not be possible in some cases.
668 /// For details, refer to the documentation of the function that provided the URI.
669 /// If not file, an error will occur.
670 ///
671 /// # Support
672 /// All.
673 pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
674 on_android!({
675 impl_se!(struct Req<'a> { uri: &'a FileUri });
676 impl_de!(struct Res;);
677
678 self.api
679 .run_mobile_plugin::<Res>("deleteFile", Req { uri })
680 .map(|_| ())
681 .map_err(Into::into)
682 })
683 }
684
685 /// Remove the **empty** directory.
686 ///
687 /// # Args
688 /// - ***uri*** :
689 /// Target directory URI.
690 /// Must be **writable**.
691 /// If not empty directory, an error will occur.
692 ///
693 /// # Support
694 /// All.
695 pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
696 on_android!({
697 impl_se!(struct Req<'a> { uri: &'a FileUri });
698 impl_de!(struct Res;);
699
700 self.api
701 .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
702 .map(|_| ())
703 .map_err(Into::into)
704 })
705 }
706
707 /// Removes a directory and all its contents. Use carefully!
708 ///
709 /// # Args
710 /// - ***uri*** :
711 /// Target directory URI.
712 /// Must be **writable**.
713 /// If not directory, an error will occur.
714 ///
715 /// # Support
716 /// All.
717 pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
718 on_android!({
719 impl_se!(struct Req<'a> { uri: &'a FileUri });
720 impl_de!(struct Res;);
721
722 self.api
723 .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
724 .map(|_| ())
725 .map_err(Into::into)
726 })
727 }
728
729 /// Build a URI of an **existing** file located at the relative path from the specified directory.
730 /// Error occurs, if the file does not exist.
731 ///
732 /// The permissions and validity period of the returned URI depend on the origin directory
733 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
734 ///
735 /// # Support
736 /// All.
737 pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
738 on_android!({
739 #[allow(deprecated)]
740 let uri = self.resolve_uri(dir, relative_path)?;
741
742 if !self.get_type(&uri)?.is_file() {
743 return Err(crate::Error { msg: format!("This is not a file: {uri:?}").into() })
744 }
745 Ok(uri)
746 })
747 }
748
749 /// Build a URI of an **existing** directory located at the relative path from the specified directory.
750 /// Error occurs, if the directory does not exist.
751 ///
752 /// The permissions and validity period of the returned URI depend on the origin directory
753 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
754 ///
755 /// # Support
756 /// All.
757 pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
758 on_android!({
759 #[allow(deprecated)]
760 let uri = self.resolve_uri(dir, relative_path)?;
761
762 if !self.get_type(&uri)?.is_dir() {
763 return Err(crate::Error { msg: format!("This is not a directory: {uri:?}").into() })
764 }
765 Ok(uri)
766 })
767 }
768
769 /// Build a URI of an entry located at the relative path from the specified directory.
770 ///
771 /// This function does not perform checks on the arguments or the returned URI.
772 /// Even if the dir argument refers to a file, no error occurs (and no panic either).
773 /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.
774 ///
775 /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead.
776 /// Or use this with [`AndroidFs::get_mime_type`].
777 ///
778 /// The permissions and validity period of the returned URI depend on the origin directory
779 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
780 ///
781 /// # Performance
782 /// This operation is relatively fast
783 /// because it does not call Kotlin API and only involves operating strings on Rust side.
784 ///
785 /// # Support
786 /// All.
787 #[deprecated = "Use AndroidFs::try_resolve_file_uri or AndroidFs::try_resolve_dir_uri instead"]
788 pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
789 on_android!({
790 let base_dir = &dir.uri;
791 let relative_path = relative_path.as_ref().trim_matches('/');
792
793 if relative_path.is_empty() {
794 return Ok(dir.clone())
795 }
796
797 Ok(FileUri {
798 document_top_tree_uri: dir.document_top_tree_uri.clone(),
799 uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
800 })
801 })
802 }
803
804 /// See [`AndroidFs::get_thumbnail_to`] for descriptions.
805 ///
806 /// If thumbnail does not wrote to dest, return false.
807 pub fn get_thumbnail_to(
808 &self,
809 src: &FileUri,
810 dest: &FileUri,
811 preferred_size: Size,
812 format: ImageFormat,
813 ) -> crate::Result<bool> {
814
815 on_android!({
816 impl_se!(struct Req<'a> {
817 src: &'a FileUri,
818 dest: &'a FileUri,
819 format: &'a str,
820 quality: u8,
821 width: u32,
822 height: u32,
823 });
824 impl_de!(struct Res { value: bool });
825
826 let (quality, format) = match format {
827 ImageFormat::Png => (1.0, "Png"),
828 ImageFormat::Jpeg => (0.75, "Jpeg"),
829 ImageFormat::Webp => (0.7, "Webp"),
830 ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
831 ImageFormat::WebpWith { quality } => (quality, "Webp"),
832 };
833 let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
834 let Size { width, height } = preferred_size;
835
836 self.api
837 .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
838 .map(|v| v.value)
839 .map_err(Into::into)
840 })
841 }
842
843 /// Query the provider to get a file thumbnail.
844 /// If thumbnail does not exist it, return None.
845 ///
846 /// Note this does not cache. Please do it in your part if need.
847 ///
848 /// # Args
849 /// - ***uri*** :
850 /// Targe file uri.
851 /// Thumbnail availablty depends on the file provider.
852 /// In general, images and videos are available.
853 /// For files in [`PrivateStorage`],
854 /// the file type must match the filename extension.
855 ///
856 /// - ***preferred_size*** :
857 /// Optimal thumbnail size desired.
858 /// This may return a thumbnail of a different size,
859 /// but never more than double the requested size.
860 /// In any case, the aspect ratio is maintained.
861 ///
862 /// - ***format*** :
863 /// Thumbnail image format.
864 /// [`ImageFormat::Jpeg`] is recommended.
865 /// If you need transparency, use others.
866 ///
867 /// # Support
868 /// All.
869 pub fn get_thumbnail(
870 &self,
871 uri: &FileUri,
872 preferred_size: Size,
873 format: ImageFormat,
874 ) -> crate::Result<Option<Vec<u8>>> {
875
876 on_android!({
877 let (tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
878 std::mem::drop(tmp_file);
879
880 let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
881 .and_then(|ok| {
882 if ok {
883 std::fs::read(&tmp_file_path)
884 .map(Some)
885 .map_err(Into::into)
886 }
887 else {
888 Ok(None)
889 }
890 });
891
892 let _ = std::fs::remove_file(&tmp_file_path);
893
894 result
895 })
896 }
897
898 /// Creates a new empty file in the specified location and returns a URI.
899 ///
900 /// The permissions and validity period of the returned URIs depend on the origin directory
901 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
902 ///
903 /// # Args
904 /// - ***dir*** :
905 /// The URI of the base directory.
906 /// Must be **read-write**.
907 ///
908 /// - ***relative_path*** :
909 /// The file path relative to the base directory.
910 /// Any missing subdirectories in the specified path will be created automatically.
911 /// If a file with the same name already exists,
912 /// the system append a sequential number to ensure uniqueness.
913 /// If no extension is present,
914 /// the system may infer one from ***mime_type*** and may append it to the file name.
915 /// But this append-extension operation depends on the model and version.
916 ///
917 /// - ***mime_type*** :
918 /// The MIME type of the file to be created.
919 /// If this is None, MIME type is inferred from the extension of ***relative_path***
920 /// and if that fails, `application/octet-stream` is used.
921 ///
922 /// # Support
923 /// All.
924 pub fn create_file(
925 &self,
926 dir: &FileUri,
927 relative_path: impl AsRef<str>,
928 mime_type: Option<&str>
929 ) -> crate::Result<FileUri> {
930
931 on_android!({
932 impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
933
934 let relative_path = relative_path.as_ref();
935
936 self.api
937 .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
938 .map_err(Into::into)
939 })
940 }
941
942 /// Recursively create a directory and all of its parent components if they are missing,
943 /// then return the URI.
944 /// If it already exists, do nothing and just return the direcotry uri.
945 ///
946 /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
947 ///
948 /// # Args
949 /// - ***dir*** :
950 /// The URI of the base directory.
951 /// Must be **read-write**.
952 ///
953 /// - ***relative_path*** :
954 /// The directory path relative to the base directory.
955 ///
956 /// # Support
957 /// All.
958 pub fn create_dir_all(
959 &self,
960 dir: &FileUri,
961 relative_path: impl AsRef<str>,
962 ) -> Result<FileUri> {
963
964 on_android!({
965 let relative_path = relative_path.as_ref().trim_matches('/');
966 if relative_path.is_empty() {
967 return Ok(dir.clone())
968 }
969
970 // TODO:
971 // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
972 let tmp_file_uri = self.create_file(
973 dir,
974 format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"),
975 Some("application/octet-stream")
976 )?;
977 let _ = self.remove_file(&tmp_file_uri);
978 let uri = self.resolve_uri(dir, relative_path)?;
979
980 Ok(uri)
981 })
982 }
983
984 /// Returns the child files and directories of the specified directory.
985 /// The order of the entries is not guaranteed.
986 ///
987 /// The permissions and validity period of the returned URIs depend on the origin directory
988 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
989 ///
990 /// # Note
991 /// The returned type is an iterator, but the file system call is not executed lazily.
992 /// Instead, all data is retrieved at once.
993 /// For directories containing thousands or even tens of thousands of entries,
994 /// this function may take several seconds to complete.
995 /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
996 ///
997 /// # Args
998 /// - ***uri*** :
999 /// Target directory URI.
1000 /// Must be **readable**.
1001 ///
1002 /// # Support
1003 /// All.
1004 pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
1005 on_android!(std::iter::Empty::<_>, {
1006 impl_se!(struct Req<'a> { uri: &'a FileUri });
1007 impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
1008 impl_de!(struct Res { entries: Vec<Obj> });
1009
1010 // TODO:
1011 // データ転送のためのデータ形式 (ResとObj) を効率的なものにしたい
1012
1013 self.api
1014 .run_mobile_plugin::<Res>("readDir", Req { uri })
1015 .map(|v| v.entries.into_iter())
1016 .map(|v| v.map(|v| match v.mime_type {
1017 Some(mime_type) => Entry::File {
1018 name: v.name,
1019 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
1020 len: v.byte_size as u64,
1021 mime_type,
1022 uri: v.uri,
1023 },
1024 None => Entry::Dir {
1025 name: v.name,
1026 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
1027 uri: v.uri,
1028 }
1029 }))
1030 .map_err(Into::into)
1031 })
1032 }
1033
1034 /// Create an **restricted** URI for the specified directory.
1035 /// This should only be used as `initial_location` in the file picker.
1036 /// It must not be used for any other purpose.
1037 ///
1038 /// This is useful when selecting (creating) new files and folders,
1039 /// but when selecting existing entries, `initial_location` is often better with None.
1040 ///
1041 /// Note this is an informal method and is not guaranteed to work reliably.
1042 /// But this URI does not cause the dialog to error.
1043 /// So please use this with the mindset that it's better than doing nothing.
1044 ///
1045 /// # Examples
1046 /// ```rust
1047 /// use tauri_plugin_android_fs::{AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1048 ///
1049 /// fn sample(app: tauri::AppHandle) {
1050 /// let api = app.android_fs();
1051 ///
1052 /// // Get URI of the top public directory in primary volume
1053 /// let initial_location = api.resolve_initial_location(
1054 /// InitialLocation::TopPublicDir,
1055 /// false,
1056 /// ).expect("Should be on Android");
1057 ///
1058 /// // Get URI of ~/Pictures/
1059 /// let initial_location = api.resolve_initial_location(
1060 /// PublicImageDir::Pictures,
1061 /// false
1062 /// ).expect("Should be on Android");
1063 ///
1064 /// // Get URI of ~/Documents/sub_dir1/sub_dir2/
1065 /// let initial_location = api.resolve_initial_location(
1066 /// InitialLocation::DirInPublicDir {
1067 /// base_dir: PublicGeneralPurposeDir::Documents.into(),
1068 /// relative_path: "sub_dir1/sub_dir2"
1069 /// },
1070 /// true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1071 /// ).expect("Should be on Android");
1072 ///
1073 /// // Open dialog with initial_location
1074 /// let _ = api.file_picker().save_file(Some(&initial_location), "", None);
1075 /// let _ = api.file_picker().pick_file(Some(&initial_location), &[]);
1076 /// let _ = api.file_picker().pick_dir(Some(&initial_location));
1077 /// }
1078 /// ```
1079 ///
1080 /// # Support
1081 /// All.
1082 ///
1083 /// Note :
1084 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
1085 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
1086 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
1087 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
1088 /// - Others dirs are available in all Android versions.
1089 pub fn resolve_initial_location<'a>(
1090 &self,
1091 dir: impl Into<InitialLocation<'a>>,
1092 create_dirs: bool
1093 ) -> crate::Result<FileUri> {
1094
1095 on_android!({
1096 const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
1097
1098 let uri = match dir.into() {
1099 InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
1100 InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
1101 InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1102 let relative_path = relative_path.trim_matches('/');
1103
1104 if relative_path.is_empty() {
1105 format!("{TOP_DIR}%3A{base_dir}")
1106 }
1107 else {
1108 if create_dirs {
1109 let _ = self.public_storage().create_dir_all(base_dir, relative_path);
1110 }
1111 let sub_dirs = encode_document_id(relative_path);
1112 format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
1113 }
1114 },
1115 InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
1116 let relative_path = &format!(
1117 "{}/{}",
1118 self.public_storage().app_dir_name()?,
1119 relative_path.trim_matches('/'),
1120 );
1121
1122 return self.resolve_initial_location(
1123 InitialLocation::DirInPublicDir { base_dir, relative_path },
1124 create_dirs
1125 )
1126 }
1127 };
1128
1129 Ok(FileUri { uri, document_top_tree_uri: None })
1130 })
1131 }
1132
1133 /// Take persistent permission to access the file, directory and its descendants.
1134 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
1135 ///
1136 /// This works by just calling, without displaying any confirmation to the user.
1137 ///
1138 /// Note that [there is a limit to the total number of URI that can be made persistent by this function.](https://stackoverflow.com/questions/71099575/should-i-release-persistableuripermission-when-a-new-storage-location-is-chosen/71100621#71100621)
1139 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
1140 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1141 /// So check by [`AndroidFs::check_persisted_uri_permission`].
1142 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1143 ///
1144 /// # Args
1145 /// - **uri** :
1146 /// URI of the target file or directory. This must be a URI taken from following :
1147 /// - [`FilePicker::pick_files`]
1148 /// - [`FilePicker::pick_file`]
1149 /// - [`FilePicker::pick_visual_medias`]
1150 /// - [`FilePicker::pick_visual_media`]
1151 /// - [`FilePicker::pick_dir`]
1152 /// - [`FilePicker::save_file`]
1153 /// - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_file`], [`AndroidFs::create_dir_all`] :
1154 /// If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function.
1155 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
1156 ///
1157 /// # Support
1158 /// All.
1159 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1160 on_android!({
1161 impl_se!(struct Req<'a> { uri: &'a FileUri });
1162 impl_de!(struct Res;);
1163
1164 self.api
1165 .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1166 .map(|_| ())
1167 .map_err(Into::into)
1168 })
1169 }
1170
1171 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1172 /// Returns false if there are only non-persistent permissions or no permissions.
1173 ///
1174 /// # Args
1175 /// - **uri** :
1176 /// URI of the target file or directory.
1177 /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself.
1178 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1179 ///
1180 /// - **mode** :
1181 /// The mode of permission you want to check.
1182 ///
1183 /// # Support
1184 /// All.
1185 pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1186 on_android!({
1187 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1188 impl_de!(struct Res { value: bool });
1189
1190 self.api
1191 .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1192 .map(|v| v.value)
1193 .map_err(Into::into)
1194 })
1195 }
1196
1197 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
1198 ///
1199 /// # Support
1200 /// All.
1201 pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1202 on_android!(std::iter::Empty::<_>, {
1203 impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1204 impl_de!(struct Res { items: Vec<Obj> });
1205
1206 self.api
1207 .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1208 .map(|v| v.items.into_iter())
1209 .map(|v| v.map(|v| {
1210 let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1211 match v.d {
1212 true => PersistedUriPermission::Dir { uri, can_read, can_write },
1213 false => PersistedUriPermission::File { uri, can_read, can_write }
1214 }
1215 }))
1216 .map_err(Into::into)
1217 })
1218 }
1219
1220 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1221 ///
1222 /// # Args
1223 /// - ***uri*** :
1224 /// URI of the target file or directory.
1225 ///
1226 /// # Support
1227 /// All.
1228 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1229 on_android!({
1230 impl_se!(struct Req<'a> { uri: &'a FileUri });
1231 impl_de!(struct Res;);
1232
1233 self.api
1234 .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1235 .map(|_| ())
1236 .map_err(Into::into)
1237 })
1238 }
1239
1240 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1241 ///
1242 /// # Support
1243 /// All.
1244 pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1245 on_android!({
1246 impl_de!(struct Res);
1247
1248 self.api
1249 .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1250 .map(|_| ())
1251 .map_err(Into::into)
1252 })
1253 }
1254
1255 /// Verify whether this plugin is available.
1256 ///
1257 /// On Android, this returns true.
1258 /// On other platforms, this returns false.
1259 pub fn is_available(&self) -> bool {
1260 cfg!(target_os = "android")
1261 }
1262
1263 /// Get the api level of this Android device.
1264 ///
1265 /// The correspondence table between API levels and Android versions can be found following.
1266 /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1267 ///
1268 /// | Android version | API Level |
1269 /// |------------------|-----------|
1270 /// | 16.0 | 36 |
1271 /// | 15.0 | 35 |
1272 /// | 14.0 | 34 |
1273 /// | 13.0 | 33 |
1274 /// | 12L | 32 |
1275 /// | 12.0 | 31 |
1276 /// | 11.0 | 30 |
1277 /// | 10.0 | 29 |
1278 /// | 9.0 | 28 |
1279 /// | 8.1 | 27 |
1280 /// | 8.0 | 26 |
1281 /// | 7.1 - 7.1.2 | 25 |
1282 /// | 7.0 | 24 |
1283 ///
1284 /// Tauri does not support Android versions below 7.
1285 pub fn api_level(&self) -> Result<usize> {
1286 on_android!({
1287 impl_de!(struct Res { value: usize });
1288
1289 static API_LEVEL: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1290
1291 if let Some(api_level) = API_LEVEL.get() {
1292 return Ok(*api_level)
1293 }
1294
1295 let api_level = self.api
1296 .run_mobile_plugin::<Res>("getApiLevel", "")?
1297 .value;
1298
1299 let _ = API_LEVEL.set(api_level);
1300
1301 Ok(api_level)
1302 })
1303 }
1304
1305
1306
1307
1308 /// See [`AndroidFs::write_via_kotlin`] for information.
1309 /// Use this if you want to write using `std::fs::File`, not entire contents.
1310 ///
1311 /// # Args
1312 /// - ***uri*** :
1313 /// Target file URI to write.
1314 ///
1315 /// - **contetns_writer** :
1316 /// A closure that accepts a mutable reference to a `std::fs::File`
1317 /// and performs the actual write operations. Note that this represents a temporary file.
1318 #[deprecated = "Use AndroidFs::open_writable_stream_via_kotlin instead."]
1319 pub fn write_via_kotlin_in<T>(
1320 &self,
1321 uri: &FileUri,
1322 contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
1323 ) -> crate::Result<T> {
1324
1325 on_android!({
1326 let (mut tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
1327
1328 let result = (|| -> Result<T> {
1329 let t = contents_writer(&mut tmp_file)?;
1330 // 反映されるまで待機する
1331 tmp_file.sync_data()?;
1332 // copy を行う前にファイルを閉じる
1333 std::mem::drop(tmp_file);
1334
1335 self.copy_via_kotlin(&(&tmp_file_path).into(), uri, None)?;
1336 Ok(t)
1337 })();
1338
1339 let _ = std::fs::remove_file(&tmp_file_path);
1340
1341 result
1342 })
1343 }
1344}