kinode_process_lib/vfs/
file.rs

1use super::{
2    parse_response, vfs_request, FileMetadata, SeekFrom, VfsAction, VfsError, VfsResponse,
3};
4use crate::{get_blob, PackageId};
5
6/// VFS (Virtual File System) helper struct for a file.
7/// Opening or creating a `File` will give you a `Result<File, VfsError>`.
8/// You can call its impl functions to interact with it.
9pub struct File {
10    pub path: String,
11    pub timeout: u64,
12}
13
14impl File {
15    /// Create a new file-manager struct with the given path and timeout.
16    pub fn new<T: Into<String>>(path: T, timeout: u64) -> Self {
17        Self {
18            path: path.into(),
19            timeout,
20        }
21    }
22
23    /// Reads the entire file, from start position.
24    /// Returns a vector of bytes.
25    pub fn read(&self) -> Result<Vec<u8>, VfsError> {
26        let message = vfs_request(&self.path, VfsAction::Read)
27            .send_and_await_response(self.timeout)
28            .unwrap()
29            .map_err(|e| VfsError::SendError(e.kind))?;
30
31        match parse_response(message.body())? {
32            VfsResponse::Read => {
33                let data = match get_blob() {
34                    Some(bytes) => bytes.bytes,
35                    None => {
36                        return Err(VfsError::ParseError {
37                            error: "no blob".to_string(),
38                            path: self.path.clone(),
39                        })
40                    }
41                };
42                Ok(data)
43            }
44            VfsResponse::Err(e) => Err(e.into()),
45            _ => Err(VfsError::ParseError {
46                error: "unexpected response".to_string(),
47                path: self.path.clone(),
48            }),
49        }
50    }
51
52    /// Reads the entire file, from start position, into buffer.
53    /// Returns the amount of bytes read.
54    pub fn read_into(&self, buffer: &mut [u8]) -> Result<usize, VfsError> {
55        let message = vfs_request(&self.path, VfsAction::Read)
56            .send_and_await_response(self.timeout)
57            .unwrap()
58            .map_err(|e| VfsError::SendError(e.kind))?;
59
60        match parse_response(message.body())? {
61            VfsResponse::Read => {
62                let data = get_blob().unwrap_or_default().bytes;
63                let len = std::cmp::min(data.len(), buffer.len());
64                buffer[..len].copy_from_slice(&data[..len]);
65                Ok(len)
66            }
67            VfsResponse::Err(e) => Err(e.into()),
68            _ => Err(VfsError::ParseError {
69                error: "unexpected response".to_string(),
70                path: self.path.clone(),
71            }),
72        }
73    }
74
75    /// Read into buffer from current cursor position
76    /// Returns the amount of bytes read.
77    pub fn read_at(&self, buffer: &mut [u8]) -> Result<usize, VfsError> {
78        let length = buffer.len() as u64;
79
80        let message = vfs_request(&self.path, VfsAction::ReadExact { length })
81            .send_and_await_response(self.timeout)
82            .unwrap()
83            .map_err(|e| VfsError::SendError(e.kind))?;
84
85        match parse_response(message.body())? {
86            VfsResponse::Read => {
87                let data = get_blob().unwrap_or_default().bytes;
88                let len = std::cmp::min(data.len(), buffer.len());
89                buffer[..len].copy_from_slice(&data[..len]);
90                Ok(len)
91            }
92            VfsResponse::Err(e) => Err(e.into()),
93            _ => Err(VfsError::ParseError {
94                error: "unexpected response".to_string(),
95                path: self.path.clone(),
96            }),
97        }
98    }
99
100    /// Reads until end of file from current cursor position
101    /// Returns a vector of bytes.
102    pub fn read_to_end(&self) -> Result<Vec<u8>, VfsError> {
103        let message = vfs_request(&self.path, VfsAction::ReadToEnd)
104            .send_and_await_response(self.timeout)
105            .unwrap()
106            .map_err(|e| VfsError::SendError(e.kind))?;
107
108        match parse_response(message.body())? {
109            VfsResponse::Read => Ok(get_blob().unwrap_or_default().bytes),
110            VfsResponse::Err(e) => Err(e),
111            _ => Err(VfsError::ParseError {
112                error: "unexpected response".to_string(),
113                path: self.path.clone(),
114            }),
115        }
116    }
117
118    /// Reads until end of file from current cursor position, converts to String.
119    /// Throws error if bytes aren't valid utf-8.
120    /// Returns a vector of bytes.
121    pub fn read_to_string(&self) -> Result<String, VfsError> {
122        let message = vfs_request(&self.path, VfsAction::ReadToString)
123            .send_and_await_response(self.timeout)
124            .unwrap()
125            .map_err(|e| VfsError::SendError(e.kind))?;
126
127        match parse_response(message.body())? {
128            VfsResponse::ReadToString(s) => Ok(s),
129            VfsResponse::Err(e) => Err(e),
130            _ => Err(VfsError::ParseError {
131                error: "unexpected response".to_string(),
132                path: self.path.clone(),
133            }),
134        }
135    }
136
137    /// Write entire slice as the new file.
138    /// Truncates anything that existed at path before.
139    pub fn write(&self, buffer: &[u8]) -> Result<(), VfsError> {
140        let message = vfs_request(&self.path, VfsAction::Write)
141            .blob_bytes(buffer)
142            .send_and_await_response(self.timeout)
143            .unwrap()
144            .map_err(|e| VfsError::SendError(e.kind))?;
145
146        match parse_response(message.body())? {
147            VfsResponse::Ok => Ok(()),
148            VfsResponse::Err(e) => Err(e),
149            _ => Err(VfsError::ParseError {
150                error: "unexpected response".to_string(),
151                path: self.path.clone(),
152            }),
153        }
154    }
155
156    /// Write buffer to file at current position, overwriting any existing data.
157    pub fn write_all(&mut self, buffer: &[u8]) -> Result<(), VfsError> {
158        let message = vfs_request(&self.path, VfsAction::WriteAll)
159            .blob_bytes(buffer)
160            .send_and_await_response(self.timeout)
161            .unwrap()
162            .map_err(|e| VfsError::SendError(e.kind))?;
163
164        match parse_response(message.body())? {
165            VfsResponse::Ok => Ok(()),
166            VfsResponse::Err(e) => Err(e),
167            _ => Err(VfsError::ParseError {
168                error: "unexpected response".to_string(),
169                path: self.path.clone(),
170            }),
171        }
172    }
173
174    /// Write buffer to the end position of file.
175    pub fn append(&mut self, buffer: &[u8]) -> Result<(), VfsError> {
176        let message = vfs_request(&self.path, VfsAction::Append)
177            .blob_bytes(buffer)
178            .send_and_await_response(self.timeout)
179            .unwrap()
180            .map_err(|e| VfsError::SendError(e.kind))?;
181
182        match parse_response(message.body())? {
183            VfsResponse::Ok => Ok(()),
184            VfsResponse::Err(e) => Err(e),
185            _ => Err(VfsError::ParseError {
186                error: "unexpected response".to_string(),
187                path: self.path.clone(),
188            }),
189        }
190    }
191
192    /// Seek file to position.
193    /// Returns the new position.
194    pub fn seek(&mut self, pos: SeekFrom) -> Result<u64, VfsError> {
195        let message = vfs_request(&self.path, VfsAction::Seek(pos))
196            .send_and_await_response(self.timeout)
197            .unwrap()
198            .map_err(|e| VfsError::SendError(e.kind))?;
199
200        match parse_response(message.body())? {
201            VfsResponse::SeekFrom {
202                new_offset: new_pos,
203            } => Ok(new_pos),
204            VfsResponse::Err(e) => Err(e),
205            _ => Err(VfsError::ParseError {
206                error: "unexpected response".to_string(),
207                path: self.path.clone(),
208            }),
209        }
210    }
211
212    /// Copies a file to path, returns a new File.
213    pub fn copy(&mut self, path: &str) -> Result<File, VfsError> {
214        let message = vfs_request(
215            &self.path,
216            VfsAction::CopyFile {
217                new_path: path.to_string(),
218            },
219        )
220        .send_and_await_response(self.timeout)
221        .unwrap()
222        .map_err(|e| VfsError::SendError(e.kind))?;
223
224        match parse_response(message.body())? {
225            VfsResponse::Ok => Ok(File {
226                path: path.to_string(),
227                timeout: self.timeout,
228            }),
229            VfsResponse::Err(e) => Err(e),
230            _ => Err(VfsError::ParseError {
231                error: "unexpected response".to_string(),
232                path: self.path.clone(),
233            }),
234        }
235    }
236
237    /// Set file length, if given size > underlying file, fills it with 0s.
238    pub fn set_len(&mut self, size: u64) -> Result<(), VfsError> {
239        let message = vfs_request(&self.path, VfsAction::SetLen(size))
240            .send_and_await_response(self.timeout)
241            .unwrap()
242            .map_err(|e| VfsError::SendError(e.kind))?;
243
244        match parse_response(message.body())? {
245            VfsResponse::Ok => Ok(()),
246            VfsResponse::Err(e) => Err(e),
247            _ => Err(VfsError::ParseError {
248                error: "unexpected response".to_string(),
249                path: self.path.clone(),
250            }),
251        }
252    }
253
254    /// Metadata of a path, returns file type and length.
255    pub fn metadata(&self) -> Result<FileMetadata, VfsError> {
256        let message = vfs_request(&self.path, VfsAction::Metadata)
257            .send_and_await_response(self.timeout)
258            .unwrap()
259            .map_err(|e| VfsError::SendError(e.kind))?;
260
261        match parse_response(message.body())? {
262            VfsResponse::Metadata(metadata) => Ok(metadata),
263            VfsResponse::Err(e) => Err(e),
264            _ => Err(VfsError::ParseError {
265                error: "unexpected response".to_string(),
266                path: self.path.clone(),
267            }),
268        }
269    }
270
271    /// Syncs path file buffers to disk.
272    pub fn sync_all(&self) -> Result<(), VfsError> {
273        let message = vfs_request(&self.path, VfsAction::SyncAll)
274            .send_and_await_response(self.timeout)
275            .unwrap()
276            .map_err(|e| VfsError::SendError(e.kind))?;
277
278        match parse_response(message.body())? {
279            VfsResponse::Ok => Ok(()),
280            VfsResponse::Err(e) => Err(e),
281            _ => Err(VfsError::ParseError {
282                error: "unexpected response".to_string(),
283                path: self.path.clone(),
284            }),
285        }
286    }
287}
288
289impl Drop for File {
290    fn drop(&mut self) {
291        vfs_request(&self.path, VfsAction::CloseFile)
292            .send()
293            .unwrap();
294    }
295}
296
297/// Creates a drive with path "/package_id/drive", gives you read and write caps.
298/// Will only work on the same package_id as you're calling it from, unless you
299/// have root capabilities.
300pub fn create_drive(
301    package_id: PackageId,
302    drive: &str,
303    timeout: Option<u64>,
304) -> Result<String, VfsError> {
305    let timeout = timeout.unwrap_or(5);
306    let path = format!("/{}/{}", package_id, drive);
307
308    let message = vfs_request(&path, VfsAction::CreateDrive)
309        .send_and_await_response(timeout)
310        .unwrap()
311        .map_err(|e| VfsError::SendError(e.kind))?;
312
313    match parse_response(message.body())? {
314        VfsResponse::Ok => Ok(path),
315        VfsResponse::Err(e) => Err(e),
316        _ => Err(VfsError::ParseError {
317            error: "unexpected response".to_string(),
318            path,
319        }),
320    }
321}
322
323/// Opens a file at path, if no file at path, creates one if boolean create is true.
324pub fn open_file(path: &str, create: bool, timeout: Option<u64>) -> Result<File, VfsError> {
325    let timeout = timeout.unwrap_or(5);
326
327    let message = vfs_request(path, VfsAction::OpenFile { create })
328        .send_and_await_response(timeout)
329        .unwrap()
330        .map_err(|e| VfsError::SendError(e.kind))?;
331
332    match parse_response(message.body())? {
333        VfsResponse::Ok => Ok(File {
334            path: path.to_string(),
335            timeout,
336        }),
337        VfsResponse::Err(e) => Err(e),
338        _ => Err(VfsError::ParseError {
339            error: "unexpected response".to_string(),
340            path: path.to_string(),
341        }),
342    }
343}
344
345/// Creates a file at path, if file found at path, truncates it to 0.
346pub fn create_file(path: &str, timeout: Option<u64>) -> Result<File, VfsError> {
347    let timeout = timeout.unwrap_or(5);
348
349    let message = vfs_request(path, VfsAction::CreateFile)
350        .send_and_await_response(timeout)
351        .unwrap()
352        .map_err(|e| VfsError::SendError(e.kind))?;
353
354    match parse_response(message.body())? {
355        VfsResponse::Ok => Ok(File {
356            path: path.to_string(),
357            timeout,
358        }),
359        VfsResponse::Err(e) => Err(e),
360        _ => Err(VfsError::ParseError {
361            error: "unexpected response".to_string(),
362            path: path.to_string(),
363        }),
364    }
365}
366
367/// Removes a file at path, errors if path not found or path is not a file.
368pub fn remove_file(path: &str, timeout: Option<u64>) -> Result<(), VfsError> {
369    let timeout = timeout.unwrap_or(5);
370
371    let message = vfs_request(path, VfsAction::RemoveFile)
372        .send_and_await_response(timeout)
373        .unwrap()
374        .map_err(|e| VfsError::SendError(e.kind))?;
375
376    match parse_response(message.body())? {
377        VfsResponse::Ok => Ok(()),
378        VfsResponse::Err(e) => Err(e.into()),
379        _ => Err(VfsError::ParseError {
380            error: "unexpected response".to_string(),
381            path: path.to_string(),
382        }),
383    }
384}