1use generic_array::GenericArray;
2use std::path::{Path, PathBuf};
3
4use crate::archive::{Archive, ArchiveEntries};
5use crate::config::SyncInfo;
6use crate::detect::ext::is_item_in_sync;
7use crate::detect::util::*;
8use crate::error::SyncError;
9use crate::state::ArchiveEntryPerReplica;
10use crate::util::FnvHashMap;
11use crate::NumRoots;
12
13use serde::{Deserialize, Serialize};
14
15mod ext;
16mod util;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Difference<N: NumRoots> {
22 pub path: PathBuf,
24
25 #[serde(bound(
26 serialize = "GenericArray<PathBuf, N>: Serialize",
27 deserialize = "GenericArray<PathBuf, N>: Deserialize<'de>"
28 ))]
29 pub roots: GenericArray<PathBuf, N>,
31
32 #[serde(bound(
33 serialize = "GenericArray<ArchiveEntryPerReplica, N>: Serialize",
34 deserialize = "GenericArray<ArchiveEntryPerReplica, N>: Deserialize<'de>"
35 ))]
36 pub previous_state: Option<GenericArray<ArchiveEntryPerReplica, N>>,
38
39 #[serde(bound(
40 serialize = "GenericArray<ArchiveEntryPerReplica, N>: Serialize",
41 deserialize = "GenericArray<ArchiveEntryPerReplica, N>: Deserialize<'de>"
42 ))]
43 pub current_state: GenericArray<ArchiveEntryPerReplica, N>,
45}
46
47impl<N: NumRoots> Difference<N> {
48 pub fn absolute_path_for_root(&self, index: usize) -> PathBuf {
49 self.roots[index].join(&self.path)
50 }
51}
52
53pub struct DetectionResult<N: NumRoots> {
55 pub differences: Vec<Difference<N>>,
56 pub statistics: DetectionStatistics,
57}
58
59impl<N: NumRoots> Default for DetectionResult<N> {
60 fn default() -> Self {
61 DetectionResult {
62 differences: Vec::new(),
63 statistics: DetectionStatistics::new(),
64 }
65 }
66}
67
68impl<N: NumRoots> DetectionResult<N> {
69 fn new() -> Self {
70 Default::default()
71 }
72
73 fn add_difference(&mut self, conflict: Difference<N>) {
74 let mut add = true;
75
76 self.differences.retain(|other| {
77 if other.path.starts_with(&conflict.path) {
78 debug!("Removing nested conflict at {:?}", other.path);
79 false
80 } else if conflict.path.starts_with(&other.path) {
81 debug!("Not adding nested conflict at {:?}", conflict.path);
82 add = false;
83 true
84 } else {
85 true
86 }
87 });
88
89 if add {
90 self.differences.push(conflict);
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
97pub struct SearchDirectories {
98 pub directories: Vec<PathBuf>,
99 pub recurse: bool,
100}
101
102impl SearchDirectories {
103 pub fn from_root() -> SearchDirectories {
105 SearchDirectories {
106 directories: vec![Path::new("").to_path_buf()],
107 recurse: true,
108 }
109 }
110
111 pub fn new(directories: Vec<PathBuf>, recurse: bool) -> Self {
112 SearchDirectories {
113 directories,
114 recurse,
115 }
116 }
117}
118
119#[derive(Debug)]
120pub struct DetectionStatistics {
122 pub archive_hits: usize,
124 pub archive_additions: usize,
126}
127
128impl Default for DetectionStatistics {
129 fn default() -> Self {
130 DetectionStatistics {
131 archive_hits: 0,
132 archive_additions: 0,
133 }
134 }
135}
136
137impl DetectionStatistics {
138 pub fn new() -> Self {
139 Default::default()
140 }
141}
142
143pub trait ProgressCallback {
145 fn reading_directory(&self, path: &Path, checked: usize, remaining: usize);
147}
148
149pub struct EmptyProgressCallback;
151
152impl ProgressCallback for EmptyProgressCallback {
153 fn reading_directory(&self, _: &Path, _: usize, _: usize) {}
154}
155
156pub fn find_updates<N, P>(
164 archive: &Archive,
165 search: &mut SearchDirectories,
166 config: &SyncInfo<N>,
167 progress_callback: &P,
168) -> Result<DetectionResult<N>, SyncError>
169where
170 N: NumRoots,
171 P: ProgressCallback,
172{
173 let mut current_entries: FnvHashMap<PathBuf, GenericArray<ArchiveEntryPerReplica, N>> =
175 Default::default();
176 let mut result = DetectionResult::new();
177 let mut read_directories = 0;
178
179 check_all_roots_exist(config.roots.iter())?;
181
182 search
183 .directories
184 .retain(|dir| !is_ignored(&config.ignore, &dir));
185
186 loop {
187 current_entries.clear();
188
189 let sd = match search.directories.pop() {
190 Some(d) => d,
191 None => break,
192 };
193
194 if sd.is_absolute() {
195 return Err(SyncError::AbsolutePathProvided(sd));
196 }
197
198 debug!("Reading dir {:?}", sd);
200 progress_callback.reading_directory(&sd, read_directories, search.directories.len());
201 read_directories += 1;
202
203 let mut sd_archive_file = archive.for_directory(&sd);
205 let mut sd_archive_entries: ArchiveEntries<N> = sd_archive_file.read()?;
206
207 scan_directory_contents(&sd, &mut current_entries, config)?;
209
210 debug!("Analysing items in {:?}", sd);
212 for (path, current_entry) in current_entries.iter_mut() {
213 let mut keep_checking = true;
214 if let Some(archive_entry) = sd_archive_entries.get(path) {
215 trace!("Checking archive files");
216 if are_archive_files_identical(archive_entry, current_entry) {
217 result.statistics.archive_hits += 1;
218 keep_checking = false;
219 }
220 }
221
222 if keep_checking {
223 if is_item_in_sync(
224 path,
225 current_entry,
226 config.compare_file_contents,
227 &config.roots,
228 )? {
229 sd_archive_entries.insert(path, current_entry.clone());
231 result.statistics.archive_additions += 1;
232 } else {
233 let difference = Difference {
236 path: path.to_path_buf(),
237 roots: config.roots.clone(),
238 previous_state: sd_archive_entries.get(path).cloned(),
239 current_state: current_entry.clone(),
240 };
241 result.add_difference(difference);
242 continue;
243 }
244 }
245
246 if let Some(root) = config.roots.last() {
250 if search.recurse && root.join(path).is_dir() {
251 search.directories.push(path.clone());
252 }
253 }
254 }
255
256 if sd_archive_entries.is_dirty() {
257 sd_archive_file.write(&mut sd_archive_entries)?;
258 }
259 }
260
261 Ok(result)
262}