1use crate::api;
2use crate::error::{Error, ErrorCode, Result};
3use crate::resource::{Handle, IO_FLAG_EOF, MAX_IO_CHUNK_BYTES};
4use serde::{Deserialize, Serialize};
5use std::sync::atomic::{AtomicU64, Ordering};
6
7static TEMP_SEQUENCE: AtomicU64 = AtomicU64::new(1);
8const ATOMIC_TEMP_ATTEMPTS: usize = 16;
9
10#[derive(Debug, Clone, Default, Serialize)]
11pub struct OpenOptions {
12 pub read: bool,
13 pub write: bool,
14 pub create: bool,
15 pub create_new: bool,
16 pub truncate: bool,
17 pub append: bool,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub mode: Option<u32>,
20}
21
22#[derive(Debug, Clone, Deserialize)]
23pub struct FileStat {
24 pub uri: String,
25 pub kind: FileKind,
26 pub size: u64,
27 pub mode: u32,
28 pub modified_unix_ms: i64,
29 #[serde(default)]
30 pub created_unix_ms: Option<i64>,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum FileKind {
36 File,
37 Directory,
38 Symlink,
39 Other,
40 #[serde(other)]
41 Unknown,
42}
43
44#[derive(Debug, Clone, Deserialize)]
45pub struct DirectoryEntry {
46 pub name: String,
47 pub uri: String,
48 pub kind: FileKind,
49}
50
51#[derive(Debug, Clone, Deserialize)]
52pub struct DirectoryPage {
53 #[serde(default)]
54 pub entries: Vec<DirectoryEntry>,
55 pub eof: bool,
56}
57
58#[derive(Debug, Clone, Deserialize)]
59pub struct WatchEvent {
60 pub sequence: u64,
61 pub kind: WatchKind,
62 pub uri: String,
63 #[serde(default)]
64 pub previous_uri: Option<String>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum WatchKind {
70 Create,
71 Change,
72 Delete,
73 Rename,
74 Overflow,
75 #[serde(other)]
76 Unknown,
77}
78
79#[derive(Debug, Clone, Deserialize)]
80pub struct MountInfo {
81 pub id: String,
82 pub uri: String,
83 pub read_only: bool,
84}
85
86#[derive(Debug, Clone, Deserialize)]
87pub struct Mounts {
88 #[serde(default)]
89 pub mounts: Vec<MountInfo>,
90}
91
92#[derive(Deserialize)]
93struct HandleResult {
94 handle: u64,
95}
96
97#[derive(Serialize)]
98struct URIArguments<'a> {
99 uri: &'a str,
100}
101
102pub struct File {
103 handle: Handle,
104}
105
106impl File {
107 pub fn open(uri: &str, options: OpenOptions) -> Result<Self> {
108 #[derive(Serialize)]
109 struct Arguments<'a> {
110 uri: &'a str,
111 options: OpenOptions,
112 }
113 let opened: HandleResult = api::call("fs.open", &Arguments { uri, options })?;
114 Ok(Self {
115 handle: Handle::new(opened.handle)?,
116 })
117 }
118
119 pub fn read(&mut self, capacity: usize) -> Result<(Vec<u8>, u32)> {
120 self.handle.read(capacity)
121 }
122
123 pub fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
124 for chunk in bytes.chunks(MAX_IO_CHUNK_BYTES) {
125 self.handle.write(chunk, 0)?;
126 }
127 Ok(())
128 }
129
130 pub fn seek(&mut self, offset: i64, whence: u32) -> Result<u64> {
131 self.handle.seek(offset, whence)
132 }
133
134 pub fn sync(&mut self) -> Result<()> {
135 #[derive(Serialize)]
136 struct Arguments {
137 handle: u64,
138 }
139 let _: serde_json::Value = api::call(
140 "fs.sync",
141 &Arguments {
142 handle: self.handle.id(),
143 },
144 )?;
145 Ok(())
146 }
147
148 pub fn close(mut self) -> Result<()> {
149 self.handle.close()
150 }
151}
152
153pub struct Directory {
154 handle: Handle,
155}
156
157impl Directory {
158 pub fn open(uri: &str) -> Result<Self> {
159 let opened: HandleResult = api::call("fs.read_dir.open", &URIArguments { uri })?;
160 Ok(Self {
161 handle: Handle::new(opened.handle)?,
162 })
163 }
164
165 pub fn next(&mut self, limit: u16) -> Result<DirectoryPage> {
166 #[derive(Serialize)]
167 struct Arguments {
168 handle: u64,
169 limit: u16,
170 }
171 api::call(
172 "fs.read_dir.next",
173 &Arguments {
174 handle: self.handle.id(),
175 limit,
176 },
177 )
178 }
179
180 pub fn close(mut self) -> Result<()> {
181 self.handle.close()
182 }
183}
184
185pub struct Watch {
186 handle: Handle,
187}
188
189impl Watch {
190 pub fn open(uri: &str) -> Result<Self> {
191 #[derive(Serialize)]
192 struct Arguments<'a> {
193 uri: &'a str,
194 recursive: bool,
195 }
196 let opened: HandleResult = api::call(
197 "fs.watch",
198 &Arguments {
199 uri,
200 recursive: false,
201 },
202 )?;
203 Ok(Self {
204 handle: Handle::new(opened.handle)?,
205 })
206 }
207
208 pub fn next(&mut self, timeout_ms: u32) -> Result<WatchEvent> {
209 #[derive(Serialize)]
210 struct Arguments {
211 handle: u64,
212 timeout_ms: u32,
213 }
214 api::call(
215 "fs.watch_next",
216 &Arguments {
217 handle: self.handle.id(),
218 timeout_ms,
219 },
220 )
221 }
222
223 pub fn close(mut self) -> Result<()> {
224 self.handle.close()
225 }
226}
227
228pub fn mounts() -> Result<Mounts> {
229 api::call("fs.mounts", &serde_json::json!({}))
230}
231
232pub fn stat(uri: &str, follow_symlinks: bool) -> Result<FileStat> {
233 #[derive(Serialize)]
234 struct Arguments<'a> {
235 uri: &'a str,
236 follow_symlinks: bool,
237 }
238 api::call(
239 "fs.stat",
240 &Arguments {
241 uri,
242 follow_symlinks,
243 },
244 )
245}
246
247pub fn read_file(uri: &str) -> Result<Vec<u8>> {
248 let mut file = File::open(
249 uri,
250 OpenOptions {
251 read: true,
252 ..OpenOptions::default()
253 },
254 )?;
255 let mut result = Vec::new();
256 loop {
257 let (chunk, flags) = file.read(MAX_IO_CHUNK_BYTES)?;
258 if chunk.is_empty() && flags & IO_FLAG_EOF == 0 {
259 return Err(Error::internal("file read made no progress"));
260 }
261 result.extend_from_slice(&chunk);
262 if flags & IO_FLAG_EOF != 0 {
263 file.close()?;
264 return Ok(result);
265 }
266 }
267}
268
269pub fn read_text(uri: &str) -> Result<String> {
270 String::from_utf8(read_file(uri)?)
271 .map_err(|_| Error::internal("file content is not valid UTF-8"))
272}
273
274pub fn write_file(uri: &str, bytes: &[u8], atomic: bool) -> Result<()> {
275 if !atomic {
276 return write_direct(uri, bytes, false);
277 }
278 for _ in 0..ATOMIC_TEMP_ATTEMPTS {
279 let temporary = atomic_temporary_uri(uri)?;
280 match write_direct(&temporary, bytes, true) {
281 Ok(()) => {
282 let result = rename(&temporary, uri, true);
283 if result.is_err() {
284 let _ = remove(&temporary, false);
285 }
286 return result;
287 }
288 Err(error) if error.code == ErrorCode::AlreadyExists => continue,
289 Err(error) => {
290 let _ = remove(&temporary, false);
291 return Err(error);
292 }
293 }
294 }
295 Err(Error::internal(
296 "could not allocate a unique same-directory atomic write file",
297 ))
298}
299
300pub fn write_text(uri: &str, text: &str, atomic: bool) -> Result<()> {
301 write_file(uri, text.as_bytes(), atomic)
302}
303
304fn write_direct(uri: &str, bytes: &[u8], create_new: bool) -> Result<()> {
305 let mut file = File::open(
306 uri,
307 OpenOptions {
308 write: true,
309 create: !create_new,
310 create_new,
311 truncate: !create_new,
312 mode: Some(0o600),
313 ..OpenOptions::default()
314 },
315 )?;
316 file.write_all(bytes)?;
317 file.sync()?;
318 file.close()
319}
320
321pub fn remove(uri: &str, recursive: bool) -> Result<()> {
322 #[derive(Serialize)]
323 struct Arguments<'a> {
324 uri: &'a str,
325 recursive: bool,
326 }
327 let _: serde_json::Value = api::call("fs.remove", &Arguments { uri, recursive })?;
328 Ok(())
329}
330
331pub fn mkdir(uri: &str, recursive: bool, mode: u32) -> Result<()> {
332 #[derive(Serialize)]
333 struct Arguments<'a> {
334 uri: &'a str,
335 recursive: bool,
336 mode: u32,
337 }
338 let _: serde_json::Value = api::call(
339 "fs.mkdir",
340 &Arguments {
341 uri,
342 recursive,
343 mode,
344 },
345 )?;
346 Ok(())
347}
348
349pub fn rename(from: &str, to: &str, overwrite: bool) -> Result<()> {
350 transfer("fs.rename", from, to, overwrite)
351}
352
353pub fn copy(from: &str, to: &str, overwrite: bool) -> Result<()> {
354 transfer("fs.copy", from, to, overwrite)
355}
356
357fn transfer(operation: &str, from: &str, to: &str, overwrite: bool) -> Result<()> {
358 #[derive(Serialize)]
359 struct Arguments<'a> {
360 from: &'a str,
361 to: &'a str,
362 overwrite: bool,
363 }
364 let _: serde_json::Value = api::call(
365 operation,
366 &Arguments {
367 from,
368 to,
369 overwrite,
370 },
371 )?;
372 Ok(())
373}
374
375pub fn set_times(uri: &str, accessed_unix_ms: i64, modified_unix_ms: i64) -> Result<()> {
376 #[derive(Serialize)]
377 struct Arguments<'a> {
378 uri: &'a str,
379 accessed_unix_ms: i64,
380 modified_unix_ms: i64,
381 }
382 let _: serde_json::Value = api::call(
383 "fs.set_times",
384 &Arguments {
385 uri,
386 accessed_unix_ms,
387 modified_unix_ms,
388 },
389 )?;
390 Ok(())
391}
392
393fn atomic_temporary_uri(uri: &str) -> Result<String> {
394 let (directory, name) = uri
395 .rsplit_once('/')
396 .filter(|(_, name)| !name.is_empty())
397 .ok_or_else(|| Error::from_abi_status(-1))?;
398 let sequence = TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
399 Ok(format!(
400 "{directory}/.{name}.redevplugin-{sequence:016x}.tmp"
401 ))
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn atomic_temporary_file_stays_in_the_same_directory() {
410 let temporary = atomic_temporary_uri("redevfs://workspace/src/data.bin").unwrap();
411 assert!(temporary.starts_with("redevfs://workspace/src/.data.bin.redevplugin-"));
412 assert!(temporary.ends_with(".tmp"));
413 }
414}