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#[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
165pub fn get_data_manager_for_package(app_package: &str) -> Result<DataManager, CoreError> {
167 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 .to_string_lossy()
258 .to_string();
259 let exec_path = std::env::current_exe()?
260 .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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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 }
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(¤t_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(¤t_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}