Rust - Wasm - Iterating over input file
So, im trying to get access to an Iterator over the contents of a file uploaded via an Input field.
I can pass the JS File into Wasm just fine via web-sys, but i cannot for the life of me figure out how to access anything other then length / name of the passed file in Rust.
I think i could pass the whole file into Wasm as a ByteArray and iterate over that, but preferrably i would like to iterate straight over the file contents without copying since the files itself will be quite large (~1 GB).
I found in the Mozilla JS docs that i should be able to access the underlying file blob, get a ReadableStream from that via the .stream() method and get a Reader from that which should be able to be iterated over, but in web-sys, the .getReader() method of the ReadableStream returns a simple JSValue which i can't do anything usefull with.
Am i missing something here or is this functionality simply missing in web-sys or is there some other way to do this? Maybe create the Iterator in JS and pass that to Rust?
I think you can do something similar using FileReader
.
Here is an example, where I log the text content of a file:
use wasm_bindgen::prelude::*;
use web_sys::{Event, FileReader, HtmlInputElement};
use wasm_bindgen::JsCast;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");
let filereader = FileReader::new().unwrap().dyn_into::<FileReader>()?;
let closure = Closure::wrap(Box::new(move |event: Event| {
let element = event.target().unwrap().dyn_into::<FileReader>().unwrap();
let data = element.result().unwrap();
let js_data = js_sys::Uint8Array::from(data);
let rust_str: String = js_data.to_string().into();
log(rust_str.as_str());
}) as Box<dyn FnMut(_)>);
filereader.set_onloadend(Some(closure.as_ref().unchecked_ref()));
closure.forget();
let fileinput: HtmlInputElement = document.create_element("input").unwrap().dyn_into::<HtmlInputElement>()?;
fileinput.set_type("file");
let closure = Closure::wrap(Box::new(move |event: Event| {
let element = event.target().unwrap().dyn_into::<HtmlInputElement>().unwrap();
let filelist = element.files().unwrap();
let file = filelist.get(0).unwrap();
filereader.read_as_text(&file).unwrap();
//log(filelist.length().to_string().as_str());
}) as Box<dyn FnMut(_)>);
fileinput.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())?;
closure.forget();
body.append_child(&fileinput)?;
Ok(())
}
And the HTML:
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<script type="module">
import init from './pkg/without_a_bundler.js';
async function run() {
await init();
}
run();
</script>
</body>
</html>
and Cargo.toml
[package]
name = "without-a-bundler"
version = "0.1.0"
authors = [""]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
js-sys = "0.3.51"
wasm-bindgen = "0.2.74"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Blob',
'BlobEvent',
'Document',
'Element',
'Event',
'File',
'FileList',
'FileReader',
'HtmlElement',
'HtmlInputElement',
'Node',
'ReadableStream',
'Window',
]
However I have no idea how to use get_reader()
of ReadableStream
, because according to the linked documentation, it should return either a ReadableStreamDefaultReader
or a ReadableStreamBYOBReader
. While the latter is experimental and I think it is therefore understandable, that it is not present in web-sys
, I do not know why ReadableStreamDefaultReader
is also not present.