browser_fs/
metadata.rs

1#![allow(clippy::len_without_is_empty)]
2
3use std::io::{Error, ErrorKind, Result};
4use std::path::Path;
5use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7use web_sys::FileSystemFileHandle;
8
9use crate::{get_directory, Entry, FileType};
10
11/// Metadata information about a file.
12///
13/// This structure is returned from the [`metadata`] function or method and
14/// represents known metadata about a file such as its permissions, size,
15/// modification times, etc.
16#[derive(Debug)]
17pub struct Metadata {
18    file_type: FileType,
19    size: u64,
20    last_modified: SystemTime,
21}
22
23impl Metadata {
24    pub(crate) async fn from_file_handle(handle: &FileSystemFileHandle) -> Result<Self> {
25        let file = crate::resolve::<web_sys::File>(handle.get_file()).await?;
26        // miliseconds since unix epoch
27        // https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified
28        let since_epoch = Duration::from_millis(file.last_modified() as u64);
29        let last_modified = UNIX_EPOCH + since_epoch;
30        // relies on Blob API
31        // https://developer.mozilla.org/en-US/docs/Web/API/Blob/size
32        let size = file.size() as u64;
33
34        Ok(Metadata {
35            file_type: FileType::File,
36            size,
37            last_modified,
38        })
39    }
40}
41
42impl Metadata {
43    /// Returns the file type for this metadata.
44    ///
45    /// # Examples
46    ///
47    /// ```no_run
48    /// # futures_lite::future::block_on(async {
49    /// let metadata = browser_fs::metadata("a.txt").await?;
50    /// println!("{:?}", metadata.file_type());
51    /// # std::io::Result::Ok(()) });
52    /// ```
53    pub fn file_type(&self) -> FileType {
54        self.file_type
55    }
56
57    /// Returns `true` if this metadata is for a directory. The
58    /// result is mutually exclusive to the result of [`Metadata::is_file`].
59    ///
60    /// # Examples
61    ///
62    /// ```no_run
63    /// # futures_lite::future::block_on(async {
64    /// let metadata = browser_fs::metadata("a.txt").await?;
65    /// assert!(!metadata.is_dir());
66    /// # std::io::Result::Ok(()) });
67    /// ```
68    pub fn is_dir(&self) -> bool {
69        self.file_type.is_dir()
70    }
71
72    /// Returns `true` if this metadata is for a regular file. The
73    /// result is mutually exclusive to the result of [`Metadata::is_dir`].
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// # futures_lite::future::block_on(async {
79    /// let metadata = browser_fs::metadata("a.txt").await?;
80    /// assert!(metadata.is_file());
81    /// # std::io::Result::Ok(()) });
82    /// ```
83    pub fn is_file(&self) -> bool {
84        self.file_type.is_file()
85    }
86
87    /// Not supported
88    pub fn is_symlink(&self) -> bool {
89        false
90    }
91
92    /// Returns the size of the file, in bytes, this metadata is for.
93    ///
94    /// # Examples
95    ///
96    /// ```no_run
97    /// # futures_lite::future::block_on(async {
98    /// let metadata = browser_fs::metadata("a.txt").await?;
99    /// assert_eq!(metadata.len(), 42);
100    /// # std::io::Result::Ok(()) });
101    /// ```
102    pub fn len(&self) -> u64 {
103        self.size
104    }
105
106    /// Not supported
107    pub async fn accessed(&self) -> Result<SystemTime> {
108        Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
109    }
110
111    /// Not supported
112    pub fn created(&self) -> Result<SystemTime> {
113        Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
114    }
115
116    /// Returns the last modification time listed in this metadata.
117    ///
118    /// # Errors
119    ///
120    /// When the entry is not a file.
121    ///
122    /// # Examples
123    ///
124    /// ```no_run
125    /// # futures_lite::future::block_on(async {
126    /// let metadata = browser_fs::metadata("a.txt").await?;
127    /// assert!(metadata.modified().is_ok());
128    /// # std::io::Result::Ok(()) });
129    /// ```
130    pub fn modified(&self) -> Result<SystemTime> {
131        if self.is_file() {
132            Ok(self.last_modified)
133        } else {
134            Err(Error::new(ErrorKind::Unsupported, "unable to provide"))
135        }
136    }
137
138    /// All the files and directories are accessible in read-write mode
139    pub fn permissions(&self) -> Permissions {
140        Permissions { readonly: false }
141    }
142}
143
144/// Representation of the various permissions on a file.
145#[derive(Debug, Clone, Copy)]
146pub struct Permissions {
147    readonly: bool,
148}
149
150impl Permissions {
151    /// Always returns true
152    pub fn readonly(&self) -> bool {
153        self.readonly
154    }
155}
156
157/// Reads metadata for a path.
158///
159/// This function will traverse symbolic links to read metadata for the target
160/// file or directory.
161///
162/// # Errors
163///
164/// An error will be returned in the following situations:
165///
166/// * `path` does not point to an existing file or directory.
167/// * The current process lacks permissions to read metadata for the path.
168/// * Some other I/O error occurred.
169///
170/// # Examples
171///
172/// ```no_run
173/// # futures_lite::future::block_on(async {
174/// let perm = browser_fs::metadata("a.txt").await?.permissions();
175/// # std::io::Result::Ok(()) });
176/// ```
177pub async fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
178    let fpath = path.as_ref();
179    let parent = if let Some(parent) = fpath.parent() {
180        get_directory(parent).await?
181    } else {
182        crate::root_directory().await?
183    };
184    let Some(fname) = fpath.file_name() else {
185        return Err(Error::new(
186            ErrorKind::InvalidInput,
187            "unable to find directory name",
188        ));
189    };
190    let entry = crate::Entry::from_directory(&parent, fname.to_string_lossy().as_ref()).await?;
191    match entry {
192        Entry::Directory(_) => Ok(Metadata {
193            file_type: FileType::Directory,
194            size: 0,
195            last_modified: SystemTime::UNIX_EPOCH,
196        }),
197        Entry::File(handle) => Metadata::from_file_handle(&handle).await,
198    }
199}