ferroui_browser/storage/
blob_readable_stream.rs1use crate::interop::{stream_helper, JsObject};
2use std::io::{self, Read, Seek, SeekFrom};
3
4pub struct BlobReadableStream {
12 js_reference: Option<JsObject>,
13 position: u64,
14 length: u64,
15 content: Option<Vec<u8>>,
16}
17
18fn disposed() -> io::Error {
19 io::Error::other("Cannot access a disposed object. Object name: 'BlobReadableStream'.")
20}
21
22fn end_of_stream() -> io::Error {
24 io::Error::new(io::ErrorKind::UnexpectedEof, "Failed to read the requested number of bytes from the stream.")
25}
26
27impl BlobReadableStream {
28 pub fn new(js_stream_reference: JsObject) -> Self {
30 let length = stream_helper::byte_length(&js_stream_reference);
31 Self::with_length(js_stream_reference, length)
32 }
33
34 fn with_length(js_stream_reference: JsObject, length: u64) -> Self {
35 Self { js_reference: Some(js_stream_reference), position: 0, length, content: None }
36 }
37
38 fn js_reference(&self) -> io::Result<&JsObject> {
39 self.js_reference.as_ref().ok_or_else(disposed)
40 }
41
42 pub fn can_read(&self) -> bool {
43 true
44 }
45
46 pub fn can_seek(&self) -> bool {
47 false
48 }
49
50 pub fn can_write(&self) -> bool {
51 false
52 }
53
54 pub fn length(&self) -> u64 {
56 self.length
57 }
58
59 pub fn position(&self) -> u64 {
60 self.position
61 }
62
63 pub async fn read_async(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
66 let num_bytes_to_read = (buffer.len() as u64).min(self.length.saturating_sub(self.position)) as usize;
67 let js_reference = self.js_reference()?.clone();
68 let bytes_read = stream_helper::slice_async(&js_reference, self.position, num_bytes_to_read)
69 .await
70 .map_err(io::Error::from)?;
71 self.complete_read(&bytes_read, num_bytes_to_read, buffer)
72 }
73
74 fn complete_read(&mut self, bytes_read: &[u8], num_bytes_to_read: usize, buffer: &mut [u8]) -> io::Result<usize> {
75 if bytes_read.len() != num_bytes_to_read {
76 return Err(end_of_stream());
77 }
78
79 self.position += bytes_read.len() as u64;
80 buffer[..bytes_read.len()].copy_from_slice(bytes_read);
81
82 Ok(bytes_read.len())
83 }
84
85 pub async fn load_async(&mut self) -> io::Result<()> {
89 if self.content.is_some() {
90 return Ok(());
91 }
92 let js_reference = self.js_reference()?.clone();
93 let content = stream_helper::slice_async(&js_reference, 0, self.length as usize).await?;
94 self.set_content(content)
95 }
96
97 fn set_content(&mut self, content: Vec<u8>) -> io::Result<()> {
98 if content.len() as u64 != self.length {
99 return Err(end_of_stream());
100 }
101 self.content = Some(content);
102 Ok(())
103 }
104
105 pub fn dispose(&mut self) {
107 self.js_reference = None;
108 self.content = None;
109 }
110}
111
112impl Read for BlobReadableStream {
113 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
114 self.js_reference()?;
115 let Some(content) = &self.content else {
116 return Err(io::Error::other("Browser supports only ReadAsync"));
117 };
118 let start = self.position.min(content.len() as u64) as usize;
119 let count = buffer.len().min(content.len() - start);
120 buffer[..count].copy_from_slice(&content[start..start + count]);
121 self.position += count as u64;
122 Ok(count)
123 }
124}
125
126impl Seek for BlobReadableStream {
127 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
128 let position = match position {
129 SeekFrom::Current(offset) => self.position as i64 + offset,
130 SeekFrom::End(offset) => self.length as i64 + offset,
131 SeekFrom::Start(offset) => offset as i64,
132 };
133 if position < 0 {
134 return Err(io::Error::new(
135 io::ErrorKind::InvalidInput,
136 "An attempt was made to move the position before the beginning of the stream.",
137 ));
138 }
139 self.position = position as u64;
140 Ok(self.position)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
149
150 fn stream(content: Option<&[u8]>, length: u64) -> BlobReadableStream {
151 let mut stream = BlobReadableStream::with_length(JsObject::NULL, length);
152 if let Some(content) = content {
153 stream.set_content(content.to_vec()).unwrap();
154 }
155 stream
156 }
157
158 #[test]
159 fn read_before_load_fails_as_upstream_read_does() {
160 let mut stream = stream(None, 4);
161 let error = stream.read(&mut [0; 4]).unwrap_err();
162 assert_eq!(error.to_string(), "Browser supports only ReadAsync");
163 }
164
165 #[test]
166 fn read_after_load_returns_the_content_and_advances() {
167 let mut stream = stream(Some(b"hello"), 5);
168 let mut buffer = [0u8; 3];
169 assert_eq!(stream.read(&mut buffer).unwrap(), 3);
170 assert_eq!(&buffer, b"hel");
171 assert_eq!(stream.position(), 3);
172 let mut rest = Vec::new();
173 stream.read_to_end(&mut rest).unwrap();
174 assert_eq!(rest, b"lo");
175 assert_eq!(stream.read(&mut buffer).unwrap(), 0);
176 }
177
178 #[test]
179 fn seek_moves_relative_to_each_origin() {
180 let mut stream = stream(Some(b"0123456789"), 10);
181 assert_eq!(stream.seek(SeekFrom::Start(4)).unwrap(), 4);
182 assert_eq!(stream.seek(SeekFrom::Current(2)).unwrap(), 6);
183 assert_eq!(stream.seek(SeekFrom::End(-1)).unwrap(), 9);
184 let mut buffer = [0u8; 4];
185 assert_eq!(stream.read(&mut buffer).unwrap(), 1);
186 assert_eq!(buffer[0], b'9');
187 assert!(stream.seek(SeekFrom::Current(-20)).is_err());
188 }
189
190 #[test]
191 fn short_slice_is_an_end_of_stream_error() {
192 let mut stream = stream(None, 10);
193 let mut buffer = [0u8; 4];
194 let error = stream.complete_read(b"ab", 4, &mut buffer).unwrap_err();
195 assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
196 assert_eq!(stream.complete_read(b"abcd", 4, &mut buffer).unwrap(), 4);
197 assert_eq!(stream.position(), 4);
198 }
199
200 #[test]
201 fn content_shorter_than_the_blob_is_an_end_of_stream_error() {
202 let mut stream = stream(None, 10);
203 let error = stream.set_content(b"abc".to_vec()).unwrap_err();
204 assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
205 assert_eq!(stream.length(), 10);
206 assert!(stream.read(&mut [0; 1]).is_err(), "nothing was loaded");
207 }
208
209 #[test]
210 fn disposed_stream_fails_to_read() {
211 let mut stream = stream(Some(b"abc"), 3);
212 stream.dispose();
213 assert!(stream.read(&mut [0; 1]).is_err());
214 }
215}