1use std::cell::RefCell;
8use std::fmt::{Display, Formatter};
9use std::fs::File;
10use std::future::{Future, poll_fn};
11use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
12use std::rc::Rc;
13use std::str::FromStr;
14use std::sync::Arc;
15use std::task::Poll;
16use std::{io, slice};
17
18use ion::class::Reflector;
19use ion::conversions::{ConversionBehavior, FromValue, IntoValue, ToValue as _};
20use ion::function::Opt;
21use ion::typedarray::{Uint8Array, Uint8ArrayWrapper};
22use ion::{Context, Error, ErrorKind, Promise, Result, TracedHeap, Value, js_class};
23use mozjs::jsval::DoubleValue;
24use runtime::globals::file::BufferSource;
25use runtime::promise::future_to_promise;
26use tokio::task::spawn_blocking;
27
28use crate::fs::{Metadata, file_error, seek_error};
29
30#[derive(Copy, Clone, Debug, Default)]
31pub enum SeekMode {
32 #[default]
33 Current,
34 Start,
35 End,
36}
37
38impl FromStr for SeekMode {
39 type Err = Error;
40
41 fn from_str(redirect: &str) -> Result<SeekMode> {
42 use SeekMode as SM;
43 match redirect {
44 "current" => Ok(SM::Current),
45 "start" => Ok(SM::Start),
46 "end" => Ok(SM::End),
47 _ => Err(Error::new("Invalid value for Enumeration SeekMode", ErrorKind::Type)),
48 }
49 }
50}
51
52impl Display for SeekMode {
53 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
54 use SeekMode as SM;
55 match self {
56 SM::Current => write!(f, "current"),
57 SM::Start => write!(f, "start"),
58 SM::End => write!(f, "end"),
59 }
60 }
61}
62
63impl<'cx> FromValue<'cx> for SeekMode {
64 type Config = ();
65
66 fn from_value(cx: &'cx Context, value: &Value, _: bool, _: ()) -> Result<SeekMode> {
67 let redirect = String::from_value(cx, value, true, ())?;
68 SeekMode::from_str(&redirect)
69 }
70}
71
72pub enum ReadResult {
73 BytesWritten(usize),
74 Buffer(Vec<u8>),
75}
76
77impl IntoValue<'_> for ReadResult {
78 fn into_value(self: Box<Self>, cx: &Context, value: &mut Value) {
79 match *self {
80 ReadResult::BytesWritten(bytes) => value.handle_mut().set(DoubleValue(bytes as f64)),
81 ReadResult::Buffer(buffer) => Uint8ArrayWrapper::from(buffer).into_typed_array(cx).to_value(cx, value),
82 }
83 }
84}
85
86#[js_class]
87pub struct FileHandle {
88 reflector: Reflector,
89 #[trace(no_trace)]
90 path: Arc<str>,
91 #[trace(no_trace)]
92 handle: Rc<RefCell<Option<File>>>,
93}
94
95impl FileHandle {
96 pub(crate) fn new(path: &str, file: File) -> FileHandle {
97 FileHandle {
98 reflector: Reflector::new(),
99 path: Arc::from(path),
100 handle: Rc::new(RefCell::new(Some(file))),
101 }
102 }
103
104 pub(crate) fn with_sync<F, T>(&self, callback: F) -> Result<T>
105 where
106 F: FnOnce(&mut File) -> Result<T>,
107 {
108 match &mut *self.handle.borrow_mut() {
109 Some(handle) => callback(handle),
110 None => Err(Error::new("File is busy due to async operation.", None)),
111 }
112 }
113
114 pub(crate) fn with_blocking_task<F, T>(&self, callback: F) -> impl Future<Output = io::Result<T>> + 'static
115 where
116 F: FnOnce(&mut File) -> io::Result<T> + Send + 'static,
117 T: Send + 'static,
118 {
119 let handle_cell = Rc::clone(&self.handle);
120
121 async move {
122 let mut taken = false;
123 let mut handle = poll_fn(|wcx| {
124 if let Ok(mut handle) = handle_cell.try_borrow_mut()
125 && let Some(file) = handle.as_mut()
126 {
127 let file = file.try_clone().ok().unwrap_or_else(|| {
128 taken = true;
129 handle.take().unwrap()
130 });
131 return Poll::Ready(file);
132 }
133
134 wcx.waker().wake_by_ref();
135 Poll::Pending
136 })
137 .await;
138
139 let (handle, result) = spawn_blocking(move || {
140 let result = callback(&mut handle);
141 (handle, result)
142 })
143 .await
144 .unwrap();
145
146 if taken {
147 handle_cell.borrow_mut().replace(handle);
148 }
149
150 result
151 }
152 }
153
154 #[expect(clippy::too_many_arguments)]
155 pub(crate) fn with_blocking_promise<'cx, F, T, A, E, D>(
156 &self, cx: &'cx Context, action: &'static str, path: Arc<str>, callback: F, callback_after: A,
157 error_callback: E, error_data: D,
158 ) -> Option<Promise<'cx>>
159 where
160 F: FnOnce(&mut File) -> io::Result<T> + Send + 'static,
161 T: for<'cx2> IntoValue<'cx2> + Send + 'static,
162 A: FnOnce() + 'static,
163 E: for<'p, 'e> FnOnce(&'static str, &'p str, &'e io::Error, D) -> Error + 'static,
164 D: 'static,
165 {
166 let task = self.with_blocking_task(callback);
167 future_to_promise(cx, move |_| async move {
168 let result = task.await.map_err(|err| error_callback(action, &path, &err, error_data));
169 callback_after();
170 result
171 })
172 }
173}
174
175#[js_class]
176impl FileHandle {
177 pub fn read<'cx>(&self, cx: &'cx Context, Opt(array): Opt<Uint8Array>) -> Option<Promise<'cx>> {
178 let path = Arc::clone(&self.path);
179 let bytes = array.as_ref().map(|array| unsafe {
180 let (ptr, len) = array.data();
181 slice::from_raw_parts_mut(ptr, len)
182 });
183 let array = array.as_ref().map(|array| TracedHeap::new(array.handle().get()));
184
185 self.with_blocking_promise(
186 cx,
187 "read",
188 path,
189 move |file| read_inner(file, bytes),
190 || drop(array),
191 file_error,
192 (),
193 )
194 }
195
196 #[ion(name = "readSync")]
197 pub fn read_sync(&self, Opt(array): Opt<Uint8Array>) -> Result<ReadResult> {
198 self.with_sync(|file| {
199 let bytes = array.as_ref().map(|array| unsafe { array.as_mut_slice() });
200 read_inner(file, bytes).map_err(|err| file_error("read", &self.path, &err, ()))
201 })
202 }
203
204 pub fn write<'cx>(
205 &self, cx: &'cx Context, #[ion(convert = false)] contents: BufferSource<'cx>,
206 ) -> Option<Promise<'cx>> {
207 let path = Arc::clone(&self.path);
208 let contents = contents.to_vec();
209 self.with_blocking_promise(
210 cx,
211 "write",
212 path,
213 move |file| file.write(&contents).map(|bytes| bytes as u64),
214 || {},
215 file_error,
216 (),
217 )
218 }
219
220 #[ion(name = "writeSync")]
221 pub fn write_sync(&self, #[ion(convert = false)] contents: BufferSource) -> Result<u64> {
222 self.with_sync(|file| {
223 let contents = unsafe { contents.as_slice() };
224 match file.write(contents) {
225 Ok(bytes) => Ok(bytes as u64),
226 Err(err) => Err(file_error("write", &self.path, &err, ())),
227 }
228 })
229 }
230
231 #[ion(name = "writeAll")]
232 pub fn write_all<'cx>(
233 &self, cx: &'cx Context, #[ion(convert = false)] contents: BufferSource<'cx>,
234 ) -> Option<Promise<'cx>> {
235 let path = Arc::clone(&self.path);
236 let contents = contents.to_vec();
237 self.with_blocking_promise(
238 cx,
239 "write",
240 path,
241 move |file| file.write_all(&contents).map(|_| ()),
242 || {},
243 file_error,
244 (),
245 )
246 }
247
248 #[ion(name = "writeAllSync")]
249 pub fn write_all_sync(&self, #[ion(convert = false)] contents: BufferSource) -> Result<()> {
250 self.with_sync(|file| {
251 let contents = unsafe { contents.as_slice() };
252 match file.write_all(contents) {
253 Ok(_) => Ok(()),
254 Err(err) => Err(file_error("write", &self.path, &err, ())),
255 }
256 })
257 }
258
259 pub fn truncate<'cx>(
260 &self, cx: &'cx Context, #[ion(convert = ConversionBehavior::EnforceRange
261 )]
262 Opt(length): Opt<u64>,
263 ) -> Option<Promise<'cx>> {
264 let path = Arc::clone(&self.path);
265 self.with_blocking_promise(
266 cx,
267 "truncate",
268 path,
269 move |file| file.set_len(length.unwrap_or(0)),
270 || {},
271 file_error,
272 (),
273 )
274 }
275
276 #[ion(name = "truncateSync")]
277 pub fn truncate_sync(
278 &self, #[ion(convert = ConversionBehavior::EnforceRange)] Opt(length): Opt<u64>,
279 ) -> Result<()> {
280 self.with_sync(|file| {
281 file.set_len(length.unwrap_or(0))
282 .map_err(|err| file_error("truncate", &self.path, &err, ()))
283 })
284 }
285
286 pub fn seek<'cx>(
287 &self, cx: &'cx Context, #[ion(convert = ConversionBehavior::EnforceRange)] offset: i64,
288 Opt(mode): Opt<SeekMode>,
289 ) -> Option<Promise<'cx>> {
290 let path = Arc::clone(&self.path);
291 let mode = mode.unwrap_or_default();
292 self.with_blocking_promise(
293 cx,
294 "seek",
295 path,
296 move |file| {
297 let seek = seek_from_mode(offset, mode);
298 file.seek(seek)
299 },
300 || {},
301 seek_error,
302 (mode, offset),
303 )
304 }
305
306 #[ion(name = "seekSync")]
307 pub fn seek_sync(
308 &self, #[ion(convert = ConversionBehavior::EnforceRange
309 )]
310 offset: i64, Opt(mode): Opt<SeekMode>,
311 ) -> Result<u64> {
312 self.with_sync(|file| {
313 let mode = mode.unwrap_or_default();
314 let seek = seek_from_mode(offset, mode);
315 file.seek(seek).map_err(|err| seek_error("", &self.path, &err, (mode, offset)))
316 })
317 }
318
319 pub fn sync<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
320 let path = Arc::clone(&self.path);
321 self.with_blocking_promise(cx, "sync", path, move |file| file.sync_all(), || {}, file_error, ())
322 }
323
324 #[ion(name = "syncSync")]
325 pub fn sync_sync(&self) -> Result<()> {
326 self.with_sync(|file| file.sync_all().map_err(|err| file_error("sync", &self.path, &err, ())))
327 }
328
329 #[ion(name = "syncData")]
330 pub fn sync_data<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
331 let path = Arc::clone(&self.path);
332 self.with_blocking_promise(
333 cx,
334 "sync data for",
335 path,
336 move |file| file.sync_data(),
337 || {},
338 file_error,
339 (),
340 )
341 }
342
343 #[ion(name = "syncDataSync")]
344 pub fn sync_data_sync(&self) -> Result<()> {
345 self.with_sync(|file| file.sync_data().map_err(|err| file_error("sync data for", &self.path, &err, ())))
346 }
347
348 pub fn metadata<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
349 let path = Arc::clone(&self.path);
350 self.with_blocking_promise(
351 cx,
352 "get metadata for",
353 path,
354 move |file| file.metadata().map(|meta| Metadata::new(&meta)),
355 || {},
356 file_error,
357 (),
358 )
359 }
360
361 #[ion(name = "metadataSync")]
362 pub fn metadata_sync(&self) -> Result<Metadata> {
363 self.with_sync(|file| {
364 file.metadata()
365 .as_ref()
366 .map(Metadata::new)
367 .map_err(|err| file_error("get metadata for", &self.path, err, ()))
368 })
369 }
370}
371
372fn read_inner(file: &mut File, bytes: Option<&mut [u8]>) -> io::Result<ReadResult> {
373 if let Some(bytes) = bytes {
374 file.read(bytes).map(ReadResult::BytesWritten)
375 } else {
376 let size = file.metadata().map(|m| m.len() as usize).ok();
377 let mut bytes = Vec::new();
378 bytes.reserve_exact(size.unwrap_or(0));
379 file.read_to_end(&mut bytes).map(|_| ReadResult::Buffer(bytes))
380 }
381}
382
383fn seek_from_mode(offset: i64, mode: SeekMode) -> SeekFrom {
384 use SeekMode as SM;
385 match mode {
386 SM::Current => SeekFrom::Current(offset),
387 SM::Start => SeekFrom::Start(u64::try_from(offset.max(0)).unwrap()),
388 SM::End => SeekFrom::End(offset),
389 }
390}