Skip to main content

librojo/syncback/
fs_snapshot.rs

1use std::{
2    collections::{HashMap, HashSet},
3    io,
4    path::{Path, PathBuf},
5};
6
7use memofs::Vfs;
8
9/// A simple representation of a subsection of a file system.
10#[derive(Default)]
11pub struct FsSnapshot {
12    /// Paths representing new files mapped to their contents.
13    added_files: HashMap<PathBuf, Vec<u8>>,
14    /// Paths representing new directories.
15    added_dirs: HashSet<PathBuf>,
16    /// Paths representing removed files.
17    removed_files: HashSet<PathBuf>,
18    /// Paths representing removed directories.
19    removed_dirs: HashSet<PathBuf>,
20}
21
22impl FsSnapshot {
23    /// Creates a new `FsSnapshot`.
24    pub fn new() -> Self {
25        Self {
26            added_files: HashMap::new(),
27            added_dirs: HashSet::new(),
28            removed_files: HashSet::new(),
29            removed_dirs: HashSet::new(),
30        }
31    }
32
33    /// Adds the given path to the `FsSnapshot` as a file with the given
34    /// contents, then returns it.
35    pub fn with_added_file<P: AsRef<Path>>(mut self, path: P, data: Vec<u8>) -> Self {
36        self.added_files.insert(path.as_ref().to_path_buf(), data);
37        self
38    }
39
40    /// Adds the given path to the `FsSnapshot` as a file with the given
41    /// then returns it.
42    pub fn with_added_dir<P: AsRef<Path>>(mut self, path: P) -> Self {
43        self.added_dirs.insert(path.as_ref().to_path_buf());
44        self
45    }
46
47    /// Merges two `FsSnapshot`s together.
48    #[inline]
49    pub fn merge(&mut self, other: Self) {
50        self.added_files.extend(other.added_files);
51        self.added_dirs.extend(other.added_dirs);
52        self.removed_files.extend(other.removed_files);
53        self.removed_dirs.extend(other.removed_dirs);
54    }
55
56    /// Merges two `FsSnapshot`s together, with a filter applied to the paths.
57    #[inline]
58    pub fn merge_with_filter<F>(&mut self, other: Self, mut predicate: F)
59    where
60        F: FnMut(&Path) -> bool,
61    {
62        self.added_files
63            .extend(other.added_files.into_iter().filter(|(k, _)| predicate(k)));
64        self.added_dirs
65            .extend(other.added_dirs.into_iter().filter(|p| predicate(p)));
66        self.removed_files
67            .extend(other.removed_files.into_iter().filter(|p| predicate(p)));
68        self.removed_dirs
69            .extend(other.removed_dirs.into_iter().filter(|p| predicate(p)));
70    }
71
72    /// Adds the provided path as a file with the given contents.
73    pub fn add_file<P: AsRef<Path>>(&mut self, path: P, data: Vec<u8>) {
74        self.added_files.insert(path.as_ref().to_path_buf(), data);
75    }
76
77    /// Adds the provided path as a directory.
78    pub fn add_dir<P: AsRef<Path>>(&mut self, path: P) {
79        self.added_dirs.insert(path.as_ref().to_path_buf());
80    }
81
82    /// Removes the provided path, as a file.
83    pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) {
84        self.removed_files.insert(path.as_ref().to_path_buf());
85    }
86
87    /// Removes the provided path, as a directory.
88    pub fn remove_dir<P: AsRef<Path>>(&mut self, path: P) {
89        self.removed_dirs.insert(path.as_ref().to_path_buf());
90    }
91
92    /// Writes the `FsSnapshot` to the provided VFS, using the provided `base`
93    /// as a root for the other paths in the `FsSnapshot`.
94    ///
95    /// This includes removals, but makes no effort to minimize work done.
96    pub fn write_to_vfs<P: AsRef<Path>>(&self, base: P, vfs: &Vfs) -> io::Result<()> {
97        let mut lock = vfs.lock();
98
99        let base_path = base.as_ref();
100        for dir_path in &self.added_dirs {
101            match lock.create_dir_all(base_path.join(dir_path)) {
102                Ok(_) => (),
103                Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
104                Err(err) => return Err(err),
105            };
106        }
107        for (path, contents) in &self.added_files {
108            lock.write(base_path.join(path), contents)?;
109        }
110        for dir_path in &self.removed_dirs {
111            lock.remove_dir_all(base_path.join(dir_path))?;
112        }
113        for path in &self.removed_files {
114            lock.remove_file(base_path.join(path))?;
115        }
116        drop(lock);
117
118        log::debug!(
119            "Wrote {} directories and {} files to the file system",
120            self.added_dirs.len(),
121            self.added_files.len()
122        );
123        log::debug!(
124            "Removed {} directories and {} files from the file system",
125            self.removed_dirs.len(),
126            self.removed_files.len()
127        );
128        Ok(())
129    }
130
131    /// Returns whether this `FsSnapshot` is empty or not.
132    #[inline]
133    pub fn is_empty(&self) -> bool {
134        self.added_files.is_empty()
135            && self.added_dirs.is_empty()
136            && self.removed_files.is_empty()
137            && self.removed_dirs.is_empty()
138    }
139
140    /// Returns a list of paths that would be added by this `FsSnapshot`.
141    #[inline]
142    pub fn added_paths(&self) -> Vec<&Path> {
143        let mut list = Vec::with_capacity(self.added_files.len() + self.added_dirs.len());
144        list.extend(self.added_files());
145        list.extend(self.added_dirs());
146
147        list
148    }
149
150    /// Returns a list of paths that would be removed by this `FsSnapshot`.
151    #[inline]
152    pub fn removed_paths(&self) -> Vec<&Path> {
153        let mut list = Vec::with_capacity(self.removed_files.len() + self.removed_dirs.len());
154        list.extend(self.removed_files());
155        list.extend(self.removed_dirs());
156
157        list
158    }
159
160    /// Returns a list of file paths that would be added by this `FsSnapshot`
161    #[inline]
162    pub fn added_files(&self) -> Vec<&Path> {
163        let mut added_files: Vec<_> = self.added_files.keys().map(PathBuf::as_path).collect();
164        added_files.sort_unstable();
165        added_files
166    }
167
168    /// Returns a list of directory paths that would be added by this `FsSnapshot`
169    #[inline]
170    pub fn added_dirs(&self) -> Vec<&Path> {
171        let mut added_dirs: Vec<_> = self.added_dirs.iter().map(PathBuf::as_path).collect();
172        added_dirs.sort_unstable();
173        added_dirs
174    }
175
176    /// Returns a list of file paths that would be removed by this `FsSnapshot`
177    #[inline]
178    pub fn removed_files(&self) -> Vec<&Path> {
179        let mut removed_files: Vec<_> = self.removed_files.iter().map(PathBuf::as_path).collect();
180        removed_files.sort_unstable();
181        removed_files
182    }
183
184    /// Returns a list of directory paths that would be removed by this `FsSnapshot`
185    #[inline]
186    pub fn removed_dirs(&self) -> Vec<&Path> {
187        let mut removed_dirs: Vec<_> = self.removed_dirs.iter().map(PathBuf::as_path).collect();
188        removed_dirs.sort_unstable();
189        removed_dirs
190    }
191}