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