summaryrefslogtreecommitdiff
path: root/extensions/web
diff options
context:
space:
mode:
authorJimmy Wärting <jimmy@warting.se>2021-07-05 15:34:37 +0200
committerGitHub <noreply@github.com>2021-07-05 15:34:37 +0200
commit2c0b0e45b72ef1b5d7fa95e1e110d07ddbc720f7 (patch)
tree3b54a6f1f156f8d105cf41ac290035c8b5f8f1c9 /extensions/web
parentea87d860beda7cd40eb6857199a00e5ba8700fd2 (diff)
refactor: asynchronous blob backing store (#10969)
Co-authored-by: Luca Casonato <hello@lcas.dev>
Diffstat (limited to 'extensions/web')
-rw-r--r--extensions/web/09_file.js258
-rw-r--r--extensions/web/11_blob_url.js11
-rw-r--r--extensions/web/13_message_port.js4
-rw-r--r--extensions/web/Cargo.toml1
-rw-r--r--extensions/web/blob.rs265
-rw-r--r--extensions/web/internal.d.ts10
-rw-r--r--extensions/web/lib.rs113
7 files changed, 493 insertions, 169 deletions
diff --git a/extensions/web/09_file.js b/extensions/web/09_file.js
index 403bbee35..5f335f0e1 100644
--- a/extensions/web/09_file.js
+++ b/extensions/web/09_file.js
@@ -67,58 +67,51 @@
return result;
}
- /**
- * @param {...Uint8Array} bytesArrays
- * @returns {Uint8Array}
- */
- function concatUint8Arrays(...bytesArrays) {
- let byteLength = 0;
- for (const bytes of bytesArrays) {
- byteLength += bytes.byteLength;
+ /** @param {(BlobReference | Blob)[]} parts */
+ async function* toIterator(parts) {
+ for (const part of parts) {
+ yield* part.stream();
}
- const finalBytes = new Uint8Array(byteLength);
- let current = 0;
- for (const bytes of bytesArrays) {
- finalBytes.set(bytes, current);
- current += bytes.byteLength;
- }
- return finalBytes;
}
/** @typedef {BufferSource | Blob | string} BlobPart */
/**
- * @param {BlobPart[]} parts
- * @param {string} endings
- * @returns {Uint8Array}
- */
+ * @param {BlobPart[]} parts
+ * @param {string} endings
+ * @returns {{ parts: (BlobReference|Blob)[], size: number }}
+ */
function processBlobParts(parts, endings) {
- /** @type {Uint8Array[]} */
- const bytesArrays = [];
+ /** @type {(BlobReference|Blob)[]} */
+ const processedParts = [];
+ let size = 0;
for (const element of parts) {
if (element instanceof ArrayBuffer) {
- bytesArrays.push(new Uint8Array(element.slice(0)));
+ const chunk = new Uint8Array(element.slice(0));
+ processedParts.push(BlobReference.fromUint8Array(chunk));
+ size += element.byteLength;
} else if (ArrayBuffer.isView(element)) {
- const buffer = element.buffer.slice(
+ const chunk = new Uint8Array(
+ element.buffer,
element.byteOffset,
- element.byteOffset + element.byteLength,
+ element.byteLength,
);
- bytesArrays.push(new Uint8Array(buffer));
+ size += element.byteLength;
+ processedParts.push(BlobReference.fromUint8Array(chunk));
} else if (element instanceof Blob) {
- bytesArrays.push(
- new Uint8Array(element[_byteSequence].buffer.slice(0)),
- );
+ processedParts.push(element);
+ size += element.size;
} else if (typeof element === "string") {
- let s = element;
- if (endings == "native") {
- s = convertLineEndingsToNative(s);
- }
- bytesArrays.push(core.encode(s));
+ const chunk = core.encode(
+ endings == "native" ? convertLineEndingsToNative(element) : element,
+ );
+ size += chunk.byteLength;
+ processedParts.push(BlobReference.fromUint8Array(chunk));
} else {
- throw new TypeError("Unreachable code (invalild element type)");
+ throw new TypeError("Unreachable code (invalid element type)");
}
}
- return concatUint8Arrays(...bytesArrays);
+ return { parts: processedParts, size };
}
/**
@@ -133,18 +126,30 @@
return normalizedType.toLowerCase();
}
- const _byteSequence = Symbol("[[ByteSequence]]");
-
- class Blob {
- get [Symbol.toStringTag]() {
- return "Blob";
+ /**
+ * Get all Parts as a flat array containing all references
+ * @param {Blob} blob
+ * @param {string[]} bag
+ * @returns {string[]}
+ */
+ function getParts(blob, bag = []) {
+ for (const part of blob[_parts]) {
+ if (part instanceof Blob) {
+ getParts(part, bag);
+ } else {
+ bag.push(part._id);
+ }
}
+ return bag;
+ }
- /** @type {string} */
- #type;
+ const _size = Symbol("Size");
+ const _parts = Symbol("Parts");
- /** @type {Uint8Array} */
- [_byteSequence];
+ class Blob {
+ #type = "";
+ [_size] = 0;
+ [_parts];
/**
* @param {BlobPart[]} blobParts
@@ -163,18 +168,20 @@
this[webidl.brand] = webidl.brand;
- /** @type {Uint8Array} */
- this[_byteSequence] = processBlobParts(
+ const { parts, size } = processBlobParts(
blobParts,
options.endings,
);
+
+ this[_parts] = parts;
+ this[_size] = size;
this.#type = normalizeType(options.type);
}
/** @returns {number} */
get size() {
webidl.assertBranded(this, Blob);
- return this[_byteSequence].byteLength;
+ return this[_size];
}
/** @returns {string} */
@@ -237,6 +244,36 @@
relativeEnd = Math.min(end, O.size);
}
}
+
+ const span = Math.max(relativeEnd - relativeStart, 0);
+ const blobParts = [];
+ let added = 0;
+
+ for (const part of this[_parts]) {
+ // don't add the overflow to new blobParts
+ if (added >= span) {
+ // Could maybe be possible to remove variable `added`
+ // and only use relativeEnd?
+ break;
+ }
+ const size = part.size;
+ if (relativeStart && size <= relativeStart) {
+ // Skip the beginning and change the relative
+ // start & end position as we skip the unwanted parts
+ relativeStart -= size;
+ relativeEnd -= size;
+ } else {
+ const chunk = part.slice(
+ relativeStart,
+ Math.min(part.size, relativeEnd),
+ );
+ added += chunk.size;
+ relativeEnd -= part.size;
+ blobParts.push(chunk);
+ relativeStart = 0; // All next sequential parts should start at 0
+ }
+ }
+
/** @type {string} */
let relativeContentType;
if (contentType === undefined) {
@@ -244,9 +281,11 @@
} else {
relativeContentType = normalizeType(contentType);
}
- return new Blob([
- O[_byteSequence].buffer.slice(relativeStart, relativeEnd),
- ], { type: relativeContentType });
+
+ const blob = new Blob([], { type: relativeContentType });
+ blob[_parts] = blobParts;
+ blob[_size] = span;
+ return blob;
}
/**
@@ -254,14 +293,18 @@
*/
stream() {
webidl.assertBranded(this, Blob);
- const bytes = this[_byteSequence];
+ const partIterator = toIterator(this[_parts]);
const stream = new ReadableStream({
type: "bytes",
/** @param {ReadableByteStreamController} controller */
- start(controller) {
- const chunk = new Uint8Array(bytes.buffer.slice(0));
- if (chunk.byteLength > 0) controller.enqueue(chunk);
- controller.close();
+ async pull(controller) {
+ while (true) {
+ const { value, done } = await partIterator.next();
+ if (done) return controller.close();
+ if (value.byteLength > 0) {
+ return controller.enqueue(value);
+ }
+ }
},
});
return stream;
@@ -282,12 +325,22 @@
async arrayBuffer() {
webidl.assertBranded(this, Blob);
const stream = this.stream();
- let bytes = new Uint8Array();
+ const bytes = new Uint8Array(this.size);
+ let offset = 0;
for await (const chunk of stream) {
- bytes = concatUint8Arrays(bytes, chunk);
+ bytes.set(chunk, offset);
+ offset += chunk.byteLength;
}
return bytes.buffer;
}
+
+ get [Symbol.toStringTag]() {
+ return "Blob";
+ }
+
+ [Symbol.for("Deno.customInspect")](inspect) {
+ return `Blob ${inspect({ size: this.size, type: this.#type })}`;
+ }
}
webidl.configurePrototype(Blob);
@@ -333,17 +386,13 @@
);
const _Name = Symbol("[[Name]]");
- const _LastModfied = Symbol("[[LastModified]]");
+ const _LastModified = Symbol("[[LastModified]]");
class File extends Blob {
- get [Symbol.toStringTag]() {
- return "File";
- }
-
/** @type {string} */
[_Name];
/** @type {number} */
- [_LastModfied];
+ [_LastModified];
/**
* @param {BlobPart[]} fileBits
@@ -373,10 +422,10 @@
this[_Name] = fileName;
if (options.lastModified === undefined) {
/** @type {number} */
- this[_LastModfied] = new Date().getTime();
+ this[_LastModified] = new Date().getTime();
} else {
/** @type {number} */
- this[_LastModfied] = options.lastModified;
+ this[_LastModified] = options.lastModified;
}
}
@@ -389,7 +438,11 @@
/** @returns {number} */
get lastModified() {
webidl.assertBranded(this, File);
- return this[_LastModfied];
+ return this[_LastModified];
+ }
+
+ get [Symbol.toStringTag]() {
+ return "File";
}
}
@@ -406,9 +459,80 @@
],
);
+ // A finalization registry to deallocate a blob part when its JS reference is
+ // garbage collected.
+ const registry = new FinalizationRegistry((uuid) => {
+ core.opSync("op_blob_remove_part", uuid);
+ });
+
+ // TODO(lucacasonato): get a better stream from Rust in BlobReference#stream
+
+ /**
+ * An opaque reference to a blob part in Rust. This could be backed by a file,
+ * in memory storage, or something else.
+ */
+ class BlobReference {
+ /**
+ * Don't use directly. Use `BlobReference.fromUint8Array`.
+ * @param {string} id
+ * @param {number} size
+ */
+ constructor(id, size) {
+ this._id = id;
+ this.size = size;
+ registry.register(this, id);
+ }
+
+ /**
+ * Create a new blob part from a Uint8Array.
+ *
+ * @param {Uint8Array} data
+ * @returns {BlobReference}
+ */
+ static fromUint8Array(data) {
+ const id = core.opSync("op_blob_create_part", data);
+ return new BlobReference(id, data.byteLength);
+ }
+
+ /**
+ * Create a new BlobReference by slicing this BlobReference. This is a copy
+ * free operation - the sliced reference will still reference the original
+ * underlying bytes.
+ *
+ * @param {number} start
+ * @param {number} end
+ * @returns {BlobReference}
+ */
+ slice(start, end) {
+ const size = end - start;
+ const id = core.opSync("op_blob_slice_part", this._id, {
+ start,
+ len: size,
+ });
+ return new BlobReference(id, size);
+ }
+
+ /**
+ * Read the entire contents of the reference blob.
+ * @returns {AsyncGenerator<Uint8Array>}
+ */
+ async *stream() {
+ yield core.opAsync("op_blob_read_part", this._id);
+
+ // let position = 0;
+ // const end = this.size;
+ // while (position !== end) {
+ // const size = Math.min(end - position, 65536);
+ // const chunk = this.slice(position, position + size);
+ // position += chunk.size;
+ // yield core.opAsync("op_blob_read_part", chunk._id);
+ // }
+ }
+ }
+
window.__bootstrap.file = {
+ getParts,
Blob,
- _byteSequence,
File,
};
})(this);
diff --git a/extensions/web/11_blob_url.js b/extensions/web/11_blob_url.js
index d030d79bd..fa0ea041c 100644
--- a/extensions/web/11_blob_url.js
+++ b/extensions/web/11_blob_url.js
@@ -15,7 +15,7 @@
((window) => {
const core = Deno.core;
const webidl = window.__bootstrap.webidl;
- const { _byteSequence } = window.__bootstrap.file;
+ const { getParts } = window.__bootstrap.file;
const { URL } = window.__bootstrap.url;
/**
@@ -31,9 +31,9 @@
});
const url = core.opSync(
- "op_file_create_object_url",
+ "op_blob_create_object_url",
blob.type,
- blob[_byteSequence],
+ getParts(blob),
);
return url;
@@ -51,10 +51,7 @@
prefix,
});
- core.opSync(
- "op_file_revoke_object_url",
- url,
- );
+ core.opSync("op_blob_revoke_object_url", url);
}
URL.createObjectURL = createObjectURL;
diff --git a/extensions/web/13_message_port.js b/extensions/web/13_message_port.js
index ae8e148f4..3bd7c692b 100644
--- a/extensions/web/13_message_port.js
+++ b/extensions/web/13_message_port.js
@@ -154,6 +154,10 @@
this[_id] = null;
}
}
+
+ get [Symbol.toStringTag]() {
+ return "MessagePort";
+ }
}
defineEventHandler(MessagePort.prototype, "message", function (self) {
diff --git a/extensions/web/Cargo.toml b/extensions/web/Cargo.toml
index eeec91036..b056baeea 100644
--- a/extensions/web/Cargo.toml
+++ b/extensions/web/Cargo.toml
@@ -14,6 +14,7 @@ repository = "https://github.com/denoland/deno"
path = "lib.rs"
[dependencies]
+async-trait = "0.1.50"
base64 = "0.13.0"
deno_core = { version = "0.92.0", path = "../../core" }
encoding_rs = "0.8.28"
diff --git a/extensions/web/blob.rs b/extensions/web/blob.rs
new file mode 100644
index 000000000..96a982677
--- /dev/null
+++ b/extensions/web/blob.rs
@@ -0,0 +1,265 @@
+use async_trait::async_trait;
+use deno_core::error::type_error;
+use deno_core::url::Url;
+use deno_core::ZeroCopyBuf;
+use serde::Deserialize;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+use deno_core::error::AnyError;
+use uuid::Uuid;
+
+use crate::Location;
+
+pub type PartMap = HashMap<Uuid, Arc<Box<dyn BlobPart + Send + Sync>>>;
+
+#[derive(Clone, Default, Debug)]
+pub struct BlobStore {
+ parts: Arc<Mutex<PartMap>>,
+ object_urls: Arc<Mutex<HashMap<Url, Arc<Blob>>>>,
+}
+
+impl BlobStore {
+ pub fn insert_part(&self, part: Box<dyn BlobPart + Send + Sync>) -> Uuid {
+ let id = Uuid::new_v4();
+ let mut parts = self.parts.lock().unwrap();
+ parts.insert(id, Arc::new(part));
+ id
+ }
+
+ pub fn get_part(
+ &self,
+ id: &Uuid,
+ ) -> Option<Arc<Box<dyn BlobPart + Send + Sync>>> {
+ let parts = self.parts.lock().unwrap();
+ let part = parts.get(&id);
+ part.cloned()
+ }
+
+ pub fn remove_part(
+ &self,
+ id: &Uuid,
+ ) -> Option<Arc<Box<dyn BlobPart + Send + Sync>>> {
+ let mut parts = self.parts.lock().unwrap();
+ parts.remove(&id)
+ }
+
+ pub fn get_object_url(
+ &self,
+ mut url: Url,
+ ) -> Result<Option<Arc<Blob>>, AnyError> {
+ let blob_store = self.object_urls.lock().unwrap();
+ url.set_fragment(None);
+ Ok(blob_store.get(&url).cloned())
+ }
+
+ pub fn insert_object_url(
+ &self,
+ blob: Blob,
+ maybe_location: Option<Url>,
+ ) -> Url {
+ let origin = if let Some(location) = maybe_location {
+ location.origin().ascii_serialization()
+ } else {
+ "null".to_string()
+ };
+ let id = Uuid::new_v4();
+ let url = Url::parse(&format!("blob:{}/{}", origin, id)).unwrap();
+
+ let mut blob_store = self.object_urls.lock().unwrap();
+ blob_store.insert(url.clone(), Arc::new(blob));
+
+ url
+ }
+
+ pub fn remove_object_url(&self, url: &Url) {
+ let mut blob_store = self.object_urls.lock().unwrap();
+ blob_store.remove(&url);
+ }
+}
+
+#[derive(Debug)]
+pub struct Blob {
+ pub media_type: String,
+
+ pub parts: Vec<Arc<Box<dyn BlobPart + Send + Sync>>>,
+}
+
+impl Blob {
+ // TODO(lucacsonato): this should be a stream!
+ pub async fn read_all(&self) -> Result<Vec<u8>, AnyError> {
+ let size = self.size();
+ let mut bytes = Vec::with_capacity(size);
+
+ for part in &self.parts {
+ let chunk = part.read().await?;
+ bytes.extend_from_slice(chunk);
+ }
+
+ assert_eq!(bytes.len(), size);
+
+ Ok(bytes)
+ }
+
+ fn size(&self) -> usize {
+ let mut total = 0;
+ for part in &self.parts {
+ total += part.size()
+ }
+ total
+ }
+}
+
+#[async_trait]
+pub trait BlobPart: Debug {
+ // TODO(lucacsonato): this should be a stream!
+ async fn read(&self) -> Result<&[u8], AnyError>;
+ fn size(&self) -> usize;
+}
+
+#[derive(Debug)]
+pub struct InMemoryBlobPart(Vec<u8>);
+
+impl From<Vec<u8>> for InMemoryBlobPart {
+ fn from(vec: Vec<u8>) -> Self {
+ Self(vec)
+ }
+}
+
+#[async_trait]
+impl BlobPart for InMemoryBlobPart {
+ async fn read(&self) -> Result<&[u8], AnyError> {
+ Ok(&self.0)
+ }
+
+ fn size(&self) -> usize {
+ self.0.len()
+ }
+}
+
+#[derive(Debug)]
+pub struct SlicedBlobPart {
+ part: Arc<Box<dyn BlobPart + Send + Sync>>,
+ start: usize,
+ len: usize,
+}
+
+#[async_trait]
+impl BlobPart for SlicedBlobPart {
+ async fn read(&self) -> Result<&[u8], AnyError> {
+ let original = self.part.read().await?;
+ Ok(&original[self.start..self.start + self.len])
+ }
+
+ fn size(&self) -> usize {
+ self.len
+ }
+}
+
+pub fn op_blob_create_part(
+ state: &mut deno_core::OpState,
+ data: ZeroCopyBuf,
+ _: (),
+) -> Result<Uuid, AnyError> {
+ let blob_store = state.borrow::<BlobStore>();
+ let part = InMemoryBlobPart(data.to_vec());
+ let id = blob_store.insert_part(Box::new(part));
+ Ok(id)
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SliceOptions {
+ start: usize,
+ len: usize,
+}
+
+pub fn op_blob_slice_part(
+ state: &mut deno_core::OpState,
+ id: Uuid,
+ options: SliceOptions,
+) -> Result<Uuid, AnyError> {
+ let blob_store = state.borrow::<BlobStore>();
+ let part = blob_store
+ .get_part(&id)
+ .ok_or_else(|| type_error("Blob part not found"))?;
+
+ let SliceOptions { start, len } = options;
+
+ let size = part.size();
+ if start + len > size {
+ return Err(type_error(
+ "start + len can not be larger than blob part size",
+ ));
+ }
+
+ let sliced_part = SlicedBlobPart { part, start, len };
+ let id = blob_store.insert_part(Box::new(sliced_part));
+
+ Ok(id)
+}
+
+pub async fn op_blob_read_part(
+ state: Rc<RefCell<deno_core::OpState>>,
+ id: Uuid,
+ _: (),
+) -> Result<ZeroCopyBuf, AnyError> {
+ let part = {
+ let state = state.borrow();
+ let blob_store = state.borrow::<BlobStore>();
+ blob_store.get_part(&id)
+ }
+ .ok_or_else(|| type_error("Blob part not found"))?;
+ let buf = part.read().await?;
+ Ok(ZeroCopyBuf::from(buf.to_vec()))
+}
+
+pub fn op_blob_remove_part(
+ state: &mut deno_core::OpState,
+ id: Uuid,
+ _: (),
+) -> Result<(), AnyError> {
+ let blob_store = state.borrow::<BlobStore>();
+ blob_store.remove_part(&id);
+ Ok(())
+}
+
+pub fn op_blob_create_object_url(
+ state: &mut deno_core::OpState,
+ media_type: String,
+ part_ids: Vec<Uuid>,
+) -> Result<String, AnyError> {
+ let mut parts = Vec::with_capacity(part_ids.len());
+ let blob_store = state.borrow::<BlobStore>();
+ for part_id in part_ids {
+ let part = blob_store
+ .get_part(&part_id)
+ .ok_or_else(|| type_error("Blob part not found"))?;
+ parts.push(part);
+ }
+
+ let blob = Blob { media_type, parts };
+
+ let maybe_location = state.try_borrow::<Location>();
+ let blob_store = state.borrow::<BlobStore>();
+
+ let url = blob_store
+ .insert_object_url(blob, maybe_location.map(|location| location.0.clone()));
+
+ Ok(url.to_string())
+}
+
+pub fn op_blob_revoke_object_url(
+ state: &mut deno_core::OpState,
+ url: String,
+ _: (),
+) -> Result<(), AnyError> {
+ let url = Url::parse(&url)?;
+ let blob_store = state.borrow::<BlobStore>();
+ blob_store.remove_object_url(&url);
+ Ok(())
+}
diff --git a/extensions/web/internal.d.ts b/extensions/web/internal.d.ts
index bc3982a88..3a2a0c1be 100644
--- a/extensions/web/internal.d.ts
+++ b/extensions/web/internal.d.ts
@@ -73,13 +73,9 @@ declare namespace globalThis {
};
declare var file: {
- Blob: typeof Blob & {
- [globalThis.__bootstrap.file._byteSequence]: Uint8Array;
- };
- readonly _byteSequence: unique symbol;
- File: typeof File & {
- [globalThis.__bootstrap.file._byteSequence]: Uint8Array;
- };
+ getParts(blob: Blob): string[];
+ Blob: typeof Blob;
+ File: typeof File;
};
declare var streams: {
diff --git a/extensions/web/lib.rs b/extensions/web/lib.rs
index 67022c7ea..634004ac9 100644
--- a/extensions/web/lib.rs
+++ b/extensions/web/lib.rs
@@ -1,13 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+mod blob;
mod message_port;
-pub use crate::message_port::create_entangled_message_port;
-pub use crate::message_port::JsMessageData;
-pub use crate::message_port::MessagePort;
-
use deno_core::error::bad_resource_id;
-use deno_core::error::null_opbuf;
use deno_core::error::range_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
@@ -16,7 +12,6 @@ use deno_core::op_async;
use deno_core::op_sync;
use deno_core::url::Url;
use deno_core::Extension;
-use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
@@ -29,23 +24,30 @@ use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
use std::cell::RefCell;
-use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
-use std::sync::Arc;
-use std::sync::Mutex;
use std::usize;
-use uuid::Uuid;
+use crate::blob::op_blob_create_object_url;
+use crate::blob::op_blob_create_part;
+use crate::blob::op_blob_read_part;
+use crate::blob::op_blob_remove_part;
+use crate::blob::op_blob_revoke_object_url;
+use crate::blob::op_blob_slice_part;
+pub use crate::blob::Blob;
+pub use crate::blob::BlobPart;
+pub use crate::blob::BlobStore;
+pub use crate::blob::InMemoryBlobPart;
+
+pub use crate::message_port::create_entangled_message_port;
use crate::message_port::op_message_port_create_entangled;
use crate::message_port::op_message_port_post_message;
use crate::message_port::op_message_port_recv_message;
+pub use crate::message_port::JsMessageData;
+pub use crate::message_port::MessagePort;
/// Load and execute the javascript code.
-pub fn init(
- blob_url_store: BlobUrlStore,
- maybe_location: Option<Url>,
-) -> Extension {
+pub fn init(blob_store: BlobStore, maybe_location: Option<Url>) -> Extension {
Extension::builder()
.js(include_js_files!(
prefix "deno:extensions/web",
@@ -75,13 +77,17 @@ pub fn init(
("op_encoding_new_decoder", op_sync(op_encoding_new_decoder)),
("op_encoding_decode", op_sync(op_encoding_decode)),
("op_encoding_encode_into", op_sync(op_encoding_encode_into)),
+ ("op_blob_create_part", op_sync(op_blob_create_part)),
+ ("op_blob_slice_part", op_sync(op_blob_slice_part)),
+ ("op_blob_read_part", op_async(op_blob_read_part)),
+ ("op_blob_remove_part", op_sync(op_blob_remove_part)),
(
- "op_file_create_object_url",
- op_sync(op_file_create_object_url),
+ "op_blob_create_object_url",
+ op_sync(op_blob_create_object_url),
),
(
- "op_file_revoke_object_url",
- op_sync(op_file_revoke_object_url),
+ "op_blob_revoke_object_url",
+ op_sync(op_blob_revoke_object_url),
),
(
"op_message_port_create_entangled",
@@ -97,7 +103,7 @@ pub fn init(
),
])
.state(move |state| {
- state.put(blob_url_store.clone());
+ state.put(blob_store.clone());
if let Some(location) = maybe_location.clone() {
state.put(Location(location));
}
@@ -381,73 +387,4 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
.map(|_| "DOMExceptionInvalidCharacterError")
})
}
-
-#[derive(Debug, Clone)]
-pub struct Blob {
- pub data: Vec<u8>,
- pub media_type: String,
-}
-
pub struct Location(pub Url);
-
-#[derive(Debug, Default, Clone)]
-pub struct BlobUrlStore(Arc<Mutex<HashMap<Url, Blob>>>);
-
-impl BlobUrlStore {
- pub fn get(&self, mut url: Url) -> Result<Option<Blob>, AnyError> {
- let blob_store = self.0.lock().unwrap();
- url.set_fragment(None);
- Ok(blob_store.get(&url).cloned())
- }
-
- pub fn insert(&self, blob: Blob, maybe_location: Option<Url>) -> Url {
- let origin = if let Some(location) = maybe_location {
- location.origin().ascii_serialization()
- } else {
- "null".to_string()
- };
- let id = Uuid::new_v4();
- let url = Url::parse(&format!("blob:{}/{}", origin, id)).unwrap();
-
- let mut blob_store = self.0.lock().unwrap();
- blob_store.insert(url.clone(), blob);
-
- url
- }
-
- pub fn remove(&self, url: &ModuleSpecifier) {
- let mut blob_store = self.0.lock().unwrap();
- blob_store.remove(&url);
- }
-}
-
-pub fn op_file_create_object_url(
- state: &mut deno_core::OpState,
- media_type: String,
- zero_copy: Option<ZeroCopyBuf>,
-) -> Result<String, AnyError> {
- let data = zero_copy.ok_or_else(null_opbuf)?;
- let blob = Blob {
- data: data.to_vec(),
- media_type,
- };
-
- let maybe_location = state.try_borrow::<Location>();
- let blob_store = state.borrow::<BlobUrlStore>();
-
- let url =
- blob_store.insert(blob, maybe_location.map(|location| location.0.clone()));
-
- Ok(url.to_string())
-}
-
-pub fn op_file_revoke_object_url(
- state: &mut deno_core::OpState,
- url: String,
- _: (),
-) -> Result<(), AnyError> {
- let url = Url::parse(&url)?;
- let blob_store = state.borrow::<BlobUrlStore>();
- blob_store.remove(&url);
- Ok(())
-}