Skip to main content

oxigeo_streaming/io/
buffer.rs

1//! Buffer management for chunked I/O operations.
2
3use crate::error::{Result, StreamingError};
4use bytes::Bytes;
5use std::collections::VecDeque;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8use tracing::debug;
9
10/// Descriptor for a data chunk.
11#[derive(Debug, Clone)]
12pub struct ChunkDescriptor {
13    /// Offset in bytes from the start of the data
14    pub offset: u64,
15
16    /// Length of the chunk in bytes
17    pub length: usize,
18
19    /// Chunk index
20    pub index: usize,
21
22    /// Total number of chunks
23    pub total_chunks: usize,
24
25    /// Whether this is the last chunk
26    pub is_last: bool,
27}
28
29impl ChunkDescriptor {
30    /// Create a new chunk descriptor.
31    pub fn new(offset: u64, length: usize, index: usize, total_chunks: usize) -> Self {
32        Self {
33            offset,
34            length,
35            index,
36            total_chunks,
37            is_last: index + 1 == total_chunks,
38        }
39    }
40
41    /// Get the end offset of this chunk.
42    pub fn end_offset(&self) -> u64 {
43        self.offset + self.length as u64
44    }
45}
46
47/// A buffer that manages chunked data.
48pub struct ChunkedBuffer {
49    /// The underlying buffer
50    inner: Arc<RwLock<ChunkedBufferInner>>,
51
52    /// Chunk size in bytes
53    chunk_size: usize,
54
55    /// Maximum buffer size in bytes
56    max_size: usize,
57}
58
59struct ChunkedBufferInner {
60    /// Queue of buffered chunks
61    chunks: VecDeque<BufferedChunk>,
62
63    /// Current size in bytes
64    current_size: usize,
65
66    /// Next chunk index to read
67    next_read_index: usize,
68
69    /// Next chunk index to write
70    next_write_index: usize,
71
72    /// Total number of chunks
73    total_chunks: Option<usize>,
74
75    /// Whether writing is complete
76    write_complete: bool,
77}
78
79struct BufferedChunk {
80    descriptor: ChunkDescriptor,
81    data: Bytes,
82}
83
84impl ChunkedBuffer {
85    /// Create a new chunked buffer.
86    pub fn new(chunk_size: usize, max_size: usize) -> Self {
87        Self {
88            inner: Arc::new(RwLock::new(ChunkedBufferInner {
89                chunks: VecDeque::new(),
90                current_size: 0,
91                next_read_index: 0,
92                next_write_index: 0,
93                total_chunks: None,
94                write_complete: false,
95            })),
96            chunk_size,
97            max_size,
98        }
99    }
100
101    /// Create a new chunked buffer with default settings.
102    pub fn with_defaults() -> Self {
103        Self::new(1024 * 1024, 100 * 1024 * 1024) // 1MB chunks, 100MB max
104    }
105
106    /// Calculate the number of chunks needed for a given size.
107    pub fn calculate_chunks(&self, total_size: u64) -> usize {
108        total_size.div_ceil(self.chunk_size as u64) as usize
109    }
110
111    /// Get a chunk descriptor for a given index.
112    pub fn descriptor_for_index(&self, index: usize, total_size: u64) -> ChunkDescriptor {
113        let total_chunks = self.calculate_chunks(total_size);
114        let offset = (index as u64) * (self.chunk_size as u64);
115        let remaining = total_size.saturating_sub(offset);
116        let length = remaining.min(self.chunk_size as u64) as usize;
117
118        ChunkDescriptor::new(offset, length, index, total_chunks)
119    }
120
121    /// Push a chunk into the buffer.
122    pub async fn push(&self, descriptor: ChunkDescriptor, data: Bytes) -> Result<()> {
123        let mut inner = self.inner.write().await;
124
125        // Check if buffer is full
126        if inner.current_size + data.len() > self.max_size {
127            return Err(StreamingError::BufferFull);
128        }
129
130        // Verify chunk index
131        if descriptor.index != inner.next_write_index {
132            return Err(StreamingError::InvalidOperation(format!(
133                "Expected chunk {}, got {}",
134                inner.next_write_index, descriptor.index
135            )));
136        }
137
138        inner.chunks.push_back(BufferedChunk {
139            descriptor: descriptor.clone(),
140            data,
141        });
142
143        inner.current_size += descriptor.length;
144        inner.next_write_index += 1;
145
146        if let Some(total) = inner.total_chunks {
147            if descriptor.index + 1 == total {
148                inner.write_complete = true;
149            }
150        } else if descriptor.is_last {
151            inner.total_chunks = Some(descriptor.total_chunks);
152            inner.write_complete = true;
153        }
154
155        debug!(
156            "Pushed chunk {} ({} bytes), buffer size: {}",
157            descriptor.index, descriptor.length, inner.current_size
158        );
159
160        Ok(())
161    }
162
163    /// Pop a chunk from the buffer.
164    pub async fn pop(&self) -> Result<Option<(ChunkDescriptor, Bytes)>> {
165        let mut inner = self.inner.write().await;
166
167        if inner.chunks.is_empty() {
168            if inner.write_complete {
169                return Ok(None);
170            } else {
171                return Err(StreamingError::Other("No chunks available".to_string()));
172            }
173        }
174
175        let chunk = inner
176            .chunks
177            .pop_front()
178            .ok_or_else(|| StreamingError::Other("Failed to pop chunk".to_string()))?;
179
180        inner.current_size = inner.current_size.saturating_sub(chunk.descriptor.length);
181        inner.next_read_index += 1;
182
183        debug!(
184            "Popped chunk {} ({} bytes), buffer size: {}",
185            chunk.descriptor.index, chunk.descriptor.length, inner.current_size
186        );
187
188        Ok(Some((chunk.descriptor, chunk.data)))
189    }
190
191    /// Peek at the next chunk without removing it.
192    pub async fn peek(&self) -> Result<Option<ChunkDescriptor>> {
193        let inner = self.inner.read().await;
194        Ok(inner.chunks.front().map(|c| c.descriptor.clone()))
195    }
196
197    /// Get the number of chunks currently in the buffer.
198    pub async fn len(&self) -> usize {
199        let inner = self.inner.read().await;
200        inner.chunks.len()
201    }
202
203    /// Check if the buffer is empty.
204    pub async fn is_empty(&self) -> bool {
205        let inner = self.inner.read().await;
206        inner.chunks.is_empty()
207    }
208
209    /// Move the write cursor to `index`, but only while the buffer is drained.
210    ///
211    /// [`Self::push`] enforces a strictly sequential producer: a chunk is only
212    /// accepted when its index equals the internal write cursor. A *pull-based*
213    /// consumer such as [`crate::io::ChunkedReader`] serves a buffer miss with a
214    /// direct read that deliberately bypasses this buffer, which leaves the
215    /// write cursor lagging behind the stream and makes every later read-ahead
216    /// `push` fail with [`StreamingError::InvalidOperation`]. Re-basing while
217    /// the buffer holds nothing lets read-ahead resume at the consumer's real
218    /// position without weakening the ordering check for buffered chunks.
219    ///
220    /// Returns `true` when the cursor was moved, `false` when the buffer still
221    /// holds chunks (in which case re-basing would reorder them and is refused).
222    pub async fn rebase_if_empty(&self, index: usize) -> bool {
223        let mut inner = self.inner.write().await;
224        if !inner.chunks.is_empty() {
225            return false;
226        }
227        inner.next_write_index = index;
228        inner.next_read_index = index;
229        debug!("Buffer write cursor re-based to chunk {index}");
230        true
231    }
232
233    /// Get the current buffer size in bytes.
234    pub async fn size_bytes(&self) -> usize {
235        let inner = self.inner.read().await;
236        inner.current_size
237    }
238
239    /// Check if writing is complete.
240    pub async fn is_complete(&self) -> bool {
241        let inner = self.inner.read().await;
242        inner.write_complete
243    }
244
245    /// Clear all chunks from the buffer.
246    pub async fn clear(&self) {
247        let mut inner = self.inner.write().await;
248        inner.chunks.clear();
249        inner.current_size = 0;
250        debug!("Buffer cleared");
251    }
252
253    /// Get buffer statistics.
254    pub async fn stats(&self) -> BufferStats {
255        let inner = self.inner.read().await;
256        BufferStats {
257            chunks_buffered: inner.chunks.len(),
258            bytes_buffered: inner.current_size,
259            max_bytes: self.max_size,
260            utilization: (inner.current_size as f64) / (self.max_size as f64),
261            chunks_read: inner.next_read_index,
262            chunks_written: inner.next_write_index,
263            total_chunks: inner.total_chunks,
264            complete: inner.write_complete,
265        }
266    }
267}
268
269/// Buffer statistics.
270#[derive(Debug, Clone)]
271pub struct BufferStats {
272    /// Number of chunks currently buffered
273    pub chunks_buffered: usize,
274
275    /// Number of bytes currently buffered
276    pub bytes_buffered: usize,
277
278    /// Maximum buffer size in bytes
279    pub max_bytes: usize,
280
281    /// Buffer utilization (0.0 to 1.0)
282    pub utilization: f64,
283
284    /// Number of chunks read
285    pub chunks_read: usize,
286
287    /// Number of chunks written
288    pub chunks_written: usize,
289
290    /// Total number of chunks (if known)
291    pub total_chunks: Option<usize>,
292
293    /// Whether writing is complete
294    pub complete: bool,
295}
296
297impl BufferStats {
298    /// Calculate progress percentage.
299    pub fn progress(&self) -> Option<f64> {
300        self.total_chunks.map(|total| {
301            if total == 0 {
302                100.0
303            } else {
304                (self.chunks_read as f64 / total as f64) * 100.0
305            }
306        })
307    }
308}
309
310/// A circular buffer for efficient chunk management.
311pub struct CircularChunkBuffer {
312    /// The underlying buffer
313    buffer: Vec<u8>,
314
315    /// Read position
316    read_pos: usize,
317
318    /// Write position
319    write_pos: usize,
320
321    /// Number of bytes available
322    available: usize,
323
324    /// Buffer capacity
325    capacity: usize,
326}
327
328impl CircularChunkBuffer {
329    /// Create a new circular buffer.
330    pub fn new(capacity: usize) -> Self {
331        Self {
332            buffer: vec![0; capacity],
333            read_pos: 0,
334            write_pos: 0,
335            available: 0,
336            capacity,
337        }
338    }
339
340    /// Write data to the buffer.
341    pub fn write(&mut self, data: &[u8]) -> Result<usize> {
342        let space_available = self.capacity - self.available;
343        let to_write = data.len().min(space_available);
344
345        if to_write == 0 {
346            return Ok(0);
347        }
348
349        let end_pos = self.write_pos + to_write;
350        if end_pos <= self.capacity {
351            // Simple case: write doesn't wrap
352            self.buffer[self.write_pos..end_pos].copy_from_slice(&data[..to_write]);
353            self.write_pos = end_pos % self.capacity;
354        } else {
355            // Write wraps around
356            let first_part = self.capacity - self.write_pos;
357            self.buffer[self.write_pos..].copy_from_slice(&data[..first_part]);
358            self.buffer[..to_write - first_part].copy_from_slice(&data[first_part..to_write]);
359            self.write_pos = to_write - first_part;
360        }
361
362        self.available += to_write;
363        Ok(to_write)
364    }
365
366    /// Read data from the buffer.
367    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
368        let to_read = buf.len().min(self.available);
369
370        if to_read == 0 {
371            return Ok(0);
372        }
373
374        let end_pos = self.read_pos + to_read;
375        if end_pos <= self.capacity {
376            // Simple case: read doesn't wrap
377            buf[..to_read].copy_from_slice(&self.buffer[self.read_pos..end_pos]);
378            self.read_pos = end_pos % self.capacity;
379        } else {
380            // Read wraps around
381            let first_part = self.capacity - self.read_pos;
382            buf[..first_part].copy_from_slice(&self.buffer[self.read_pos..]);
383            buf[first_part..to_read].copy_from_slice(&self.buffer[..to_read - first_part]);
384            self.read_pos = to_read - first_part;
385        }
386
387        self.available -= to_read;
388        Ok(to_read)
389    }
390
391    /// Get the number of bytes available to read.
392    pub fn available(&self) -> usize {
393        self.available
394    }
395
396    /// Get the amount of free space in the buffer.
397    pub fn space_available(&self) -> usize {
398        self.capacity - self.available
399    }
400
401    /// Check if the buffer is empty.
402    pub fn is_empty(&self) -> bool {
403        self.available == 0
404    }
405
406    /// Check if the buffer is full.
407    pub fn is_full(&self) -> bool {
408        self.available == self.capacity
409    }
410
411    /// Clear the buffer.
412    pub fn clear(&mut self) {
413        self.read_pos = 0;
414        self.write_pos = 0;
415        self.available = 0;
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[tokio::test]
424    async fn test_chunked_buffer() {
425        let buffer = ChunkedBuffer::new(1024, 10240);
426
427        let desc = ChunkDescriptor::new(0, 1024, 0, 10);
428        let data = Bytes::from(vec![0u8; 1024]);
429
430        buffer.push(desc.clone(), data.clone()).await.ok();
431
432        assert_eq!(buffer.len().await, 1);
433        assert_eq!(buffer.size_bytes().await, 1024);
434
435        let popped = buffer.pop().await.ok().flatten();
436        assert!(popped.is_some());
437
438        assert_eq!(buffer.len().await, 0);
439    }
440
441    #[test]
442    fn test_circular_buffer() {
443        let mut buffer = CircularChunkBuffer::new(10);
444
445        // Write some data
446        let written = buffer.write(&[1, 2, 3, 4, 5]).ok();
447        assert_eq!(written, Some(5));
448        assert_eq!(buffer.available(), 5);
449
450        // Read some data
451        let mut read_buf = [0u8; 3];
452        let read = buffer.read(&mut read_buf).ok();
453        assert_eq!(read, Some(3));
454        assert_eq!(read_buf, [1, 2, 3]);
455        assert_eq!(buffer.available(), 2);
456
457        // Write more data (should wrap)
458        let written = buffer.write(&[6, 7, 8, 9, 10]).ok();
459        assert_eq!(written, Some(5));
460        assert_eq!(buffer.available(), 7);
461    }
462
463    #[test]
464    fn test_chunk_descriptor() {
465        let desc = ChunkDescriptor::new(0, 1024, 0, 10);
466        assert_eq!(desc.offset, 0);
467        assert_eq!(desc.length, 1024);
468        assert_eq!(desc.end_offset(), 1024);
469        assert!(!desc.is_last);
470
471        let last_desc = ChunkDescriptor::new(9216, 1024, 9, 10);
472        assert!(last_desc.is_last);
473    }
474}