use std::{io, path::Path};
use js_sys::{Function, Promise, Reflect};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetFileOptions,
FileSystemSyncAccessHandle,
};
use crate::current_dir;
use super::{
super::File,
OpenDirType,
error::opfs_err,
file_lock::try_lock_path,
open_dir,
options::{CreateFileMode, CreateSyncAccessHandleOptions, SyncAccessMode},
root::root,
virtualize,
};
#[cfg_attr(feature = "opfs_tracing", tracing::instrument(level = "trace", fields(path = %path.as_ref().to_string_lossy())))]
pub(crate) async fn open_file(
path: impl AsRef<Path>,
create: CreateFileMode,
mode: SyncAccessMode,
truncate: bool,
) -> io::Result<File> {
let virt_path = virtualize::virtualize(path.as_ref())?;
let lock_guard = try_lock_path(&virt_path).ok_or_else(|| {
io::Error::new(
io::ErrorKind::WouldBlock,
"file is already opened by another handle",
)
})?;
let handle = get_fs_handle(path, create).await?;
let sync_access_handle = match create_sync_access_handle(&handle, mode).await {
Ok(h) => h,
Err(e) => {
return Err(e);
}
};
if truncate {
sync_access_handle.truncate_with_u32(0).map_or_else(
|err| {
sync_access_handle.close();
Err(opfs_err(err))
},
|_| {
sync_access_handle.flush().map_err(|err| {
sync_access_handle.close();
opfs_err(err)
})
},
)?;
}
Ok(File {
sync_access_handle,
pos: None,
lock_guard,
})
}
pub(crate) async fn get_fs_handle(
path: impl AsRef<Path>,
create: CreateFileMode,
) -> io::Result<FileSystemFileHandle> {
let (dir_entry, name) = resolve_parent(path.as_ref()).await?;
match create {
CreateFileMode::Create => get_raw_handle(&name, &dir_entry, true).await,
CreateFileMode::CreateNew => {
match get_raw_handle(&name, &dir_entry, false).await {
Ok(_) => Err(io::Error::from(io::ErrorKind::AlreadyExists)),
Err(_) => get_raw_handle(&name, &dir_entry, true).await,
}
}
CreateFileMode::NotCreate => get_raw_handle(&name, &dir_entry, false).await,
}
}
async fn resolve_parent(path: &Path) -> io::Result<(FileSystemDirectoryHandle, String)> {
let virt = virtualize::virtualize(path)?;
let parent = virt.parent();
let name = match virt.file_name() {
Some(os_str) => Ok(os_str.to_string_lossy().to_string()),
None => Err(io::Error::from(io::ErrorKind::InvalidFilename)),
}?;
let dir_entry = match parent {
Some(parent_path) => {
open_dir(
parent_path,
if parent_path == current_dir()? {
OpenDirType::CreateRecursive
} else {
OpenDirType::NotCreate
},
)
.await?
}
None => root().await?,
};
Ok((dir_entry, name))
}
async fn get_raw_handle(
name: &str,
dir_entry: &FileSystemDirectoryHandle,
create: bool,
) -> io::Result<FileSystemFileHandle> {
let option = FileSystemGetFileOptions::new();
option.set_create(create);
JsFuture::from(dir_entry.get_file_handle_with_options(name, &option))
.await
.map_err(opfs_err)
.map(|v| v.unchecked_into::<FileSystemFileHandle>())
}
async fn create_sync_access_handle(
handle: &FileSystemFileHandle,
mode: SyncAccessMode,
) -> io::Result<FileSystemSyncAccessHandle> {
let file_handle_js_value = JsValue::from(handle);
let promise = Reflect::get(&file_handle_js_value, &"createSyncAccessHandle".into())
.map_err(opfs_err)?
.unchecked_into::<Function>()
.call1(
&file_handle_js_value,
&CreateSyncAccessHandleOptions::from(mode).into(),
)
.map_err(opfs_err)?
.unchecked_into::<Promise>();
JsFuture::from(promise)
.await
.map_err(opfs_err)
.map(|v| v.unchecked_into::<FileSystemSyncAccessHandle>())
}