Feat/js metadata (#1548)

* feat: metadata effect

* feat: Metadata for effects

* chore: Add in the new types
This commit is contained in:
J M
2022-06-16 15:58:48 -06:00
committed by GitHub
parent 9c41090a7a
commit 452c8ea2d9
7 changed files with 144 additions and 22 deletions

View File

@@ -26,6 +26,7 @@ export namespace ExpectedExports {
) => Promise<ResultType<MigrationRes>>;
}
/** Used to reach out from the pure js runtime */
export type Effects = {
/** Usable when not sandboxed */
@@ -33,6 +34,7 @@ export type Effects = {
input: { path: string; volumeId: string; toWrite: string },
): Promise<void>;
readFile(input: { volumeId: string; path: string }): Promise<string>;
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
/** Create a directory. Usable when not sandboxed */
createDir(input: { volumeId: string; path: string }): Promise<string>;
/** Remove a directory. Usable when not sandboxed */
@@ -60,7 +62,20 @@ export type Effects = {
/** Sandbox mode lets us read but not write */
is_sandboxed(): boolean;
};
export type Metadata = {
fileType: string,
isDir: boolean,
isFile: boolean,
isSymlink: boolean,
len: number,
modified?: Date,
accessed?: Date,
created?: Date,
readonly: boolean,
}
export type MigrationRes = {
configured: boolean;

View File

@@ -1,7 +1,5 @@
//@ts-check
// @ts-ignore
import Deno from "/deno_global.js";
// @ts-ignore
import * as mainModule from "/embassy.js";
/**
* This is using the simplified json pointer spec, using no escapes and arrays
@@ -20,45 +18,42 @@ function jsonPointerValue(obj, pointer) {
return obj;
}
// @ts-ignore
function maybeDate(value) {
if (!value) return value;
return new Date(value)
}
const writeFile = ({ path, volumeId, toWrite }) => Deno.core.opAsync("write_file", volumeId, path, toWrite);
// @ts-ignore
const readFile = ({ volumeId, path }) => Deno.core.opAsync("read_file", volumeId, path);
// @ts-ignore
const metadata = async ({ volumeId, path }) => {
const data = await Deno.core.opAsync("metadata", volumeId, path)
return {
...data,
modified: maybeDate(data.modified),
created: maybeDate(data.created),
accessed: maybeDate(data.accessed),
}
};
const removeFile = ({ volumeId, path }) => Deno.core.opAsync("remove_file", volumeId, path);
// @ts-ignore
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
// @ts-ignore
const writeJsonFile = ({ volumeId, path, toWrite }) =>
writeFile({
volumeId,
path,
toWrite: JSON.stringify(toWrite),
});
// @ts-ignore
const readJsonFile = async ({ volumeId, path }) => JSON.parse(await readFile({ volumeId, path }));
// @ts-ignore
const createDir = ({ volumeId, path }) => Deno.core.opAsync("create_dir", volumeId, path);
// @ts-ignore
const removeDir = ({ volumeId, path }) => Deno.core.opAsync("remove_dir", volumeId, path);
// @ts-ignore
const trace = (x) => Deno.core.opSync("log_trace", x);
// @ts-ignore
const warn = (x) => Deno.core.opSync("log_warn", x);
// @ts-ignore
const error = (x) => Deno.core.opSync("log_error", x);
// @ts-ignore
const debug = (x) => Deno.core.opSync("log_debug", x);
// @ts-ignore
const info = (x) => Deno.core.opSync("log_info", x);
// @ts-ignore
const currentFunction = Deno.core.opSync("current_function");
//@ts-ignore
const input = Deno.core.opSync("get_input");
// @ts-ignore
const setState = (x) => Deno.core.opSync("set_value", x);
const effects = {
writeFile,
@@ -74,6 +69,7 @@ const effects = {
removeFile,
createDir,
removeDir,
metadata
};
const runFunction = jsonPointerValue(mainModule, currentFunction);

View File

@@ -15,6 +15,7 @@ use helpers::NonDetachingJoinHandle;
use models::{PackageId, ProcedureName, Version, VolumeId};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::time::SystemTime;
use std::{path::Path, sync::Arc};
use std::{path::PathBuf, pin::Pin};
use tokio::io::AsyncReadExt;
@@ -60,6 +61,20 @@ impl JsError {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataJs {
file_type: String,
is_dir: bool,
is_file: bool,
is_symlink: bool,
len: u64,
modified: Option<u64>,
accessed: Option<u64>,
created: Option<u64>,
readonly: bool,
}
#[cfg(target_arch = "x86_64")]
const SNAPSHOT_BYTES: &[u8] = include_bytes!("./artifacts/JS_SNAPSHOT.bin");
@@ -240,6 +255,7 @@ impl JsExecutionEnvironment {
fn declarations() -> Vec<OpDecl> {
vec![
fns::read_file::decl(),
fns::metadata::decl(),
fns::write_file::decl(),
fns::remove_file::decl(),
fns::create_dir::decl(),
@@ -332,6 +348,8 @@ mod fns {
use models::VolumeId;
use crate::{system_time_as_unix_ms, MetadataJs};
use super::{AnswerState, JsContext};
#[op]
@@ -359,6 +377,54 @@ mod fns {
Ok(answer)
}
#[op]
async fn metadata(
state: Rc<RefCell<OpState>>,
volume_id: VolumeId,
path_in: PathBuf,
) -> Result<MetadataJs, AnyError> {
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))?;
//get_path_for in volume.rs
let new_file = volume_path.join(path_in);
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 answer = tokio::fs::metadata(new_file).await?;
let metadata_js = MetadataJs {
file_type: format!("{:?}", answer.file_type()),
is_dir: answer.is_dir(),
is_file: answer.is_file(),
is_symlink: answer.is_symlink(),
len: answer.len(),
modified: answer
.modified()
.ok()
.as_ref()
.and_then(system_time_as_unix_ms),
accessed: answer
.accessed()
.ok()
.as_ref()
.and_then(system_time_as_unix_ms),
created: answer
.created()
.ok()
.as_ref()
.and_then(system_time_as_unix_ms),
readonly: answer.permissions().readonly(),
};
Ok(metadata_js)
}
#[op]
async fn write_file(
state: Rc<RefCell<OpState>>,
volume_id: VolumeId,
@@ -564,3 +630,12 @@ mod fns {
Ok(child.starts_with(parent))
}
}
fn system_time_as_unix_ms(system_time: &SystemTime) -> Option<u64> {
system_time
.duration_since(SystemTime::UNIX_EPOCH)
.ok()?
.as_millis()
.try_into()
.ok()
}