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::{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}