use std::io::Write;
use super::cli::{SplitOptions, ZE_OK};
use super::messages::{self as m, printf};
use super::readzip::ZFile;
use super::readzip::{self, CFile, InArchive, cstr};
use super::sink::{Closed, FileSink};
use super::split::{self, Prompt};
use super::zipcloak::fail_exit;
use super::zipfile::{self, CENHEAD, LOCHEAD, PutError, ZEntry, ZOut};
use super::zipup::write_failed;
use super::{Exit, Flavour, Zr, scan};
use crate::compat::host::{self, Host};
const ZE_PARMS: i32 = 16;
const ZE_BIG: i32 = 6;
const ZE_WRITE: i32 = 14;
const ZE_NAME: i32 = 13;
const ZE_CREAT: i32 = 15;
const END_RECORD: u64 = 18 + 4;
const STDIO_BUF: usize = 4096;
const INDEX: &[u8] = b"zipsplit.idx";
fn simple(a: &mut [u64], c: u64, d: u64) -> usize {
let (mut t, mut k) = (0u64, 0usize);
for x in a.iter_mut() {
let room = c.wrapping_sub(if k == 0 { d } else { 0 });
if x.wrapping_add(t) > room {
k += 1;
t = 0;
}
t = t.wrapping_add(*x);
*x = k as u64;
}
k + 1
}
fn greedy(z: &Zr, a: &mut [u64], c: u64, d: u64) -> usize {
let n = a.len();
let e: Vec<u64> = a.to_vec();
let mut s: Vec<usize> = (0..n).collect();
let tot = e.iter().fold(0u64, |t, &x| t.wrapping_add(x));
let mut m = (tot.wrapping_add(c).wrapping_sub(1) / c).wrapping_sub(1) as usize;
scan::qsort(z.flavour, &mut s, &|x, y| e[y].cmp(&e[x]));
let over = c.wrapping_add(1);
loop {
m = m.wrapping_add(1);
let mut rooms = std::collections::BTreeSet::new();
let mut b = vec![c; m];
if m > 0 {
b[0] = c.wrapping_sub(d);
}
for (j, &r) in b.iter().enumerate() {
rooms.insert((r, j));
}
let mut i = 0;
while i < n {
let item = e[s[i]];
let Some(&(r, k)) = rooms.range((item, 0usize)..).next() else {
break;
};
if r >= over {
break;
}
rooms.remove(&(r, k));
rooms.insert((r - item, k));
b[k] = r - item;
a[s[i]] = k as u64;
i += 1;
}
if i >= n {
return m;
}
}
}
struct Template {
prefix: Vec<u8>,
digits: usize,
}
impl Template {
fn name(&self, n: u64) -> Vec<u8> {
let mut v = self.prefix.clone();
v.extend_from_slice(format!("{n:0width$}.zip", width = self.digits).as_bytes());
v
}
}
struct Made {
dir: Vec<u8>,
template: Template,
index: bool,
parts: u64,
}
impl Made {
fn destroy(&self, z: &Zr) {
if self.index {
let mut n = self.dir.clone();
n.extend_from_slice(INDEX);
let _ = std::fs::remove_file(z.host.path(&n));
}
for k in (1..=self.parts).rev() {
let mut n = self.dir.clone();
n.extend_from_slice(&self.template.name(k));
let _ = std::fs::remove_file(z.host.path(&n));
}
}
}
enum Part {
Redo,
End(Exit),
}
fn retry(z: &mut Zr, p: &Prompt) -> bool {
z.mesg_write(m::REDO_DISK.as_bytes());
split::fgets_n(p, 10).is_some_and(|l| matches!(l.first(), Some(b'y' | b'Y')))
}
pub(crate) fn run(z: &mut Zr, o: SplitOptions) -> Exit {
host::install_zip_abort(&z.host, m::SPLIT_INTERRUPTED);
z.set_line(false);
let zipfile = o.zipfile.clone();
z.o.zipfile = Some(zipfile.clone());
z.o.in_path = Some(zipfile.clone());
z.o.out_path = Some(zipfile.clone());
let mut ar = match readzip::read_archive(z) {
Ok(ar) => ar,
Err(x) => return x,
};
if ar.zfiles.is_empty() {
return z.ziperr(ZE_NAME, &zipfile);
}
let zcount = ar.zfiles.len();
let c = o.capacity.wrapping_sub(END_RECORD);
let mut i = o.room;
let mut a: Vec<u64> = Vec::with_capacity(zcount);
let mut t: u64 = 0;
for zf in &ar.zfiles {
let e = &zf.e;
if o.index {
i = i.wrapping_add(e.iname.len() as i64 + 6 + 1);
}
let sz = (8 + LOCHEAD + CENHEAD)
.wrapping_add(2 * e.iname.len() as u64)
.wrapping_add(2 * e.cextra.len() as u64)
.wrapping_add(e.comment.len() as u64)
.wrapping_add(e.siz);
a.push(sz);
t = t.wrapping_add(sz);
if sz > c {
let mut w = Vec::new();
printf(
&mut w,
m::ENTRY_LARGER_THAN_MAX,
&[c.to_string().as_bytes()],
);
z.zipwarn(&w, b"");
z.zipwarn(m::USE_N_TO_SET.as_bytes(), b"");
return z.ziperr(ZE_BIG, cstr(&zf.zname));
}
}
let d = i as u64;
let s = if o.simple {
simple(&mut a, c, d)
} else {
let mut p = a.clone();
let s = simple(&mut a, c, d);
let g = greedy(z, &mut p, c, d);
if s <= g {
s
} else {
a = p;
g
}
};
let eff = (200u64.wrapping_mul(t.wrapping_add(c).wrapping_sub(1) / c) / s as u64).div_ceil(2);
let mut line = Vec::new();
printf(
&mut line,
m::SPLIT_WILL_BE_MADE,
&[
s.to_string().as_bytes(),
if o.report_only { b"ould" } else { b"ill" },
(eff as i64).to_string().as_bytes(),
],
);
z.stdout_write(&line);
if o.report_only {
return Exit { code: ZE_OK };
}
let mut dir = o.tempath.clone().unwrap_or_default();
if o.tempath.is_some() && !dir.is_empty() && dir.last() != Some(&b'/') {
dir.push(b'/');
}
let mut bins: Vec<Vec<usize>> = vec![Vec::new(); s];
for (j, &g) in a.iter().enumerate() {
bins[g as usize].push(j);
}
let mut k = 1usize;
let mut j = s;
while j >= 10 {
k += 1;
j /= 10;
}
if k > 7 {
return z.ziperr(ZE_PARMS, m::WAY_TOO_MANY.as_bytes());
}
let name = cstr(&zipfile);
let q = match name.iter().rposition(|&b| b == b'/') {
Some(p) => &name[p + 1..],
None => name,
};
let mut prefix = Vec::new();
let mut g = 0u8;
for &ch in q.iter().chain(std::iter::once(&0u8)) {
g = ch;
if g == 0 || g == b'.' || prefix.len() >= 8 - k {
break;
}
prefix.push(g);
}
if prefix.is_empty() {
prefix.push(b'_');
} else if g.is_ascii_digit() {
let last = prefix.len() - 1;
prefix[last] = if prefix[last] == b'_' { b'-' } else { b'_' };
}
let mut made = Made {
dir,
template: Template { prefix, digits: k },
index: false,
parts: 0,
};
let _guard = host::arm_split_cleanup(&z.host, &made.dir, &made.template.prefix, k);
let mut ia = InArchive { f: None };
match CFile::open(z, &zipfile) {
Ok(f) => ia.f = Some(f),
Err(_) => return z.ziperr(ZE_NAME, &zipfile),
}
z.current_in_disk = 0;
let p = split::prompt(z);
let budget = match &z.host {
Host::Sandbox(s) => Some(s.budget.clone()),
Host::Real => None,
};
let mut out = ZOut::new(Box::new(Closed), z.o.force_zip64, None);
out.unicode = z.unicode_out();
let job = Job {
o: &o,
p,
a: &a,
bins: &bins,
budget,
};
let mut j = 0usize;
while j < s {
match make_part(z, &mut ar.zfiles, &job, j, &mut made, &mut ia, &mut out) {
Ok(()) => j += 1,
Err(Part::Redo) => {}
Err(Part::End(x)) => {
made.destroy(z);
return x;
}
}
}
if o.pause {
z.mesg_write(m::SPLIT_DONE.as_bytes());
}
Exit { code: ZE_OK }
}
struct Job<'a> {
o: &'a SplitOptions,
p: Prompt,
a: &'a [u64],
bins: &'a [Vec<usize>],
budget: Option<std::sync::Arc<std::sync::atomic::AtomicU64>>,
}
fn part_error(z: &mut Zr, job: &Job, code: i32, path: &[u8], e: &std::io::Error) -> Part {
if job.o.pause && retry(z, &job.p) {
return Part::Redo;
}
Part::End(z.ziperr_os(code, path, e))
}
fn make_part(
z: &mut Zr,
zfiles: &mut [ZFile],
job: &Job,
j: usize,
made: &mut Made,
ia: &mut InArchive,
out: &mut ZOut,
) -> Result<(), Part> {
let s = job.bins.len();
z.current_in_disk = 0;
if job.o.pause {
let mut l = Vec::new();
printf(
&mut l,
m::INSERT_DISK,
&[(j + 1).to_string().as_bytes(), s.to_string().as_bytes()],
);
z.mesg_write(&l);
let _ = split::fgets_n(&job.p, 10);
}
if j == 0 && job.o.index {
let mut path = made.dir.clone();
path.extend_from_slice(INDEX);
let mut l = Vec::new();
printf(&mut l, m::SPLIT_CREATING, &[&path]);
z.mesg_write(&l);
made.index = true;
host::split_progress(made.parts, made.index);
let f = match std::fs::File::create(z.host.path(&path)) {
Ok(f) => f,
Err(e) => return Err(part_error(z, job, ZE_CREAT, &path, &e)),
};
let mut w = FileSink::stdio(
STDIO_BUF,
f,
job.budget.clone(),
z.flavour == Flavour::Apple,
);
let mut buf = Vec::new();
for (idx, zf) in zfiles.iter().enumerate() {
let n = format!("{:>5}", job.a[idx] + 1);
printf(&mut buf, m::INDEX_LINE, &[n.as_bytes(), cstr(&zf.zname)]);
}
let r = w.write_all(&buf).and_then(|()| w.flush());
if let Err(e) = r {
return Err(part_error(z, job, ZE_WRITE, &path, &e));
}
}
let mut path = made.dir.clone();
path.extend_from_slice(&made.template.name(j as u64 + 1));
let mut l = Vec::new();
printf(&mut l, m::SPLIT_CREATING, &[&path]);
z.mesg_write(&l);
made.parts = j as u64 + 1;
host::split_progress(made.parts, made.index);
let f = match std::fs::File::create(z.host.path(&path)) {
Ok(f) => f,
Err(e) => return Err(part_error(z, job, ZE_CREAT, &path, &e)),
};
out.restart(Box::new(FileSink::stdio(
STDIO_BUF,
f,
job.budget.clone(),
z.flavour == Flavour::Apple,
)));
let mut tempzn = 0u64;
for &g in &job.bins[j] {
if let Err(f) = readzip::zipcopy(z, ia, out, &mut tempzn, &mut zfiles[g]) {
return Err(Part::End(fail_exit(z, f, m::NULL_TEXT.as_bytes())));
}
}
let mut central: Vec<ZEntry> = job.bins[j].iter().map(|&g| zfiles[g].e.clone()).collect();
let mut at = out.w.offset();
match zipfile::write_central_as(&mut central, b"", out, &mut at, &mut |_, _| {}, true) {
Ok(_) => {}
Err(PutError::Big { .. }) => {
if job.o.pause && retry(z, &job.p) {
return Err(Part::Redo);
}
return Err(Part::End(z.ziperr(ZE_WRITE, &path)));
}
Err(PutError::Write(err)) => return Err(Part::End(write_failed(z, &err))),
}
let flushed = out.w.flush();
let failed = flushed.err().or_else(|| out.w.ferror());
if let Some(e) = failed {
return Err(part_error(z, job, ZE_WRITE, &path, &e));
}
Ok(())
}
#[cfg(test)]
pub(super) fn simple_for_tests(a: &mut [u64], c: u64, d: u64) -> usize {
simple(a, c, d)
}