tauri_plugin_android_fs/api/
writable_stream.rs

1use crate::*;
2
3
4/// A stream for writing to a file on Android.
5///
6/// Implements [`std::io::Write`], so it can be used for writing.  
7/// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
8///
9/// After writing, call [`WritableStream::reflect`] to apply changes.  
10///
11/// # Inner
12/// This is a wrapper around [`std::fs::File`].  
13/// In most cases, it points to the actual target file, but it may also refer to a temporary file.  
14/// For temporary files, calling [`WritableStream::reflect`] applies the changes to the actual target. 
15pub struct WritableStream<R: tauri::Runtime> {
16    app: tauri::AppHandle<R>,
17    writer: Option<std::fs::File>,
18    writer_attr: Option<WriterAttr>,
19}
20
21enum WriterAttr {
22    ActualTarget,
23    TempBuffer {
24        writer_path: std::path::PathBuf,
25        actual_target_file_uri: FileUri,
26    },
27}
28
29impl<R: tauri::Runtime> WritableStream<R> {
30
31    #[allow(unused)]
32    pub(crate) fn new(
33        app: tauri::AppHandle<R>,
34        file_uri: FileUri,
35        need_write_via_kotlin: bool
36    ) -> Result<Self> {
37
38        let api = app.android_fs();
39        let (writer, writer_attr) = match need_write_via_kotlin {
40            true => {
41                let (tmp_file, tmp_file_path) = api.private_storage().create_new_tmp_file()?;
42                let attr = WriterAttr::TempBuffer { 
43                    writer_path: tmp_file_path, 
44                    actual_target_file_uri: file_uri
45                };
46                (tmp_file, attr)
47            },
48            false => {
49                 let file = api.open_file_writable(&file_uri)?;
50                 (file, WriterAttr::ActualTarget)
51            }
52        };
53      
54        Ok(Self {
55            app,
56            writer: Some(writer),
57            writer_attr: Some(writer_attr),
58        })
59    }
60}
61
62impl<R: tauri::Runtime> WritableStream<R> {
63
64    /// [`WritableStream`] is a wrapper around [`std::fs::File`].
65    /// In most cases, it points to the actual target file, but it may also refer to a temporary file.
66    ///
67    /// For actual target files, this function does nothing.
68    ///
69    /// For temporary files, calling this function applies the changes to the actual target, and remove the temporary file.
70    /// This may take as long as the write operation or even longer.
71    /// Note that if the file is located on cloud storage or similar, the function returns
72    /// without waiting for the uploading to complete.
73    /// 
74    /// If not called explicitly, the same process is performed asynchronously on drop, 
75    /// and no error is returned. 
76    pub fn reflect(mut self) -> Result<()> {
77        let Some(writer) = self.writer.take() else {
78            return Ok(())
79        };
80        let Some(writer_attr) = self.writer_attr.take() else {
81            return Ok(())
82        };
83
84        if let WriterAttr::TempBuffer { 
85            writer_path, 
86            actual_target_file_uri, 
87        } = writer_attr {
88
89            // 反映されるまで待機する
90            let result1 = writer.sync_data();
91            // copy を行う前にファイルを閉じる
92            std::mem::drop(writer);
93
94            let result2 = self.app
95                .android_fs()
96                .copy_via_kotlin(&(writer_path.clone().into()), &actual_target_file_uri, None);
97
98            let _ = std::fs::remove_file(&writer_path);
99
100            result1?;
101            result2?;
102        }
103
104        Ok(())
105    }
106
107    /// [`WritableStream`] is a wrapper around [`std::fs::File`].  
108    /// In most cases, it points to the actual target file, but it may also refer to a temporary file.  
109    ///
110    /// For actual target files, calls [`std::fs::File::sync_all`].  
111    /// For temporary files, this function does nothing.  
112    pub fn sync_all(&mut self) -> std::io::Result<()> {
113        let Some(writer) = self.writer.as_mut() else {
114            return Ok(())
115        };
116        let Some(writer_attr) = self.writer_attr.as_ref() else {
117            return Ok(())
118        };
119        
120        if let WriterAttr::ActualTarget = writer_attr {
121            writer.sync_all()?;
122        }
123        Ok(())
124    }
125
126    /// [`WritableStream`] is a wrapper around [`std::fs::File`].  
127    /// In most cases, it points to the actual target file, but it may also refer to a temporary file.  
128    ///
129    /// For actual target files, calls [`std::fs::File::sync_data`].  
130    /// For temporary files, this function does nothing.  
131    pub fn sync_data(&mut self) -> std::io::Result<()> {
132        let Some(writer) = self.writer.as_mut() else {
133            return Ok(())
134        };
135        let Some(writer_attr) = self.writer_attr.as_ref() else {
136            return Ok(())
137        };
138        
139        if let WriterAttr::ActualTarget = writer_attr {
140            writer.sync_data()?;
141        }
142        Ok(())
143    }
144}
145
146impl<R: tauri::Runtime> std::io::Write for WritableStream<R> {
147
148    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
149        match self.writer.as_mut() {
150            Some(w) => w.write(buf),
151            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
152        }
153    }
154
155    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
156        match self.writer.as_mut() {
157            Some(w) => w.write_all(buf),
158            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
159        }
160    }
161
162    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
163        match self.writer.as_mut() {
164            Some(w) => w.write_vectored(bufs),
165            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
166        }
167    }
168
169    fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
170        match self.writer.as_mut() {
171            Some(w) => w.write_fmt(fmt),
172            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
173        }
174    }
175
176    fn flush(&mut self) -> std::io::Result<()> {
177        match self.writer.as_mut() {
178            Some(w) => w.flush(),
179            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
180        }
181    }
182}
183
184impl<R: tauri::Runtime> std::ops::Drop for WritableStream<R> {
185
186    fn drop(&mut self) {
187        // reflect が行われた場合、以下で return される
188        let Some(writer) = self.writer.take() else {
189            return
190        };
191        let Some(writer_attr) = self.writer_attr.take() else {
192            return
193        };
194
195        // reflect が行われなかった場合、保険として reflect と同じ処理を行う。
196        // ただし drop 内ではエラーの伝搬も panic も行えない。
197        // よって std::io::BufWriter の drop 実装と同じようにエラーは握りつぶす。
198
199        if let WriterAttr::TempBuffer { 
200            writer_path, 
201            actual_target_file_uri, 
202        } = writer_attr {
203
204            let app = self.app.clone();
205            let src = writer;
206            let (src_uri, src_path) = (writer_path.clone().into(), writer_path);
207            let dest_uri = actual_target_file_uri.clone();
208
209            // 時間がかかるので別スレッドに委託する
210            tauri::async_runtime::spawn_blocking(move || {
211                // 反映されるまで待機する
212                let _ = src.sync_data();
213                // copy を行う前にファイルを閉じる
214                std::mem::drop(src);
215
216                let _ = app.android_fs().copy_via_kotlin(&src_uri, &dest_uri, None);
217                let _ = std::fs::remove_file(src_path);
218            });
219        }
220    }
221}