|
| 1 | +# Mid-Level File Api |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +This is a mid-level API wrapper around the various File related JavaScript apis. The possibility for a higher level API is left open. |
| 6 | + |
| 7 | +The mid-level file API aims to implement File I/O on top of the raw JS apis found here: https://w3c.github.io/FileAPI/ which includes listing files, creating files, and reading files. |
| 8 | + |
| 9 | +## The API |
| 10 | + |
| 11 | +First we have a way to turn a `web_sys::FileList` into a vector of `File`s (we'll look a the `File` struct below): |
| 12 | + |
| 13 | +```rust |
| 14 | +impl From<web_sys::FileList> for Vec<File> { ... } |
| 15 | +``` |
| 16 | + |
| 17 | +Next we the trait `BlobLike` |
| 18 | +```rust |
| 19 | +trait BlobLike { |
| 20 | + fn size(&self) -> u64 { ... } |
| 21 | + |
| 22 | + // the mime crate and mimes from blobs both conform to rfc6838 |
| 23 | + #[cfg(feature = "mime")] |
| 24 | + fn mime_type(&self) -> Result<mime::Mime, mime::FromStrError> { ... } |
| 25 | + |
| 26 | + fn raw_mime_type(&self) -> String { ... } |
| 27 | + |
| 28 | + fn as_raw(&self) -> &web_sys::Blob; |
| 29 | + |
| 30 | + fn slice(&self, start: u64, end: u64) -> Self |
| 31 | +} |
| 32 | +``` |
| 33 | +There are two structs that implement this trait: `Blob` and `File`. |
| 34 | + |
| 35 | +```rust |
| 36 | +#[derive(Debug, Clone)] |
| 37 | +struct Blob { ... } |
| 38 | + |
| 39 | +impl Blob { |
| 40 | + fn new<T>(contents: T) -> Blob |
| 41 | + where |
| 42 | + T: std::convert::Into<BlobContents> // We'll look at BlobContents below |
| 43 | + { ... } |
| 44 | + |
| 45 | + fn new_with_options<T>(contents: T, mime_type: String) -> Blob |
| 46 | + where |
| 47 | + T: std::convert::Into<BlobContents> |
| 48 | + { ... } |
| 49 | +} |
| 50 | + |
| 51 | +impl From<web_sys::Blob> for Blob { ... } |
| 52 | + |
| 53 | +impl BlobLike for Blob { ... } |
| 54 | + |
| 55 | +#[derive(Debug, Clone)] |
| 56 | +pub struct File { ... } |
| 57 | + |
| 58 | +impl File { |
| 59 | + fn new<T>( |
| 60 | + name: String, |
| 61 | + contents: T, |
| 62 | + ) -> File |
| 63 | + where |
| 64 | + T: std::convert::Into<BlobContents>, |
| 65 | + { ... } |
| 66 | + |
| 67 | + fn new_with_options<T>( |
| 68 | + name: String, |
| 69 | + contents: T, |
| 70 | + mime_type: Option<String>, |
| 71 | + last_modified_date: Option<u64>, |
| 72 | + ) -> File |
| 73 | + where |
| 74 | + T: std::convert::Into<BlobContents>, |
| 75 | + { ... } |
| 76 | + |
| 77 | + fn name(&self) -> String { ... } |
| 78 | + |
| 79 | + fn last_modified_since_epoch(&self) -> Duration { ... } |
| 80 | + |
| 81 | + fn to_blob(&self) -> Blob { ... } |
| 82 | +} |
| 83 | + |
| 84 | +impl BlobLike for File { ... } |
| 85 | +``` |
| 86 | + |
| 87 | +`BlobContents` is simply a new-type around `wasm_bindgen::JsValue`s that can be used as the content of `Blob`s and `File`s: |
| 88 | + |
| 89 | +```rust |
| 90 | +#[derive(Debug, Clone)] |
| 91 | +pub struct BlobContents { |
| 92 | + inner: wasm_bindgen::JsValue, |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +There are there conversions from types into `BlobContents` only for the types that make sense: |
| 97 | + |
| 98 | +```rust |
| 99 | +impl std::convert::Into<BlobContents> for &str |
| 100 | +impl std::convert::Into<BlobContents> for &[u8] |
| 101 | +impl std::convert::Into<BlobContents> for Blob |
| 102 | +impl std::convert::Into<BlobContents> for js_sys::ArrayBuffer |
| 103 | +``` |
| 104 | + |
| 105 | +Lastly there's the `FileReader` which allows reading from BlobLike objects. We'll have two implementations of this, one based on callbacks and the other based on futures. |
| 106 | + |
| 107 | +The callbacks implementation has three categories of callbacks: start, progress and read. Start and progress callbacks are directly related to the `onloadstart` and `onprogress` callbacks on `web_sys::FileReader`. The read variety of callbacks, are a combination of `onload`, `onloadend`, `onloaderror`, and `onabort`. The callback receives a result which is an error if the underlying read was aborted or errored. |
| 108 | + |
| 109 | +The futures implementation likewise exposes success, error, and abort through the fact that futures are `Result`-like. Progress events are exposed as a stream. In the future, we may expose the entire lifecycle of a read through a stream. |
| 110 | + |
| 111 | +```rust |
| 112 | +mod callbacks { |
| 113 | + #[derive(Debug)] |
| 114 | + pub struct FileReader { ... } |
| 115 | + |
| 116 | + impl FileReader { |
| 117 | + fn new() -> FileReader { ... } |
| 118 | + |
| 119 | + fn read_to_string<F>(self, blob: &impl BlobLike, callback: F) |
| 120 | + where F: FnOnce(Result<String, FileReadError>) { ... }; |
| 121 | + |
| 122 | + fn read_to_data_url<F>(self, blob: &impl BlobLike, callback: F) |
| 123 | + where F: FnOnce(Result<String, FileReadError>) { ... }; |
| 124 | + |
| 125 | + fn read_to_array_buffer<F>(self, blob: &impl BlobLike, callback: F) |
| 126 | + where F: FnOnce(Result<&web_sys::ArrayBuffer, FileReadError>) { ... }; |
| 127 | + |
| 128 | + fn on_progress<F>(&mut self, callback: F) |
| 129 | + where F: FnMut(ProgressEvent) + 'static { ... } |
| 130 | + |
| 131 | + fn on_load_start<F>(&mut self, callback: F) |
| 132 | + where F: FnOnce(LoadStartEvent) + 'static { ... } |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +mod futures { |
| 137 | + #[derive(Debug)] |
| 138 | + pub struct FileReader { ... } |
| 139 | + |
| 140 | + impl FileReader { |
| 141 | + fn new() -> FileReader { ... } |
| 142 | + |
| 143 | + fn read_to_string(self, blob: &impl BlobLike) -> ReadAsString { ... } |
| 144 | + |
| 145 | + fn read_to_data_url(self, blob: &impl BlobLike) -> ReadAsDataUrl { ... } |
| 146 | + |
| 147 | + fn read_to_array_buffer(self, blob: &impl BlobLike) -> ReadAsArrayBuffer { ... } |
| 148 | + |
| 149 | + fn on_progress(&self) -> OnProgressStream { } |
| 150 | + |
| 151 | + fn on_load_start(&self) -> OnLoadStartFuture { } |
| 152 | + } |
| 153 | + |
| 154 | + pub struct ReadAsString { ... } |
| 155 | + impl Future for ReadAsString { |
| 156 | + type Item = String; |
| 157 | + type Error = FileReadError; |
| 158 | + ... |
| 159 | + } |
| 160 | + |
| 161 | + // Make sure that dropping the Future properly aborts the reading |
| 162 | + impl std::ops::Drop for ReadAsString { |
| 163 | + fn drop(&mut self) { |
| 164 | + if self.inner.ready_state() < 2 { |
| 165 | + self.inner.abort(); |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | +} |
| 170 | +``` |
0 commit comments