#[cfg(target_arch = "wasm32")]
extern "C" {
fn readUserFileRange(user_file_id: u32, buf_ptr: u64, buf_len: u64, file_offset: u64) -> u64;
fn readUrlSync(url_ptr: usize, url_len: usize, buf_ptr_out: *mut u32, buf_len_out: *mut u32) -> u32;
}
enum UniversalFileInner {
FullyLoaded { data: std::sync::Arc<Vec<u8>>, pos: u64 },
#[cfg(any(doc, not(target_arch = "wasm32")))]
LocalFile { path: String, file: Option<std::fs::File> },
#[cfg(any(doc, target_arch = "wasm32"))]
WasmFile { id: usize, size: u64, pos: u64 },
}
pub struct UniversalFile(UniversalFileInner);
fn is_absolute_url(path: &str) -> bool {
path.starts_with("http://") || path.starts_with("https://")
}
#[cfg(not(target_arch = "wasm32"))]
fn get_local_file<'a>(path: &'a str, file: &'a mut Option<std::fs::File>) -> std::io::Result<&'a std::fs::File> {
if file.is_none() {
*file = Some(std::fs::File::open(path)?);
}
Ok(file.as_ref().unwrap())
}
impl UniversalFile {
pub fn open(path: &str) -> std::io::Result<Self> {
if is_absolute_url(path) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("'path' is an absolute URL, use 'open_url' instead: {}", path),
));
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(Self(UniversalFileInner::LocalFile { path: path.to_string(), file: Some(std::fs::File::open(path)?) }))
}
#[cfg(target_arch = "wasm32")]
{
Self::open_url_sync_wasm(path)
}
}
pub fn open_url(url: &str) -> std::io::Result<Self> {
if !is_absolute_url(url) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("'url' is not an absolute URL, use 'open' instead: {}", url),
));
}
#[cfg(not(target_arch = "wasm32"))]
{
Self::open_url_sync_native(url)
}
#[cfg(target_arch = "wasm32")]
{
Self::open_url_sync_wasm(url)
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn from_wasm_file(id: usize, size: u64) -> Self {
Self(UniversalFileInner::WasmFile { id, size, pos: 0 })
}
#[cfg(target_arch = "wasm32")]
fn open_url_sync_wasm(url: &str) -> std::io::Result<Self> {
let chars = url.chars().collect::<Vec<char>>();
unsafe {
let mut buf_ptr_out: u32 = 0;
let mut buf_len_out: u32 = 0;
if readUrlSync(chars.as_ptr() as usize, chars.len() as usize, &mut buf_ptr_out, &mut buf_len_out) == 1 {
let data = Vec::<u8>::from_raw_parts(buf_ptr_out as *mut u8, buf_len_out as usize, buf_len_out as usize);
Ok(Self(UniversalFileInner::FullyLoaded { data: std::sync::Arc::new(data), pos: 0 }))
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Error while loading {}; check the browser console for details", url),
))
}
}
}
#[cfg(not(target_arch = "wasm32"))]
fn open_url_sync_native(url: &str) -> std::io::Result<Self> {
if let Ok(resp) = ureq::get(url).call() {
let mut buffer: Vec<u8> = Vec::new();
if std::io::Read::read_to_end(&mut resp.into_reader(), &mut buffer).is_ok() {
Ok(Self(UniversalFileInner::FullyLoaded { data: std::sync::Arc::new(buffer), pos: 0 }))
} else {
Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Error while reading {}", url)))
}
} else {
Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Error while loading {}", url)))
}
}
}
pub fn read_to_string(path: &str) -> std::io::Result<String> {
#[cfg(not(target_arch = "wasm32"))]
{
std::fs::read_to_string(path)
}
#[cfg(target_arch = "wasm32")]
{
let mut file = UniversalFile::open(path)?;
let mut buffer = String::new();
std::io::Read::read_to_string(&mut file, &mut buffer)?;
Ok(buffer)
}
}
impl Clone for UniversalFile {
fn clone(&self) -> Self {
match &self.0 {
UniversalFileInner::FullyLoaded { data, pos: _ } => {
Self(UniversalFileInner::FullyLoaded { data: std::sync::Arc::clone(data), pos: 0 })
}
#[cfg(not(target_arch = "wasm32"))]
UniversalFileInner::LocalFile { path, file: _ } => {
Self(UniversalFileInner::LocalFile { path: path.clone(), file: None })
}
#[cfg(target_arch = "wasm32")]
UniversalFileInner::WasmFile { id, size, pos: _ } => {
Self(UniversalFileInner::WasmFile { id: *id, size: *size, pos: 0 })
}
}
}
}
impl std::io::Read for UniversalFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.0 {
UniversalFileInner::FullyLoaded { data, pos } => {
let amt = std::cmp::min(*pos, data.len() as u64);
let mut read_buf = &data[(amt as usize)..];
let bytes_read = std::io::Read::read(&mut read_buf, buf)?;
*pos += bytes_read as u64;
Ok(bytes_read)
}
#[cfg(not(target_arch = "wasm32"))]
UniversalFileInner::LocalFile { path, file } => get_local_file(path, file)?.read(buf),
#[cfg(target_arch = "wasm32")]
UniversalFileInner::WasmFile { id, size: _, pos } => unsafe {
let bytes_read: u64 = readUserFileRange(*id as u32, buf.as_ptr() as u64, buf.len() as u64, *pos);
*pos += bytes_read;
Ok(bytes_read as usize)
},
}
}
}
fn update_pos(pos: &mut u64, size: u64, style: std::io::SeekFrom) -> std::io::Result<u64> {
let (base_pos, offset) = match style {
std::io::SeekFrom::Start(n) => {
*pos = n;
return Ok(n);
}
std::io::SeekFrom::End(n) => (size, n),
std::io::SeekFrom::Current(n) => (*pos, n),
};
let new_pos =
if offset >= 0 { base_pos.checked_add(offset as u64) } else { base_pos.checked_sub((offset.wrapping_neg()) as u64) };
match new_pos {
Some(n) => {
*pos = n;
Ok(*pos)
}
None => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid seek to a negative or overflowing position")),
}
}
impl std::io::Seek for UniversalFile {
fn seek(&mut self, style: std::io::SeekFrom) -> std::io::Result<u64> {
match &mut self.0 {
UniversalFileInner::FullyLoaded { data, pos } => update_pos(pos, data.len() as u64, style),
#[cfg(not(target_arch = "wasm32"))]
UniversalFileInner::LocalFile { path, file } => get_local_file(path, file)?.seek(style),
#[cfg(target_arch = "wasm32")]
UniversalFileInner::WasmFile { id: _, size, pos } => update_pos(pos, *size, style),
}
}
}
impl crate::ReadSeek for UniversalFile {}
impl std::fmt::Debug for UniversalFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<UniversalFile>")
}
}