Skip to main content

braidz_writer/
lib.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::{io::Write, path::Path};
5
6mod zip_dir;
7
8#[derive(thiserror::Error, Debug)]
9pub enum Error {
10    #[error("IO error: {source}")]
11    IoError {
12        #[from]
13        source: std::io::Error,
14    },
15    #[error("zip error: {source}")]
16    ZipError {
17        #[from]
18        source: zip::result::ZipError,
19    },
20}
21
22// zip the output_dirname directory
23pub fn dir_to_braidz<P1: AsRef<Path>, P2: AsRef<Path>>(
24    output_dirname: P1,
25    output_zipfile: P2,
26) -> Result<(), Error> {
27    let mut file = std::fs::File::create(&output_zipfile)?;
28
29    let header = "BRAIDZ file. This is a standard ZIP file with a \
30                        specific schema. You can view the contents of this \
31                        file at https://braidz.strawlab.org/\n";
32    file.write_all(header.as_bytes())?;
33
34    let walkdir = walkdir::WalkDir::new(&output_dirname);
35
36    // Reorder the results to save the README_MD_FNAME file first
37    // so that the first bytes of the file have it. This is why we
38    // special-case the file here.
39    let mut readme_entry: Option<walkdir::DirEntry> = None;
40
41    let mut files = Vec::new();
42    for entry in walkdir.into_iter().filter_map(|e| e.ok()) {
43        if entry.file_name() == braid_types::README_MD_FNAME {
44            readme_entry = Some(entry);
45        } else {
46            files.push(entry);
47        }
48    }
49    if let Some(entry) = readme_entry {
50        files.insert(0, entry);
51    }
52
53    let mut zipw = zip::ZipWriter::new(file);
54    // Since most of our files are already compressed as .gz files,
55    // we do not bother attempting to compress again. This would
56    // cost significant computation but wouldn't save much space.
57    // (The compressed files should all end with .gz so we could
58    // theoretically compress the uncompressed files by a simple
59    // file name filter. However, the README.md file should ideally
60    // remain uncompressed and as the first file so that inspecting
61    // the braidz file will show this.)
62    let options = zip::write::SimpleFileOptions::default()
63        .compression_method(zip::CompressionMethod::Stored)
64        .large_file(true)
65        .unix_permissions(0o755);
66
67    zip_dir::zip_dir(&mut files.into_iter(), &output_dirname, &mut zipw, options)?;
68    zipw.finish()?;
69    Ok(())
70}