diff --git a/src/carrier.rs b/src/carrier.rs index 5db5faf..ca91abe 100644 --- a/src/carrier.rs +++ b/src/carrier.rs @@ -1057,6 +1057,34 @@ impl DavFile for CarrierFile { fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()> { self.touch(); Box::pin(async move { + // V-06: Sparse Writes — Lücke zwischen bisherigem Dateiende und Cursor mit Nullen füllen + if self.cursor > self.file_size { + let target = self.cursor; + while self.file_size < target { + let block_idx = (self.file_size / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; + let offset_in_block = (self.file_size % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; + let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block; + let to_pad = ((target - self.file_size) as usize).min(space_in_block); + + let block_data = self.ensure_block_loaded(block_idx)?; + if block_data.len() < offset_in_block + to_pad { + block_data.resize(offset_in_block + to_pad, 0); + } + + let mut is_full = false; + if let Some((_, ref d, ref mut dirty)) = self.cached_block { + *dirty = true; + is_full = d.len() >= CARRIER_BLOCK_PAYLOAD_SIZE; + } + + self.file_size += to_pad as u64; + + if is_full { + self.flush_cached_block()?; + } + } + } + let mut src = &buf[..]; while !src.is_empty() { diff --git a/src/vfs.rs b/src/vfs.rs index 7c5fa36..41c5bec 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -396,6 +396,34 @@ impl DavFile for SanctumFile { fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()> { self.touch(); Box::pin(async move { + // V-06: Sparse Writes — Lücke zwischen bisherigem Dateiende und Cursor mit Nullen füllen + if self.cursor > self.file_size { + let target = self.cursor; + while self.file_size < target { + let chunk_idx = (self.file_size / CHUNK_SIZE as u64) as u32; + let offset_in_chunk = (self.file_size % CHUNK_SIZE as u64) as usize; + let space_in_chunk = CHUNK_SIZE - offset_in_chunk; + let to_pad = ((target - self.file_size) as usize).min(space_in_chunk); + + let chunk_data = self.ensure_chunk_loaded(chunk_idx)?; + if chunk_data.len() < offset_in_chunk + to_pad { + chunk_data.resize(offset_in_chunk + to_pad, 0); + } + + let mut is_full = false; + if let Some((_, ref d, ref mut dirty)) = self.cached_chunk { + *dirty = true; + is_full = d.len() >= CHUNK_SIZE; + } + + self.file_size += to_pad as u64; + + if is_full { + self.flush_cached_chunk_and_size()?; + } + } + } + let mut src = &buf[..]; while !src.is_empty() { @@ -1488,4 +1516,56 @@ mod tests { let read_bytes = read_file.read_bytes(test_data.len()).await.unwrap(); assert_eq!(&read_bytes[..], test_data); } + + #[tokio::test] + async fn test_v06_sparse_write_zero_fills_gap() { + use std::io::SeekFrom; + + let (fs, _dir) = create_test_fs(true); + let path = DavPath::new("/sparse_test.bin").unwrap(); + + let mut opts = OpenOptions::default(); + opts.write = true; + opts.create_new = true; + let mut file = fs.open(&path, opts).await.unwrap(); + + // 1. Schreibe 5 Bytes am Anfang (Chunk 0) + file.write_bytes(Bytes::copy_from_slice(b"START")) + .await + .unwrap(); + + // 2. Springe weit über das Dateiende hinaus in Chunk 2 (2,5 MB) + let seek_pos = 2 * CHUNK_SIZE as u64 + 500; + file.seek(SeekFrom::Start(seek_pos)).await.unwrap(); + + // 3. Schreibe 3 Bytes an Position seek_pos + file.write_bytes(Bytes::copy_from_slice(b"END")) + .await + .unwrap(); + file.flush().await.unwrap(); + drop(file); + + // 4. Verifiziere Dateigröße + let node = fs.resolve_path("/sparse_test.bin").unwrap().unwrap(); + assert_eq!(node.size, seek_pos + 3); + + // 5. Lese Datei vollständig ein und prüfe Nullen in der Lücke + let mut read_opts = OpenOptions::default(); + read_opts.read = true; + let mut read_file = fs.open(&path, read_opts).await.unwrap(); + + // Anfang lesen + let start_bytes = read_file.read_bytes(5).await.unwrap(); + assert_eq!(&start_bytes[..], b"START"); + + // Lücke in Chunk 0 und 1 muss mit Nullen gefüllt sein + let zero_gap = read_file.read_bytes(1000).await.unwrap(); + assert_eq!(zero_gap.len(), 1000); + assert!(zero_gap.iter().all(|&b| b == 0)); + + // Springe zum Ende und lies die geschriebenen Daten + read_file.seek(SeekFrom::Start(seek_pos)).await.unwrap(); + let end_bytes = read_file.read_bytes(3).await.unwrap(); + assert_eq!(&end_bytes[..], b"END"); + } }