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());
    }
}