Skip to main content

portable_network_archive/command/
concat.rs

1#[cfg(not(feature = "memmap"))]
2use crate::command::core::run_across_archive;
3#[cfg(feature = "memmap")]
4use crate::command::core::run_across_archive_mem as run_across_archive;
5use crate::{
6    command::{Command, core::collect_split_archives},
7    utils::{self, PathWithCwd},
8};
9use anyhow::Context;
10use clap::{ArgGroup, Parser, ValueHint};
11use pna::Archive;
12#[cfg(feature = "memmap")]
13use std::io;
14use std::path::PathBuf;
15
16#[derive(Parser, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17#[command(
18    group(ArgGroup::new("archive-args").args(["files", "archives"]).required(true)),
19    group(ArgGroup::new("overwrite-flag").args(["overwrite", "no_overwrite"])),
20)]
21pub(crate) struct ConcatCommand {
22    #[arg(long, help = "Overwrite file")]
23    overwrite: bool,
24    #[arg(
25        long,
26        help = "Do not overwrite files. This is the inverse option of --overwrite"
27    )]
28    no_overwrite: bool,
29    #[arg(help = "Archive files to concatenate (deprecated, use --files)", value_hint = ValueHint::FilePath)]
30    archives: Vec<PathBuf>,
31    #[arg(short, long, help = "Archive files to concatenate", value_hint = ValueHint::FilePath)]
32    files: Vec<PathBuf>,
33}
34
35impl Command for ConcatCommand {
36    #[inline]
37    fn execute(self, _ctx: &crate::cli::GlobalContext) -> anyhow::Result<()> {
38        concat_entry(self)
39    }
40}
41
42#[hooq::hooq(anyhow)]
43fn concat_entry(args: ConcatCommand) -> anyhow::Result<()> {
44    let mut archives = if args.files.is_empty() {
45        if !args.archives.is_empty() {
46            log::warn!("positional `archive` is deprecated, use `--file` instead");
47        }
48        args.archives
49    } else {
50        args.files
51    };
52    let archive = archives.remove(0);
53    for item in &archives {
54        if !utils::fs::is_pna(item)? {
55            anyhow::bail!("{} is not a pna file", item.display());
56        }
57    }
58    let file = utils::fs::file_create(&archive, args.overwrite)
59        .with_context(|| format!("failed to create `{}`", PathWithCwd::new(&archive)))?;
60    let mut archive = Archive::write_header(file)?;
61
62    for item in &archives {
63        let archives = collect_split_archives(item)?;
64        #[cfg(feature = "memmap")]
65        {
66            let mmaps = archives
67                .into_iter()
68                .map(utils::mmap::Mmap::try_from)
69                .collect::<io::Result<Vec<_>>>()?;
70            let archives = mmaps.iter().map(|m| m.as_ref());
71            run_across_archive(
72                archives,
73                #[hooq::skip_all]
74                |reader| {
75                    for entry in reader.raw_entries_slice() {
76                        archive.add_entry(entry?)?;
77                    }
78                    Ok(())
79                },
80                false,
81            )?;
82        }
83        #[cfg(not(feature = "memmap"))]
84        {
85            run_across_archive(
86                archives,
87                #[hooq::skip_all]
88                |reader| {
89                    for entry in reader.raw_entries() {
90                        archive.add_entry(entry?)?;
91                    }
92                    Ok(())
93                },
94                false,
95            )?;
96        }
97    }
98    archive.finalize()?;
99    Ok(())
100}