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