portable_network_archive/command/
concat.rs1#[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}