mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
feat: Make the rename effect (#1669)
* feat: Make the rename effect * chore: Change to dst and src * chore: update the remove file to use dst src
This commit is contained in:
@@ -333,3 +333,47 @@ async fn js_action_var_arg() {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn js_action_test_rename() {
|
||||||
|
let js_action = JsProcedure { args: vec![] };
|
||||||
|
let path: PathBuf = "test/js_action_execute/"
|
||||||
|
.parse::<PathBuf>()
|
||||||
|
.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-rename".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<serde_json::Value> = None;
|
||||||
|
let timeout = Some(Duration::from_secs(10));
|
||||||
|
js_action
|
||||||
|
.execute::<serde_json::Value, serde_json::Value>(
|
||||||
|
&path,
|
||||||
|
&package_id,
|
||||||
|
&package_version,
|
||||||
|
name,
|
||||||
|
&volumes,
|
||||||
|
input,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@@ -763,6 +763,51 @@ export const action = {
|
|||||||
async 'js-action-var-arg'(_effects, _input, testInput) {
|
async 'js-action-var-arg'(_effects, _input, testInput) {
|
||||||
|
|
||||||
assert(testInput == 42, "Input should be passed in");
|
assert(testInput == 42, "Input should be passed in");
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
copyable: false,
|
||||||
|
message: "Done",
|
||||||
|
version: "0",
|
||||||
|
qr: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async 'test-rename'(effects, _input) {
|
||||||
|
|
||||||
|
await effects.writeFile({
|
||||||
|
volumeId: 'main',
|
||||||
|
path: 'test-rename.txt',
|
||||||
|
toWrite: 'Hello World',
|
||||||
|
});
|
||||||
|
await effects.rename({
|
||||||
|
srcVolume: 'main',
|
||||||
|
srcPath: 'test-rename.txt',
|
||||||
|
dstVolume: 'main',
|
||||||
|
dstPath: 'test-rename-2.txt',
|
||||||
|
});
|
||||||
|
|
||||||
|
const readIn = await effects.readFile({
|
||||||
|
volumeId: 'main',
|
||||||
|
path: 'test-rename-2.txt',
|
||||||
|
});
|
||||||
|
assert(readIn === 'Hello World', "Contents should be the same");
|
||||||
|
|
||||||
|
await effects.removeFile({
|
||||||
|
path: "test-rename-2.txt",
|
||||||
|
volumeId: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
await effects.removeFile({
|
||||||
|
path: "test-rename.txt",
|
||||||
|
volumeId: "main",
|
||||||
|
});
|
||||||
|
assert(false, "Should not be able to remove file that doesn't exist");
|
||||||
|
}
|
||||||
|
catch (_){}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
copyable: false,
|
copyable: false,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
|
||||||
import Deno from "/deno_global.js";
|
import Deno from "/deno_global.js";
|
||||||
import * as mainModule from "/embassy.js";
|
import * as mainModule from "/embassy.js";
|
||||||
|
|
||||||
|
function requireParam(param) {
|
||||||
|
throw new Error(`Missing required parameter ${param}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is using the simplified json pointer spec, using no escapes and arrays
|
* This is using the simplified json pointer spec, using no escapes and arrays
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
@@ -20,52 +24,83 @@ function jsonPointerValue(obj, pointer) {
|
|||||||
|
|
||||||
function maybeDate(value) {
|
function maybeDate(value) {
|
||||||
if (!value) return value;
|
if (!value) return value;
|
||||||
return new Date(value)
|
return new Date(value);
|
||||||
}
|
}
|
||||||
const writeFile = ({ path, volumeId, toWrite }) => Deno.core.opAsync("write_file", volumeId, path, toWrite);
|
const writeFile = (
|
||||||
|
{
|
||||||
|
path = requireParam("path"),
|
||||||
|
volumeId = requireParam("volumeId"),
|
||||||
|
toWrite = requireParam("toWrite"),
|
||||||
|
} = requireParam("options"),
|
||||||
|
) => Deno.core.opAsync("write_file", volumeId, path, toWrite);
|
||||||
|
|
||||||
const readFile = ({ volumeId, path }) => Deno.core.opAsync("read_file", volumeId, path);
|
const readFile = (
|
||||||
const metadata = async ({ volumeId, path }) => {
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
const data = await Deno.core.opAsync("metadata", volumeId, path)
|
) => Deno.core.opAsync("read_file", volumeId, path);
|
||||||
|
const rename = (
|
||||||
|
{
|
||||||
|
srcVolume = requireParam("srcVolume"),
|
||||||
|
dstVolume = requireParam("dstVolume"),
|
||||||
|
srcPath = requireParam("srcPath"),
|
||||||
|
dstPath = requireParam("dstPath"),
|
||||||
|
} = requireParam("options"),
|
||||||
|
) => Deno.core.opAsync("rename", srcVolume, srcPath, dstVolume, dstPath);
|
||||||
|
const metadata = async (
|
||||||
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
|
) => {
|
||||||
|
const data = await Deno.core.opAsync("metadata", volumeId, path);
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
modified: maybeDate(data.modified),
|
modified: maybeDate(data.modified),
|
||||||
created: maybeDate(data.created),
|
created: maybeDate(data.created),
|
||||||
accessed: maybeDate(data.accessed),
|
accessed: maybeDate(data.accessed),
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
const removeFile = ({ volumeId, path }) => Deno.core.opAsync("remove_file", volumeId, path);
|
const removeFile = (
|
||||||
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
|
) => Deno.core.opAsync("remove_file", volumeId, path);
|
||||||
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
|
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
|
||||||
|
|
||||||
const writeJsonFile = ({ volumeId, path, toWrite }) =>
|
const writeJsonFile = (
|
||||||
|
{
|
||||||
|
volumeId = requireParam("volumeId"),
|
||||||
|
path = requireParam("path"),
|
||||||
|
toWrite = requireParam("toWrite"),
|
||||||
|
} = requireParam("options"),
|
||||||
|
) =>
|
||||||
writeFile({
|
writeFile({
|
||||||
volumeId,
|
volumeId,
|
||||||
path,
|
path,
|
||||||
toWrite: JSON.stringify(toWrite),
|
toWrite: JSON.stringify(toWrite),
|
||||||
});
|
});
|
||||||
const readJsonFile = async ({ volumeId, path }) => JSON.parse(await readFile({ volumeId, path }));
|
const readJsonFile = async (
|
||||||
const createDir = ({ volumeId, path }) => Deno.core.opAsync("create_dir", volumeId, path);
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
const removeDir = ({ volumeId, path }) => Deno.core.opAsync("remove_dir", volumeId, path);
|
) => JSON.parse(await readFile({ volumeId, path }));
|
||||||
const trace = (x) => Deno.core.opSync("log_trace", x);
|
const createDir = (
|
||||||
const warn = (x) => Deno.core.opSync("log_warn", x);
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
const error = (x) => Deno.core.opSync("log_error", x);
|
) => Deno.core.opAsync("create_dir", volumeId, path);
|
||||||
const debug = (x) => Deno.core.opSync("log_debug", x);
|
const removeDir = (
|
||||||
const info = (x) => Deno.core.opSync("log_info", x);
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
const fetch = async (url, options = null) => {
|
) => Deno.core.opAsync("remove_dir", volumeId, path);
|
||||||
const {body, ...response} = await Deno.core.opAsync("fetch", url, options);
|
const trace = (whatToTrace = requireParam('whatToTrace')) => Deno.core.opSync("log_trace", whatToTrace);
|
||||||
const textValue = Promise.resolve(body)
|
const warn = (whatToTrace = requireParam('whatToTrace')) => Deno.core.opSync("log_warn", whatToTrace);
|
||||||
|
const error = (whatToTrace = requireParam('whatToTrace')) => Deno.core.opSync("log_error", whatToTrace);
|
||||||
|
const debug = (whatToTrace = requireParam('whatToTrace')) => Deno.core.opSync("log_debug", whatToTrace);
|
||||||
|
const info = (whatToTrace = requireParam('whatToTrace')) => Deno.core.opSync("log_info", whatToTrace);
|
||||||
|
const fetch = async (url = requireParam ('url'), options = null) => {
|
||||||
|
const { body, ...response } = await Deno.core.opAsync("fetch", url, options);
|
||||||
|
const textValue = Promise.resolve(body);
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
text() {
|
text() {
|
||||||
return textValue
|
return textValue;
|
||||||
},
|
},
|
||||||
json() {
|
json() {
|
||||||
return textValue.then(x => JSON.parse(x))
|
return textValue.then((x) => JSON.parse(x));
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const currentFunction = Deno.core.opSync("current_function");
|
const currentFunction = Deno.core.opSync("current_function");
|
||||||
const input = Deno.core.opSync("get_input");
|
const input = Deno.core.opSync("get_input");
|
||||||
const variable_args = Deno.core.opSync("get_variable_args");
|
const variable_args = Deno.core.opSync("get_variable_args");
|
||||||
@@ -85,14 +120,15 @@ const effects = {
|
|||||||
removeFile,
|
removeFile,
|
||||||
createDir,
|
createDir,
|
||||||
removeDir,
|
removeDir,
|
||||||
metadata
|
metadata,
|
||||||
|
rename,
|
||||||
};
|
};
|
||||||
|
|
||||||
const runFunction = jsonPointerValue(mainModule, currentFunction);
|
const runFunction = jsonPointerValue(mainModule, currentFunction);
|
||||||
(async () => {
|
(async () => {
|
||||||
if (typeof runFunction !== "function") {
|
if (typeof runFunction !== "function") {
|
||||||
error(`Expecting ${ currentFunction } to be a function`);
|
error(`Expecting ${currentFunction} to be a function`);
|
||||||
throw new Error(`Expecting ${ currentFunction } to be a function`);
|
throw new Error(`Expecting ${currentFunction} to be a function`);
|
||||||
}
|
}
|
||||||
const answer = await runFunction(effects, input, ...variable_args);
|
const answer = await runFunction(effects, input, ...variable_args);
|
||||||
setState(answer);
|
setState(answer);
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ impl JsExecutionEnvironment {
|
|||||||
fns::read_file::decl(),
|
fns::read_file::decl(),
|
||||||
fns::metadata::decl(),
|
fns::metadata::decl(),
|
||||||
fns::write_file::decl(),
|
fns::write_file::decl(),
|
||||||
|
fns::rename::decl(),
|
||||||
fns::remove_file::decl(),
|
fns::remove_file::decl(),
|
||||||
fns::create_dir::decl(),
|
fns::create_dir::decl(),
|
||||||
fns::remove_dir::decl(),
|
fns::remove_dir::decl(),
|
||||||
@@ -529,6 +530,56 @@ mod fns {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
|
async fn rename(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
src_volume: VolumeId,
|
||||||
|
src_path: PathBuf,
|
||||||
|
dst_volume: VolumeId,
|
||||||
|
dst_path: PathBuf,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
let volume_path = ctx
|
||||||
|
.volumes
|
||||||
|
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &src_volume)
|
||||||
|
.ok_or_else(|| anyhow!("There is no {} in volumes", src_volume))?;
|
||||||
|
let volume_path_out = ctx
|
||||||
|
.volumes
|
||||||
|
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &dst_volume)
|
||||||
|
.ok_or_else(|| anyhow!("There is no {} in volumes", dst_volume))?;
|
||||||
|
if ctx.volumes.readonly(&dst_volume) {
|
||||||
|
bail!("Volume {} is readonly", dst_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_file = volume_path.join(src_path);
|
||||||
|
let parent_old_file = old_file
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
||||||
|
// With the volume check
|
||||||
|
if !is_subset(&volume_path, &parent_old_file).await? {
|
||||||
|
bail!(
|
||||||
|
"Path '{}' has broken away from parent '{}'",
|
||||||
|
old_file.to_string_lossy(),
|
||||||
|
volume_path.to_string_lossy(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_file = volume_path_out.join(dst_path);
|
||||||
|
let parent_new_file = new_file
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
||||||
|
// With the volume check
|
||||||
|
if !is_subset(&volume_path_out, &parent_new_file).await? {
|
||||||
|
bail!(
|
||||||
|
"Path '{}' has broken away from parent '{}'",
|
||||||
|
new_file.to_string_lossy(),
|
||||||
|
volume_path_out.to_string_lossy(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tokio::fs::rename(old_file, new_file).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[op]
|
||||||
async fn remove_file(
|
async fn remove_file(
|
||||||
state: Rc<RefCell<OpState>>,
|
state: Rc<RefCell<OpState>>,
|
||||||
volume_id: VolumeId,
|
volume_id: VolumeId,
|
||||||
|
|||||||
Reference in New Issue
Block a user