wpress_oxide/
writer.rs

1use crate::{
2    common::{ArchiveError, Header, EOF_BLOCK},
3    FileParseError,
4};
5use std::{
6    fs::File,
7    io::{copy, Write},
8    path::{Path, PathBuf},
9};
10
11/// Structure to write multiple files and corresponding metadata into a wpress archive.
12pub struct Writer {
13    file: std::fs::File,
14    paths: Vec<PathBuf>,
15}
16impl Writer {
17    /// Creates a new `Writer` with the destination being the path supplied.
18    pub fn new<P: AsRef<Path>>(path: P) -> Result<Writer, ArchiveError> {
19        Ok(Writer {
20            file: File::create(path).map_err(ArchiveError::FileCreation)?,
21            paths: vec![],
22        })
23    }
24
25    /// Lazily adds paths to the `Writer`. It merely tells the `Writer` to note the supplied path
26    /// and does not write to the underlying file. To write to the underlying file, use the
27    /// `write` method after `add`ing all the files.
28    pub fn add<P: AsRef<Path>>(&mut self, path: P) -> Result<(), ArchiveError> {
29        let path = path.as_ref();
30        // If the given path is a directory,
31        // recursively add all the files and
32        // subdirectories inside it.
33        if path.is_dir() {
34            for entry in path.read_dir().map_err(ArchiveError::EntryAddition)? {
35                self.add(entry.map_err(ArchiveError::EntryAddition)?.path())?;
36            }
37        } else if path.is_file() {
38            self.paths.push(path.to_path_buf());
39        }
40        // Do not add symbolic links or devices.
41        Ok(())
42    }
43
44    /// Writes header structures and associated data to the underlying file handle. Since the
45    /// object is consumed, the file is closed on drop, making sure we cannot incorrectly write
46    /// multiple times to the same file.
47    pub fn write(mut self) -> Result<(), ArchiveError> {
48        for path in self.paths.iter() {
49            let header = Header::from_file_metadata(path)?;
50            let mut handle = File::open(path).map_err(FileParseError::FileRead)?;
51            self.file
52                .write_all(&header.bytes)
53                .map_err(ArchiveError::FileWrite)?;
54            copy(&mut handle, &mut self.file).map_err(ArchiveError::FileWrite)?;
55        }
56        // This marks the end of the file.
57        self.file
58            .write_all(EOF_BLOCK)
59            .map_err(ArchiveError::FileWrite)?;
60        Ok(())
61    }
62
63    pub fn files_count(&self) -> usize {
64        self.paths.len()
65    }
66}