rabia_persistence/
file_system.rs1use async_trait::async_trait;
2use rabia_core::{persistence::PersistenceLayer, RabiaError, Result};
3use std::path::{Path, PathBuf};
4use tokio::fs;
5
6#[derive(Debug, Clone)]
11pub struct FileSystemPersistence {
12 state_file_path: PathBuf,
13}
14
15impl FileSystemPersistence {
16 pub async fn new<P: AsRef<Path>>(data_dir: P) -> Result<Self> {
27 let data_dir = data_dir.as_ref();
28
29 if !data_dir.exists() {
31 fs::create_dir_all(data_dir).await.map_err(|e| {
32 RabiaError::persistence(format!("Failed to create data directory: {}", e))
33 })?;
34 }
35
36 let state_file_path = data_dir.join("state.dat");
37
38 Ok(Self { state_file_path })
39 }
40
41 pub fn new_sync<P: AsRef<Path>>(data_dir: P) -> Result<Self> {
54 let runtime = tokio::runtime::Runtime::new()
55 .map_err(|e| RabiaError::internal(format!("Failed to create runtime: {}", e)))?;
56 runtime.block_on(Self::new(data_dir))
57 }
58}
59
60#[async_trait]
61impl PersistenceLayer for FileSystemPersistence {
62 async fn save_state(&self, state: &[u8]) -> Result<()> {
63 let temp_file_path = self.state_file_path.with_extension("tmp");
65
66 fs::write(&temp_file_path, state).await.map_err(|e| {
67 RabiaError::persistence(format!("Failed to write state to temp file: {}", e))
68 })?;
69
70 fs::rename(&temp_file_path, &self.state_file_path)
72 .await
73 .map_err(|e| {
74 RabiaError::persistence(format!("Failed to rename temp file to state file: {}", e))
75 })?;
76
77 Ok(())
78 }
79
80 async fn load_state(&self) -> Result<Option<Vec<u8>>> {
81 if !self.state_file_path.exists() {
82 return Ok(None);
83 }
84
85 match fs::read(&self.state_file_path).await {
86 Ok(data) => Ok(Some(data)),
87 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
88 Err(e) => Err(RabiaError::persistence(format!(
89 "Failed to read state file: {}",
90 e
91 ))),
92 }
93 }
94}