use anyhow::Result;
use clap::Parser;
use std::time::Instant;
use tokio::fs;
use crate::cli::args::args_def::Args;
use crate::cli::commands::list::list_partitions;
use crate::cli::commands::metadata_saver::handle_metadata_extraction;
use crate::cli::payload::extractor::extract_partitions;
use crate::cli::payload::file_detector::{PayloadType, detect_payload_type};
use crate::cli::payload::partition_filter::filter_partitions;
use crate::cli::payload::payload_loader::load_payload;
#[cfg(feature = "prefetch")]
use crate::cli::payload::prefetch_extractor::extract_partitions_prefetch;
use crate::cli::ui::ui_print::UiOutput;
use crate::cli::verification::validator::verify_extracted_partitions;
use payload_dumper::utils::{format_elapsed_time, format_size};
pub async fn run() -> Result<()> {
let args = Args::parse();
let is_stdout = args.out.to_string_lossy() == "-";
let ui = UiOutput::new(args.quiet, is_stdout);
let thread_count = if args.no_parallel {
1
} else {
args.threads.unwrap_or_else(|| num_cpus::get())
};
ui.println(format!("- Initialized {} thread(s)", thread_count));
let start_time = Instant::now();
let main_pb = ui.create_spinner("Starting...");
if let Ok(metadata) = fs::metadata(&args.payload_path).await
&& metadata.len() > 1024 * 1024
{
ui.pb_eprintln(format!(
"- Processing file: {}, size: {}",
args.payload_path.display(),
format_size(metadata.len())
));
}
if !is_stdout {
fs::create_dir_all(&args.out).await?;
}
ui.update_spinner(&main_pb, "Detecting file type...");
let payload_type = detect_payload_type(
&args.payload_path,
args.user_agent.as_deref(),
args.cookies.as_deref(),
args.dns.as_deref(),
)
.await?;
ui.update_spinner(&main_pb, "Parsing payload...");
let payload_info = load_payload(
&args.payload_path,
payload_type,
args.user_agent.as_deref(),
args.cookies.as_deref(),
args.dns.as_deref(),
&ui,
)
.await?;
let manifest = payload_info.manifest;
let data_offset = payload_info.data_offset;
if let Some(security_patch) = &manifest.security_patch_level {
ui.pb_eprintln(format!("- Security Patch: {}", security_patch));
}
if let Some(mode) = &args.metadata
&& !args.list
{
ui.println("- Extracting metadata...");
match handle_metadata_extraction(
&manifest,
&args.out,
data_offset,
mode,
&args.images,
is_stdout,
)
.await
{
Ok(()) => {
ui.clear()?;
return Ok(());
}
Err(e) => {
ui.finish_spinner(main_pb, "Failed to save metadata");
return Err(e);
}
}
}
if args.list {
ui.clear()?;
if let Some(mode) = &args.metadata {
if let Err(e) = handle_metadata_extraction(
&manifest,
&args.out,
data_offset,
mode,
&args.images,
is_stdout,
)
.await
{
ui.error(format!("Failed to save metadata: {}", e));
}
if is_stdout {
return Ok(());
}
}
println!();
list_partitions(&manifest);
return Ok(());
}
let block_size = manifest.block_size.unwrap_or(4096);
let partitions_to_extract = filter_partitions(&manifest, &args.images);
if partitions_to_extract.is_empty() {
ui.finish_spinner(main_pb, "No partitions to extract");
ui.clear()?;
return Ok(());
}
ui.println(format!(
"- Found {} partitions to extract",
partitions_to_extract.len()
));
let is_remote = matches!(
payload_type,
PayloadType::RemoteZip | PayloadType::RemoteBin
);
let failed_partitions = if args.prefetch && is_remote {
#[cfg(feature = "prefetch")]
{
ui.println("- Using prefetch mode for remote extraction");
ui.update_spinner(&main_pb, "Downloading and extracting partitions...");
let url = args.payload_path.to_string_lossy().to_string();
let payload_offset = match payload_type {
PayloadType::RemoteZip => {
use payload_dumper::zip::core_parser::ZipParser;
let http_reader = payload_dumper::http::HttpReader::new(
url.clone(),
args.user_agent.as_deref(),
args.cookies.as_deref(),
args.dns.as_deref(),
)
.await?;
let entry = ZipParser::find_payload_entry(&http_reader).await?;
ZipParser::get_data_offset(&http_reader, &entry).await?
}
PayloadType::RemoteBin => 0, _ => unreachable!(),
};
extract_partitions_prefetch(
&args,
&partitions_to_extract,
data_offset,
block_size as u64,
url,
payload_offset,
&ui,
)
.await?
}
#[cfg(not(feature = "prefetch"))]
{
return Err(anyhow::anyhow!(
"Prefetch mode requires the 'prefetch' feature"
));
}
} else {
ui.update_spinner(
&main_pb,
if args.no_parallel {
"Processing partitions..."
} else {
"Extracting partitions..."
},
);
extract_partitions(
&args,
&partitions_to_extract,
data_offset,
block_size as u64,
payload_info.reader,
&ui,
)
.await?
};
verify_extracted_partitions(&partitions_to_extract, &failed_partitions, &args, &ui).await?;
let elapsed_time = format_elapsed_time(start_time.elapsed());
if failed_partitions.is_empty() {
ui.finish_spinner(
main_pb,
format!(
"All partitions extracted successfully! (in {})",
elapsed_time
),
);
ui.println_final(format!(
"\n- Extraction completed successfully in {}. Output directory: {:?}",
elapsed_time, args.out,
));
} else {
ui.finish_spinner(
main_pb,
format!(
"Completed with {} failed partitions. (in {})",
failed_partitions.len(),
elapsed_time
),
);
ui.eprintln_final(format!(
"\n- Extraction completed with {} failed partitions in {}. Output directory: {:?}",
failed_partitions.len(),
elapsed_time,
args.out,
));
}
Ok(())
}