rew_ops/
lib.rs

1use rew_data_manager::{DataFormat, DataManager};
2use rew_core::utils::find_app_path;
3use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
4use deno_core::OpState;
5use deno_core::error::CoreError;
6use serde::{Deserialize, Serialize};
7use serde_yaml;
8use std::cell::RefCell;
9use std::fs::{self, File};
10use std::io::{self, Read, Write};
11use deno_core::{op2};
12use rew_core::{RuntimeState};
13use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15use std::rc::Rc;
16use rew_vfile::{VIRTUAL_FILES, add_virtual_file};
17
18#[op2]
19#[serde]
20pub fn op_get_args(state: Rc<RefCell<OpState>>) -> Result<serde_json::Value, CoreError> {
21  let state = state.borrow();
22  let runtime_args = state.borrow::<RuntimeState>();
23  Ok(serde_json::json!(runtime_args.args.clone()))
24}
25
26
27// Base64 encoding/decoding operations
28#[op2]
29#[string]
30pub fn op_to_base64(#[serde] data: serde_json::Value) -> Result<String, CoreError> {
31  match data {
32    serde_json::Value::String(text) => Ok(BASE64.encode(text.as_bytes())),
33    serde_json::Value::Array(bytes) => {
34      let buffer: Result<Vec<u8>, _> = bytes
35        .iter()
36        .map(|v| {
37          if let serde_json::Value::Number(n) = v {
38            n.as_u64().map(|n| n as u8).ok_or_else(|| {
39              CoreError::Io(io::Error::new(
40                io::ErrorKind::InvalidData,
41                "Invalid byte value",
42              ))
43            })
44          } else {
45            Err(CoreError::Io(io::Error::new(
46              io::ErrorKind::InvalidData,
47              "Expected number in byte array",
48            )))
49          }
50        })
51        .collect();
52
53      match buffer {
54        Ok(bytes) => Ok(BASE64.encode(bytes)),
55        Err(e) => Err(e),
56      }
57    }
58    _ => Err(CoreError::Io(io::Error::new(
59      io::ErrorKind::InvalidData,
60      "Expected string or array of bytes for base64 encoding",
61    ))),
62  }
63}
64
65#[op2]
66#[serde]
67pub fn op_from_base64(
68  #[string] encoded: String,
69  #[serde] options: Option<Base64DecodeOptions>,
70) -> Result<serde_json::Value, CoreError> {
71  let options = options.unwrap_or_default();
72
73  let decoded = BASE64
74    .decode(encoded.as_bytes())
75    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
76
77  if options.as_string {
78    let text = String::from_utf8(decoded)
79      .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
80    Ok(serde_json::Value::String(text))
81  } else {
82    Ok(serde_json::Value::Array(
83      decoded
84        .into_iter()
85        .map(|b| serde_json::Value::Number(b.into()))
86        .collect(),
87    ))
88  }
89}
90
91#[derive(Deserialize, Default)]
92struct Base64DecodeOptions {
93  as_string: bool,
94}
95
96#[op2]
97#[string]
98pub fn op_find_app(#[string] filepath: String, _: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
99  let current_file = Path::new(&filepath);
100
101  let app_path = find_app_path(current_file);
102
103  Ok(String::from(
104    app_path.unwrap_or(PathBuf::from("")).to_str().unwrap(),
105  ))
106}
107
108#[op2]
109#[string]
110pub fn op_yaml_to_string(
111  #[serde] data: serde_json::Value,
112  _: Rc<RefCell<OpState>>,
113) -> Result<String, CoreError> {
114  let yaml = serde_yaml::to_string(&data)
115    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
116
117  Ok(yaml)
118}
119
120#[op2]
121#[serde]
122pub fn op_string_to_yaml(
123  #[string] yaml_str: String,
124  _: Rc<RefCell<OpState>>,
125) -> Result<serde_json::Value, CoreError> {
126  let value: serde_json::Value = serde_yaml::from_str(&yaml_str)
127    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
128
129  Ok(value)
130}
131
132#[op2]
133#[serde]
134pub fn op_app_loadconfig(
135  #[string] app_path: String,
136  _: Rc<RefCell<OpState>>,
137) -> Result<serde_json::Value, CoreError> {
138  let app_path = Path::new(&app_path);
139
140  if !app_path.exists() {
141    return Err(CoreError::Io(io::Error::new(
142      io::ErrorKind::NotFound,
143      format!("App path not found: {}", app_path.display()),
144    )));
145  }
146
147  let config_path = app_path.join("app.yaml");
148
149  if !config_path.exists() {
150    return Err(CoreError::Io(io::Error::new(
151      io::ErrorKind::NotFound,
152      format!("App config not found: {}", config_path.display()),
153    )));
154  }
155
156  let config_str = fs::read_to_string(&config_path)
157    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))?;
158
159  let config: serde_json::Value = serde_yaml::from_str(&config_str)
160    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
161
162  Ok(config)
163}
164
165// Helper function to get DataManager for a specific app package
166pub fn get_data_manager_for_package(app_package: &str) -> Result<DataManager, CoreError> {
167  // For now, use "default" as the user ID
168  // In a real implementation, you'd get this from user authentication
169  let user_id = "default";
170
171  DataManager::new(user_id, app_package)
172    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
173}
174
175#[op2]
176#[string]
177pub fn op_data_read(
178  #[string] app_package: String,
179  #[string] key: String,
180  _: Rc<RefCell<OpState>>,
181) -> Result<String, CoreError> {
182  let data_manager = get_data_manager_for_package(&app_package)?;
183  data_manager
184    .read(&key)
185    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
186}
187
188#[op2(async)]
189pub async fn  op_data_write(
190  #[string] app_package: String,
191  #[string] key: String,
192  #[string] content: String,
193  _: Rc<RefCell<OpState>>,
194) -> Result<(), CoreError> {
195  let data_manager = get_data_manager_for_package(&app_package)?;
196  data_manager
197    .write(&key, &content)
198    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
199}
200
201#[op2(async)]
202pub async fn  op_data_delete(
203  #[string] app_package: String,
204  #[string] key: String,
205  _: Rc<RefCell<OpState>>,
206) -> Result<(), CoreError> {
207  let data_manager = get_data_manager_for_package(&app_package)?;
208  data_manager
209    .delete(&key)
210    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
211}
212
213#[op2(fast)]
214pub fn op_data_exists(
215  #[string] app_package: String,
216  #[string] key: String,
217  _: Rc<RefCell<OpState>>,
218) -> Result<bool, CoreError> {
219  let data_manager = get_data_manager_for_package(&app_package)?;
220  Ok(data_manager.exists(&key))
221}
222
223#[op2]
224#[string]
225pub fn op_data_list(
226  #[string] app_package: String,
227  #[string] prefix: String,
228  _: Rc<RefCell<OpState>>,
229) -> Result<String, CoreError> {
230  let data_manager = get_data_manager_for_package(&app_package)?;
231  let files = data_manager
232    .list(&prefix)
233    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))?;
234
235  serde_json::to_string(&files).map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
236}
237
238#[op2]
239#[serde]
240pub fn op_data_read_binary(
241  #[string] app_package: String,
242  #[string] key: String,
243  _: Rc<RefCell<OpState>>,
244) -> Result<Vec<u8>, CoreError> {
245  let data_manager = get_data_manager_for_package(&app_package)?;
246  data_manager
247    .read_binary(&key)
248    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
249}
250
251#[op2]
252#[string]
253pub fn op_fetch_env(_: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
254  let env_vars: HashMap<String, String> = std::env::vars().collect();
255  let cwd = std::env::current_dir()?
256    // .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))?;
257    .to_string_lossy()
258    .to_string();
259  let exec_path = std::env::current_exe()?
260    // .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))?;
261    .to_string_lossy()
262    .to_string();
263
264  let result = serde_json::json!({
265    "env": env_vars,
266    "cwd": cwd,
267    "execPath": exec_path,
268    "tempDir": std::env::temp_dir(),
269    "rewPath": rew_core::utils::get_rew_root()
270  });
271
272  serde_json::to_string(&result)
273    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))
274}
275
276#[op2(async)]
277pub async fn  op_data_write_binary(
278  #[string] app_package: String,
279  #[string] key: String,
280  #[serde] data: Vec<u8>,
281  _: Rc<RefCell<OpState>>,
282) -> Result<(), CoreError> {
283  let data_manager = get_data_manager_for_package(&app_package)?;
284  data_manager
285    .write_binary(&key, &data)
286    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
287}
288
289#[op2]
290#[serde]
291pub fn op_data_read_yaml(
292  #[string] app_package: String,
293  #[string] key: String,
294  _: Rc<RefCell<OpState>>,
295) -> Result<serde_json::Value, CoreError> {
296  let data_manager = get_data_manager_for_package(&app_package)?;
297  data_manager
298    .read_yaml(&key)
299    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
300}
301
302#[op2(async)]
303pub async fn  op_data_write_yaml(
304  #[string] app_package: String,
305  #[string] key: String,
306  #[serde] data: serde_json::Value,
307  _: Rc<RefCell<OpState>>,
308) -> Result<(), CoreError> {
309  let data_manager = get_data_manager_for_package(&app_package)?;
310  data_manager
311    .write_yaml(&key, &data)
312    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
313}
314
315#[op2]
316#[serde]
317pub fn op_data_get_info(
318  #[string] app_package: String,
319  #[string] key: String,
320  _: Rc<RefCell<OpState>>,
321) -> Result<(bool, String), CoreError> {
322  let data_manager = get_data_manager_for_package(&app_package)?;
323  let (exists, format) = data_manager
324    .get_file_info(&key)
325    .map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))?;
326
327  let format_str = match format {
328    DataFormat::Text => "text",
329    DataFormat::Json => "json",
330    DataFormat::Yaml => "yaml",
331    DataFormat::Binary => "binary",
332  };
333
334  Ok((exists, format_str.to_string()))
335}
336
337#[op2]
338#[string]
339pub fn op_data_get_path(
340  #[string] app_package: String,
341  _: Rc<RefCell<OpState>>,
342) -> Result<String, CoreError> {
343  let data_manager = get_data_manager_for_package(&app_package)?;
344
345  Ok(data_manager.get_path("").to_string_lossy().to_string())
346}
347
348#[op2]
349#[string]
350pub fn op_os_info_os(_: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
351  Ok(std::env::consts::OS.to_string())
352}
353
354#[op2]
355#[string]
356pub fn op_os_info_arch(_: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
357  Ok(std::env::consts::ARCH.to_string())
358}
359
360#[op2]
361#[string]
362pub fn op_os_info_family(_: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
363  Ok(std::env::consts::FAMILY.to_string())
364}
365
366use rand::rngs::StdRng;
367use rand::{Rng, RngCore, SeedableRng, distributions::Alphanumeric};
368use std::hash::Hash;
369use std::hash::Hasher;
370
371#[op2]
372#[serde]
373pub fn op_rand_from(
374  #[bigint] min: usize,
375  #[bigint] max: usize,
376  #[string] seed: Option<String>,
377) -> usize {
378  let mut rng: Box<dyn RngCore> = match seed {
379    Some(s) => {
380      let mut hasher = std::collections::hash_map::DefaultHasher::new();
381      s.hash(&mut hasher);
382      Box::new(StdRng::seed_from_u64(hasher.finish()))
383    }
384    _ => Box::new(rand::thread_rng()),
385  };
386
387  if min == max {
388    return min;
389  }
390
391  let (low, high) = if min < max { (min, max) } else { (max, min) };
392
393  rng.gen_range(low..=high)
394}
395
396#[op2]
397#[string]
398pub fn op_vfile_set(#[string] full_path: String, #[string] content: String) -> String {
399  add_virtual_file(full_path.as_str(), content.as_str());
400  "".to_string()
401}
402
403#[op2]
404#[string]
405pub fn op_vfile_get(#[string] full_path: String) -> String {
406  if let Some(v) = VIRTUAL_FILES
407    .lock()
408    .unwrap()
409    .iter()
410    .find(|(p, _)| *p == full_path)
411  {
412    return v.1.clone();
413  }
414  "".to_string()
415}
416
417#[op2]
418#[string]
419pub fn op_gen_uid(length: i32, #[string] seed: Option<String>) -> String {
420  if let Some(seed_str) = seed {
421    let mut hasher = std::collections::hash_map::DefaultHasher::new();
422    seed_str.hash(&mut hasher);
423
424    let seed = hasher.finish();
425    let mut rng = StdRng::seed_from_u64(seed);
426
427    (0..length)
428      .map(|_| rng.sample(Alphanumeric) as char)
429      .collect()
430  } else {
431    let mut rng = rand::thread_rng();
432
433    (0..length)
434      .map(|_| rng.sample(Alphanumeric) as char)
435      .collect()
436  }
437}
438
439#[op2]
440#[serde]
441pub fn op_terminal_size() -> Result<(u16, u16), std::io::Error> {
442  #[cfg(unix)]
443  {
444    use libc::{STDOUT_FILENO, TIOCGWINSZ, ioctl, winsize};
445
446    let mut ws: winsize = unsafe { std::mem::zeroed() };
447
448    let result = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut ws) };
449
450    if result == -1 {
451      return Err(std::io::Error::last_os_error());
452    }
453
454    Ok((ws.ws_col, ws.ws_row))
455  }
456
457  #[cfg(windows)]
458  {
459    use std::mem::zeroed;
460    use std::ptr::null_mut;
461    use winapi::um::handleapi::INVALID_HANDLE_VALUE;
462    use winapi::um::processenv::GetStdHandle;
463    use winapi::um::winbase::STD_OUTPUT_HANDLE;
464    use winapi::um::wincon::{CONSOLE_SCREEN_BUFFER_INFO, GetConsoleScreenBufferInfo};
465
466    unsafe {
467      let handle = GetStdHandle(STD_OUTPUT_HANDLE);
468      if handle == INVALID_HANDLE_VALUE {
469        return Err(std::io::Error::last_os_error());
470      }
471
472      let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = zeroed();
473      if GetConsoleScreenBufferInfo(handle, &mut csbi) == 0 {
474        return Err(std::io::Error::last_os_error());
475      }
476
477      let width = (csbi.srWindow.Right - csbi.srWindow.Left + 1) as u16;
478      let height = (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) as u16;
479
480      Ok((width, height))
481    }
482  }
483}
484
485
486#[op2]
487#[serde]
488pub fn op_fs_read(
489  #[string] current_file: String,
490  #[string] filepath: String,
491  #[serde] options: Option<ReadOptions>,
492  _: Rc<RefCell<OpState>>,
493) -> Result<serde_json::Value, CoreError> {
494  let current_file_path = Path::new(&current_file);
495  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
496  let full_path = base_dir.join(filepath);
497
498  let options = options.unwrap_or_default();
499
500  if options.binary {
501    let mut file = File::open(&full_path).map_err(CoreError::Io)?;
502    let mut buffer = Vec::new();
503    file.read_to_end(&mut buffer).map_err(CoreError::Io)?;
504
505    Ok(serde_json::Value::Array(
506      buffer
507        .into_iter()
508        .map(|b| serde_json::Value::Number(b.into()))
509        .collect(),
510    ))
511  } else {
512    let content = fs::read_to_string(&full_path).map_err(CoreError::Io)?;
513    Ok(serde_json::Value::String(content))
514  }
515}
516
517#[derive(Deserialize, Default)]
518struct ReadOptions {
519  binary: bool,
520}
521
522#[op2(async)]
523pub async fn op_fs_write(
524  #[string] current_file: String,
525  #[string] filepath: String,
526  #[serde] content: serde_json::Value,
527  #[serde] options: Option<WriteOptions>,
528  _: Rc<RefCell<OpState>>,
529) -> Result<(), CoreError> {
530  let current_file_path = Path::new(&current_file);
531  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
532
533  let full_path = base_dir.join(filepath);
534
535  let options = options.unwrap_or_default();
536
537  if let Some(parent) = full_path.parent() {
538    if options.create_dirs {
539      fs::create_dir_all(parent).map_err(CoreError::Io)?;
540    }
541  }
542
543  if options.binary {
544    if let serde_json::Value::Array(bytes) = content {
545      let buffer: Result<Vec<u8>, _> = bytes
546        .iter()
547        .map(|v| {
548          if let serde_json::Value::Number(n) = v {
549            n.as_u64().map(|n| n as u8).ok_or_else(|| {
550              CoreError::Io(io::Error::new(
551                io::ErrorKind::InvalidData,
552                "Invalid byte value",
553              ))
554            })
555          } else {
556            Err(CoreError::Io(io::Error::new(
557              io::ErrorKind::InvalidData,
558              "Expected number in byte array",
559            )))
560          }
561        })
562        .collect();
563
564      fs::write(&full_path, buffer?).map_err(CoreError::Io)?;
565    } else {
566      return Err(CoreError::Io(io::Error::new(
567        io::ErrorKind::InvalidData,
568        "Expected array of bytes for binary write",
569      )));
570    }
571  } else if let serde_json::Value::String(text) = content {
572    let mut file = File::create(&full_path).map_err(CoreError::Io)?;
573    file.write_all(text.as_bytes()).map_err(CoreError::Io)?;
574  } else {
575    return Err(CoreError::Io(io::Error::new(
576      io::ErrorKind::InvalidData,
577      "Expected string for text write",
578    )));
579  }
580
581  Ok(())
582}
583
584#[derive(Deserialize, Default)]
585struct WriteOptions {
586  binary: bool,
587  create_dirs: bool,
588}
589
590use sha2::{Digest, Sha256};
591#[op2]
592#[string]
593pub fn  op_fs_sha(
594  #[string] current_file: String,
595  #[string] filepath: String,
596  _: Rc<RefCell<OpState>>,
597) -> Result<String, CoreError> {
598  let current_file_path = Path::new(&current_file);
599  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
600
601  let full_path = base_dir.join(filepath);
602
603  let file_bytes = fs::read(&full_path)?;
604  let mut hasher = Sha256::new();
605  hasher.update(file_bytes);
606  let hash = hasher.finalize();
607
608  Ok(format!("{:x}", hash))
609}
610
611#[op2(fast)]
612pub fn  op_fs_exists(
613  #[string] current_file: String,
614  #[string] filepath: String,
615  _: Rc<RefCell<OpState>>,
616) -> Result<bool, CoreError> {
617  let current_file_path = Path::new(&current_file);
618  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
619
620  let full_path = base_dir.join(filepath);
621
622  Ok(full_path.exists())
623}
624
625#[op2(async)]
626pub async fn op_fs_rm(
627  #[string] current_file: String,
628  #[string] filepath: String,
629  #[serde] options: Option<RemoveOptions>,
630  _: Rc<RefCell<OpState>>,
631) -> Result<(), CoreError> {
632  let current_file_path = Path::new(&current_file);
633  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
634
635  let full_path = base_dir.join(filepath);
636
637  let options = options.unwrap_or_default();
638
639  if full_path.is_dir() {
640    if options.recursive {
641      fs::remove_dir_all(&full_path).map_err(CoreError::Io)?;
642    } else {
643      fs::remove_dir(&full_path).map_err(CoreError::Io)?;
644    }
645  } else {
646    fs::remove_file(&full_path).map_err(CoreError::Io)?;
647  }
648
649  Ok(())
650}
651
652#[derive(Deserialize, Default)]
653struct RemoveOptions {
654  recursive: bool,
655}
656
657#[op2(async)]
658pub async fn op_fs_mkdir(
659  #[string] current_file: String,
660  #[string] dirpath: String,
661  #[serde] options: Option<MkdirOptions>,
662  _: Rc<RefCell<OpState>>,
663) -> Result<(), CoreError> {
664  let current_file_path = Path::new(&current_file);
665  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
666
667  let full_path = base_dir.join(dirpath);
668
669  let options = options.unwrap_or_default();
670
671  if options.recursive {
672    fs::create_dir_all(&full_path).map_err(CoreError::Io)?;
673  } else {
674    fs::create_dir(&full_path).map_err(CoreError::Io)?;
675  }
676
677  Ok(())
678}
679
680#[derive(Deserialize, Default)]
681struct MkdirOptions {
682  recursive: bool,
683}
684
685#[op2]
686#[string]
687pub fn  op_fs_readdir(
688  #[string] current_file: String,
689  #[string] dirpath: String,
690  #[serde] options: Option<ReaddirOptions>,
691  _: Rc<RefCell<OpState>>,
692) -> Result<String, CoreError> {
693  let current_file_path = Path::new(&current_file);
694  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
695
696  let full_path = base_dir.join(dirpath);
697
698  let options = options.unwrap_or_default();
699
700  let entries = fs::read_dir(&full_path).map_err(CoreError::Io)?;
701
702  let mut result = Vec::new();
703
704  for entry in entries {
705    let entry = entry.map_err(CoreError::Io)?;
706    let file_type = entry.file_type().map_err(CoreError::Io)?;
707
708    if !options.include_hidden {
709      if let Some(file_name) = entry.path().file_name() {
710        if let Some(name_str) = file_name.to_str() {
711          if name_str.starts_with(".") {
712            continue;
713          }
714        }
715      }
716    }
717
718    if let Some(filter_type) = &options.filter_type {
719      match filter_type.as_str() {
720        "file" => {
721          if !file_type.is_file() {
722            continue;
723          }
724        }
725        "directory" => {
726          if !file_type.is_dir() {
727            continue;
728          }
729        }
730        "symlink" => {
731          if !file_type.is_symlink() {
732            continue;
733          }
734        }
735        _ => {}
736      }
737    }
738
739    let metadata = entry.metadata().map_err(CoreError::Io)?;
740
741    let entry_info = DirEntryInfo {
742      name: entry.file_name().to_string_lossy().to_string(),
743      path: entry.path().to_string_lossy().to_string(),
744      is_file: file_type.is_file(),
745      is_directory: file_type.is_dir(),
746      is_symlink: file_type.is_symlink(),
747      size: metadata.len(),
748      modified: metadata
749        .modified()
750        .ok()
751        .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
752        .map(|d| d.as_secs()),
753      created: metadata
754        .created()
755        .ok()
756        .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
757        .map(|d| d.as_secs()),
758    };
759
760    result.push(entry_info);
761  }
762
763  if let Some(sort_by) = &options.sort_by {
764    match sort_by.as_str() {
765      "name" => result.sort_by(|a, b| a.name.cmp(&b.name)),
766      "size" => result.sort_by(|a, b| a.size.cmp(&b.size)),
767      "modified" => result.sort_by(|a, b| a.modified.cmp(&b.modified)),
768      "type" => result.sort_by(|a, b| a.is_directory.cmp(&b.is_directory).reverse()),
769      _ => {}
770    }
771  }
772
773  serde_json::to_string(&result).map_err(|e| CoreError::Io(io::Error::new(io::ErrorKind::Other, e)))
774}
775
776#[derive(Deserialize, Default)]
777struct ReaddirOptions {
778  include_hidden: bool,
779  filter_type: Option<String>,
780  sort_by: Option<String>,
781}
782
783#[derive(Serialize)]
784struct DirEntryInfo {
785  name: String,
786  path: String,
787  is_file: bool,
788  is_directory: bool,
789  is_symlink: bool,
790  size: u64,
791  modified: Option<u64>,
792  created: Option<u64>,
793}
794
795#[op2]
796#[string]
797pub fn  op_fs_stats(
798  #[string] current_file: String,
799  #[string] filepath: String,
800  _: Rc<RefCell<OpState>>,
801) -> Result<String, CoreError> {
802  let current_file_path = Path::new(&current_file);
803  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
804
805  let full_path = base_dir.join(filepath);
806
807  let metadata = fs::metadata(&full_path).map_err(CoreError::Io)?;
808
809  let stats = serde_json::json!({
810      "isFile": metadata.is_file(),
811      "isDirectory": metadata.is_dir(),
812      "isSymlink": metadata.file_type().is_symlink(),
813      "size": metadata.len(),
814      "modified": metadata.modified().ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_secs()),
815      "created": metadata.created().ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_secs()),
816      "accessed": metadata.accessed().ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_secs()),
817      "permissions": {
818          "readonly": metadata.permissions().readonly(),
819          // "mode": metadata.permissions().mode(),
820      }
821  });
822
823  Ok(stats.to_string())
824}
825
826#[op2(async)]
827pub async fn op_fs_copy(
828  #[string] current_file: String,
829  #[string] src: String,
830  #[string] dest: String,
831  #[serde] options: Option<CopyOptions>,
832  _: Rc<RefCell<OpState>>,
833) -> Result<(), CoreError> {
834  let current_file_path = Path::new(&current_file);
835  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
836
837  let src_path = base_dir.join(src);
838  let dest_path = base_dir.join(dest);
839
840  let options = options.unwrap_or_default();
841
842  if src_path.is_dir() {
843    if options.recursive {
844      copy_dir_recursive(&src_path, &dest_path, &options).map_err(CoreError::Io)?;
845    } else {
846      return Err(CoreError::Io(io::Error::new(
847        io::ErrorKind::InvalidInput,
848        "Source is a directory, but recursive option is not set",
849      )));
850    }
851  } else {
852    if let Some(parent) = dest_path.parent() {
853      if options.create_dirs {
854        fs::create_dir_all(parent).map_err(CoreError::Io)?;
855      }
856    }
857
858    fs::copy(&src_path, &dest_path).map_err(CoreError::Io)?;
859  }
860
861  Ok(())
862}
863
864#[derive(Deserialize, Default)]
865pub struct CopyOptions {
866  pub recursive: bool,
867  pub create_dirs: bool,
868  pub overwrite: bool,
869}
870
871pub fn copy_dir_recursive(src: &Path, dest: &Path, options: &CopyOptions) -> io::Result<()> {
872  if !dest.exists() {
873    fs::create_dir_all(dest)?;
874  }
875
876  for entry in fs::read_dir(src)? {
877    let entry = entry?;
878    let src_path = entry.path();
879    let dest_path = dest.join(entry.file_name());
880
881    if src_path.is_dir() {
882      copy_dir_recursive(&src_path, &dest_path, options)?;
883    } else {
884      if dest_path.exists() && !options.overwrite {
885        continue;
886      }
887      fs::copy(&src_path, &dest_path)?;
888    }
889  }
890
891  Ok(())
892}
893
894#[op2(async)]
895pub async fn op_fs_rename(
896  #[string] current_file: String,
897  #[string] src: String,
898  #[string] dest: String,
899  _: Rc<RefCell<OpState>>,
900) -> Result<(), CoreError> {
901  let current_file_path = Path::new(&current_file);
902  let base_dir = current_file_path.parent().unwrap_or(Path::new("."));
903
904  let src_path = base_dir.join(src);
905  let dest_path = base_dir.join(dest);
906
907  fs::rename(&src_path, &dest_path).map_err(CoreError::Io)?;
908
909  Ok(())
910}
911
912#[op2]
913#[string]
914pub fn  op_fs_cwdir(state: Rc<RefCell<OpState>>) -> Result<String, CoreError> {
915  let state = state.borrow();
916  let runtime_state = state.borrow::<RuntimeState>();
917
918  Ok(runtime_state.current_dir.to_string_lossy().to_string())
919}