diff --git a/backend/src/procedure/js_scripts.rs b/backend/src/procedure/js_scripts.rs index c9920161c..9f7af0b8f 100644 --- a/backend/src/procedure/js_scripts.rs +++ b/backend/src/procedure/js_scripts.rs @@ -599,6 +599,51 @@ async fn js_action_test_zero_dir() { .unwrap() .unwrap(); } +#[tokio::test] +async fn js_action_test_read_dir() { + let js_action = JsProcedure { args: vec![] }; + let path: PathBuf = "test/js_action_execute/" + .parse::() + .unwrap() + .canonicalize() + .unwrap(); + let package_id = "test-package".parse().unwrap(); + let package_version: Version = "0.3.0.3".parse().unwrap(); + let name = ProcedureName::Action("test-read-dir".parse().unwrap()); + let volumes: Volumes = serde_json::from_value(serde_json::json!({ + "main": { + "type": "data" + }, + "compat": { + "type": "assets" + }, + "filebrowser" :{ + "package-id": "filebrowser", + "path": "data", + "readonly": true, + "type": "pointer", + "volume-id": "main", + } + })) + .unwrap(); + let input: Option = None; + let timeout = Some(Duration::from_secs(10)); + js_action + .execute::( + &path, + &package_id, + &package_version, + name, + &volumes, + input, + timeout, + ProcessGroupId(0), + None, + ) + .await + .unwrap() + .unwrap(); +} #[tokio::test] async fn js_rsync() { diff --git a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js index fff384549..5a4b9d0e7 100644 --- a/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js +++ b/backend/test/js_action_execute/package-data/scripts/test-package/0.3.0.3/embassy.js @@ -888,6 +888,49 @@ export const action = { }, }; }, + + /** + * Test is for the feature of listing what's in a dir + * @param {*} effects + * @param {*} _input + * @returns + */ + async "test-read-dir"(effects, _input) { + await effects + .removeDir({ + volumeId: "main", + path: "test-read-dir", + }) + .catch(() => {}); + await effects.createDir({ + volumeId: "main", + path: "test-read-dir/deep/123", + }); + await effects.writeFile({ + path: "./test-read-dir/broken.log", + toWrite: "This is a test", + volumeId: "main", + }) + let readDir = JSON.stringify(await effects.readDir({ + volumeId: "main", + path: "test-read-dir", + })) + const expected = '["broken.log","deep"]' + assert(readDir === expected, `Failed to match the input (${readDir}) === (${expected}) of readDir`) + + await effects.removeDir({ + volumeId: "main", + path: "test-read-dir", + }); + return { + result: { + copyable: false, + message: "Done", + version: "0", + qr: false, + }, + }; + }, /** * Created this test because of issue * https://github.com/Start9Labs/embassy-os/issues/2121 diff --git a/libs/js_engine/src/artifacts/loadModule.js b/libs/js_engine/src/artifacts/loadModule.js index 6aa0e36b8..6686c0d6d 100644 --- a/libs/js_engine/src/artifacts/loadModule.js +++ b/libs/js_engine/src/artifacts/loadModule.js @@ -114,6 +114,10 @@ const readJsonFile = async ( const createDir = ( { volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"), ) => Deno.core.opAsync("create_dir", volumeId, path); + +const readDir = ( + { volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"), +) => Deno.core.opAsync("read_dir", volumeId, path); const removeDir = ( { volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"), ) => Deno.core.opAsync("remove_dir", volumeId, path); @@ -186,7 +190,8 @@ const effects = { sleep, runDaemon, signalGroup, - runRsync + runRsync, + readDir }; const defaults = { diff --git a/libs/js_engine/src/lib.rs b/libs/js_engine/src/lib.rs index 0312bfd0b..db9538ffc 100644 --- a/libs/js_engine/src/lib.rs +++ b/libs/js_engine/src/lib.rs @@ -281,6 +281,7 @@ impl JsExecutionEnvironment { fns::remove_file::decl(), fns::create_dir::decl(), fns::remove_dir::decl(), + fns::read_dir::decl(), fns::current_function::decl(), fns::log_trace::decl(), fns::log_warn::decl(), @@ -844,6 +845,49 @@ mod fns { tokio::fs::create_dir_all(new_file).await?; Ok(()) } + #[op] + async fn read_dir( + state: Rc>, + volume_id: VolumeId, + path_in: PathBuf, + ) -> Result, AnyError> { + let volume_path = { + let state = state.borrow(); + let ctx: &JsContext = state.borrow(); + let volume_path = ctx + .volumes + .path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id) + .ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?; + volume_path + }; + let new_file = volume_path.join(path_in); + + // With the volume check + if !is_subset(&volume_path, &new_file).await? { + bail!( + "Path '{}' has broken away from parent '{}'", + new_file.to_string_lossy(), + volume_path.to_string_lossy(), + ); + } + let mut reader = tokio::fs::read_dir(&new_file).await?; + let mut paths: Vec = Vec::new(); + let origin_path = format!("{}/", new_file.to_str().unwrap_or_default()); + let remove_new_file = |other_path: String| other_path.replacen(&origin_path, "", 1); + let has_origin_path = |other_path: &String| other_path.starts_with(&origin_path); + while let Some(entry) = reader.next_entry().await? { + entry + .path() + .to_str() + .into_iter() + .map(ToString::to_string) + .filter(&has_origin_path) + .map(&remove_new_file) + .for_each(|x| paths.push(x)); + } + paths.sort(); + Ok(paths) + } #[op] fn current_function(state: &mut OpState) -> Result {