Skip to main content

ferrijs_std/buffer/
file.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use crate::utils::time;
4use rquickjs::{
5    atom::PredefinedAtom, class::Trace, function::Opt, ArrayBuffer, Coerced, Ctx, Exception, Result,
6    Value,
7};
8
9use super::blob::Blob;
10
11#[rquickjs::class]
12#[derive(Trace, Clone, rquickjs::JsLifetime)]
13pub struct File<'js> {
14    blob: Blob<'js>,
15    filename: String,
16    last_modified: i64,
17}
18
19#[rquickjs::methods]
20impl<'js> File<'js> {
21    #[qjs(constructor)]
22    fn new(
23        ctx: Ctx<'js>,
24        data: Value<'js>,
25        filename: Coerced<String>,
26        options: Opt<Value<'js>>,
27    ) -> Result<Self> {
28        let mut last_modified = time::now_millis();
29
30        if let Some(ref opts) = options.0 {
31            if opts.is_bool() || opts.is_float() || opts.is_int() || opts.is_string() {
32                return Err(Exception::throw_type(&ctx, "Invalid options"));
33            }
34
35            if let Some(v) = opts.as_object() {
36                if let Some(x) = v.get::<_, Option<Coerced<i64>>>("lastModified")? {
37                    last_modified = x.0;
38                }
39            }
40        }
41
42        let blob = Blob::from_parts(ctx, Opt(Some(data)), options)?;
43
44        Ok(Self {
45            blob,
46            filename: filename.0,
47            last_modified,
48        })
49    }
50
51    #[qjs(get)]
52    pub fn size(&self) -> usize {
53        self.blob.size()
54    }
55
56    #[qjs(get)]
57    pub fn name(&self) -> String {
58        self.filename.clone()
59    }
60
61    #[qjs(get, rename = "type")]
62    pub fn mime_type(&self) -> String {
63        self.blob.mime_type()
64    }
65
66    #[qjs(get, rename = "lastModified")]
67    pub fn last_modified(&self) -> i64 {
68        self.last_modified
69    }
70
71    pub fn slice(
72        &self,
73        ctx: Ctx<'js>,
74        start: Opt<isize>,
75        end: Opt<isize>,
76        content_type: Opt<Value<'js>>,
77    ) -> Result<Blob<'js>> {
78        self.blob.slice_blob(&ctx, start.0, end.0, content_type.0)
79    }
80
81    pub async fn text(&self) -> String {
82        self.blob.text().await
83    }
84
85    #[qjs(rename = "arrayBuffer")]
86    pub async fn array_buffer(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
87        self.blob.array_buffer(ctx).await
88    }
89
90    pub async fn bytes(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
91        self.blob.bytes(ctx).await
92    }
93
94    pub fn stream(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
95        self.blob.stream(ctx)
96    }
97
98    #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
99    pub fn to_string_tag() -> &'static str {
100        stringify!(File)
101    }
102}
103
104impl<'js> File<'js> {
105    pub fn from_bytes(
106        ctx: &Ctx<'js>,
107        data: Vec<u8>,
108        filename: String,
109        mime_type: Option<String>,
110    ) -> Result<Self> {
111        // Local delta: upstream passes the bytes through `into_js`, which
112        // makes a JS ARRAY OF NUMBERS — and `new Blob([...])` stringifies
113        // every non-BufferSource part, so `File.from_bytes(b"hi")` produced
114        // a file whose text was "104105". `Blob::from_bytes` wraps the bytes
115        // in an ArrayBuffer, which is what the constructor expects.
116        let blob = Blob::from_bytes(ctx, data, mime_type.clone())?;
117
118        Ok(Self {
119            blob,
120            filename,
121            last_modified: time::now_millis(),
122        })
123    }
124
125    pub fn get_blob(&self) -> Blob<'js> {
126        self.blob.clone()
127    }
128
129    pub fn set_filename(&mut self, filename: String) {
130        self.filename = filename;
131    }
132}