rust 如何在一个大的输入文件上进行重写?

1yjd4xko  于 2023-10-20  发布在  其他
关注(0)|答案(5)|浏览(118)

我试图通过输入字段上传的文件内容访问迭代器。
我可以通过web-sys将JS文件传递到Wasm中,但是我无法在Rust中找到除了长度和名称之外的其他内容。
我想我可以把整个文件作为一个ByteArray传递到Wasm中,并在上面进行复制,但最好是直接在文件内容上进行复制,而不进行复制,因为文件本身会很大(~1 GB)。
我在Mozilla JS文档中发现,我应该能够访问底层文件blob,通过.stream()方法从中获取ReadableStream,并从应该能够迭代的地方获取Reader。但是在web-sys中,ReadableStream的.getReader()方法返回一个简单的JSValue,我不能用它做任何有用的事情。
我在这里错过了什么,或者这个功能只是在web-sys中缺少,或者有其他方法可以做到这一点?也许在JS中创建Iterator并将其传递给Rust?

3pvhb19x

3pvhb19x1#

我设法得到了一个使用read_as_binary_string的工作示例。
这里的代码
lib.rs

use js_sys::JsString;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{console, Event, FileReader, HtmlInputElement};

#[wasm_bindgen(start)]
pub fn main_wasm() {
    let my_file: Rc<RefCell<Vec<u8>>> = Rc::new(RefCell::new(Vec::new()));
    set_file_reader(&my_file);
}

fn set_file_reader(file: &Rc<RefCell<Vec<u8>>>) {
    let filereader = FileReader::new().unwrap().dyn_into::<FileReader>().unwrap();
    let my_file = Rc::clone(&file);

    let onload = Closure::wrap(Box::new(move |event: Event| {
        let element = event.target().unwrap().dyn_into::<FileReader>().unwrap();
        let data = element.result().unwrap();
        let file_string: JsString = data.dyn_into::<JsString>().unwrap();
        let file_vec: Vec<u8> = file_string.iter().map(|x| x as u8).collect();
        *my_file.borrow_mut() = file_vec;
        console::log_1(&format!("file loaded: {:?}", file_string).into());
    }) as Box<dyn FnMut(_)>);

    filereader.set_onloadend(Some(onload.as_ref().unchecked_ref()));
    onload.forget();

    let fileinput: HtmlInputElement = web_sys::window()
        .unwrap()
        .document()
        .expect("should have a document.")
        .create_element("input")
        .unwrap()
        .dyn_into::<HtmlInputElement>()
        .unwrap();

    fileinput.set_id("file-upload");
    fileinput.set_type("file");

    web_sys::window()
        .unwrap()
        .document()
        .unwrap()
        .body()
        .expect("document should have a body")
        .append_child(&fileinput)
        .unwrap();

    let callback = 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).expect("should have a file handle.");
        filereader.read_as_binary_string(&_file).unwrap();
    }) as Box<dyn FnMut(_)>);

    fileinput
        .add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
        .unwrap();
    callback.forget();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <noscript
      >This page contains webassembly and javascript content, please enable
      javascript in your browser.</noscript
    >
    <script src="./stack.js"></script>
    <script>
      wasm_bindgen("./stack_bg.wasm");
    </script>
  </body>
</html>

Cargo.toml

[package]
name = "stack"
version = "0.1.0"
authors = [""]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
js-sys = "0.3.55"

wee_alloc = { version = "0.4.2", optional = true }

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Window',
  'console',
  'Event',
  'FileReader',
  'File',
  'FileList',
  'HtmlInputElement']

[dev-dependencies]
wasm-bindgen-test = "0.2"

[dependencies.wasm-bindgen]
version = "0.2.70"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
debug = false

你可以在这里查看示例:http://rustwasmfileinput.glitch.me/

mi7gmzs6

mi7gmzs62#

你最好的选择是使用wasm_streams crate,它将从.stream()方法获得的ReadableStream等Web流API连接到Rust流API。
官方示例使用Fetch API作为源代码,但这部分也与您的File用例相关:https://github.com/MattiasBuelens/wasm-streams/blob/f6dacf58a8826dc67923ab4a3bae87635690ca64/examples/fetch_as_stream.rs#L25-L33

let body = ReadableStream::from_raw(raw_body.dyn_into().unwrap_throw());

// Convert the JS ReadableStream to a Rust stream
let mut stream = body.into_stream();

// Consume the stream, logging each individual chunk
while let Some(Ok(chunk)) = stream.next().await {
    console::log_1(&chunk);
}
hpcdzsge

hpcdzsge3#

我想你可以用FileReader做类似的事情。
下面是一个例子,我记录了一个文件的文本内容:

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(())
}

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>

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',
]

但是我不知道如何使用ReadableStreamget_reader(),因为根据链接的documentation,它应该返回ReadableStreamDefaultReaderReadableStreamBYOBReader。虽然后者是实验性的,我认为这是可以理解的,它不存在于web-sys中,我不知道为什么ReadableStreamDefaultReader也不存在。

k4ymrczo

k4ymrczo4#

你应该使用ReadableStreamDefaultReader::new()。

let stream: ReadableStream = response.body().unwrap();
let reader = ReadableStreamDefaultReader::new(&stream)?;

然后你可以像使用in JS一样使用ReadableStreamDefaultReader.read()
你还需要struct来实现结构化:

#[derive(serde::Serialize, serde::Deserialize)]
struct ReadableStreamDefaultReadResult<T> {
    pub value: T,
    pub done: bool,
}

以下是使用示例:

loop {
    let reader_promise = JsFuture::from(reader.read());
    let result = reader_promise.await?;

    let result: ReadableStreamDefaultReadResult<Option<Vec<u8>>> =
        serde_wasm_bindgen::from_value(result).unwrap();

    if result.done {
        break;
    }

    // here you can read chunk of bytes from `result.value`
}
pw136qt2

pw136qt25#

我将在这里分享我昨天遇到的repository,它正好说明了这一点。
实际上,它读取文件的第一个字节。考虑到结果是即时的,它显然没有将整个文件加载到内存中,而是从文件句柄中阅读。
此外,您还可以使用FileSystemSyncAccessHandle,它提供了一个细粒度的read()方法,包括偏移量。

相关问题