mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Feat/js metadata (#1548)
* feat: metadata effect * feat: Metadata for effects * chore: Add in the new types
This commit is contained in:
@@ -6,7 +6,7 @@ Wants=avahi-daemon.service nginx.service tor.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=RUST_LOG=embassy_init=debug,embassy=debug
|
||||
Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug
|
||||
ExecStart=/usr/local/bin/embassy-init
|
||||
RemainAfterExit=true
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Requires=embassy-init.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=RUST_LOG=embassyd=debug,embassy=debug
|
||||
Environment=RUST_LOG=embassyd=debug,embassy=debug,js_engine=debug
|
||||
ExecStart=/usr/local/bin/embassyd
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
@@ -20,7 +20,7 @@ fn inner_main() -> Result<(), Error> {
|
||||
),
|
||||
context: matches => {
|
||||
if let Err(_) = std::env::var("RUST_LOG") {
|
||||
std::env::set_var("RUST_LOG", "embassy=warn");
|
||||
std::env::set_var("RUST_LOG", "embassy=warn,js_engine=warn");
|
||||
}
|
||||
EmbassyLogger::init();
|
||||
SdkContext::init(matches)?
|
||||
|
||||
@@ -89,6 +89,42 @@ export async function getConfig(effects) {
|
||||
effects.warn("warn");
|
||||
effects.error("error");
|
||||
effects.info("info");
|
||||
|
||||
{
|
||||
const metadata = await effects.metadata({
|
||||
path: "./test.log",
|
||||
volumeId: "main",
|
||||
})
|
||||
|
||||
if (typeof metadata.fileType !== 'string') {
|
||||
throw new TypeError("File type is not a string")
|
||||
}
|
||||
if (typeof metadata.isDir !== 'boolean' ) {
|
||||
throw new TypeError("isDir is not a boolean")
|
||||
}
|
||||
if (typeof metadata.isFile !== 'boolean' ) {
|
||||
throw new TypeError("isFile is not a boolean")
|
||||
}
|
||||
if (typeof metadata.isSymlink !== 'boolean' ) {
|
||||
throw new TypeError("isSymlink is not a boolean")
|
||||
}
|
||||
if (typeof metadata.len !== 'number' ) {
|
||||
throw new TypeError("len is not a number")
|
||||
}
|
||||
if (!(metadata.modified instanceof Date )) {
|
||||
throw new TypeError("modified is not a Date")
|
||||
}
|
||||
if (!(metadata.accessed instanceof Date )) {
|
||||
throw new TypeError("accessed is not a Date")
|
||||
}
|
||||
if (!(metadata.created instanceof Date )) {
|
||||
throw new TypeError("created is not a Date")
|
||||
}
|
||||
if (typeof metadata.readonly !== 'boolean' ) {
|
||||
throw new TypeError("readonly is not a boolean")
|
||||
}
|
||||
effects.error(JSON.stringify(metadata))
|
||||
}
|
||||
return {
|
||||
result: {
|
||||
spec: {
|
||||
|
||||
15
libs/artifacts/types.d.ts
vendored
15
libs/artifacts/types.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user