<* Compare `C3 zip` vs `7z` extraction External dependencies: 7z, diff *> module verify_zip; import std; import libc; fn int main(String[] args) { if (args.len < 2) { io::printfn("Usage: %s [-r|--recursive] [-o|--output ] ", args[0]); return 1; } bool recursive = false; String zip_dir; String output_dir; for (int i = 1; i < args.len; i++) { String arg = args[i]; switch (arg) { case "-r": case "--recursive": recursive = true; case "-o": case "--output": if (++i >= args.len) { io::printfn("Error: %s requires a directory path", arg); return 1; } output_dir = args[i]; default: if (arg.starts_with("-")) { io::printfn("Error: unknown option %s", arg); return 1; } if (zip_dir) { io::printfn("Error: multiple zip directories specified ('%s' and '%s')", zip_dir, arg); return 1; } zip_dir = arg; } } if (!zip_dir) { io::printfn("Error: no zip directory specified."); return 1; } return process_dir(zip_dir, recursive, output_dir); } fn int process_dir(String dir, bool recursive, String output_dir) { PathList? files = path::ls(tmem, path::temp(dir)!!); if (catch excuse = files) { io::printfn("Could not open directory: %s (Excuse: %s)", dir, excuse); return 1; } foreach (p : files) { String name = p.basename(); if (name == "." || name == "..") continue; String zip_path = path::temp(dir)!!.tappend(name)!!.str_view(); if (file::is_dir(zip_path)) { if (recursive) { if (process_dir(zip_path, recursive, output_dir) != 0) return 1; } continue; } if (!name.ends_with(".zip")) continue; ulong size = 0; File? f = file::open(zip_path, "rb"); if (try fh = f) { (void)fh.seek(0, Seek.END); size = fh.seek(0, Seek.CURSOR) ?? 0; fh.close()!!; } io::printf("Verifying %-40s [%7d KB] ", name[:(min(name.len, 40))], size / 1024); switch (verify_one(zip_path, output_dir)) { case 0: io::printfn("%sFAILED%s ❌", Ansi.RED, Ansi.RESET); return 1; case 1: io::printfn("%sPASSED%s ✅", Ansi.GREEN, Ansi.RESET); default: io::printn(); } } return 0; } fn int verify_one(String zip_path, String output_dir) { Path extract_root; if (output_dir) { extract_root = path::temp(output_dir)!!; } else { extract_root = path::temp_directory(tmem)!!; } String name = (String)path::temp(zip_path)!!.basename(); Path temp_c3 = extract_root.tappend(name.tconcat("_c3"))!!; Path temp_7z = extract_root.tappend(name.tconcat("_7z"))!!; (void)path::mkdir(temp_c3, true); (void)path::mkdir(temp_7z, true); ZipArchive? archive = zip::open(zip_path, "r"); if (catch excuse = archive) { io::printfn("%sFAIL%s (open: %s)", Ansi.RED, Ansi.RESET, excuse); return 0; } defer (void)archive.close(); Time start = time::now(); if (catch excuse = archive.extract(temp_c3.str_view())) { if (excuse == zip::ENCRYPTED_FILE) { io::printf("%sSKIPPED%s (Encrypted)", Ansi.YELLOW, Ansi.RESET); return 2; } io::printfn("%sFAIL%s (extract: %s)", Ansi.RED, Ansi.RESET, excuse); return 0; } Duration c3_time = time::now() - start; start = time::now(); if (!extract_7z(zip_path, temp_7z.str_view())) { io::printfn("%sFAIL%s (7z extract)", Ansi.RED, Ansi.RESET); return 0; } Duration p7_time = time::now() - start; io::printf(" [C3: %5d ms, 7z: %5d ms]", (long)c3_time / 1000, (long)p7_time / 1000); io::print(" Comparing... "); if (!compare_dirs(temp_c3.str_view(), temp_7z.str_view())) { io::printfn("%sFAIL%s (Differences found)", Ansi.RED, Ansi.RESET); return 0; } // keep files on error for manual verification (void)path::rmtree(temp_c3); (void)path::rmtree(temp_7z); return 1; } fn bool extract_7z(String zip_path, String output_dir) { String out_opt = "-o".tconcat(output_dir); String[] cmd = { "7z", "x", zip_path, out_opt, "-y", "-bb0" }; SubProcess? proc = process::create(cmd, { .search_user_path = true }); if (catch excuse = proc) return false; return (int)proc.join()!! == 0; } fn bool compare_dirs(String dir1, String dir2) { String[] cmd = { "diff", "-r", dir1, dir2 }; SubProcess? proc = process::create(cmd, { .search_user_path = true, .inherit_stdio = true }); if (catch excuse = proc) return false; int res = (int)proc.join()!!; return res == 0; }