Skip to content

Instantly share code, notes, and snippets.

@rylev
Last active April 13, 2019 16:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rylev/b4991af28b2d1b6bed769b19c1611182 to your computer and use it in GitHub Desktop.
Save rylev/b4991af28b2d1b6bed769b19c1611182 to your computer and use it in GitHub Desktop.
File API Proposal

First we have FileList which is simply a list of Files. This is an opaque struct that offers a way to iterate over Files and get individual files by index:

#[derive(Debug, Clone)]
struct FileList { ... }

impl FileList {
  fn get(index: usize) -> Option<File> { ... }

  fn len(&self) -> usize { ... }

  fn iter(&self) -> FileListIter { ... }
  
  fn as_raw(&self) -> &web_sys::FileList { ... }
}

impl AsRef<web_sys::FileList> for FileList { ... }
impl From<web_sys::FileList> for FileList { ... }
impl Index<usize> for FileList {
    type Output = File;
    fn index(&self, index: usize) -> &Self::Output { ... }
}

There is no FileList::new since creating a raw web_sys::FileList is not possible without going through a web_sys::HtmlInputElement.

Next we have a blob module that includes the trait BlobLike

trait BlobLike {
    fn size(&self) -> u64 { ... }

    // the mime crate and mimes from blobs both conform to rfc6838 
    #[cfg(feature = "mime")]
    fn mime_type(&self) -> Result<mime::Mime, mime::FromStrError> { ... }

    fn raw_mime_type(&self) -> String { ... }

    fn as_raw(&self) -> &web_sys::Blob;
}

There are two structs that implement this trait: Blob and File.

#[derive(Debug, Clone)]
struct Blob { ... }

impl Blob {
  fn new<T>(content: T) -> Blob
    where
        T: std::convert::Into<BlobContents> // We'll look at BlobContents below
    { ... }

  fn new_with_options<T>(content: T, mime_type: Option<String>) -> Blob
    where
        T: std::convert::Into<BlobContents> 
    { ... }

  fn slice(&self, start: usize, end: usize) -> Blob { ... }
}

impl From<web_sys::Blob> for Blob { ... }


impl BlobLike for Blob { ... }

#[derive(Debug, Clone)]
pub struct File { ... }

impl File {
  fn new<T>(
        name: String,
        contents: Option<T>,
        mime_type: Option<String>,
        last_modified_date: Option<u64>,
    ) -> File
    where
        T: std::convert::Into<BlobContents>,
    { ... }

  fn name(&self) -> String { ... }

  fn last_modified_date(&self) -> u64 { ... }

  fn slice(&self, start: usize, end: usize) -> File { ... }
  
  fn as_blob(self) -> Blob { ... }
}

impl BlobLike for File { ... }

Both Blob and File come with builders that allow for creating new Blobs and Files in the builder style and not having to use new directly.

BlobContents is simply a new-type around wasm_bindgen::JsValues that can be used as the content of Blobs and Files:

#[derive(Debug, Clone)]
pub struct BlobContents {
    inner: wasm_bindgen::JsValue,
}

There are there conversions from types into BlobContents only for the types that make sense:

impl std::convert::Into<BlobContents> for &str
impl std::convert::Into<BlobContents> for &[u8] 
impl std::convert::Into<BlobContents> for Blob 
impl std::convert::Into<BlobContents> for js_sys::ArrayBuffer

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.

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 varierty 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.

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.

mod callbacks {
  #[derive(Debug)]
  pub struct FileReader { ... }

  impl FileReader {
    fn new() -> FileReader { ... }

    fn read_as_string<F>(self, blob: &impl BlobLike, callback: F)
        where F: FnOnce(Result<String, FileReadError>) { ... };

    fn read_as_data_url<F>(self, blob: &impl BlobLike, callback: F)
        where F: FnOnce(Result<String, FileReadError>) { ... };

    fn read_as_array_buffer<F>(self, blob: &impl BlobLike, callback: F)
        where F: FnOnce(Result<&web_sys::ArrayBuffer, FileReadError>) { ... };

    fn on_progress<F>(&mut self, callback: F) 
      where F: FnMut(ProgressEvent) + 'static { ... }

    fn on_load_start<F>(&mut self, callback: F) 
      where F: FnOnce(LoadStartEvent) + 'static { ... }
  }
}

mod futures {
  #[derive(Debug)]
  pub struct FileReader { ... }

  impl FileReader {
    fn new() -> FileReader { ... }

    fn read_as_string(self, blob: &impl BlobLike) -> ReadAs<String> { ... }

    fn read_as_data_url(self, blob: &impl BlobLike) -> ReadAs<String> { ... }

    fn read_as_array_buffer(self, blob: &impl BlobLike) -> ReadAs<js_sys::ArrayBuffer>
    
    fn on_progress(&self) -> OnProgressStream { }
    
    fn on_load_start(&self) -> OnLoadStartFuture { }
  }
  
  pub struct ReadAs<T> { ... }
  impl<T> Future for ReadAs<T> {
      type Item = T;
      type Error = FileReadError;
      ... 
  }

  // Make sure that dropping the Future properly aborts the reading
  impl<T> std::ops::Drop for ReadAs<T> {
      fn drop(&mut self) {
          if self.inner.ready_state() < 2 {
              self.inner.abort();
          }
      }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment