stowr_core/
dedup.rs

1use std::collections::HashMap;
2use sha2::{Sha256, Digest};
3use serde::{Deserialize, Serialize};
4use anyhow::Result;
5
6/// 内容去重器
7/// 
8/// 通过计算文件的SHA256哈希值来识别完全相同的文件,
9/// 实现内容级别的去重存储。
10#[derive(Debug)]
11pub struct ContentDeduplicator {
12    /// 哈希值到存储ID的映射
13    hash_to_storage: HashMap<String, String>,
14    /// 存储ID到引用计数的映射
15    ref_counts: HashMap<String, u32>,
16    /// 存储ID到哈希值的反向映射
17    storage_to_hash: HashMap<String, String>,
18}
19
20/// 去重存储信息
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct DedupInfo {
23    /// 是否为引用(重复文件)
24    pub is_reference: bool,
25    /// 原始存储ID(对于引用文件指向的原文件ID)
26    pub original_storage_id: Option<String>,
27    /// 文件哈希值
28    pub hash: String,
29    /// 引用计数
30    pub ref_count: u32,
31}
32
33impl ContentDeduplicator {
34    /// 创建新的内容去重器
35    pub fn new() -> Self {
36        Self {
37            hash_to_storage: HashMap::new(),
38            ref_counts: HashMap::new(),
39            storage_to_hash: HashMap::new(),
40        }
41    }
42
43    /// 计算数据的SHA256哈希值
44    pub fn calculate_hash(data: &[u8]) -> String {
45        let mut hasher = Sha256::new();
46        hasher.update(data);
47        format!("{:x}", hasher.finalize())
48    }
49
50    /// 检查文件是否重复
51    /// 
52    /// 返回 Some(storage_id) 如果文件已存在,None 如果是新文件
53    pub fn check_duplicate(&mut self, hash: &str) -> Option<String> {
54        if let Some(storage_id) = self.hash_to_storage.get(hash) {
55            // 增加引用计数
56            *self.ref_counts.entry(storage_id.clone()).or_insert(0) += 1;
57            Some(storage_id.clone())
58        } else {
59            None
60        }
61    }
62
63    /// 注册新文件
64    /// 
65    /// 当存储新文件时调用,建立哈希值和存储ID的映射
66    pub fn register_file(&mut self, hash: String, storage_id: String) {
67        self.hash_to_storage.insert(hash.clone(), storage_id.clone());
68        self.storage_to_hash.insert(storage_id.clone(), hash);
69        self.ref_counts.insert(storage_id, 1);
70    }
71
72    /// 移除文件引用
73    /// 
74    /// 减少引用计数,如果计数为0则完全移除
75    /// 返回是否应该删除物理文件
76    pub fn remove_reference(&mut self, storage_id: &str) -> bool {
77        if let Some(count) = self.ref_counts.get_mut(storage_id) {
78            *count -= 1;
79            if *count == 0 {
80                // 引用计数为0,清理所有相关映射
81                self.ref_counts.remove(storage_id);
82                if let Some(hash) = self.storage_to_hash.remove(storage_id) {
83                    self.hash_to_storage.remove(&hash);
84                }
85                true // 应该删除物理文件
86            } else {
87                false // 还有其他引用,不删除物理文件
88            }
89        } else {
90            true // 如果找不到引用记录,默认删除
91        }
92    }
93
94    /// 通过哈希值移除引用
95    /// 
96    /// 减少对应存储的引用计数,如果计数为0则完全移除
97    /// 返回是否应该删除物理文件
98    pub fn remove_hash_reference(&mut self, hash: &str) -> bool {
99        if let Some(storage_id) = self.hash_to_storage.get(hash) {
100            let storage_id = storage_id.clone(); // 避免借用冲突
101            self.remove_reference(&storage_id)
102        } else {
103            true // 如果找不到对应的存储,默认删除
104        }
105    }
106
107    /// 通过哈希值增加引用
108    /// 
109    /// 增加对应存储的引用计数
110    pub fn add_hash_reference(&mut self, hash: &str, storage_id: &str) {
111        if let Some(existing_storage_id) = self.hash_to_storage.get(hash) {
112            // 验证存储ID是否匹配
113            if existing_storage_id == storage_id {
114                // 增加引用计数
115                *self.ref_counts.entry(storage_id.to_string()).or_insert(0) += 1;
116            }
117        } else {
118            // 如果哈希不存在,这可能是一个错误状态,但我们可以尝试修复
119            self.hash_to_storage.insert(hash.to_string(), storage_id.to_string());
120            self.storage_to_hash.insert(storage_id.to_string(), hash.to_string());
121            *self.ref_counts.entry(storage_id.to_string()).or_insert(0) += 1;
122        }
123    }
124
125    /// 获取文件的去重信息
126    pub fn get_dedup_info(&self, storage_id: &str) -> Option<DedupInfo> {
127        if let Some(hash) = self.storage_to_hash.get(storage_id) {
128            let ref_count = self.ref_counts.get(storage_id).copied().unwrap_or(0);
129            Some(DedupInfo {
130                is_reference: ref_count > 1,
131                original_storage_id: None, // 对于原文件,这个字段为None
132                hash: hash.clone(),
133                ref_count,
134            })
135        } else {
136            None
137        }
138    }
139
140    /// 获取引用信息(用于引用文件)
141    pub fn get_reference_info(&self, hash: &str) -> Option<DedupInfo> {
142        if let Some(storage_id) = self.hash_to_storage.get(hash) {
143            let ref_count = self.ref_counts.get(storage_id).copied().unwrap_or(0);
144            Some(DedupInfo {
145                is_reference: true,
146                original_storage_id: Some(storage_id.clone()),
147                hash: hash.to_string(),
148                ref_count,
149            })
150        } else {
151            None
152        }
153    }
154
155    /// 获取所有存储的统计信息
156    pub fn get_stats(&self) -> DedupStats {
157        let total_files = self.ref_counts.values().sum::<u32>();
158        let unique_files = self.ref_counts.len() as u32;
159        let duplicate_files = total_files.saturating_sub(unique_files);
160        
161        DedupStats {
162            total_files,
163            unique_files,
164            duplicate_files,
165            dedup_ratio: if total_files > 0 {
166                duplicate_files as f32 / total_files as f32
167            } else {
168                0.0
169            },
170        }
171    }
172
173    /// 从索引数据重建去重器状态
174    pub fn rebuild_from_index(&mut self, entries: Vec<(String, String, u32)>) -> Result<()> {
175        // entries: (storage_id, hash, ref_count)
176        self.hash_to_storage.clear();
177        self.ref_counts.clear();
178        self.storage_to_hash.clear();
179
180        for (storage_id, hash, ref_count) in entries {
181            self.hash_to_storage.insert(hash.clone(), storage_id.clone());
182            self.storage_to_hash.insert(storage_id.clone(), hash);
183            self.ref_counts.insert(storage_id, ref_count);
184        }
185
186        Ok(())
187    }
188}
189
190impl Default for ContentDeduplicator {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196/// 去重统计信息
197#[derive(Debug, Clone)]
198pub struct DedupStats {
199    /// 总文件数(包括重复)
200    pub total_files: u32,
201    /// 唯一文件数
202    pub unique_files: u32,
203    /// 重复文件数
204    pub duplicate_files: u32,
205    /// 去重率(重复文件数/总文件数)
206    pub dedup_ratio: f32,
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_deduplicator_basic() {
215        let mut dedup = ContentDeduplicator::new();
216        
217        // 测试新文件
218        let hash1 = "abc123".to_string();
219        assert_eq!(dedup.check_duplicate(&hash1), None);
220        
221        // 注册文件
222        dedup.register_file(hash1.clone(), "storage1".to_string());
223        
224        // 测试重复文件
225        assert_eq!(dedup.check_duplicate(&hash1), Some("storage1".to_string()));
226        
227        // 测试引用计数
228        let info = dedup.get_dedup_info("storage1").unwrap();
229        assert_eq!(info.ref_count, 2); // 1 original + 1 reference
230    }
231
232    #[test]
233    fn test_hash_calculation() {
234        let data = b"Hello, World!";
235        let hash = ContentDeduplicator::calculate_hash(data);
236        assert!(!hash.is_empty());
237        assert_eq!(hash.len(), 64); // SHA256 produces 64-character hex string
238    }
239
240    #[test]
241    fn test_remove_reference() {
242        let mut dedup = ContentDeduplicator::new();
243        
244        dedup.register_file("hash1".to_string(), "storage1".to_string());
245        dedup.check_duplicate("hash1"); // 增加一个引用
246        
247        // 移除一个引用,应该不删除文件
248        assert!(!dedup.remove_reference("storage1"));
249        
250        // 移除最后一个引用,应该删除文件
251        assert!(dedup.remove_reference("storage1"));
252    }
253
254    #[test]
255    fn test_remove_reference_by_hash() {
256        let mut dedup = ContentDeduplicator::new();
257        
258        dedup.register_file("hash1".to_string(), "storage1".to_string());
259        dedup.check_duplicate("hash1"); // 增加一个引用
260        
261        // 通过哈希值移除引用,应该不删除文件
262        assert!(!dedup.remove_hash_reference("hash1"));
263        
264        // 通过哈希值移除最后一个引用,应该删除文件
265        assert!(dedup.remove_hash_reference("hash1"));
266    }
267
268    #[test]
269    fn test_add_reference_by_hash() {
270        let mut dedup = ContentDeduplicator::new();
271        
272        dedup.register_file("hash1".to_string(), "storage1".to_string());
273        
274        // 通过哈希值增加引用
275        dedup.add_hash_reference("hash1", "storage1");
276        
277        // 引用计数应该增加
278        let info = dedup.get_dedup_info("storage1").unwrap();
279        assert_eq!(info.ref_count, 2);
280        
281        // 对于不存在的哈希,增加引用应该创建新的映射
282        dedup.add_hash_reference("hash2", "storage2");
283        assert_eq!(dedup.hash_to_storage.get("hash2"), Some(&"storage2".to_string()));
284    }
285}