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}