casc_storage/archive/
archive_writer.rs

1//! Archive file writer for creating and appending to archives
2
3use crate::error::Result;
4use std::fs::{File, OpenOptions};
5use std::io::{BufWriter, Seek, SeekFrom, Write};
6use std::path::Path;
7use tracing::debug;
8
9/// Writer for CASC archive files
10pub struct ArchiveWriter {
11    writer: BufWriter<File>,
12    current_offset: u64,
13    archive_id: u16,
14}
15
16impl ArchiveWriter {
17    /// Create a new archive file
18    pub fn create(path: &Path, archive_id: u16) -> Result<Self> {
19        debug!("Creating new archive: {:?}", path);
20
21        let file = OpenOptions::new()
22            .create(true)
23            .write(true)
24            .truncate(true)
25            .open(path)?;
26
27        Ok(Self {
28            writer: BufWriter::new(file),
29            current_offset: 0,
30            archive_id,
31        })
32    }
33
34    /// Open an existing archive for appending
35    pub fn append(path: &Path, archive_id: u16) -> Result<Self> {
36        debug!("Opening archive for append: {:?}", path);
37
38        let file = OpenOptions::new().append(true).open(path)?;
39
40        let current_offset = file.metadata()?.len();
41
42        Ok(Self {
43            writer: BufWriter::new(file),
44            current_offset,
45            archive_id,
46        })
47    }
48
49    /// Write data to the archive and return the offset
50    pub fn write(&mut self, data: &[u8]) -> Result<u64> {
51        let offset = self.current_offset;
52
53        self.writer.write_all(data)?;
54        self.current_offset += data.len() as u64;
55
56        debug!(
57            "Wrote {} bytes to archive {} at offset {:x}",
58            data.len(),
59            self.archive_id,
60            offset
61        );
62
63        Ok(offset)
64    }
65
66    /// Write data with alignment padding
67    pub fn write_aligned(&mut self, data: &[u8], alignment: u64) -> Result<u64> {
68        // Align current offset
69        let padding_needed = if self.current_offset % alignment != 0 {
70            alignment - (self.current_offset % alignment)
71        } else {
72            0
73        };
74
75        if padding_needed > 0 {
76            let padding = vec![0u8; padding_needed as usize];
77            self.writer.write_all(&padding)?;
78            self.current_offset += padding_needed;
79        }
80
81        self.write(data)
82    }
83
84    /// Flush the writer
85    pub fn flush(&mut self) -> Result<()> {
86        self.writer.flush()?;
87        Ok(())
88    }
89
90    /// Get the current offset in the archive
91    pub fn current_offset(&self) -> u64 {
92        self.current_offset
93    }
94
95    /// Get the archive ID
96    pub fn archive_id(&self) -> u16 {
97        self.archive_id
98    }
99
100    /// Seek to a specific position in the archive
101    pub fn seek(&mut self, pos: u64) -> Result<()> {
102        self.writer.seek(SeekFrom::Start(pos))?;
103        self.current_offset = pos;
104        Ok(())
105    }
106
107    /// Get the current size of the archive
108    pub fn size(&mut self) -> Result<u64> {
109        self.flush()?;
110        let metadata = self.writer.get_ref().metadata()?;
111        Ok(metadata.len())
112    }
113}