browser-fs 0.1.0

A browser-based filesystem implementation for WebAssembly applications
Documentation
#![allow(clippy::len_without_is_empty)]

use std::io::{Error, ErrorKind, Result};
use std::path::Path;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use web_sys::FileSystemFileHandle;

use crate::{get_directory, Entry, FileType};

/// Metadata information about a file.
///
/// This structure is returned from the [`metadata`] function or method and
/// represents known metadata about a file such as its permissions, size,
/// modification times, etc.
#[derive(Debug)]
pub struct Metadata {
    file_type: FileType,
    size: u64,
    last_modified: SystemTime,
}

impl Metadata {
    pub(crate) async fn from_file_handle(handle: &FileSystemFileHandle) -> Result<Self> {
        let file = crate::resolve::<web_sys::File>(handle.get_file()).await?;
        // miliseconds since unix epoch
        // https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified
        let since_epoch = Duration::from_millis(file.last_modified() as u64);
        let last_modified = UNIX_EPOCH + since_epoch;
        // relies on Blob API
        // https://developer.mozilla.org/en-US/docs/Web/API/Blob/size
        let size = file.size() as u64;

        Ok(Metadata {
            file_type: FileType::File,
            size,
            last_modified,
        })
    }
}

impl Metadata {
    /// Returns the file type for this metadata.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # futures_lite::future::block_on(async {
    /// let metadata = browser_fs::metadata("a.txt").await?;
    /// println!("{:?}", metadata.file_type());
    /// # std::io::Result::Ok(()) });
    /// ```
    pub fn file_type(&self) -> FileType {
        self.file_type
    }

    /// Returns `true` if this metadata is for a directory. The
    /// result is mutually exclusive to the result of [`Metadata::is_file`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # futures_lite::future::block_on(async {
    /// let metadata = browser_fs::metadata("a.txt").await?;
    /// assert!(!metadata.is_dir());
    /// # std::io::Result::Ok(()) });
    /// ```
    pub fn is_dir(&self) -> bool {
        self.file_type.is_dir()
    }

    /// Returns `true` if this metadata is for a regular file. The
    /// result is mutually exclusive to the result of [`Metadata::is_dir`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # futures_lite::future::block_on(async {
    /// let metadata = browser_fs::metadata("a.txt").await?;
    /// assert!(metadata.is_file());
    /// # std::io::Result::Ok(()) });
    /// ```
    pub fn is_file(&self) -> bool {
        self.file_type.is_file()
    }

    /// Not supported
    pub fn is_symlink(&self) -> bool {
        false
    }

    /// Returns the size of the file, in bytes, this metadata is for.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # futures_lite::future::block_on(async {
    /// let metadata = browser_fs::metadata("a.txt").await?;
    /// assert_eq!(metadata.len(), 42);
    /// # std::io::Result::Ok(()) });
    /// ```
    pub fn len(&self) -> u64 {
        self.size
    }

    /// Not supported
    pub async fn accessed(&self) -> Result<SystemTime> {
        Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
    }

    /// Not supported
    pub fn created(&self) -> Result<SystemTime> {
        Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
    }

    /// Returns the last modification time listed in this metadata.
    ///
    /// # Errors
    ///
    /// When the entry is not a file.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # futures_lite::future::block_on(async {
    /// let metadata = browser_fs::metadata("a.txt").await?;
    /// assert!(metadata.modified().is_ok());
    /// # std::io::Result::Ok(()) });
    /// ```
    pub fn modified(&self) -> Result<SystemTime> {
        if self.is_file() {
            Ok(self.last_modified)
        } else {
            Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
        }
    }

    /// All the files and directories are accessible in read-write mode
    pub fn permissions(&self) -> Permissions {
        Permissions { readonly: false }
    }
}

/// Representation of the various permissions on a file.
#[derive(Debug, Clone, Copy)]
pub struct Permissions {
    readonly: bool,
}

impl Permissions {
    /// Always returns true
    pub fn readonly(&self) -> bool {
        self.readonly
    }
}

/// Reads metadata for a path.
///
/// This function will traverse symbolic links to read metadata for the target
/// file or directory.
///
/// # Errors
///
/// An error will be returned in the following situations:
///
/// * `path` does not point to an existing file or directory.
/// * The current process lacks permissions to read metadata for the path.
/// * Some other I/O error occurred.
///
/// # Examples
///
/// ```no_run
/// # futures_lite::future::block_on(async {
/// let perm = browser_fs::metadata("a.txt").await?.permissions();
/// # std::io::Result::Ok(()) });
/// ```
pub async fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
    let fpath = path.as_ref();
    let parent = if let Some(parent) = fpath.parent() {
        get_directory(parent).await?
    } else {
        crate::root_directory().await?
    };
    let Some(fname) = fpath.file_name() else {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            "unable to find directory name",
        ));
    };
    let entry = crate::Entry::from_directory(&parent, fname.to_string_lossy().as_ref()).await?;
    match entry {
        Entry::Directory(_) => Ok(Metadata {
            file_type: FileType::Directory,
            size: 0,
            last_modified: SystemTime::UNIX_EPOCH,
        }),
        Entry::File(handle) => Metadata::from_file_handle(&handle).await,
    }
}