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.
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 changes are applied on drop, but no error is returned. 
75    pub fn reflect(mut self) -> Result<()> {
76        let Some(writer) = self.writer.take() else {
77            return Ok(())
78        };
79        let Some(writer_attr) = self.writer_attr.take() else {
80            return Ok(())
81        };
82
83        if let WriterAttr::TempBuffer { 
84            writer_path, 
85            actual_target_file_uri, 
86        } = writer_attr {
87
88            // 反映されるまで待機する
89            let result1 = writer.sync_data();
90            // copy を行う前にファイルを閉じる
91            std::mem::drop(writer);
92
93            let result2 = self.app
94                .android_fs()
95                .copy_via_kotlin(&(writer_path.clone().into()), &actual_target_file_uri, None);
96
97            let result3 = std::fs::remove_file(&writer_path);
98
99            result1?;
100            result2?;
101            result3?;
102        }
103
104        Ok(())
105    }
106
107    #[allow(unused)]
108    pub(crate) fn close_without_reflect(mut self) -> Result<()> {
109        let Some(writer) = self.writer.take() else {
110            return Ok(())
111        };
112        let Some(writer_attr) = self.writer_attr.take() else {
113            return Ok(())
114        };
115
116        if let WriterAttr::TempBuffer { writer_path, .. } = writer_attr {
117            std::mem::drop(writer);
118            std::fs::remove_file(&writer_path)?;
119        }
120
121        Ok(())
122    }
123
124    /// [`WritableStream`] is a wrapper around [`std::fs::File`].  
125    /// In most cases, it points to the actual target file, but it may also refer to a temporary file.  
126    ///
127    /// For actual target files, calls [`std::fs::File::sync_all`].  
128    /// For temporary files, this function does nothing.  
129    pub fn sync_all(&mut self) -> std::io::Result<()> {
130        let Some(writer) = self.writer.as_mut() else {
131            return Ok(())
132        };
133        let Some(writer_attr) = self.writer_attr.as_ref() else {
134            return Ok(())
135        };
136        
137        if let WriterAttr::ActualTarget = writer_attr {
138            writer.sync_all()?;
139        }
140        Ok(())
141    }
142
143    /// [`WritableStream`] is a wrapper around [`std::fs::File`].  
144    /// In most cases, it points to the actual target file, but it may also refer to a temporary file.  
145    ///
146    /// For actual target files, calls [`std::fs::File::sync_data`].  
147    /// For temporary files, this function does nothing.  
148    pub fn sync_data(&mut self) -> std::io::Result<()> {
149        let Some(writer) = self.writer.as_mut() else {
150            return Ok(())
151        };
152        let Some(writer_attr) = self.writer_attr.as_ref() else {
153            return Ok(())
154        };
155        
156        if let WriterAttr::ActualTarget = writer_attr {
157            writer.sync_data()?;
158        }
159        Ok(())
160    }
161}
162
163impl<R: tauri::Runtime> std::io::Write for WritableStream<R> {
164
165    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
166        match self.writer.as_mut() {
167            Some(w) => w.write(buf),
168            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
169        }
170    }
171
172    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
173        match self.writer.as_mut() {
174            Some(w) => w.write_all(buf),
175            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
176        }
177    }
178
179    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
180        match self.writer.as_mut() {
181            Some(w) => w.write_vectored(bufs),
182            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
183        }
184    }
185
186    fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
187        match self.writer.as_mut() {
188            Some(w) => w.write_fmt(fmt),
189            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
190        }
191    }
192
193    fn flush(&mut self) -> std::io::Result<()> {
194        match self.writer.as_mut() {
195            Some(w) => w.flush(),
196            None => Err(std::io::Error::new(std::io::ErrorKind::Other, "writer missing")),
197        }
198    }
199}
200
201impl<R: tauri::Runtime> std::ops::Drop for WritableStream<R> {
202
203    fn drop(&mut self) {
204        // reflect が行われた場合、以下で return される
205        let Some(writer) = self.writer.take() else {
206            return
207        };
208        let Some(writer_attr) = self.writer_attr.take() else {
209            return
210        };
211
212        // reflect が行われなかった場合、保険として reflect と同じ処理を行う。
213        // ただし drop 内ではエラーの伝搬も panic も行えない。
214        // よって std::io::BufWriter の drop 実装と同じようにエラーは握りつぶす。
215
216        if let WriterAttr::TempBuffer { 
217            writer_path, 
218            actual_target_file_uri, 
219        } = writer_attr {
220
221            let app = self.app.clone();
222            let src = writer;
223            let (src_uri, src_path) = (writer_path.clone().into(), writer_path);
224            let dest_uri = actual_target_file_uri.clone();
225
226            // 時間がかかるので別スレッドに委託する
227            tauri::async_runtime::spawn_blocking(move || {
228                // 反映されるまで待機する
229                let _ = src.sync_data();
230                // copy を行う前にファイルを閉じる
231                std::mem::drop(src);
232
233                let _ = app.android_fs().copy_via_kotlin(&src_uri, &dest_uri, None);
234                let _ = std::fs::remove_file(src_path);
235            });
236        }
237    }
238}