ccsync_core/
comparison.rs1mod diff;
10mod hash;
11mod timestamp;
12
13#[cfg(test)]
14mod integration_tests;
15
16use std::path::Path;
17
18use serde::{Deserialize, Serialize};
19
20pub use diff::DiffGenerator;
21pub use hash::FileHasher;
22pub use timestamp::TimestampComparator;
23
24use crate::error::Result;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "kebab-case")]
29pub enum ConflictStrategy {
30 Fail,
32 Overwrite,
34 Skip,
36 Newer,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum ComparisonResult {
43 Identical,
45 SourceOnly,
47 DestinationOnly,
49 Conflict {
51 source_newer: bool,
53 strategy: ConflictStrategy,
55 },
56}
57
58pub struct FileComparator;
60
61impl Default for FileComparator {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl FileComparator {
68 #[must_use]
70 pub const fn new() -> Self {
71 Self
72 }
73
74 pub fn compare(
80 source: &Path,
81 destination: &Path,
82 strategy: ConflictStrategy,
83 ) -> Result<ComparisonResult> {
84 let source_exists = source.exists();
85 let dest_exists = destination.exists();
86
87 match (source_exists, dest_exists) {
88 (false, false) => {
89 anyhow::bail!(
90 "Neither source nor destination file exists: source={}, dest={}",
91 source.display(),
92 destination.display()
93 )
94 }
95 (true, false) => Ok(ComparisonResult::SourceOnly),
96 (false, true) => Ok(ComparisonResult::DestinationOnly),
97 (true, true) => {
98 let source_hash = FileHasher::hash(source)?;
100 let dest_hash = FileHasher::hash(destination)?;
101
102 if source_hash == dest_hash {
103 Ok(ComparisonResult::Identical)
104 } else {
105 let source_newer = TimestampComparator::is_newer(source, destination)?;
107 Ok(ComparisonResult::Conflict {
108 source_newer,
109 strategy,
110 })
111 }
112 }
113 }
114 }
115
116 pub fn generate_diff(source: &Path, destination: &Path) -> Result<String> {
122 DiffGenerator::generate(source, destination)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_conflict_strategy_types() {
132 assert_eq!(ConflictStrategy::Fail, ConflictStrategy::Fail);
133 assert_ne!(ConflictStrategy::Fail, ConflictStrategy::Overwrite);
134 }
135
136 #[test]
137 fn test_comparison_result_types() {
138 let identical = ComparisonResult::Identical;
139 let source_only = ComparisonResult::SourceOnly;
140 assert_ne!(identical, source_only);
141 }
142}