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}