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