virtual_filesystem/
file.rs

1use std::fs;
2use std::io::{Read, Seek, Write};
3use std::path::PathBuf;
4
5/// The type of a file.
6#[derive(Debug, Copy, Clone, Eq, PartialEq)]
7pub enum FileType {
8    /// A directory.
9    Directory,
10    /// A file.
11    File,
12    /// The file type is unknown or unsupported.
13    Unknown,
14}
15
16impl From<fs::FileType> for FileType {
17    fn from(value: fs::FileType) -> Self {
18        if value.is_dir() {
19            Self::Directory
20        } else if value.is_file() {
21            Self::File
22        } else {
23            Self::Unknown
24        }
25    }
26}
27
28/// A directory entry.
29#[derive(Debug, Clone)]
30pub struct DirEntry {
31    /// The path to the file.
32    pub path: PathBuf,
33    /// Metadata about the file.
34    pub metadata: Metadata,
35}
36
37impl DirEntry {
38    /// Returns true if the entry is a directory.
39    pub fn is_directory(&self) -> bool {
40        self.metadata.is_directory()
41    }
42
43    /// Returns true if the entry is a file.
44    pub fn is_file(&self) -> bool {
45        self.metadata.is_directory()
46    }
47
48    /// Returns the length of the file, in bytes.
49    #[allow(clippy::len_without_is_empty)]
50    pub fn len(&self) -> u64 {
51        self.metadata.len()
52    }
53}
54
55/// Metadata about a file.
56#[derive(Debug, Clone, Eq, PartialEq)]
57pub struct Metadata {
58    /// True if the entry is a directory.
59    pub file_type: FileType,
60    /// The length of the file.
61    pub len: u64,
62}
63
64impl Metadata {
65    /// Returns metadata for a directory
66    pub fn directory() -> Self {
67        Self {
68            file_type: FileType::Directory,
69            len: 0,
70        }
71    }
72
73    /// Returns metadata for a file.
74    pub fn file(len: u64) -> Self {
75        Self {
76            file_type: FileType::File,
77            len,
78        }
79    }
80
81    /// Returns true if the entry is a directory.
82    pub fn is_directory(&self) -> bool {
83        self.file_type == FileType::Directory
84    }
85
86    /// Returns true if the entry is a file.
87    pub fn is_file(&self) -> bool {
88        self.file_type == FileType::File
89    }
90
91    /// Returns the length of the file, in bytes.
92    #[allow(clippy::len_without_is_empty)]
93    pub fn len(&self) -> u64 {
94        self.len
95    }
96}
97
98impl From<fs::Metadata> for Metadata {
99    fn from(value: fs::Metadata) -> Self {
100        Self {
101            file_type: value.file_type().into(),
102            len: value.len(),
103        }
104    }
105}
106
107/// Options for opening a file. The default mode is read-only.
108#[derive(Debug)]
109pub struct OpenOptions {
110    /// True if the file should be able to be appended to.
111    pub append: bool,
112    /// True if the file should be created if not present.
113    pub create: bool,
114    /// True if the file should be able to be read.
115    pub read: bool,
116    /// True if the file should be truncated.
117    pub truncate: bool,
118    /// True if the file should be written to.
119    pub write: bool,
120}
121
122impl From<&OpenOptions> for fs::OpenOptions {
123    fn from(value: &OpenOptions) -> Self {
124        Self::new()
125            .create(value.create)
126            .append(value.append)
127            .truncate(value.truncate)
128            .read(value.read)
129            .clone()
130    }
131}
132
133impl OpenOptions {
134    /// # Arguments
135    /// `append`: If true, the file should be opened with the cursor set to the end of the file,
136    /// rather than overwriting the file contents. Note that setting this to true will implicitly
137    /// enable writing.  
138    pub fn append(mut self, append: bool) -> Self {
139        if append {
140            self.write = true;
141        }
142        self.append = append;
143        self.truncate = !append;
144        self
145    }
146
147    /// # Arguments
148    /// `append`: If true, the file should be created if it does not exist. Note that setting this
149    /// to true will implicitly enable writing.  
150    pub fn create(mut self, create: bool) -> Self {
151        if create {
152            self.write = true;
153        }
154        self.create = true;
155        self
156    }
157
158    /// # Arguments
159    /// `read`: If true, the file should be able to be read in entirety.  
160    pub fn read(mut self, read: bool) -> Self {
161        self.read = read;
162        self
163    }
164
165    /// # Arguments
166    /// `truncate`: If true, the file should be opened with the cursor set to the beginning of the
167    /// file, overwriting all contents. Note that setting this to true will implicitly enable
168    /// writing.  
169    pub fn truncate(mut self, truncate: bool) -> Self {
170        if truncate {
171            self.write = true;
172        }
173        self.append = !truncate;
174        self.truncate = truncate;
175        self
176    }
177
178    /// # Arguments
179    /// `write`: If true, the file should be able to be written. By default, this will truncate
180    /// the contents of the file, unless `append` is set.
181    pub fn write(mut self, write: bool) -> Self {
182        self.write = write;
183        self
184    }
185}
186
187impl Default for OpenOptions {
188    fn default() -> Self {
189        Self {
190            append: false,
191            create: false,
192            read: true,
193            truncate: false,
194            write: false,
195        }
196    }
197}
198
199/// A file that can be read.
200pub trait File: Read + Write + Seek {
201    /// Returns the directory entry for the file.
202    fn metadata(&self) -> crate::Result<Metadata>;
203
204    /// Reads a file into a vector.
205    fn read_into_vec(&mut self) -> crate::Result<Vec<u8>> {
206        let mut vec = Vec::with_capacity(self.metadata()?.len() as usize);
207        self.read_to_end(&mut vec)?;
208        Ok(vec)
209    }
210
211    /// Reads a file into a string.
212    fn read_into_string(&mut self) -> crate::Result<String> {
213        let mut str = String::with_capacity(self.metadata()?.len() as usize);
214        self.read_to_string(&mut str)?;
215        Ok(str)
216    }
217}