Reputation: 2542
How can I pass a File
to be read within the WebAssembly memory context?
Reading a file in the browser with JavaScript is easy:
<input class="file-selector" type="file" id="files" name="files[]" />
I was able to bootstrap WebAssembly code written in Rust with the crate stdweb, add an event listener to the DOM element and fire up a FileReader
:
let reader = FileReader::new();
let file_input_element: InputElement = document().query_selector(".file-selector").unwrap().unwrap().try_into().unwrap();
file_input_element.add_event_listener(enclose!( (reader, file_input_element) move |event: InputEvent| {
// mystery part
}));
In JavaScript, I would get the file from the element and pass it to the reader, however, the API of stdweb needs the following signature:
pub fn read_as_array_buffer<T: IBlob>(&self, blob: &T) -> Result<(), TODO>
I have no idea how to implement IBlob
and I am sure that I am missing something obvious either with the stdweb API or in my understanding of WebAssembly/Rust. I was hoping that there is something less verbose than converting stuff to UTF-8.
Upvotes: 17
Views: 9014
Reputation: 678
The following code is what I use to interact with another javascript library to read a sql file all without using javascript. This is based on the wasm-bindgen library, and I believe may be helpful to newer folks stumbling onto this answer.
[wasm_bindgen]
pub fn load_accounts_from_file_with_balances(file_input : web_sys::HtmlInputElement) {
//Check the file list from the input
let filelist = file_input.files().expect("Failed to get filelist from File Input!");
//Do not allow blank inputs
if filelist.length() < 1 {
alert("Please select at least one file.");
return;
}
if filelist.get(0) == None {
alert("Please select a valid file");
return;
}
let file = filelist.get(0).expect("Failed to get File from filelist!");
let file_reader : web_sys::FileReader = match web_sys::FileReader::new() {
Ok(f) => f,
Err(e) => {
alert("There was an error creating a file reader");
log(&JsValue::as_string(&e).expect("error converting jsvalue to string."));
web_sys::FileReader::new().expect("")
}
};
let fr_c = file_reader.clone();
// create onLoadEnd callback
let onloadend_cb = Closure::wrap(Box::new(move |_e: web_sys::ProgressEvent| {
let array = js_sys::Uint8Array::new(&fr_c.result().unwrap());
let len = array.byte_length() as usize;
log(&format!("Blob received {}bytes: {:?}", len, array.to_vec()));
// here you can for example use the received image/png data
let db : Database = Database::new(array);
//Prepare a statement
let stmt : Statement = db.prepare(&sql_helper_utility::sql_load_accounts_with_balances());
stmt.getAsObject();
// Bind new values
stmt.bind();
while stmt.step() { //
let row = stmt.getAsObject();
log(&("Here is a row: ".to_owned() + &stringify(row).to_owned()));
}
}) as Box<dyn Fn(web_sys::ProgressEvent)>);
file_reader.set_onloadend(Some(onloadend_cb.as_ref().unchecked_ref()));
file_reader.read_as_array_buffer(&file).expect("blob not readable");
onloadend_cb.forget();
}
Upvotes: 4
Reputation: 2542
It works when the FileReader
itself is passed from JavaScript to WebAssembly. It also seems like a clean approach because the data has to be read by the JavaScript API anyway - no need to call JS from WASM.
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Read to wasm</title>
</head>
<body>
<input type="file" id="file-input"/>
<script src="reader.js"></script>
<script>
var fileReader = new FileReader();
fileReader.onloadend = e => Rust.reader
.then(reader=> {
window.alert(reader.print_result(fileReader));
});
var fileInputElement = document.getElementById("file-input");
fileInputElement.addEventListener("change", e => fileReader.readAsText(fileInputElement.files[0]));
</script>
</body>
</html>
main.rs
#![feature(proc_macro)]
#[macro_use]
extern crate stdweb;
use stdweb::js_export;
use stdweb::web::FileReader;
use stdweb::web::FileReaderResult;
#[js_export]
fn print_result(file_reader: FileReader) -> String {
match file_reader.result() {
Some(value) => match value {
FileReaderResult::String(value) => value,
_ => String::from("not a text"),
}
None => String::from("empty")
}
}
fn main() {
stdweb::initialize();
stdweb::event_loop();
}
Upvotes: 11
Reputation: 7820
I managed to access the file object and pass it to the FileReader
in the following way:
let reader = FileReader::new();
let file_input_element: InputElement = document()
.query_selector(".file-selector")
.unwrap()
.unwrap()
.try_into()
.unwrap();
file_input_element.add_event_listener(
enclose!( (reader, file_input_element) move |event: InputEvent| {
let file = js!{return @{&file_input_element}.files[0]};
let real_file: stdweb::web::Blob = file.try_into().unwrap();
reader.read_as_text(&real_file);
}
This code compiles. However, the data never gets available via reader.result()
.
Upvotes: 1