windows_projfs/
source.rs

1use std::{
2    self,
3    ffi::OsStr,
4    fs::DirEntry,
5    io::{
6        self,
7        Read,
8    },
9    ops::ControlFlow,
10    path::{
11        Path,
12        PathBuf,
13    },
14};
15
16/// A `DirectoryEntry` represents all possible entry types
17/// which can be contained within the file system.
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub enum DirectoryEntry {
20    /// The entry is a directory
21    Directory(DirectoryInfo),
22
23    /// The entry is a single file
24    File(FileInfo),
25}
26
27impl DirectoryEntry {
28    pub fn name(&self) -> &str {
29        match self {
30            Self::Directory(dir) => &dir.directory_name,
31            Self::File(file) => &file.file_name,
32        }
33    }
34}
35
36impl From<FileInfo> for DirectoryEntry {
37    fn from(value: FileInfo) -> Self {
38        Self::File(value)
39    }
40}
41
42impl From<DirectoryInfo> for DirectoryEntry {
43    fn from(value: DirectoryInfo) -> Self {
44        Self::Directory(value)
45    }
46}
47
48impl TryFrom<DirEntry> for DirectoryEntry {
49    type Error = std::io::Error;
50
51    fn try_from(value: DirEntry) -> Result<Self, Self::Error> {
52        use std::os::windows::fs::MetadataExt;
53
54        let file_name = value.file_name().to_string_lossy().to_string();
55        let file_type = value.file_type()?;
56        let metadata = value.metadata()?;
57        if file_type.is_dir() {
58            Ok(DirectoryInfo {
59                directory_name: file_name,
60                directory_attributes: metadata.file_attributes(),
61
62                creation_time: metadata.creation_time(),
63                last_access_time: metadata.last_access_time(),
64                last_write_time: metadata.last_write_time(),
65            }
66            .into())
67        } else if file_type.is_file() {
68            Ok(FileInfo {
69                file_name,
70                file_size: metadata.len(),
71                file_attributes: metadata.file_attributes(),
72
73                creation_time: metadata.creation_time(),
74                last_access_time: metadata.last_access_time(),
75                last_write_time: metadata.last_write_time(),
76            }
77            .into())
78        } else {
79            Err(io::Error::other("file type is not supported"))
80        }
81    }
82}
83
84/// Supported attributes for files.
85///
86/// Note:
87/// The file size should be matching else the client might expect more
88/// or less content when trying to receive the file.
89#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
90pub struct FileInfo {
91    pub file_name: String,
92    pub file_size: u64,
93    pub file_attributes: u32,
94
95    pub creation_time: u64,
96    pub last_access_time: u64,
97    pub last_write_time: u64,
98}
99
100/// Supported attributes for directories
101#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
102pub struct DirectoryInfo {
103    pub directory_name: String,
104    pub directory_attributes: u32,
105
106    pub creation_time: u64,
107    pub last_access_time: u64,
108    pub last_write_time: u64,
109}
110
111/// Implementation for the data source of the projected file system.
112pub trait ProjectedFileSystemSource {
113    /// Return a list of directory entries contained at that specific path.
114    /// Return an empty list to indicate that the directory is empty or does not exists.
115    fn list_directory(&self, path: &Path) -> Vec<DirectoryEntry>;
116
117    /// Return information about the target path.  
118    /// The path can be any of the previously returned `DirectoryEntry`s.  
119    ///  
120    /// If the target entry does not exists, return `None`.  
121    ///
122    /// Note:  
123    /// The default implementation is for convinience and should be overridden as  
124    /// looping trough all directory entries might come with a performance penalty.
125    fn get_directory_entry(&self, path: &Path) -> Option<DirectoryEntry> {
126        let directory = path.parent().map(Path::to_path_buf).unwrap_or_default();
127        let file_name = path.file_name().map(OsStr::to_string_lossy)?;
128
129        self.list_directory(&directory)
130            .into_iter()
131            .find(|entry| entry.name() == file_name)
132    }
133
134    /// Return a stream to the file contents of `path`.  
135    ///   
136    /// Note:
137    /// The returned Box<dyn Read> must respect the byte_offset and will not be read  
138    /// past `length` bytes.
139    fn stream_file_content(
140        &self,
141        path: &Path,
142        byte_offset: usize,
143        length: usize,
144    ) -> std::io::Result<Box<dyn Read>>;
145
146    /// Handle file system notifications.
147    /// All pre-notifications can be cancelled.
148    fn handle_notification(&self, _notification: &Notification) -> ControlFlow<()> {
149        ControlFlow::Continue(())
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
154pub enum FileCloseAction {
155    /// File has been closed and deleted
156    Deleted,
157
158    /// File has been close and the contents modified
159    Modified,
160
161    /// File has been closed but the contents have not changed
162    NoModification,
163}
164
165#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
166pub struct ProjectedFile {
167    pub file_id: u128,
168    pub is_directory: bool,
169    pub path: PathBuf,
170}
171
172#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
173pub struct FileRenameInfo {
174    pub source: Option<PathBuf>,
175    pub destination: Option<PathBuf>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
179pub enum Notification {
180    FileCreated(ProjectedFile),
181    FileOpened(ProjectedFile),
182    FileClosed(ProjectedFile, FileCloseAction),
183    FileOverwritten(ProjectedFile),
184
185    PreFileRename(FileRenameInfo),
186    FileRenamed(FileRenameInfo),
187
188    PreSetHardlink(ProjectedFile),
189    HardlinkCreated(ProjectedFile),
190
191    PreFileDelete(ProjectedFile),
192    FilePreConvertToFull(ProjectedFile),
193}
194
195impl Notification {
196    /// Returns `true` if the action can be cancelled  
197    /// by returning `ControlFlow::Break`
198    pub fn is_cancelable(&self) -> bool {
199        #[allow(clippy::match_like_matches_macro)]
200        match self {
201            Self::PreFileRename(_) => true,
202            Self::PreFileDelete(_) => true,
203            Self::PreSetHardlink(_) => true,
204            Self::FilePreConvertToFull(_) => true,
205            _ => false,
206        }
207    }
208}