Skip to main content

blockworx_opfs/
file.rs

1//! One entry's bytes, through a writable stream.
2//!
3//! Every write here opens a writable, writes, and closes it: the stream is
4//! a swap file the browser puts in place only when it is closed, so a
5//! reader caught between the calls sees what was there before rather than
6//! half of each.
7
8use js_sys::Uint8Array;
9use wasm_bindgen::JsCast as _;
10use web_sys::{
11    File, FileSystemCreateWritableOptions, FileSystemFileHandle, FileSystemWritableFileStream,
12};
13
14use crate::fault::{faulted, mistaken, settled};
15
16/// Whether the writable that is opened starts from what the file already
17/// holds.
18#[derive(Clone, Copy, PartialEq, Eq, Debug)]
19enum Keeping {
20    Nothing,
21    Everything,
22}
23
24/// # Errors
25/// The browser's refusal, or a handle that answers something that is not a
26/// file.
27pub async fn read(handle: &FileSystemFileHandle) -> std::io::Result<Vec<u8>> {
28    let bytes = settled(opened(handle).await?.array_buffer()).await?;
29    Ok(Uint8Array::new(&bytes).to_vec())
30}
31
32/// Whole file or nothing.
33///
34/// # Errors
35/// As [`read`].
36pub async fn write(handle: &FileSystemFileHandle, bytes: &[u8]) -> std::io::Result<()> {
37    let stream = writable(handle, Keeping::Nothing).await?;
38    put(&stream, bytes).await?;
39    close(stream).await
40}
41
42/// Add to the end, which is how the manifest grows.
43///
44/// # Errors
45/// As [`read`].
46pub async fn append(handle: &FileSystemFileHandle, bytes: &[u8]) -> std::io::Result<()> {
47    let end = opened(handle).await?.size();
48    let stream = writable(handle, Keeping::Everything).await?;
49    settled(stream.seek_with_f64(end).map_err(faulted)?).await?;
50    put(&stream, bytes).await?;
51    close(stream).await
52}
53
54/// # Errors
55/// As [`read`].
56pub async fn truncate(handle: &FileSystemFileHandle, len: u64) -> std::io::Result<()> {
57    let stream = writable(handle, Keeping::Everything).await?;
58    settled(stream.truncate_with_f64(len as f64).map_err(faulted)?).await?;
59    close(stream).await
60}
61
62async fn opened(handle: &FileSystemFileHandle) -> std::io::Result<File> {
63    settled(handle.get_file())
64        .await?
65        .dyn_into()
66        .map_err(mistaken("a file"))
67}
68
69async fn writable(
70    handle: &FileSystemFileHandle,
71    keeping: Keeping,
72) -> std::io::Result<FileSystemWritableFileStream> {
73    let options = FileSystemCreateWritableOptions::new();
74    options.set_keep_existing_data(matches!(keeping, Keeping::Everything));
75    settled(handle.create_writable_with_options(&options))
76        .await?
77        .dyn_into()
78        .map_err(mistaken("a writable stream"))
79}
80
81async fn put(stream: &FileSystemWritableFileStream, bytes: &[u8]) -> std::io::Result<()> {
82    settled(stream.write_with_u8_array(bytes).map_err(faulted)?).await?;
83    Ok(())
84}
85
86/// By value, because a stream that has been closed has nothing more to say
87/// and a second write to one is the browser's refusal rather than ours.
88async fn close(stream: FileSystemWritableFileStream) -> std::io::Result<()> {
89    settled(stream.close()).await?;
90    Ok(())
91}