hyperloglog/storage/
file.rs1use crate::{HyperLogLog, Result, HllError};
2use crate::storage::Storage;
3use async_trait::async_trait;
4use std::path::{Path, PathBuf};
5use tokio::fs;
6use tokio::io::{AsyncReadExt, AsyncWriteExt};
7
8#[derive(Debug, Clone)]
10pub struct FileStorage {
11 base_path: PathBuf,
12}
13
14impl FileStorage {
15 pub async fn new(base_path: impl AsRef<Path>) -> Result<Self> {
17 let base_path = base_path.as_ref().to_path_buf();
18 fs::create_dir_all(&base_path).await?;
19
20 Ok(Self { base_path })
21 }
22
23 fn key_to_path(&self, key: &str) -> PathBuf {
24 self.base_path.join(format!("{}.hll", key))
25 }
26}
27
28#[async_trait]
29impl Storage for FileStorage {
30 async fn store(&self, key: &str, hll: &HyperLogLog) -> Result<()> {
31 let path = self.key_to_path(key);
32 let serialized = serde_json::to_vec(hll)?;
33
34 let mut file = fs::File::create(&path).await?;
35 file.write_all(&serialized).await?;
36 file.flush().await?;
37
38 Ok(())
39 }
40
41 async fn load(&self, key: &str) -> Result<HyperLogLog> {
42 let path = self.key_to_path(key);
43
44 if !path.exists() {
45 return Err(HllError::NotFound(key.to_string()));
46 }
47
48 let mut file = fs::File::open(&path).await?;
49 let mut contents = Vec::new();
50 file.read_to_end(&mut contents).await?;
51
52 let hll = serde_json::from_slice(&contents)?;
53 Ok(hll)
54 }
55
56 async fn delete(&self, key: &str) -> Result<()> {
57 let path = self.key_to_path(key);
58
59 if path.exists() {
60 fs::remove_file(&path).await?;
61 }
62
63 Ok(())
64 }
65
66 async fn exists(&self, key: &str) -> Result<bool> {
67 let path = self.key_to_path(key);
68 Ok(path.exists())
69 }
70
71 async fn list_keys(&self) -> Result<Vec<String>> {
72 let mut keys = Vec::new();
73 let mut entries = fs::read_dir(&self.base_path).await?;
74
75 while let Some(entry) = entries.next_entry().await? {
76 let path = entry.path();
77 if let Some(ext) = path.extension() {
78 if ext == "hll" {
79 if let Some(stem) = path.file_stem() {
80 if let Some(key) = stem.to_str() {
81 keys.push(key.to_string());
82 }
83 }
84 }
85 }
86 }
87
88 Ok(keys)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[tokio::test]
97 async fn test_file_storage() {
98 let temp_dir = std::env::temp_dir().join("hll_test");
99 let storage = FileStorage::new(&temp_dir).await.unwrap();
100
101 let mut hll = HyperLogLog::new(10).unwrap();
102 hll.add_str("test1");
103 hll.add_str("test2");
104
105 storage.store("test_key", &hll).await.unwrap();
106 assert!(storage.exists("test_key").await.unwrap());
107
108 let loaded = storage.load("test_key").await.unwrap();
109 assert_eq!(loaded.precision(), hll.precision());
110
111 storage.delete("test_key").await.unwrap();
112 assert!(!storage.exists("test_key").await.unwrap());
113
114 let _ = fs::remove_dir_all(&temp_dir).await;
115 }
116}