1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
use crate::{
archive::{ArchiveHeader, Entry, EntryPart, SolidEntries, PNA_HEADER},
chunk::{ChunkType, ChunkWriter},
};
use std::io::{self, Write};
/// A writer for Portable-Network-Archive.
pub struct ArchiveWriter<W: Write> {
w: W,
archive_number: u32,
}
impl<W: Write> ArchiveWriter<W> {
/// Writes the PNA archive header to the given `Write` object.
///
/// # Arguments
///
/// * `write` - The `Write` object to write the header to.
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
/// use libpna::ArchiveWriter;
///
/// let file = File::create("example.pna").unwrap();
/// let mut archive_writer = ArchiveWriter::write_header(file).unwrap();
/// archive_writer.finalize().unwrap();
/// ```
pub fn write_header(write: W) -> io::Result<Self> {
Self::write_header_with_archive_number(write, 0)
}
fn write_header_with_archive_number(mut write: W, archive_number: u32) -> io::Result<Self> {
write.write_all(PNA_HEADER)?;
let mut chunk_writer = ChunkWriter::from(write);
chunk_writer.write_chunk((
ChunkType::AHED,
ArchiveHeader::new(0, 0, archive_number)
.to_bytes()
.as_slice(),
))?;
Ok(Self {
w: chunk_writer.into_inner(),
archive_number,
})
}
/// Adds solid entries to the current writer.
///
/// This function takes an `entries` that implement the `SolidEntries` trait.
/// The `entries` are converted to bytes and written to the writer.
/// The function returns the number of bytes written.
///
/// # Arguments
///
/// * `entries`: An `entries` that implement the `SolidEntries` trait.
///
/// # Returns
///
/// The number of bytes written to the writer.
///
/// # Errors
///
/// This function may return an `io::Error` if the writing operation fails.
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
/// use libpna::{ArchiveWriter, SolidEntriesBuilder, WriteOptionBuilder};
///
/// let file = File::create("example.pna").unwrap();
/// let mut archive_writer = ArchiveWriter::write_header(file).unwrap();
/// let solid_builder = SolidEntriesBuilder::new(WriteOptionBuilder::new().build()).unwrap();
/// let entries = solid_builder.build().unwrap();
/// archive_writer.add_solid_entries(entries).unwrap();
/// archive_writer.finalize().unwrap();
/// ```
pub fn add_solid_entries(&mut self, entries: impl SolidEntries) -> io::Result<usize> {
let bytes = entries.into_bytes();
self.w.write_all(&bytes)?;
Ok(bytes.len())
}
/// Adds a new entry to the archive.
///
/// # Arguments
///
/// * `entry` - The entry to add to the archive.
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
/// use libpna::{ArchiveWriter, EntryBuilder, WriteOptionBuilder};
///
/// let file = File::create("example.pna").unwrap();
/// let mut archive_writer = ArchiveWriter::write_header(file).unwrap();
/// archive_writer.add_entry(EntryBuilder::new_file("example.txt".into(), WriteOptionBuilder::new().build()).unwrap().build().unwrap()).unwrap();
/// archive_writer.finalize().unwrap();
/// ```
pub fn add_entry(&mut self, entry: impl Entry) -> io::Result<usize> {
let bytes = entry.into_bytes();
self.w.write_all(&bytes)?;
Ok(bytes.len())
}
/// Adds a part of an entry to the archive.
///
/// # Arguments
///
/// * `entry_part` - The part of an entry to add to the archive.
///
/// # Examples
///
/// ```no_run
/// use std::fs::File;
/// use libpna::{ArchiveWriter, EntryPart, EntryBuilder, WriteOptionBuilder};
///
/// let part1_file = File::create("example.part1.pna").unwrap();
/// let mut part1_writer = ArchiveWriter::write_header(part1_file).unwrap();
/// let entry = EntryBuilder::new_file("example.txt".into(), WriteOptionBuilder::new().build()).unwrap().build().unwrap();
/// part1_writer.add_entry_part(EntryPart::from(entry)).unwrap();
///
/// let part2_file = File::create("example.part2.pna").unwrap();
/// let part2_writer = part1_writer.split_to_next_archive(part2_file).unwrap();
/// part2_writer.finalize().unwrap();
/// ```
pub fn add_entry_part(&mut self, entry_part: EntryPart) -> io::Result<usize> {
let mut chunk_writer = ChunkWriter::from(&mut self.w);
let mut written_len = 0;
for chunk in entry_part.0 {
written_len += chunk_writer.write_chunk(chunk)?;
}
Ok(written_len)
}
fn add_next_archive_marker(&mut self) -> io::Result<usize> {
let mut chunk_writer = ChunkWriter::from(&mut self.w);
chunk_writer.write_chunk((ChunkType::ANXT, [].as_slice()))
}
pub fn split_to_next_archive<OW: Write>(mut self, writer: OW) -> io::Result<ArchiveWriter<OW>> {
let next_archive_number = self.archive_number + 1;
self.add_next_archive_marker()?;
self.finalize()?;
ArchiveWriter::write_header_with_archive_number(writer, next_archive_number)
}
pub fn finalize(mut self) -> io::Result<W> {
let mut chunk_writer = ChunkWriter::from(&mut self.w);
chunk_writer.write_chunk((ChunkType::AEND, [].as_slice()))?;
Ok(self.w)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode() {
let writer = ArchiveWriter::write_header(Vec::new()).expect("failed to write header");
let file = writer.finalize().expect("failed to finalize");
let expected = include_bytes!("../../../resources/test/empty.pna");
assert_eq!(file.as_slice(), expected.as_slice());
}
}