Skip to main content

commy_sdk_rust/
file_accessor.rs

1//! File accessor abstraction for local and remote variable files
2
3use crate::error::Result;
4use memmap2::Mmap;
5use std::path::PathBuf;
6
7/// Trait for accessing variable file data (local or remote)
8#[async_trait::async_trait]
9pub trait FileAccessor: Send + Sync {
10    /// Read bytes from the file
11    async fn read_bytes(&self, offset: u64, size: u64) -> Result<Vec<u8>>;
12
13    /// Write bytes to the file
14    async fn write_bytes(&self, offset: u64, data: &[u8]) -> Result<()>;
15
16    /// Get total file size
17    async fn file_size(&self) -> Result<u64>;
18
19    /// Check if this is a local file accessor
20    fn is_local(&self) -> bool;
21
22    /// Resize file to new size
23    async fn resize(&self, new_size: u64) -> Result<()>;
24}
25
26/// Local file accessor using memory mapping
27pub struct LocalFileAccessor {
28    file_path: PathBuf,
29    mmap: Mmap,
30}
31
32impl LocalFileAccessor {
33    /// Create a new local file accessor
34    pub async fn new(file_path: PathBuf) -> Result<Self> {
35        let file = std::fs::OpenOptions::new()
36            .read(true)
37            .write(true)
38            .create(true)
39            .open(&file_path)?;
40
41        let mmap = unsafe { memmap2::MmapMut::map_mut(&file)? };
42        let mmap = mmap.make_read_only()?;
43        // Note: mmap keeps the file open via its file descriptor
44
45        Ok(Self {
46            file_path,
47            mmap,
48        })
49    }
50
51    /// Get file path
52    pub fn path(&self) -> &PathBuf {
53        &self.file_path
54    }
55
56    /// Get direct reference to mapped memory (zero-copy)
57    pub fn as_slice(&self) -> &[u8] {
58        &self.mmap[..]
59    }
60}
61
62#[async_trait::async_trait]
63impl FileAccessor for LocalFileAccessor {
64    async fn read_bytes(&self, offset: u64, size: u64) -> Result<Vec<u8>> {
65        let start = offset as usize;
66        let end = (offset + size) as usize;
67
68        if end > self.mmap.len() {
69            return Err(crate::error::CommyError::InvalidOffset(
70                format!("Read extends beyond file bounds"),
71            ));
72        }
73
74        Ok(self.mmap[start..end].to_vec())
75    }
76
77    async fn write_bytes(&self, _offset: u64, _data: &[u8]) -> Result<()> {
78        // Local files are read-only after mapping - writes go through the watcher
79        Err(crate::error::CommyError::InvalidState(
80            "Cannot write directly to local accessor; use file watcher".to_string(),
81        ))
82    }
83
84    async fn file_size(&self) -> Result<u64> {
85        Ok(self.mmap.len() as u64)
86    }
87
88    fn is_local(&self) -> bool {
89        true
90    }
91
92    async fn resize(&self, _new_size: u64) -> Result<()> {
93        Err(crate::error::CommyError::InvalidState(
94            "Cannot resize local mapped file".to_string(),
95        ))
96    }
97}
98
99/// Remote file accessor for WSS-synced data
100pub struct RemoteFileAccessor {
101    /// In-memory buffer containing the file data
102    buffer: std::sync::Arc<tokio::sync::RwLock<Vec<u8>>>,
103}
104
105impl RemoteFileAccessor {
106    /// Create a new remote file accessor
107    pub fn new() -> Self {
108        Self {
109            buffer: std::sync::Arc::new(tokio::sync::RwLock::new(Vec::new())),
110        }
111    }
112
113    /// Update the entire buffer
114    pub async fn update_buffer(&self, data: Vec<u8>) -> Result<()> {
115        let mut buf = self.buffer.write().await;
116        *buf = data;
117        Ok(())
118    }
119
120    /// Get reference to the buffer (for read access)
121    pub async fn get_buffer(&self) -> Vec<u8> {
122        self.buffer.read().await.clone()
123    }
124}
125
126#[async_trait::async_trait]
127impl FileAccessor for RemoteFileAccessor {
128    async fn read_bytes(&self, offset: u64, size: u64) -> Result<Vec<u8>> {
129        let buf = self.buffer.read().await;
130        let start = offset as usize;
131        let end = (offset + size) as usize;
132
133        if end > buf.len() {
134            return Err(crate::error::CommyError::InvalidOffset(
135                format!("Read extends beyond buffer bounds"),
136            ));
137        }
138
139        Ok(buf[start..end].to_vec())
140    }
141
142    async fn write_bytes(&self, offset: u64, data: &[u8]) -> Result<()> {
143        let mut buf = self.buffer.write().await;
144        let start = offset as usize;
145        let end = start + data.len();
146
147        if end > buf.len() {
148            buf.resize(end, 0);
149        }
150
151        buf[start..end].copy_from_slice(data);
152        Ok(())
153    }
154
155    async fn file_size(&self) -> Result<u64> {
156        Ok(self.buffer.read().await.len() as u64)
157    }
158
159    fn is_local(&self) -> bool {
160        false
161    }
162
163    async fn resize(&self, new_size: u64) -> Result<()> {
164        let mut buf = self.buffer.write().await;
165        buf.resize(new_size as usize, 0);
166        Ok(())
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[tokio::test]
175    async fn test_remote_file_accessor() {
176        let accessor = RemoteFileAccessor::new();
177
178        // Update with data
179        accessor
180            .update_buffer(vec![1, 2, 3, 4, 5, 6, 7, 8])
181            .await
182            .unwrap();
183
184        // Read it back
185        let data = accessor.read_bytes(0, 4).await.unwrap();
186        assert_eq!(data, vec![1, 2, 3, 4]);
187
188        // Verify it's not local
189        assert!(!accessor.is_local());
190    }
191
192    #[tokio::test]
193    async fn test_remote_write_bytes() {
194        let accessor = RemoteFileAccessor::new();
195        accessor.update_buffer(vec![0; 8]).await.unwrap();
196
197        accessor
198            .write_bytes(2, &[99, 88, 77])
199            .await
200            .unwrap();
201
202        let data = accessor.read_bytes(0, 8).await.unwrap();
203        assert_eq!(data, vec![0, 0, 99, 88, 77, 0, 0, 0]);
204    }
205}