use crate::Sealed;
use std::{
ops::Deref,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use wasm_bindgen::{prelude::*, throw_str, JsCast};
pub trait BlobContents: Sealed {
unsafe fn into_jsvalue(self) -> JsValue;
}
impl Sealed for &str {}
impl BlobContents for &str {
unsafe fn into_jsvalue(self) -> JsValue {
self.as_bytes().into_jsvalue()
}
}
impl Sealed for &[u8] {}
impl BlobContents for &[u8] {
unsafe fn into_jsvalue(self) -> JsValue {
js_sys::Uint8Array::view(self).into()
}
}
impl Sealed for js_sys::ArrayBuffer {}
impl BlobContents for js_sys::ArrayBuffer {
unsafe fn into_jsvalue(self) -> JsValue {
self.into()
}
}
impl Sealed for js_sys::JsString {}
impl BlobContents for js_sys::JsString {
unsafe fn into_jsvalue(self) -> JsValue {
self.into()
}
}
impl Sealed for Blob {}
impl BlobContents for Blob {
unsafe fn into_jsvalue(self) -> JsValue {
self.into()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Blob {
inner: web_sys::Blob,
}
impl Blob {
pub fn new<T>(content: T) -> Blob
where
T: BlobContents,
{
Blob::new_with_options(content, None)
}
pub fn new_with_options<T>(content: T, mime_type: Option<&str>) -> Blob
where
T: BlobContents,
{
let properties = web_sys::BlobPropertyBag::new();
if let Some(mime_type) = mime_type {
properties.set_type(mime_type);
}
let parts = js_sys::Array::of1(&unsafe { content.into_jsvalue() });
let inner = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &properties);
Blob::from(inner.unwrap_throw())
}
pub fn slice(&self, start: u64, end: u64) -> Self {
let start = safe_u64_to_f64(start);
let end = safe_u64_to_f64(end);
let b: &web_sys::Blob = self.as_ref();
Blob::from(b.slice_with_f64_and_f64(start, end).unwrap_throw())
}
pub fn size(&self) -> u64 {
safe_f64_to_u64(self.inner.size())
}
#[cfg(feature = "mime")]
pub fn mime_type(&self) -> Result<mime::Mime, mime::FromStrError> {
self.raw_mime_type().parse()
}
pub fn raw_mime_type(&self) -> String {
self.inner.type_()
}
}
impl From<web_sys::Blob> for Blob {
fn from(blob: web_sys::Blob) -> Self {
Blob { inner: blob }
}
}
impl From<web_sys::File> for Blob {
fn from(file: web_sys::File) -> Self {
Blob { inner: file.into() }
}
}
impl From<Blob> for web_sys::Blob {
fn from(blob: Blob) -> Self {
blob.inner
}
}
impl From<Blob> for JsValue {
fn from(blob: Blob) -> Self {
blob.inner.into()
}
}
impl AsRef<web_sys::Blob> for Blob {
fn as_ref(&self) -> &web_sys::Blob {
self.inner.as_ref()
}
}
impl AsRef<JsValue> for Blob {
fn as_ref(&self) -> &JsValue {
self.inner.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct File {
inner: Blob,
}
impl File {
pub fn new<T>(name: &str, contents: T) -> File
where
T: BlobContents,
{
Self::new_with_options(name, contents, None, None)
}
pub fn new_with_options<T>(
name: &str,
contents: T,
mime_type: Option<&str>,
last_modified_time: Option<SystemTime>,
) -> File
where
T: BlobContents,
{
let options = web_sys::FilePropertyBag::new();
if let Some(mime_type) = mime_type {
options.set_type(mime_type);
}
if let Some(last_modified_time) = last_modified_time {
let duration = match last_modified_time.duration_since(UNIX_EPOCH) {
Ok(duration) => safe_u128_to_f64(duration.as_millis()),
Err(time_err) => -safe_u128_to_f64(time_err.duration().as_millis()),
};
options.set_last_modified(duration);
}
let parts = js_sys::Array::of1(&unsafe { contents.into_jsvalue() });
let inner = web_sys::File::new_with_u8_array_sequence_and_options(&parts, name, &options)
.unwrap_throw();
File::from(inner)
}
pub fn name(&self) -> String {
let f: &web_sys::File = self.as_ref();
f.name()
}
pub fn last_modified_time(&self) -> SystemTime {
let f: &web_sys::File = self.as_ref();
match f.last_modified() {
pos if pos >= 0.0 => UNIX_EPOCH + Duration::from_millis(safe_f64_to_u64(pos)),
neg => UNIX_EPOCH - Duration::from_millis(safe_f64_to_u64(-neg)),
}
}
pub fn slice(&self, start: u64, end: u64) -> Self {
let blob = self.deref().slice(start, end);
let raw_mime_type = self.raw_mime_type();
let mime_type = if raw_mime_type.is_empty() {
None
} else {
Some(raw_mime_type)
};
File::new_with_options(
&self.name(),
blob,
mime_type.as_deref(),
Some(self.last_modified_time()),
)
}
}
impl From<web_sys::File> for File {
fn from(file: web_sys::File) -> Self {
File {
inner: Blob::from(web_sys::Blob::from(file)),
}
}
}
impl Deref for File {
type Target = Blob;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl AsRef<web_sys::File> for File {
fn as_ref(&self) -> &web_sys::File {
<Blob as AsRef<web_sys::Blob>>::as_ref(&self.inner).unchecked_ref()
}
}
impl AsRef<web_sys::Blob> for File {
fn as_ref(&self) -> &web_sys::Blob {
self.inner.as_ref()
}
}
impl From<File> for Blob {
fn from(file: File) -> Self {
file.inner
}
}
fn safe_u64_to_f64(number: u64) -> f64 {
if number > (js_sys::Number::MAX_SAFE_INTEGER as u64) {
throw_str("a rust number was too large and could not be represented in JavaScript");
}
number as f64
}
fn safe_u128_to_f64(number: u128) -> f64 {
const MAX_SAFE_INTEGER: u128 = js_sys::Number::MAX_SAFE_INTEGER as u128; if number > MAX_SAFE_INTEGER {
throw_str("a rust number was too large and could not be represented in JavaScript");
}
number as f64
}
fn safe_f64_to_u64(number: f64) -> u64 {
if number > js_sys::Number::MAX_SAFE_INTEGER {
throw_str("a rust number was too large and could not be represented in JavaScript");
}
if number.fract() != 0.0 {
throw_str(
"a number could not be converted to an integer because it was not a whole number",
);
}
number as u64
}