1use super::buffer::{ChunkDescriptor, ChunkedBuffer};
4use crate::error::{Result, StreamingError};
5use async_trait::async_trait;
6use bytes::Bytes;
7use std::path::{Path, PathBuf};
8use tokio::fs::File;
9use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
10use tracing::{debug, info};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ChunkStrategy {
15 FixedSize(usize),
17
18 Adaptive {
20 min_size: usize,
22 max_size: usize,
24 target_memory: usize,
26 },
27
28 LineBased {
30 max_lines: usize,
32 max_bytes: usize,
34 },
35}
36
37impl ChunkStrategy {
38 pub fn chunk_size_for_index(&self, _index: usize, available_memory: usize) -> usize {
40 match self {
41 ChunkStrategy::FixedSize(size) => *size,
42 ChunkStrategy::Adaptive {
43 min_size,
44 max_size,
45 target_memory,
46 } => {
47 let target_size = available_memory.min(*target_memory);
49 target_size.max(*min_size).min(*max_size)
50 }
51 ChunkStrategy::LineBased { max_bytes, .. } => *max_bytes,
52 }
53 }
54}
55
56impl Default for ChunkStrategy {
57 fn default() -> Self {
58 ChunkStrategy::FixedSize(1024 * 1024) }
60}
61
62#[async_trait]
64pub trait ChunkedIO: Send + Sync {
65 async fn read_chunk(&mut self, descriptor: &ChunkDescriptor) -> Result<Bytes>;
67
68 async fn write_chunk(&mut self, descriptor: &ChunkDescriptor, data: Bytes) -> Result<()>;
70
71 async fn total_size(&self) -> Result<u64>;
73
74 async fn flush(&mut self) -> Result<()>;
76}
77
78pub struct FileChunkedIO {
80 path: PathBuf,
82
83 read_file: Option<File>,
85
86 write_file: Option<File>,
88
89 #[allow(dead_code)]
91 strategy: ChunkStrategy,
92
93 direct_io: bool,
95}
96
97impl FileChunkedIO {
98 pub async fn new<P: AsRef<Path>>(path: P, strategy: ChunkStrategy) -> Result<Self> {
100 let path = path.as_ref().to_path_buf();
101
102 Ok(Self {
103 path,
104 read_file: None,
105 write_file: None,
106 strategy,
107 direct_io: false,
108 })
109 }
110
111 pub async fn open_read(&mut self) -> Result<()> {
113 if self.read_file.is_some() {
114 return Ok(());
115 }
116
117 let file = File::open(&self.path).await.map_err(StreamingError::Io)?;
118
119 info!("Opened file for reading: {:?}", self.path);
120 self.read_file = Some(file);
121 Ok(())
122 }
123
124 pub async fn open_write(&mut self) -> Result<()> {
126 if self.write_file.is_some() {
127 return Ok(());
128 }
129
130 let file = File::create(&self.path).await.map_err(StreamingError::Io)?;
131
132 info!("Opened file for writing: {:?}", self.path);
133 self.write_file = Some(file);
134 Ok(())
135 }
136
137 pub fn with_direct_io(mut self, enable: bool) -> Self {
139 self.direct_io = enable;
140 self
141 }
142}
143
144#[async_trait]
145impl ChunkedIO for FileChunkedIO {
146 async fn read_chunk(&mut self, descriptor: &ChunkDescriptor) -> Result<Bytes> {
147 if self.read_file.is_none() {
148 self.open_read().await?;
149 }
150
151 let file = self
152 .read_file
153 .as_mut()
154 .ok_or_else(|| StreamingError::InvalidState("File not open".to_string()))?;
155
156 file.seek(SeekFrom::Start(descriptor.offset))
158 .await
159 .map_err(StreamingError::Io)?;
160
161 let mut buffer = vec![0u8; descriptor.length];
163 let bytes_read = file
164 .read_exact(&mut buffer)
165 .await
166 .map_err(StreamingError::Io)?;
167
168 debug!(
169 "Read chunk {} at offset {} ({} bytes)",
170 descriptor.index, descriptor.offset, bytes_read
171 );
172
173 Ok(Bytes::from(buffer))
174 }
175
176 async fn write_chunk(&mut self, descriptor: &ChunkDescriptor, data: Bytes) -> Result<()> {
177 if self.write_file.is_none() {
178 self.open_write().await?;
179 }
180
181 let file = self
182 .write_file
183 .as_mut()
184 .ok_or_else(|| StreamingError::InvalidState("File not open".to_string()))?;
185
186 file.seek(SeekFrom::Start(descriptor.offset))
188 .await
189 .map_err(StreamingError::Io)?;
190
191 file.write_all(&data).await.map_err(StreamingError::Io)?;
193
194 debug!(
195 "Wrote chunk {} at offset {} ({} bytes)",
196 descriptor.index,
197 descriptor.offset,
198 data.len()
199 );
200
201 Ok(())
202 }
203
204 async fn total_size(&self) -> Result<u64> {
205 let metadata = tokio::fs::metadata(&self.path)
206 .await
207 .map_err(StreamingError::Io)?;
208
209 Ok(metadata.len())
210 }
211
212 async fn flush(&mut self) -> Result<()> {
213 if let Some(file) = &mut self.write_file {
214 file.flush().await.map_err(StreamingError::Io)?;
215 file.sync_all().await.map_err(StreamingError::Io)?;
216 }
217 Ok(())
218 }
219}
220
221pub struct MemoryChunkedIO {
223 buffer: Vec<u8>,
225
226 #[allow(dead_code)]
228 strategy: ChunkStrategy,
229}
230
231impl MemoryChunkedIO {
232 pub fn new(size: usize, strategy: ChunkStrategy) -> Self {
234 Self {
235 buffer: vec![0u8; size],
236 strategy,
237 }
238 }
239
240 pub fn buffer(&self) -> &[u8] {
242 &self.buffer
243 }
244}
245
246#[async_trait]
247impl ChunkedIO for MemoryChunkedIO {
248 async fn read_chunk(&mut self, descriptor: &ChunkDescriptor) -> Result<Bytes> {
249 let start = descriptor.offset as usize;
250 let end = start + descriptor.length;
251
252 if end > self.buffer.len() {
253 return Err(StreamingError::InvalidOperation(
254 "Chunk exceeds buffer size".to_string(),
255 ));
256 }
257
258 let data = self.buffer[start..end].to_vec();
259 Ok(Bytes::from(data))
260 }
261
262 async fn write_chunk(&mut self, descriptor: &ChunkDescriptor, data: Bytes) -> Result<()> {
263 let start = descriptor.offset as usize;
264 let end = start + descriptor.length;
265
266 if end > self.buffer.len() {
267 return Err(StreamingError::InvalidOperation(
268 "Chunk exceeds buffer size".to_string(),
269 ));
270 }
271
272 self.buffer[start..end].copy_from_slice(&data);
273 Ok(())
274 }
275
276 async fn total_size(&self) -> Result<u64> {
277 Ok(self.buffer.len() as u64)
278 }
279
280 async fn flush(&mut self) -> Result<()> {
281 Ok(())
283 }
284}
285
286pub struct CachedChunkedIO<T: ChunkedIO> {
288 inner: T,
290
291 cache: ChunkedBuffer,
293
294 prefetch_count: usize,
296}
297
298impl<T: ChunkedIO> CachedChunkedIO<T> {
299 pub fn new(inner: T, cache_size: usize, prefetch_count: usize) -> Self {
301 Self {
302 inner,
303 cache: ChunkedBuffer::new(1024 * 1024, cache_size),
304 prefetch_count,
305 }
306 }
307
308 pub async fn prefetch(&mut self, start_index: usize, total_size: u64) -> Result<()> {
310 let total_chunks = self.cache.calculate_chunks(total_size);
311
312 for i in 0..self.prefetch_count {
313 let index = start_index + i;
314 if index >= total_chunks {
315 break;
316 }
317
318 let descriptor = self.cache.descriptor_for_index(index, total_size);
319 let data = self.inner.read_chunk(&descriptor).await?;
320 self.cache.push(descriptor, data).await?;
321 }
322
323 Ok(())
324 }
325}
326
327#[async_trait]
328impl<T: ChunkedIO> ChunkedIO for CachedChunkedIO<T> {
329 async fn read_chunk(&mut self, descriptor: &ChunkDescriptor) -> Result<Bytes> {
330 if let Some((_, data)) = self.cache.pop().await? {
332 return Ok(data);
333 }
334
335 self.inner.read_chunk(descriptor).await
337 }
338
339 async fn write_chunk(&mut self, descriptor: &ChunkDescriptor, data: Bytes) -> Result<()> {
340 self.inner.write_chunk(descriptor, data).await
341 }
342
343 async fn total_size(&self) -> Result<u64> {
344 self.inner.total_size().await
345 }
346
347 async fn flush(&mut self) -> Result<()> {
348 self.inner.flush().await
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[tokio::test]
357 async fn test_memory_chunked_io() {
358 let mut io = MemoryChunkedIO::new(10240, ChunkStrategy::FixedSize(1024));
359
360 let descriptor = ChunkDescriptor::new(0, 1024, 0, 10);
361 let data = Bytes::from(vec![42u8; 1024]);
362
363 io.write_chunk(&descriptor, data.clone()).await.ok();
364
365 let read_data = io.read_chunk(&descriptor).await.ok();
366 assert!(read_data.is_some());
367 assert_eq!(read_data.expect("chunk read should succeed").len(), 1024);
368 }
369
370 #[test]
371 fn test_chunk_strategy() {
372 let strategy = ChunkStrategy::FixedSize(1024);
373 assert_eq!(strategy.chunk_size_for_index(0, 2048), 1024);
374
375 let adaptive = ChunkStrategy::Adaptive {
376 min_size: 512,
377 max_size: 2048,
378 target_memory: 1024,
379 };
380 assert_eq!(adaptive.chunk_size_for_index(0, 1500), 1024);
382 assert_eq!(adaptive.chunk_size_for_index(0, 500), 512);
384 assert_eq!(adaptive.chunk_size_for_index(0, 3000), 1024);
387 }
388}