use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use crate::error::{Error, Result};
#[derive(Debug)]
pub struct DigestWriter {
path: PathBuf,
}
impl DigestWriter {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn write(&self, crc32_value: u32) -> Result<()> {
let file = File::create(&self.path).map_err(|e| {
Error::storage(format!(
"Failed to create Digest.crc32 file {:?}: {}",
self.path, e
))
})?;
let mut writer = BufWriter::new(file);
write!(writer, "{}", crc32_value).map_err(|e| {
Error::storage(format!(
"Failed to write CRC32 value to Digest.crc32: {}",
e
))
})?;
writer
.flush()
.map_err(|e| Error::storage(format!("Failed to flush Digest.crc32: {}", e)))?;
let file = writer
.into_inner()
.map_err(|e| Error::storage(format!("Failed to extract file from buffer: {}", e)))?;
file.sync_all()
.map_err(|e| Error::storage(format!("Failed to sync Digest.crc32 to disk: {}", e)))?;
Ok(())
}
pub fn compute_crc32(file_path: &PathBuf) -> Result<u32> {
use std::io::Read;
let mut file = File::open(file_path).map_err(|e| {
Error::storage(format!(
"Failed to open file {:?} for CRC32: {}",
file_path, e
))
})?;
let mut hasher = crc32fast::Hasher::new();
let mut buffer = vec![0u8; 64 * 1024];
loop {
let bytes_read = file.read(&mut buffer).map_err(|e| {
Error::storage(format!(
"Failed to read file {:?} for CRC32: {}",
file_path, e
))
})?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
Ok(hasher.finalize())
}
pub fn write_for_file(&self, component_path: &PathBuf) -> Result<u32> {
let crc32 = Self::compute_crc32(component_path)?;
self.write(crc32)?;
Ok(crc32)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::io::Write as StdWrite;
use tempfile::TempDir;
#[test]
fn test_digest_writer_basic() {
let temp_dir = TempDir::new().unwrap();
let digest_path = temp_dir.path().join("nb-1-big-Digest.crc32");
let writer = DigestWriter::new(digest_path.clone());
writer.write(1041978312).unwrap();
assert!(digest_path.exists());
let contents = fs::read_to_string(&digest_path).unwrap();
assert_eq!(contents, "1041978312");
let bytes = fs::read(&digest_path).unwrap();
assert_eq!(bytes.len(), 10); assert_ne!(bytes[bytes.len() - 1], b'\n');
}
#[test]
fn test_digest_writer_zero() {
let temp_dir = TempDir::new().unwrap();
let digest_path = temp_dir.path().join("nb-1-big-Digest.crc32");
let writer = DigestWriter::new(digest_path.clone());
writer.write(0).unwrap();
let contents = fs::read_to_string(&digest_path).unwrap();
assert_eq!(contents, "0");
}
#[test]
fn test_digest_writer_max_value() {
let temp_dir = TempDir::new().unwrap();
let digest_path = temp_dir.path().join("nb-1-big-Digest.crc32");
let writer = DigestWriter::new(digest_path.clone());
writer.write(u32::MAX).unwrap();
let contents = fs::read_to_string(&digest_path).unwrap();
assert_eq!(contents, format!("{}", u32::MAX));
}
#[test]
fn test_compute_crc32() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.db");
let test_data = b"Hello, World! This is test data for CRC32.";
fs::write(&test_file, test_data).unwrap();
let crc32 = DigestWriter::compute_crc32(&test_file).unwrap();
let expected_crc32 = crc32fast::hash(test_data);
assert_eq!(crc32, expected_crc32);
}
#[test]
fn test_compute_crc32_empty_file() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("empty.db");
File::create(&test_file).unwrap();
let crc32 = DigestWriter::compute_crc32(&test_file).unwrap();
assert_eq!(crc32, 0);
}
#[test]
fn test_compute_crc32_large_file() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("large.db");
let mut file = File::create(&test_file).unwrap();
let chunk = vec![0x42u8; 1024]; for _ in 0..100 {
file.write_all(&chunk).unwrap();
}
file.sync_all().unwrap();
drop(file);
let crc32 = DigestWriter::compute_crc32(&test_file).unwrap();
let expected_chunk = vec![0x42u8; 1024];
let mut hasher = crc32fast::Hasher::new();
for _ in 0..100 {
hasher.update(&expected_chunk);
}
let expected_crc32 = hasher.finalize();
assert_eq!(crc32, expected_crc32);
}
#[test]
fn test_write_for_file() {
let temp_dir = TempDir::new().unwrap();
let data_file = temp_dir.path().join("nb-1-big-Data.db");
let digest_file = temp_dir.path().join("nb-1-big-Digest.crc32");
let test_data = b"Test SSTable data component";
fs::write(&data_file, test_data).unwrap();
let writer = DigestWriter::new(digest_file.clone());
let crc32 = writer.write_for_file(&data_file).unwrap();
let contents = fs::read_to_string(&digest_file).unwrap();
assert_eq!(contents, format!("{}", crc32));
let expected_crc32 = crc32fast::hash(test_data);
assert_eq!(crc32, expected_crc32);
}
#[test]
fn test_compute_crc32_nonexistent_file() {
let temp_dir = TempDir::new().unwrap();
let nonexistent = temp_dir.path().join("does-not-exist.db");
let result = DigestWriter::compute_crc32(&nonexistent);
assert!(result.is_err());
}
}