mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add read_file_as_chunks method to ReaderService
To read large files by small pieces.
This commit is contained in:
parent
797a43bb12
commit
c63636b22a
@ -11,7 +11,8 @@
|
||||
- Added handling of files of `input` element. There is new `ChangeData::Files` variant
|
||||
of `onchange` handler.
|
||||
|
||||
- Added `ReaderService` to read data from `File` instances.
|
||||
- Added `ReaderService` to read data from `File` instances. It supports two methods: `read_file`
|
||||
to read an entire file at a time and `read_file_by_chunks` to read a file by small pieces of data.
|
||||
|
||||
- New example `file_upload` that prints sizes of uploaded files.
|
||||
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
use yew::{html, ChangeData, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
|
||||
use yew::services::reader::{File, FileChunk, FileData, ReaderService, ReaderTask};
|
||||
|
||||
pub struct Model {
|
||||
link: ComponentLink<Model>,
|
||||
reader: ReaderService,
|
||||
tasks: Vec<ReaderTask>,
|
||||
files: Vec<FileData>,
|
||||
files: Vec<String>,
|
||||
by_chunks: bool,
|
||||
}
|
||||
|
||||
type Chunks = bool;
|
||||
|
||||
pub enum Msg {
|
||||
Loaded(FileData),
|
||||
Files(Vec<File>),
|
||||
Chunk(FileChunk),
|
||||
Files(Vec<File>, Chunks),
|
||||
ToggleByChunks,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
@ -23,21 +28,37 @@ impl Component for Model {
|
||||
link,
|
||||
tasks: vec![],
|
||||
files: vec![],
|
||||
by_chunks: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Loaded(file) => {
|
||||
self.files.push(file);
|
||||
let info = format!("file: {:?}", file);
|
||||
self.files.push(info);
|
||||
}
|
||||
Msg::Files(files) => {
|
||||
Msg::Chunk(chunk) => {
|
||||
let info = format!("chunk: {:?}", chunk);
|
||||
self.files.push(info);
|
||||
}
|
||||
Msg::Files(files, chunks) => {
|
||||
for file in files.into_iter() {
|
||||
let callback = self.link.send_back(Msg::Loaded);
|
||||
let task = self.reader.read_file(file, callback);
|
||||
let task = {
|
||||
if chunks {
|
||||
let callback = self.link.send_back(Msg::Chunk);
|
||||
self.reader.read_file_by_chunks(file, callback, 10)
|
||||
} else {
|
||||
let callback = self.link.send_back(Msg::Loaded);
|
||||
self.reader.read_file(file, callback)
|
||||
}
|
||||
};
|
||||
self.tasks.push(task);
|
||||
}
|
||||
}
|
||||
Msg::ToggleByChunks => {
|
||||
self.by_chunks = !self.by_chunks;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@ -45,15 +66,22 @@ impl Component for Model {
|
||||
|
||||
impl Renderable<Model> for Model {
|
||||
fn view(&self) -> Html<Self> {
|
||||
let flag = self.by_chunks;
|
||||
html! {
|
||||
<div>
|
||||
<input type="file", multiple=true, onchange=|value| {
|
||||
let mut result = Vec::new();
|
||||
if let ChangeData::Files(files) = value {
|
||||
result.extend(files);
|
||||
}
|
||||
Msg::Files(result)
|
||||
},/>
|
||||
<div>
|
||||
<input type="file", multiple=true, onchange=|value| {
|
||||
let mut result = Vec::new();
|
||||
if let ChangeData::Files(files) = value {
|
||||
result.extend(files);
|
||||
}
|
||||
Msg::Files(result, flag)
|
||||
},/>
|
||||
</div>
|
||||
<div>
|
||||
<label>{ "By chunks" }</label>
|
||||
<input type="checkbox", checked=flag, onclick=|_| Msg::ToggleByChunks, />
|
||||
</div>
|
||||
<ul>
|
||||
{ for self.files.iter().map(|f| self.view_file(f)) }
|
||||
</ul>
|
||||
@ -63,9 +91,9 @@ impl Renderable<Model> for Model {
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_file(&self, file: &FileData) -> Html<Self> {
|
||||
fn view_file(&self, data: &str) -> Html<Self> {
|
||||
html! {
|
||||
<li>{ format!("file: {}, size: {}", file.name, file.content.len()) }</li>
|
||||
<li>{ data }</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! Service to load files using `FileReader`.
|
||||
|
||||
pub use stdweb::web::File;
|
||||
use std::cmp;
|
||||
pub use stdweb::web::{Blob, IBlob, File};
|
||||
use stdweb::web::{
|
||||
IEventTarget,
|
||||
FileReader,
|
||||
@ -11,10 +12,11 @@ use stdweb::web::{
|
||||
use stdweb::web::event::{
|
||||
LoadEndEvent,
|
||||
};
|
||||
use stdweb::unstable::TryInto;
|
||||
use callback::Callback;
|
||||
use super::Task;
|
||||
|
||||
/// Struct that represents data of file.
|
||||
/// Struct that represents data of a file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileData {
|
||||
/// Name of loaded file.
|
||||
@ -23,6 +25,25 @@ pub struct FileData {
|
||||
pub content: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Struct that represents a chunk of a file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FileChunk {
|
||||
/// Reading of chunks started. Equals **0%** progress.
|
||||
Started {
|
||||
/// Name of loaded file.
|
||||
name: String,
|
||||
},
|
||||
/// The next data chunk that read. Also provides a progress value.
|
||||
DataChunk {
|
||||
/// The chunk of binary data.
|
||||
data: Vec<u8>,
|
||||
/// The progress value in interval: `0 < progress <= 1`.
|
||||
progress: f32,
|
||||
},
|
||||
/// Reading of chunks finished. Equals **100%** progress.
|
||||
Finished,
|
||||
}
|
||||
|
||||
/// A reader service attached to a user context.
|
||||
#[derive(Default)]
|
||||
pub struct ReaderService {}
|
||||
@ -33,12 +54,12 @@ impl ReaderService {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Reads all bytes from files and returns them with a callback.
|
||||
/// Reads all bytes from a file and returns them with a callback.
|
||||
pub fn read_file(&mut self, file: File, callback: Callback<FileData>) -> ReaderTask {
|
||||
let file_reader = FileReader::new();
|
||||
let reader = file_reader.clone();
|
||||
let name = file.name();
|
||||
file_reader.add_event_listener(move |event: LoadEndEvent| {
|
||||
file_reader.add_event_listener(move |_event: LoadEndEvent| {
|
||||
match reader.result() {
|
||||
Some(FileReaderResult::String(_)) => {
|
||||
unreachable!();
|
||||
@ -59,6 +80,60 @@ impl ReaderService {
|
||||
file_reader,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads data chunks from a file and returns them with a callback.
|
||||
pub fn read_file_by_chunks(&mut self, file: File, callback: Callback<FileChunk>, chunk_size: usize) -> ReaderTask {
|
||||
let file_reader = FileReader::new();
|
||||
let name = file.name();
|
||||
let mut position = 0;
|
||||
let total_size = file.len() as usize;
|
||||
let reader = file_reader.clone();
|
||||
file_reader.add_event_listener(move |_event: LoadEndEvent| {
|
||||
match reader.result() {
|
||||
// This branch is used to start reading
|
||||
Some(FileReaderResult::String(_)) => {
|
||||
let started = FileChunk::Started {
|
||||
name: name.clone(),
|
||||
};
|
||||
callback.emit(started);
|
||||
}
|
||||
// This branch is used to send a chunk value
|
||||
Some(FileReaderResult::ArrayBuffer(buffer)) => {
|
||||
let array: TypedArray<u8> = buffer.into();
|
||||
let chunk = FileChunk::DataChunk {
|
||||
data: array.to_vec(),
|
||||
progress: position as f32 / total_size as f32,
|
||||
};
|
||||
callback.emit(chunk);
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
// Read the next chunk
|
||||
if position < total_size {
|
||||
let file = &file;
|
||||
let from = position;
|
||||
let to = cmp::min(position + chunk_size, total_size);
|
||||
position = to;
|
||||
// TODO Implement `slice` method in `stdweb`
|
||||
let blob: Blob = (js! {
|
||||
return @{file}.slice(@{from as u32}, @{to as u32});
|
||||
})
|
||||
.try_into()
|
||||
.unwrap();
|
||||
reader.read_as_array_buffer(&blob).unwrap();
|
||||
} else {
|
||||
let finished = FileChunk::Finished;
|
||||
callback.emit(finished);
|
||||
}
|
||||
});
|
||||
let blob: Blob = (js! {
|
||||
return (new Blob());
|
||||
}).try_into().unwrap();
|
||||
file_reader.read_as_text(&blob).unwrap();
|
||||
ReaderTask {
|
||||
file_reader,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to control reading.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user