use crate::{
cli::{
FileArgsCompat, PasswordArgs, PrivateChunkType, SolidEntriesTransformStrategy,
SolidEntriesTransformStrategyArgs,
},
command::{
Command, ask_password,
core::{
SplitArchiveReader, TransformStrategyKeepSolid, TransformStrategyUnSolid,
collect_split_archives,
},
},
utils::{PathPartExt, env::NamedTempFile},
};
use clap::{Args, Parser, ValueHint};
use pna::{Metadata, NormalEntry, RawChunk, prelude::*};
use std::path::PathBuf;
#[derive(Args, Clone, Eq, PartialEq, Hash, Debug)]
pub(crate) struct StripOptions {
#[arg(
long,
visible_alias = "preserve-timestamps",
help = "Preserve file timestamps"
)]
keep_timestamp: bool,
#[arg(
long,
visible_alias = "preserve-permissions",
help = "Preserve file permissions"
)]
keep_permission: bool,
#[arg(
long,
visible_alias = "preserve-xattrs",
help = "Preserve extended attributes"
)]
keep_xattr: bool,
#[arg(long, visible_alias = "preserve-acls", help = "Preserve ACLs")]
keep_acl: bool,
#[arg(long, visible_alias = "preserve-private_chunks", value_name = "CHUNK_TYPE", help = "Keep private chunks. If no CHUNK_TYPE is specified, all private chunks are kept", value_delimiter = ',', num_args = 0..)]
pub(crate) keep_private: Option<Vec<PrivateChunkType>>,
}
#[derive(Parser, Clone, Eq, PartialEq, Hash, Debug)]
pub(crate) struct StripCommand {
#[command(flatten)]
pub(crate) strip_options: StripOptions,
#[command(flatten)]
transform_strategy: SolidEntriesTransformStrategyArgs,
#[arg(long, help = "Output file path", value_hint = ValueHint::AnyPath)]
pub(crate) output: Option<PathBuf>,
#[command(flatten)]
pub(crate) password: PasswordArgs,
#[command(flatten)]
pub(crate) file: FileArgsCompat,
}
impl Command for StripCommand {
#[inline]
fn execute(self, _ctx: &crate::cli::GlobalContext) -> anyhow::Result<()> {
strip_metadata(self)
}
}
#[hooq::hooq(anyhow)]
fn strip_metadata(args: StripCommand) -> anyhow::Result<()> {
let password = ask_password(args.password)?;
let archive = args.file.archive();
let mut source = SplitArchiveReader::new(collect_split_archives(&archive)?)?;
let output_path = args.output.unwrap_or_else(|| archive.remove_part());
let mut temp_file =
NamedTempFile::new(|| output_path.parent().unwrap_or_else(|| ".".as_ref()))?;
match args.transform_strategy.strategy() {
SolidEntriesTransformStrategy::UnSolid => source.transform_entries(
temp_file.as_file_mut(),
password.as_deref(),
#[hooq::skip_all]
|entry| Ok(Some(strip_entry_metadata(entry?, &args.strip_options))),
TransformStrategyUnSolid,
),
SolidEntriesTransformStrategy::KeepSolid => source.transform_entries(
temp_file.as_file_mut(),
password.as_deref(),
#[hooq::skip_all]
|entry| Ok(Some(strip_entry_metadata(entry?, &args.strip_options))),
TransformStrategyKeepSolid,
),
}?;
drop(source);
temp_file.persist(output_path)?;
Ok(())
}
#[inline]
fn strip_entry_metadata<T>(mut entry: NormalEntry<T>, options: &StripOptions) -> NormalEntry<T>
where
T: Clone,
RawChunk<T>: Chunk,
{
let mut metadata = Metadata::new();
if options.keep_permission {
metadata = metadata.with_permission(entry.metadata().permission().cloned());
}
if options.keep_timestamp {
metadata = metadata.with_accessed(entry.metadata().accessed());
metadata = metadata.with_created(entry.metadata().created());
metadata = metadata.with_modified(entry.metadata().modified());
}
entry = entry.with_metadata(metadata);
if !options.keep_xattr {
entry = entry.with_xattrs(&[]);
}
let keep_private_all = options
.keep_private
.as_ref()
.is_some_and(|it| it.is_empty());
let mut keep_private_chunks = Vec::new();
if options.keep_acl {
keep_private_chunks.push(crate::chunk::faCl);
keep_private_chunks.push(crate::chunk::faCe);
}
if let Some(chunks) = &options.keep_private {
keep_private_chunks.extend(chunks.iter().map(|it| it.0))
}
let filtered = entry
.extra_chunks()
.iter()
.filter(|it| keep_private_all || keep_private_chunks.contains(&it.ty()))
.cloned()
.collect::<Vec<_>>();
entry.with_extra_chunks(filtered)
}