Skip to content

Commit

Permalink
feat(bindings/nodejs): stat op
Browse files Browse the repository at this point in the history
Signed-off-by: suyanhanx <[email protected]>
  • Loading branch information
suyanhanx committed Mar 4, 2023
1 parent 9f22547 commit 797973c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 40 deletions.
10 changes: 6 additions & 4 deletions bindings/nodejs/__test__/index.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ test('test memory write & read', async (t) => {

await o.write(new TextEncoder().encode(content))

let meta = await o.meta()
t.is(meta.size, content.length)
let meta = await o.stat()
t.is(meta.mode, 0)
t.is(meta.contentLength, content.length)

let res = await o.read()
t.is(content, new TextDecoder().decode(res))
Expand All @@ -47,8 +48,9 @@ test('test memory write & read synchronously', (t) => {

o.writeSync(new TextEncoder().encode(content))

let meta = o.metaSync()
t.is(meta.size, content.length)
let meta = o.statSync()
t.is(meta.mode, 0)
t.is(meta.contentLength, content.length)

let res = o.readSync()
t.is(content, new TextDecoder().decode(res))
Expand Down
32 changes: 29 additions & 3 deletions bindings/nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@

/* auto-generated by NAPI-RS */

export const enum ObjectMode {
/** FILE means the object has data to read. */
FILE = 0,
/** DIR means the object can be listed. */
DIR = 1,
/** Unknown means we don't know what we can do on this object. */
Unknown = 2
}
export class OperatorFactory {
static memory(): Operator
}
Expand All @@ -13,14 +21,32 @@ export class Memory {
export class Operator {
object(path: string): object
}
export class ObjectMetadata {
export class ObjectMeta {
location: string
lastModified: number
size: number
}
export class ObjectMetadata {
/** Mode of this object. */
mode: ObjectMode
/** Content-Disposition of this object */
contentDisposition?: string
/** Content Length of this object */
contentLength?: number
/** Content MD5 of this object. */
contentMd5?: string
/** Content Range of this object. */
contentRange?: Array<number>
/** Content Type of this object. */
contentType?: string
/** ETag of this object. */
etag?: string
/** Last Modified of this object. */
lastModified: number
}
export class Object {
meta(): Promise<ObjectMetadata>
metaSync(): ObjectMetadata
stat(): Promise<ObjectMetadata>
statSync(): ObjectMetadata
write(content: Buffer): Promise<void>
writeSync(content: Buffer): void
read(): Promise<Buffer>
Expand Down
4 changes: 3 additions & 1 deletion bindings/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,12 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { OperatorFactory, Memory, Operator, ObjectMetadata, Object } = nativeBinding
const { OperatorFactory, Memory, Operator, ObjectMeta, ObjectMode, ObjectMetadata, Object } = nativeBinding

module.exports.OperatorFactory = OperatorFactory
module.exports.Memory = Memory
module.exports.Operator = Operator
module.exports.ObjectMeta = ObjectMeta
module.exports.ObjectMode = ObjectMode
module.exports.ObjectMetadata = ObjectMetadata
module.exports.Object = Object
107 changes: 75 additions & 32 deletions bindings/nodejs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,68 +87,111 @@ impl Operator {

#[allow(dead_code)]
#[napi]
pub struct ObjectMetadata {
pub struct ObjectMeta {
pub location: String,
pub last_modified: i64,
pub size: u32
}

#[napi]
pub enum ObjectMode {
/// FILE means the object has data to read.
FILE,
/// DIR means the object can be listed.
DIR,
/// Unknown means we don't know what we can do on this object.
Unknown,
}

#[allow(dead_code)]
#[napi]
pub struct ObjectMetadata {
/// Mode of this object.
pub mode: ObjectMode,

/// Content-Disposition of this object
pub content_disposition: Option<String>,
/// Content Length of this object
pub content_length: Option<u32>,
/// Content MD5 of this object.
pub content_md5: Option<String>,
/// Content Range of this object.
pub content_range: Option<Vec<u32>>,
/// Content Type of this object.
pub content_type: Option<String>,
/// ETag of this object.
pub etag: Option<String>,
/// Last Modified of this object.
pub last_modified: i64,
}

#[napi]
pub struct Object {
inner: opendal::Object
}

fn exact_meta(meta: opendal::ObjectMetadata) -> Result<ObjectMetadata> {
let content_range = meta
.content_range()
.unwrap_or_default();
let range = content_range.range().unwrap_or_default();
let range_out: Vec<u32> = vec![
u32::try_from(range.start).ok().unwrap_or_default(),
u32::try_from(range.end).ok().unwrap_or_default(),
u32::try_from(content_range.size().unwrap_or_default()).ok().unwrap_or_default()
];

let (secs, nsecs) = meta
.last_modified()
.map(|v| (v.unix_timestamp(), v.nanosecond()))
.unwrap_or((0, 0));


Ok(ObjectMetadata {
mode: match meta.mode() {
opendal::ObjectMode::DIR => ObjectMode::DIR,
opendal::ObjectMode::FILE => ObjectMode::FILE,
opendal::ObjectMode::Unknown => ObjectMode::Unknown,
},
content_disposition: meta.content_disposition().map(|s| s.to_string()),
content_length: u32::try_from(meta.content_length()).ok(),
content_md5: meta.content_md5().map(|s| s.to_string()),
content_range: Some(range_out),
content_type: meta.content_type().map(|s| s.to_string()),
etag: meta.etag().map(|s| s.to_string()),
last_modified: DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp_opt(secs, nsecs)
.expect("returning timestamp must be valid"),
Utc,
).timestamp(),
})
}

#[napi]
impl Object {
pub fn new(op: opendal::Object) -> Self {
Self { inner: op }
}

#[napi]
pub async fn meta(&self) -> Result<ObjectMetadata> {
pub async fn stat(&self) -> Result<ObjectMetadata> {
let meta = self.inner
.stat()
.await
.map_err(format_napi_error)
.unwrap();

let (secs, nsecs) = meta
.last_modified()
.map(|v| (v.unix_timestamp(), v.nanosecond()))
.unwrap_or((0, 0));

Ok(ObjectMetadata {
location: self.inner.path().to_string(),
last_modified: DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp_opt(secs, nsecs)
.expect("returning timestamp must be valid"),
Utc,
).timestamp(),
size: meta.content_length() as u32
})
exact_meta(meta)
}

#[napi(js_name="metaSync")]
pub fn blocking_meta(&self) -> Result<ObjectMetadata> {
#[napi(js_name="statSync")]
pub fn blocking_stat(&self) -> Result<ObjectMetadata> {
let meta = self.inner
.blocking_stat()
.map_err(format_napi_error)
.unwrap();

let (secs, nsecs) = meta
.last_modified()
.map(|v| (v.unix_timestamp(), v.nanosecond()))
.unwrap_or((0, 0));

Ok(ObjectMetadata {
location: self.inner.path().to_string(),
last_modified: DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp_opt(secs, nsecs)
.expect("returning timestamp must be valid"),
Utc,
).timestamp(),
size: meta.content_length() as u32
})
exact_meta(meta)
}

#[napi]
Expand Down

0 comments on commit 797973c

Please sign in to comment.