Skip to main content

jax_daemon/
clone_state.rs

1//! Hidden directory state management for cloned buckets
2//!
3//! This module manages the `.jax` directory that tracks the state of cloned buckets
4//! on the local filesystem.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use uuid::Uuid;
10
11use common::crypto::BLAKE3_HASH_SIZE;
12use common::linked_data::{Hash, Link};
13
14/// Name of the hidden directory used to track clone state
15pub const CLONE_STATE_DIR: &str = ".jax";
16
17/// Configuration stored in the .jax directory
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct CloneConfig {
20    /// The bucket ID being cloned
21    pub bucket_id: Uuid,
22    /// The bucket name
23    pub bucket_name: String,
24    /// The last synced manifest link
25    pub last_synced_link: Link,
26    /// The last synced height
27    pub last_synced_height: u64,
28}
29
30/// Mapping of filesystem paths to their content hashes
31/// This allows detecting local changes without full decryption
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct PathHashMap {
34    /// Map from relative path to (blob_hash, plaintext_hash)
35    pub entries: HashMap<PathBuf, (Hash, [u8; BLAKE3_HASH_SIZE])>,
36}
37
38impl PathHashMap {
39    pub fn new() -> Self {
40        Self {
41            entries: HashMap::new(),
42        }
43    }
44
45    pub fn insert(
46        &mut self,
47        path: PathBuf,
48        blob_hash: Hash,
49        plaintext_hash: [u8; BLAKE3_HASH_SIZE],
50    ) {
51        self.entries.insert(path, (blob_hash, plaintext_hash));
52    }
53}
54
55/// Manages the hidden .jax directory state
56pub struct CloneStateManager {
57    /// Root directory of the clone (contains .jax directory)
58    root_dir: PathBuf,
59}
60
61impl CloneStateManager {
62    /// Create a new state manager for a clone directory
63    pub fn new(root_dir: PathBuf) -> Self {
64        Self { root_dir }
65    }
66
67    /// Get the path to the .jax directory
68    pub fn state_dir(&self) -> PathBuf {
69        self.root_dir.join(CLONE_STATE_DIR)
70    }
71
72    /// Get the path to the config file
73    fn config_path(&self) -> PathBuf {
74        self.state_dir().join("config.json")
75    }
76
77    /// Get the path to the hash map file
78    fn hash_map_path(&self) -> PathBuf {
79        self.state_dir().join("hashes.json")
80    }
81
82    /// Initialize the .jax directory for a new clone
83    pub fn init(&self, config: CloneConfig) -> Result<(), CloneStateError> {
84        let state_dir = self.state_dir();
85
86        // Create .jax directory
87        std::fs::create_dir_all(&state_dir)?;
88
89        // Write config
90        self.write_config(&config)?;
91
92        // Initialize empty hash map
93        self.write_hash_map(&PathHashMap::new())?;
94
95        Ok(())
96    }
97
98    /// Check if this directory has been initialized as a clone
99    pub fn is_initialized(&self) -> bool {
100        self.config_path().exists()
101    }
102
103    /// Read the clone configuration
104    pub fn read_config(&self) -> Result<CloneConfig, CloneStateError> {
105        let config_data = std::fs::read_to_string(self.config_path())?;
106        let config: CloneConfig = serde_json::from_str(&config_data)?;
107        Ok(config)
108    }
109
110    /// Write the clone configuration
111    pub fn write_config(&self, config: &CloneConfig) -> Result<(), CloneStateError> {
112        let config_json = serde_json::to_string_pretty(config)?;
113        std::fs::write(self.config_path(), config_json)?;
114        Ok(())
115    }
116
117    /// Write the path hash map
118    pub fn write_hash_map(&self, hash_map: &PathHashMap) -> Result<(), CloneStateError> {
119        let hash_map_json = serde_json::to_string_pretty(hash_map)?;
120        std::fs::write(self.hash_map_path(), hash_map_json)?;
121        Ok(())
122    }
123
124    /// Update the last synced state
125    pub fn update_sync_state(&self, link: Link, height: u64) -> Result<(), CloneStateError> {
126        let mut config = self.read_config()?;
127        config.last_synced_link = link;
128        config.last_synced_height = height;
129        self.write_config(&config)?;
130        Ok(())
131    }
132}
133
134#[derive(Debug, thiserror::Error)]
135pub enum CloneStateError {
136    #[error("I/O error: {0}")]
137    Io(#[from] std::io::Error),
138    #[error("JSON error: {0}")]
139    Json(#[from] serde_json::Error),
140    #[error("Clone directory not initialized (no .jax directory found)")]
141    NotInitialized,
142}