Source file : rezip_lib.adb
------------------------------------------------------------------------------
-- File: rezip_lib.adb
-- Description: Recompression tool to make archives smaller.
-- Core moved from Rezip (main). Still Q&D !
-- Author: Gautier de Montmollin
------------------------------------------------------------------------------
--
-- To do:
-- * In order to facilitate customization, ReZip could have a config file
-- ( http://sf.net/projects/ini-files/ ) to store external packer
-- program names. See ZipMax as an example...
--
-- External programs used (feel free to customize/add/remove):
-- 7-Zip, KZip, Zip (info-zip), AdvZip, DeflOpt
-- Web URL's: see Zipper_specification below or run ReZip without arguments.
with Zip.Create,
Zip.Compress,
Zip.Headers;
with Flexible_temp_files;
with UnZip;
with Zip_Streams;
with Zip_Console_IO;
with Ada.Calendar,
Ada.Characters.Handling,
Ada.Directories,
Ada.Float_Text_IO,
Ada.Integer_Text_IO,
Ada.IO_Exceptions,
Ada.Numerics.Discrete_Random,
Ada.Numerics.Elementary_Functions,
Ada.Numerics.Float_Random,
Ada.Streams.Stream_IO,
Ada.Strings.Fixed,
Ada.Strings.Unbounded,
Ada.Text_IO,
Ada.Unchecked_Deallocation;
with Dual_IO;
with Interfaces;
with GNAT.OS_Lib;
package body Rezip_lib is
function S (Source : Ada.Strings.Unbounded.Unbounded_String) return String
renames Ada.Strings.Unbounded.To_String;
function U (Source : String) return Ada.Strings.Unbounded.Unbounded_String
renames Ada.Strings.Unbounded.To_Unbounded_String;
use Ada.Strings.Unbounded;
use Interfaces;
-- This info might be better read from a config file...
--
type Zipper_Specification is record
name, title, URL, options : Unbounded_String;
expanded_options : Unbounded_String;
-- ^ Options with dynamically expanded tokens
made_by_version : Unsigned_16;
pkzm : Zip.PKZip_method;
limit : Zip.Zip_64_Data_Size_Type;
-- ^ Compression is considered too slow or unefficient beyond limit (if not 0).
-- E.g., kzip's algorithm might be O(N^2) or worse; on large files,
-- deflate_e or other methods are better anyway
randomized : Boolean;
end record;
NN : constant Unbounded_String := Null_Unbounded_String;
-- Give up recompression above a certain data size for some external packers like KZip
-- or Zopfli.
--
kzip_zopfli_limit : constant := 2_000_000;
type Approach is
(original,
presel_2, presel_1,
shrink,
reduce_4,
deflate_3,
deflate_r,
bzip2_3, bzip2_2, bzip2_1,
lzma_3, lzma_2,
external_01, external_02, external_03, external_04,
external_05, external_06, external_07, external_08,
external_09, external_10, external_11, external_12,
external_13, external_14, external_15);
subtype Internal is Approach
range Approach'Succ (Approach'First) .. Approach'Pred (external_01);
subtype External is Approach
range external_01 .. Approach'Last;
ext : array (External) of Zipper_Specification :=
( -- Zip 2.32 or later:
(U ("zip"), U ("Zip"), U ("http://info-zip.org/"),
U ("-9"), NN, 20, Zip.deflate, 0, False),
-- 7-Zip 4.64 or later; Deflate:
(U ("7z"),
U ("7-Zip"), U ("http://7-zip.org/"),
U ("a -tzip -mm=deflate -mfb=258 -mpass=#RAND#(7,15) -mmc=10000"),
NN, 20, Zip.deflate, 0, True),
(U ("7z"),
U ("7-Zip"), NN,
U ("a -tzip -mm=deflate64 -mfb=257 -mpass=15 -mmc=10000"),
NN, 21, Zip.deflate_e, 0, False),
-- KZip:
(U ("kzip"), U ("KZIP"), U ("http://www.advsys.net/ken/utils.htm"),
U ("/rn /b0"), NN, 20, Zip.deflate, kzip_zopfli_limit, True),
(U ("kzip"), U ("KZIP"), NN,
U ("/rn /b#RAND_EXP#(1,2048)"), NN, 20, Zip.deflate, kzip_zopfli_limit, True),
-- Zip 3.0 or later; BZip2:
(U ("zip"), U ("Zip"), NN,
U ("-#RAND#(1,9) -Z bzip2"), NN, 46, Zip.bzip2_meth, 0, True),
-- 7z:
(U ("7z"), U ("7-Zip"), NN,
U ("a -tzip -mm=BZip2:d=#RAND#(1,9)00k:pass=7"), NN, 46, Zip.bzip2_meth, 0, True),
-- 7-Zip 9.20 or later; LZMA:
(U ("7z"), U ("7-Zip"), NN,
U ("a -tzip -mm=LZMA -mx=9"), NN, 63, Zip.lzma_meth, 0, False),
(U ("7z"), U ("7-Zip"), NN, -- LZ77: BT3 or BT4, dictionary size 2**19 = 512 KiB
U ("a -tzip -mm=LZMA:a=2:d=19:mf=bt#RAND#(3,5):fb=273:lc=0:lp=2"), NN, 63, Zip.lzma_meth, 0, False),
(U ("7z"), U ("7-Zip"), NN, -- LZ77: BT3 or BT4, dictionary size 2**25 = 32 MiB
U ("a -tzip -mm=LZMA:a=2:d=25:mf=bt#RAND#(3,5):fb=273:lc=7"), NN, 63, Zip.lzma_meth, 0, False),
(U ("7z"), U ("7-Zip"), NN, -- LZ77: BT3 or BT4, dictionary size 2**26 = 64 MiB
U ("a -tzip -mm=LZMA:a=2:d=26:mf=bt#RAND#(3,5):fb=273:lc=8:lp0:pb1"), NN, 63, Zip.lzma_meth, 0, False),
(U ("7z"), U ("7-Zip"), NN, -- Super-randomized version
U ("a -tzip -mm=LZMA:a=2:d=#RAND_EXP#(1,65535)k:mf=bt#RAND#(2,5):fb=#RAND#(128,273):" &
"lc=#RAND#(0,8):lp#RAND#(0,4):pb#RAND#(0,4)"),
NN, 63, Zip.lzma_meth, 0, True),
-- AdvZip: advancecomp v1.19+ interesting for the Zopfli algorithm
(U ("advzip"), U ("AdvZip"), U ("http://advancemame.sf.net/comp-readme.html"),
U ("-a -2"), NN, 20, Zip.deflate, 0, False),
(U ("advzip"), U ("AdvZip"), NN,
U ("-a -3"), NN, 20, Zip.deflate, 0, False),
(U ("advzip"), U ("AdvZip"), NN,
U ("-a -4"), NN, 20, Zip.deflate, kzip_zopfli_limit, False));
defl_opt : constant Zipper_Specification :=
(U ("deflopt"), U ("DeflOpt"), U ("http://www.walbeehm.com/download/"),
NN, NN, 0, Zip.deflate, 0, False);
use Ada.Strings.Fixed, Ada.Strings;
procedure Rezip (
from_zip_file : String;
to_zip_file : String;
format_choice : Zip_format_set := all_formats; -- force output into selected format set
touch : Boolean := False; -- set time stamps to now
lower : Boolean := False; -- set full file names to lower case
delete_comment : Boolean := False; -- delete zip comment
randomized_stable : Positive := 1;
log_file : String := "";
html_report : String := "";
alt_tmp_file_radix : String := ""; -- e.g. "X:\temp\rz_"
internal_only : Boolean := False -- Zip-Ada algorithms only, no ext. call
)
is
use Zip.Create;
use Zip_Streams;
use Ada.Calendar, Ada.Characters.Handling, Ada.Directories, Ada.Text_IO;
package DFIO is new Dual_IO.Float_IO (Float);
procedure Rip_data (
archive : Zip.Zip_Info; -- from this archive...
input : in out Root_Zipstream_Type'Class;
data_name : String; -- extract this data
rip_rename : String; -- to this file (compressed)
unzip_rename : String; -- and this one (uncompressed)
header : out Zip.Headers.Local_File_Header
)
is
file_index : Zip_Streams.ZS_Index_Type;
comp_size : Zip.Zip_64_Data_Size_Type;
uncomp_size : Zip.Zip_64_Data_Size_Type;
file_out : Ada.Streams.Stream_IO.File_Type;
dummy_encoding : Zip.Zip_Name_Encoding;
dummy_crc : Unsigned_32;
mem : Zip_Streams.ZS_Index_Type;
head_extra : Zip.Headers.Local_File_Header_Extension;
dummy_offset : Unsigned_64 := 0; -- Initialized for avoiding random value = 16#FFFF_FFFF#
use UnZip, Ada.Streams.Stream_IO;
begin
Zip.Find_Offset (
info => archive,
name => data_name,
name_encoding => dummy_encoding,
file_index => file_index,
comp_size => comp_size,
uncomp_size => uncomp_size,
crc_32 => dummy_crc
);
Set_Index (input, file_index);
Zip.Headers.Read_and_Check (input, header);
-- Skip name
Set_Index (input,
Index (input) + Zip_Streams.ZS_Size_Type (header.filename_length)
);
mem := Index (input);
if header.extra_field_length >= 4 then
Zip.Headers.Read_and_Check (input, head_extra);
Zip.Headers.Interpret
(head_extra,
header.dd.uncompressed_size,
header.dd.compressed_size,
dummy_offset);
end if;
-- Skip extra field
Set_Index (input, mem + Zip_Streams.ZS_Size_Type (header.extra_field_length));
-- * Get the data, compressed
Ada.Streams.Stream_IO.Create (file_out, Out_File, rip_rename);
Zip.Copy_Chunk (input, Stream (file_out).all, Integer (comp_size));
Close (file_out);
if unzip_rename /= "" then
-- * Get the data, uncompressed
Extract (
from => archive,
what => data_name,
rename => unzip_rename,
options =>
(test_only => False,
junk_directories => False,
case_sensitive_match => True,
extract_as_text => False
)
);
end if;
end Rip_data;
Approach_to_Method : constant array (Internal) of Zip.Compress.Compression_Method :=
(shrink => Zip.Compress.Shrink,
reduce_4 => Zip.Compress.Reduce_4,
deflate_3 => Zip.Compress.Deflate_3,
deflate_r => Zip.Compress.Deflate_R,
bzip2_1 => Zip.Compress.BZip2_1,
bzip2_2 => Zip.Compress.BZip2_2,
bzip2_3 => Zip.Compress.BZip2_3,
lzma_2 => Zip.Compress.LZMA_2,
lzma_3 => Zip.Compress.LZMA_3,
presel_1 => Zip.Compress.Preselection_1,
presel_2 => Zip.Compress.Preselection_2);
type Packer_info is record
size : Zip.Zip_64_Data_Size_Type;
zfm : Unsigned_16;
count : Natural;
saved : Integer_64; -- Number of bytes saved by chosen method
-- NB: can be negative if -defl chosen: suboptimal recompression,
-- but compatible method.
saved_ex_aequo : Integer_64; -- Number of bytes saved if method is as good as
-- the winning method.
uncomp_size : Unsigned_64;
-- summed uncompressed sizes might be more than 2**32
expanded_options : Unbounded_String;
iter : Positive; -- iterations needed
LZMA_EOS : Boolean;
end record;
type Packer_info_array is array (Approach) of Packer_info;
type Dir_entry;
type p_Dir_entry is access Dir_entry;
--
type Dir_entry is record
head : Zip.Headers.Central_File_Header;
name : Unbounded_String;
next : p_Dir_entry := null;
chosen_approach : Approach := original;
info : Packer_info_array;
end record;
function Radix return String is
begin
if alt_tmp_file_radix = "" then
return Flexible_temp_files.Radix;
else
return alt_tmp_file_radix;
end if;
end Radix;
function Temp_name (
is_compressed : Boolean;
appr : Approach
)
return String
is
initial : constant array (Boolean) of Character := ('u', 'c');
begin
return
Radix &
"_!" & initial (is_compressed) &
'!' & Trim (Integer'Image (Approach'Pos (appr)), Left) &
"!_.tmp";
end Temp_name;
function Img (a : Approach; html : Boolean) return String is
function Repl (s : String) return String is
t : String := s;
begin
for i in t'Range loop
if html and t (i) = ':' then t (i) := ' '; end if; -- Break too long texts within a cell.
end loop;
return t;
end Repl;
begin
if a in External then
return "External: " & S (ext (a).title) & ", " & Repl (S (ext (a).expanded_options));
else
declare
s : constant String := Approach'Image (a);
begin
return s (s'First) & To_Lower (s (s'First + 1 .. s'Last) & (Approach'Width - s'Length + 1) * ' ');
end;
end if;
end Img;
-- From AZip_Common...
function Image_1000 (r : Zip.Zip_64_Data_Size_Type; separator : Character := ''') return String is
s : constant String := Zip.Zip_64_Data_Size_Type'Image (r);
t : String (s'First .. s'First + (s'Length * 4) / 3);
j, c : Natural;
begin
-- For signed integers
-- if r < 0 then
-- return '-' & Image_1000(abs r, separator);
-- end if;
--
-- We build result string t from right to left
j := t'Last + 1;
c := 0;
for i in reverse s'First .. s'Last loop
exit when s (i) = ' ' or s (i) = '-';
if c > 0 and then c mod 3 = 0 then
j := j - 1;
t (j) := separator;
end if;
j := j - 1;
t (j) := s (i);
c := c + 1;
end loop;
return t (j .. t'Last);
end Image_1000;
function Image_1000 (r : Integer_64; separator : Character := ''') return String is
s : constant String := Integer_64'Image (r);
t : String (s'First .. s'First + (s'Length * 4) / 3);
j, c : Natural;
begin
-- For signed integers
if r < 0 then
return '-' & Image_1000 (abs r, separator);
end if;
-- We build result string t from right to left
j := t'Last + 1;
c := 0;
for i in reverse s'First .. s'Last loop
exit when s (i) = ' ' or s (i) = '-';
if c > 0 and then c mod 3 = 0 then
j := j - 1;
t (j) := separator;
end if;
j := j - 1;
t (j) := s (i);
c := c + 1;
end loop;
return t (j .. t'Last);
end Image_1000;
procedure Call_External
(packer : String;
args : String;
is_tool_needed : Boolean)
is
use GNAT.OS_Lib;
procedure Dispose is
new Ada.Unchecked_Deallocation (Argument_List, Argument_List_Access);
list : Argument_List_Access;
ok : Boolean;
begin
Dual_IO.Put_Line (packer & " [" & args & ']');
list := Argument_String_To_List (args);
GNAT.OS_Lib.Spawn (packer, list.all, ok);
Dispose (list);
if not ok then
declare
msg : constant String := " cannot call external tool """ & packer &
""", or it has returned an error.";
begin
Dual_IO.New_Line;
Dual_IO.Put_Line ("**************");
if is_tool_needed then
Dual_IO.Put_Line ("ReZip ERROR:" & msg);
raise External_Tool_Failed;
else
Dual_IO.Put_Line ("ReZip warning:" & msg);
end if;
end;
end if;
end Call_External;
seed_iterator : Natural;
procedure Call_External_Expanded
(packer : String;
options : String;
other_args : String;
expand : in out Unbounded_String) -- expanded arguments
is
type Token is (rand, rand_exp);
begin
expand := U (options);
for t in Token loop
-- Replace all tokens: #<t>#(a,b)
loop
declare
tok : constant String := '#' & Token'Image (t) & '#';
idx : constant Natural := Index (expand, tok);
par : Natural;
replace_by : Unbounded_String;
begin
-- put_line("Token: " & Token'Image(t) & " " & S(expand));
exit when idx = 0; -- No more of token t to replace
par := Index (expand, ")", idx);
declare
opt : constant String := S (expand); -- partially processed option string
curr : constant String := opt (idx + 1 .. opt'Last); -- current option
par_a : constant Natural := Index (curr, "(");
par_z : constant Natural := Index (curr, ")");
comma : constant Natural := Index (curr, ",");
n1, n2, n : Integer;
begin
n1 := Integer'Value (curr (par_a + 1 .. comma - 1));
n2 := Integer'Value (curr (comma + 1 .. par_z - 1));
case t is
when rand =>
-- Replace #RAND#(n1,n2) by a number between n1 and n2.
-- Uniform distribution: U(n1,n2).
declare
subtype rng is Integer range n1 .. n2;
package Rnd is new Ada.Numerics.Discrete_Random (rng);
gen : Rnd.Generator;
begin
Rnd.Reset (gen, seed_iterator); -- seed_iterator is itself randomized.
seed_iterator := seed_iterator + 1;
n := Rnd.Random (gen);
end;
replace_by := U (Trim (Integer'Image (n), Left));
when rand_exp =>
-- Replace #RAND_EXP#(n1,n2) by a number between n1 and n2.
-- Strong bias towards small numbers (rather close to n1 than to n2).
--
-- Example (k=1, n1=1, n2=100): P(X in [1;10]) = 1/2; P(X in [10;100]) = 1/2.
--
-- The CDF is: F(x) = ((log x - log n1) / (log n2 - log n1)) ^ (1/k).
--
declare
use Ada.Numerics.Float_Random, Ada.Numerics.Elementary_Functions;
gen : Generator;
l1, l2, l, u : Float;
k : constant := 2;
begin
Reset (gen, seed_iterator); -- seed_iterator is itself randomized.
seed_iterator := seed_iterator + 1;
u := Random (gen); -- u is Uniform in [0;1]
l1 := Log (Float (n1));
l2 := Log (Float (n2));
l := l1 + (l2 - l1) * (u ** k);
n := Integer (Exp (l));
end;
replace_by := U (Trim (Integer'Image (n), Left));
end case;
Replace_Slice (expand, idx, par, S (replace_by));
end;
end;
end loop;
end loop;
Call_External (packer, S (expand) & ' ' & other_args, is_tool_needed => True);
end Call_External_Expanded;
function Temp_Zip_Name return String is
begin
return Simple_Name (Radix) & "_$temp$.zip";
end Temp_Zip_Name;
procedure Try_deleting_Temp_Zip_File is
begin
if Exists (Temp_Zip_Name) then
Delete_File (Temp_Zip_Name);
end if;
exception
when Ada.IO_Exceptions.Use_Error =>
null;
end Try_deleting_Temp_Zip_File;
procedure Process_External
(packer : String;
options : String;
out_name : String;
is_rand : Boolean;
is_deflate : Boolean;
info : out Packer_info)
is
rand_winner : constant String := Simple_Name (Radix) & "_$rand$.tmp";
options_winner : Unbounded_String;
data_name : constant String := Simple_Name (Temp_name (False, original));
header : Zip.Headers.Local_File_Header;
MyStream : aliased File_Zipstream;
cur_dir : constant String := Current_Directory;
size_memory : array (1 .. randomized_stable) of Zip.Zip_64_Data_Size_Type := (others => 0);
current_size : Zip.Zip_64_Data_Size_Type := 0;
zfm : Unsigned_16;
attempt : Positive := 1;
dummy_exp_opt : Unbounded_String;
zi_ext : Zip.Zip_Info;
begin
-- We jump into the TEMP directory, to avoid putting pathes into the
-- temporary zip file.
Set_Directory (Containing_Directory (Radix));
loop
Try_deleting_Temp_Zip_File; -- remove (eventually broken) zip
Call_External_Expanded (
packer,
options,
Temp_Zip_Name & ' ' & data_name,
info.expanded_options
);
if (not Exists (Temp_Zip_Name)) and then Ada.Directories.Size (data_name) = 0 then
-- ADVZip 1.19 doesn't create a zip file for a 0-size entry; we call Zip instead...
Call_External_Expanded ("zip", "", Temp_Zip_Name & ' ' & data_name, dummy_exp_opt);
end if;
if is_deflate then
-- Post processing of "deflated" entry with DeflOpt:
Call_External (S (defl_opt.name), Temp_Zip_Name, is_tool_needed => False);
end if;
-- Now, rip
Set_Name (MyStream, Temp_Zip_Name);
Open (MyStream, In_File);
Zip.Load (zi_ext, MyStream, True);
Rip_data (
archive => zi_ext,
input => MyStream,
data_name => data_name,
rip_rename => out_name,
unzip_rename => "",
header => header
);
Close (MyStream);
Try_deleting_Temp_Zip_File;
--
if randomized_stable = 1 or not is_rand then -- normal behaviour (1 attempt)
current_size := header.dd.compressed_size;
zfm := header.zip_type;
info.iter := 1;
exit;
end if;
--
-- Here, we process the cases where compressed sizes need
-- to be reduced and we expect a stable size over n=randomized_stable
-- attempts.
--
if attempt = 1 or else
header.dd.compressed_size < current_size -- better size
then
current_size := header.dd.compressed_size;
zfm := header.zip_type;
if Exists (rand_winner) then
Delete_File (rand_winner);
end if;
Rename (out_name, rand_winner);
options_winner := info.expanded_options;
end if;
--
-- Manage the array of last n=randomized_stable sizes
--
if attempt > size_memory'Last then
for i in size_memory'First + 1 .. size_memory'Last loop
size_memory (i - 1) := size_memory (i);
end loop;
size_memory (size_memory'Last) := current_size;
else
size_memory (attempt) := current_size;
end if;
--
-- Check stability over n=randomized_stable attempts
--
if attempt >= randomized_stable then
if size_memory (randomized_stable) = size_memory (1) then
if Exists (out_name) then
Delete_File (out_name);
end if;
Rename (rand_winner, out_name);
info.expanded_options := options_winner;
info.iter := attempt;
exit;
end if;
end if;
attempt := attempt + 1;
end loop;
info.size := current_size;
info.uncomp_size := header.dd.uncompressed_size;
-- uncomp_size should not matter (always the same).
info.zfm := zfm;
info.LZMA_EOS := (zfm = 14) and (header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
-- We jump back to the startup directory.
Set_Directory (cur_dir);
end Process_External;
-- Compress data as raw compressed data
procedure Process_Internal_Raw (a : Approach; e : in out Dir_entry) is
File_in : aliased File_Zipstream;
File_out : aliased File_Zipstream;
begin
Set_Name (File_in, Temp_name (False, original));
Open (File_in, In_File);
Set_Name (File_out, Temp_name (True, a));
Create (File_out, Out_File);
Zip.Compress.Compress_Data
(
input => File_in,
output => File_out,
input_size_known => True,
input_size => e.head.short_info.dd.uncompressed_size,
method => Approach_to_Method (a),
feedback => Zip_Console_IO.My_feedback'Access,
password => "",
content_hint => Zip.Compress.Guess_Type_from_Name (S (e.name)),
CRC => e.head.short_info.dd.crc_32,
-- we take the occasion to compute the CRC if not
-- yet available (e.g. JAR)
output_size => e.info (a).size,
zip_type => e.info (a).zfm
);
e.info (a).LZMA_EOS := e.info (a).zfm = 14;
Close (File_in);
Close (File_out);
end Process_Internal_Raw;
-- Compress data as a temp Zip archive (like external methods), then call post-processing.
-- Currently, only the DeflOpt post-processor is considered.
--
procedure Process_Internal_as_Zip (a : Approach; e : in out Dir_entry) is
zip_file : aliased File_Zipstream;
archive : Zip_Create_Info;
data_name : constant String := Simple_Name (Temp_name (False, original));
zi_ext : Zip.Zip_Info;
header : Zip.Headers.Local_File_Header;
MyStream : aliased File_Zipstream;
cur_dir : constant String := Current_Directory;
begin
Set_Directory (Containing_Directory (Radix));
Create_Archive (archive, zip_file'Unchecked_Access, Temp_Zip_Name);
Set (archive, Approach_to_Method (a));
Add_File (archive, data_name);
Finish (archive);
-- Post processing of "deflated" entry with DeflOpt:
Call_External (S (defl_opt.name), Temp_Zip_Name, is_tool_needed => False);
-- Now, rip
Set_Name (MyStream, Temp_Zip_Name);
Open (MyStream, In_File);
Zip.Load (zi_ext, MyStream, True);
Rip_data (
archive => zi_ext,
input => MyStream,
data_name => data_name,
rip_rename => Temp_name (True, a),
unzip_rename => "",
header => header
);
e.info (a).size := header.dd.compressed_size;
e.info (a).zfm := header.zip_type;
e.info (a).LZMA_EOS :=
(header.zip_type = 14) and (header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
Close (MyStream);
Try_deleting_Temp_Zip_File;
Set_Directory (cur_dir);
end Process_Internal_as_Zip;
time_0 : constant Ada.Calendar.Time := Clock;
procedure Repack_contents (orig_name, repacked_name, html_report_name : String)
is
zi : Zip.Zip_Info;
MyStream : aliased File_Zipstream;
list, e, curr : p_Dir_entry := null;
repacked_zip_file : aliased File_Zipstream;
null_packer_info : constant Packer_info := (0, 0, 0, 0, 0, 0, NN, 1, False);
total : Packer_info_array := (others => null_packer_info);
-- total(a).count counts the files where approach 'a' was optimal
-- total(a).saved counts the saved bytes when approach 'a' was optimal
total_choice : Packer_info := null_packer_info;
summary : Ada.Text_IO.File_Type;
T0, T1 : Ada.Calendar.Time;
repack_duration : Duration;
--
type Approach_Filtering is array (Approach) of Boolean;
consider_a_priori : Approach_Filtering;
--
lightred : constant String := "#ff8696";
lightorange : constant String := "#ffe0b0";
color_for_original : constant String := lightorange;
color_for_winner : constant String := "lightgreen";
use Ada.Float_Text_IO, Ada.Integer_Text_IO;
procedure Process_one (unique_name : String) is
comp_size : Zip.Zip_64_Data_Size_Type;
uncomp_size : Zip.Zip_64_Data_Size_Type;
choice : Approach := original;
deco : constant String := "-->-->-->" & (20 + unique_name'Length) * '-';
mth : Zip.PKZip_method;
consider : Approach_Filtering;
gain, gain_a : Integer_64;
--
procedure Winner_Color is
begin
if e.info (choice).size < e.info (original).size then
Put (summary, "<td bgcolor=" & color_for_winner & "><b>");
-- We were able to reduce the size. :-)
elsif e.info (choice).size = e.info (original).size then
if choice = original then
Put (summary, "<td bgcolor=" & color_for_original & "><b>");
else
-- Something else with exactly the same size as the
-- original was chosen.
-- Happens only if we force another format.
Put (summary, "<td bgcolor=lightblue><b>");
end if;
-- Original was already the best.
else
Put (summary, "<td bgcolor=" & lightred & "><b>");
-- Forced to a format with a less efficient compression. :-(
end if;
end Winner_Color;
--
use Zip;
needs_zip64 : Boolean;
fh_extra : Zip.Headers.Local_File_Header_Extension;
ex_aequo : Boolean;
begin
-- Start with the set of approaches that has been decided for all entries.
consider := consider_a_priori;
if unique_name = "" or else
(unique_name (unique_name'Last) = '\'
or unique_name (unique_name'Last) = '/'
)
then
return; -- directories are useless entries!
end if;
total_choice.count := total_choice.count + 1;
Dual_IO.Close_and_Append_Log; -- have an up to date copy on file system
Dual_IO.Put_Line (deco);
Dual_IO.Put_Line (
' ' &
Integer'Image ((100 * total_choice.count) / Zip.Entries (zi)) &
"% - Processing " &
unique_name & ',' &
Integer'Image (total_choice.count) &
" of" &
Integer'Image (Zip.Entries (zi))
);
Dual_IO.Put_Line (deco);
Dual_IO.New_Line;
--
e := new Dir_entry;
if curr = null then
curr := e;
list := e;
else
curr.next := e;
curr := e;
end if;
e.name := U (unique_name);
e.head.made_by_version := 20; -- version 2.0
e.head.comment_length := 0;
e.head.disk_number_start := 0;
e.head.internal_attributes := 0; -- 0: seems binary; 1, text
e.head.external_attributes := 0;
--
Dual_IO.Put (" Phase 1: dump & unzip -");
Rip_data (
archive => zi,
input => MyStream,
data_name => unique_name,
rip_rename => Temp_name (True, original),
unzip_rename => Temp_name (False, original),
header => e.head.short_info
);
--
if touch then
e.head.short_info.file_timedate := Zip.Convert (time_0);
end if;
if lower then
e.name := U (To_Lower (S (e.name)));
end if;
-- Get reliable data from zi
Zip.Get_Sizes (
info => zi,
name => unique_name,
comp_size => comp_size,
uncomp_size => uncomp_size
);
Dual_IO.Put_Line (" done");
--
-- Apply limitations: skip some methods if certain conditions are met.
-- For instance:
-- Shrink may in rare cases be better, but only for tiny files.
-- KZip and Zopfli are excellent but really too slow on large files.
--
for a in Approach loop
case a is
when original =>
null;
when shrink =>
consider (a) := consider (a) and uncomp_size <= 6000;
when reduce_4 =>
consider (a) := consider (a) and uncomp_size <= 9000;
when External =>
consider (a) := consider (a) and (ext (a).limit = 0 or uncomp_size <= ext (a).limit);
when others =>
null;
end case;
end loop;
Dual_IO.Put_Line (" Phase 2: try different tactics...");
--
Try_all_approaches :
--
for a in Approach loop
if consider (a) then
Dual_IO.Put (" -o-> " & Img (a, html => False));
e.info (a).iter := 1;
case a is
--
when original =>
-- This is from the original .zip - just record size and method
e.info (a).size := comp_size;
e.info (a).zfm := e.head.short_info.zip_type;
e.info (a).LZMA_EOS :=
(e.info (a).zfm = 14) and
(e.head.short_info.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
mth := Zip.Method_from_Code (e.info (a).zfm);
--
when Internal =>
if Approach_to_Method (a) in Zip.Compress.Deflation_Method
and not internal_only
then
-- We will post-process our internal Deflate with DeflOpt.
Process_Internal_as_Zip (a, e.all);
else
Process_Internal_Raw (a, e.all);
end if;
when External =>
Dual_IO.New_Line;
Process_External (
S (ext (a).name),
S (ext (a).options),
Temp_name (True, a),
ext (a).randomized,
ext (a).pkzm = Zip.deflate,
e.info (a)
);
e.head.made_by_version := ext (a).made_by_version;
ext (a).expanded_options := e.info (a).expanded_options;
--
end case;
total (a).size := total (a).size + e.info (a).size;
if e.info (a).size < e.info (choice).size then
-- Hurra, we found a smaller size than previous choice!
choice := a;
end if;
if choice = original and not format_choice (mth) then
-- This occurs if we want to make an archive with only a certain set of formats,
-- for instance deflate_or_store, which is the most compatible.
-- Since approach _a_ uses a format in the desired set, the choice will be
-- forced out of original, even with a worse size.
choice := a;
end if;
Dual_IO.New_Line;
end if;
end loop Try_all_approaches;
--
total_choice.size := total_choice.size + e.info (choice).size;
total (choice).count := total (choice).count + 1;
total_choice.uncomp_size :=
total_choice.uncomp_size + Unsigned_64 (uncomp_size);
gain := Integer_64 (e.info (original).size) - Integer_64 (e.info (choice).size);
total (choice).saved := total (choice).saved + gain;
-- We award now the ex-aequo's. Caution: multiple counting if you take the sum of totals
-- over all approachs, but it is good for knowing the strength of an individual approach.
for a in Approach loop
if consider (a) then
gain_a := Integer_64 (e.info (original).size) - Integer_64 (e.info (a).size);
if gain_a = gain then
total (a).saved_ex_aequo := total (a).saved_ex_aequo + gain;
end if;
end if;
end loop;
total_choice.saved := total_choice.saved + gain;
--
Dual_IO.New_Line;
Dual_IO.Put (
" Phase 3: Winner is " & Img (choice, html => False) &
"; gain in bytes:" & Integer_64'Image (gain) &
"; writing data -"
);
-- * Summary outputs
Put (summary,
"<tr><td>" &
Trim (Integer'Image (total_choice.count), Left) &
-- '/' &
-- Trim(Integer'Image(Zip.Entries(zi)),Left) &
"</td>" &
"<td bgcolor=lightgrey><tt>" & unique_name & "</tt>, " &
Image_1000 (uncomp_size) & "</td>");
for a in Approach loop
if consider_a_priori (a) then
ex_aequo := e.info (a).size = e.info (choice).size;
if not consider (a) then
Put (summary, "<td bgcolor=lightgray>skipped");
elsif a = choice then
Winner_Color;
elsif ex_aequo then
Put (summary, "<td bgcolor=lightblue><b>");
elsif a = original then
Put (summary, "<td bgcolor=" & color_for_original & '>');
else
Put (summary, "<td>");
end if;
if consider (a) then
Put (summary, Image_1000 (e.info (a).size));
end if;
if ex_aequo then
Put (summary, "</b>");
end if;
Put (summary, "</td>");
end if;
end loop;
-- Recall winner approach:
Put
(summary,
"<td" &
(if choice = original then " bgcolor=" & color_for_original else "") & '>' &
Img (choice, html => True) & "</td>");
-- Recall winner format:
Put
(summary,
"<td" &
(if choice = original then
" bgcolor=" & color_for_original
elsif e.info (choice).size < e.info (original).size then
" bgcolor=" & color_for_winner
elsif e.info (choice).size > e.info (original).size then
" bgcolor=" & lightred
else
"") &
'>' & Zip.Image (Zip.Method_from_Code (e.info (choice).zfm)) & "</td>");
-- Recall original format:
Put
(summary,
"<td bgcolor=" & color_for_original & '>' &
Zip.Image (Zip.Method_from_Code (e.info (original).zfm)) & "</td>");
-- Recall winner size:
Winner_Color;
Put (summary, Image_1000 (e.info (choice).size));
Put (summary, "</b></td><td>");
if e.info (original).size > 0 then
Put (
summary,
100.0 * Float (e.info (choice).size) / Float (e.info (original).size),
3, 2, 0
);
Put (summary, "%");
end if;
Put (summary, "</td><td>");
if uncomp_size > 0 then
Put (
summary,
100.0 * Float (e.info (choice).size) / Float (uncomp_size),
3, 2, 0
);
Put (summary, "%");
end if;
Put (summary, "</td><td>");
Put (summary, Image_1000 (uncomp_size));
Put (summary, "</td><td>");
Put (summary, Integer'Image (e.info (choice).iter));
Put_Line (summary, "</td></tr>");
--
-- Write winning data:
--
e.head.short_info.extra_field_length := 0; -- We choose to ignore it...
-- No data descriptor after data (bit 3); no EOS for LZMA (bit 1):
e.head.short_info.bit_flag :=
e.head.short_info.bit_flag and (2#1111_1111_1111_0101#);
-- Set the LZMA EOS flag if present in winner entry (checked by 7-Zip v.17.01):
if e.info (choice).LZMA_EOS then
e.head.short_info.bit_flag := e.head.short_info.bit_flag or Zip.Headers.LZMA_EOS_Flag_Bit;
end if;
-- Set or adjust the pre-data data descriptor:
-- NB: even if missing pre-data, CRC will have been computed
-- at least with one internal method
e.head.short_info.dd.uncompressed_size := uncomp_size;
-- Put the winning size and method
e.head.short_info.dd.compressed_size := e.info (choice).size;
e.head.short_info.zip_type := e.info (choice).zfm;
e.head.local_header_offset := Unsigned_64 (Index (repacked_zip_file)) - 1;
needs_zip64 :=
Zip.Headers.Needs_Local_Zip_64_Header_Extension
(e.head.short_info, e.head.local_header_offset);
Zip.Headers.Write
(repacked_zip_file, e.head.short_info,
(if needs_zip64 then Zip.Headers.force_zip_64 else Zip.Headers.force_empty));
String'Write (repacked_zip_file'Access, S (e.name));
if needs_zip64 then
fh_extra.tag := 1;
fh_extra.size := Zip.Headers.local_header_extension_short_length - 4;
fh_extra.value_64 (1) := e.head.short_info.dd.uncompressed_size;
fh_extra.value_64 (2) := e.head.short_info.dd.compressed_size;
fh_extra.value_64 (3) := e.head.local_header_offset; -- Not actually written.
Zip.Headers.Write (repacked_zip_file, fh_extra, True);
end if;
-- Copy the compressed data
Zip.Copy_File (Temp_name (True, choice), repacked_zip_file);
Dual_IO.Put_Line (" done");
Dual_IO.New_Line;
end Process_one;
procedure Process_all is new Zip.Traverse (Process_one);
ed : Zip.Headers.End_of_Central_Dir;
function Webcolor (a : Approach) return String is
v : Float;
sr, sg, sb : String (1 .. 10);
begin
if a = original then
return color_for_original;
end if;
if total_choice.saved > 0 and
-- with options like -defl ot -fast_dec, we may have
-- negative values or other strange things:
total (a).saved >= 0
then
v := Float (total (a).saved) / Float (total_choice.saved);
-- ^ contribution of approach 'a'
else
v := 0.0;
end if;
Put (sr, 512 + Integer (144.0 + 111.0 * (1.0 - v)), 16);
sb := sr;
Put (sg, 512 + Integer (238.0 + 17.0 * (1.0 - v)), 16);
return
'#' &
sr (sr'Last - 2 .. sr'Last - 1) &
sg (sg'Last - 2 .. sg'Last - 1) &
sb (sb'Last - 2 .. sb'Last - 1);
end Webcolor;
meth : Zip.Compress.Compression_Method;
begin -- Repack_contents
T0 := Clock;
for a in Approach loop
case a is
when original =>
consider_a_priori (a) := True;
when Internal =>
meth := Approach_to_Method (a);
case meth is
when Zip.Compress.Single_Method =>
consider_a_priori (a) := format_choice (Zip.Compress.Method_to_Format (meth));
when Zip.Compress.Multi_Method =>
-- For the sake of simplicity, we consider the Multi_Method's
-- only when all formats are admitted.
consider_a_priori (a) := format_choice = all_formats;
end case;
when External =>
consider_a_priori (a) := format_choice (ext (a).pkzm) and not internal_only;
end case;
end loop;
Set_Name (MyStream, orig_name);
Open (MyStream, In_File);
Zip.Load (zi, MyStream, True);
Set_Name (repacked_zip_file, repacked_name);
Create (repacked_zip_file, Out_File);
Create (summary, Out_File, html_report_name);
--
-- HTML Report begins here.
--
Put_Line (summary,
"<html><head><title>ReZip summary for file "
& orig_name & "</title></head>"
);
Put_Line (summary, "<style>.container { overflow-y: auto; height: 87%; }");
Put_Line (summary, "td_approach { width:115px; }");
Put_Line (summary, "</style><body>");
Put_Line (summary, "<font face=""Calibri, Arial, Tahoma""> <!-- Set font for the whole page !-->");
Put_Line (summary,
"<h2><a target=_blank href=" & Zip.web &
">ReZip</a> summary for file " & orig_name & "</h2>"
);
Put_Line (summary,
"ReZip - Zip-Ada Library version " & Zip.version & " dated " & Zip.reference
);
if format_choice /= all_formats then
Put_Line (summary,
"<br><table border=0 cellpadding=0 cellspacing=0>" &
"<tr bgcolor=" & lightred &
"><td><b>An option that filters methods is on, " &
"result(s) may be sub-optimal - see details at bottom.</b></td></tr></table><br>"
);
end if;
Put_Line (summary, "<div class=""container""><table border=1 cellpadding=1 cellspacing=1>");
Put (summary,
"<tr bgcolor=lightyellow><td></td>" &
"<td align=right valign=top><b>Approach:</b></td>"
);
for a in Approach loop
if consider_a_priori (a) then
if a in External then
ext (a).expanded_options := ext (a).options;
end if;
Put
(summary,
"<td valign=top class=""td_approach""" &
(if a = original then " bgcolor=" & color_for_original else "") & '>' &
Img (a, html => True) & "</td>");
end if;
end loop;
Put_Line (summary, "</tr>");
Put (summary,
"<tr bgcolor=lightyellow><td></td>" &
"<td bgcolor=lightgrey valign=bottom><b>File name, uncompressed size:</b></td>"
);
-- Additionally, we show a row with the Approach's Compression_Method's output format (the
-- Zip.PKZip_method). If it is not unique, we mention it.
for a in Approach loop
if consider_a_priori (a) then
case a is
when original =>
Put (summary, "<td align=right bgcolor=#dddd00 class=""td_approach"">Approach's<br>format →</td>");
when Internal =>
Put (summary, "<td bgcolor=#fafa64>");
meth := Approach_to_Method (a);
case meth is
when Zip.Compress.Single_Method =>
Put (summary, Zip.Image (Zip.Compress.Method_to_Format (meth)));
when Zip.Compress.Multi_Method =>
Put (summary, "(Various formats)");
end case;
Put (summary, "</td>");
when External =>
Put (summary, "<td bgcolor=#fafa64>" & Zip.Image (ext (a).pkzm) & "</td>");
end case;
end if;
end loop;
Put_Line (summary,
"<td><b>Choice</b></td>" &
"<td bgcolor=#dddd00>Choice's<br>method/<br>format</td>" &
"<td>Original<br>method/<br>format</td>" &
"<td>Smallest<br>size</td>" &
"<td>% of<br>original</td>" &
"<td>% of<br>uncompressed</td>" &
"<td>Uncompressed<br>size</td>" &
"<td>Iterations</td></tr>"
);
--
-- 1/ Recompress each file into the new archive:
--
Process_all (zi);
--
-- 2/ Almost done - write Central Directory:
--
ed.central_dir_offset := Unsigned_64 (Index (repacked_zip_file)) - 1;
ed.total_entries := 0;
ed.central_dir_size := 0;
ed.main_comment_length := 0;
declare
comment : constant String := Zip.Zip_Comment (zi);
needs_64, needs_local_zip64 : Boolean;
fh_extra : Zip.Headers.Local_File_Header_Extension;
ed64l : Zip.Headers.Zip64_End_of_Central_Dir_Locator;
ed64 : Zip.Headers.Zip64_End_of_Central_Dir;
begin
if not delete_comment then
ed.main_comment_length := comment'Length;
end if;
-- Restart at the beginning of the list
e := list;
needs_64 := False;
while e /= null loop
ed.total_entries := ed.total_entries + 1;
needs_local_zip64 :=
Zip.Headers.Needs_Local_Zip_64_Header_Extension
(e.head.short_info, e.head.local_header_offset);
if needs_local_zip64 then
e.head.short_info.extra_field_length := Zip.Headers.local_header_extension_length;
fh_extra.tag := 1;
fh_extra.size := Zip.Headers.local_header_extension_length - 4;
fh_extra.value_64 (1) := e.head.short_info.dd.uncompressed_size;
fh_extra.value_64 (2) := e.head.short_info.dd.compressed_size;
fh_extra.value_64 (3) := e.head.local_header_offset;
e.head.short_info.dd.uncompressed_size := 16#FFFF_FFFF#;
e.head.short_info.dd.compressed_size := 16#FFFF_FFFF#;
e.head.local_header_offset := 16#FFFF_FFFF#;
needs_64 := True;
end if;
Zip.Headers.Write (repacked_zip_file, e.head);
String'Write (repacked_zip_file'Access, S (e.name));
if needs_local_zip64 then
Zip.Headers.Write (repacked_zip_file, fh_extra, False);
end if;
ed.central_dir_size :=
ed.central_dir_size +
Zip.Headers.central_header_length +
Unsigned_64 (e.head.short_info.filename_length) +
Unsigned_64 (e.head.short_info.extra_field_length);
e := e.next;
end loop;
ed.disknum := 0;
ed.disknum_with_start := 0;
ed.disk_total_entries := ed.total_entries;
if needs_64 then
ed64l.number_of_the_disk_with_the_start_of_the_zip64_end_of_central_dir := 0;
ed64l.relative_offset_of_the_zip64_end_of_central_dir_record :=
Unsigned_64 (Index (repacked_zip_file) - 1);
ed64l.total_number_of_disks := 1;
--
ed64.size := 44;
ed64.version_made_by := 16#2D#;
ed64.version_needed_to_extract := 16#2D#;
ed64.number_of_this_disk := ed.disknum;
ed64.number_of_the_disk_with_the_start_of_the_central_directory := ed.disknum_with_start;
ed64.total_number_of_entries_in_the_central_directory_on_this_disk := ed.disk_total_entries;
ed64.total_number_of_entries_in_the_central_directory := ed.total_entries;
ed64.size_of_the_central_directory := ed.central_dir_size;
ed64.offset_of_start_of_central_directory := ed.central_dir_offset;
Zip.Headers.Write (repacked_zip_file, ed64);
--
Zip.Headers.Write (repacked_zip_file, ed64l);
--
ed.disk_total_entries := 16#FFFF#;
ed.total_entries := 16#FFFF#;
ed.central_dir_size := 16#FFFF_FFFF#;
ed.central_dir_offset := 16#FFFF_FFFF#;
end if;
Zip.Headers.Write (repacked_zip_file, ed);
if not delete_comment then
String'Write (repacked_zip_file'Access, comment);
end if;
end;
Close (repacked_zip_file);
Close (MyStream);
--
-- Cleanup.
--
for a in Approach loop
if consider_a_priori (a) then
if Exists (Temp_name (True, a)) then
Delete_File (Temp_name (True, a));
end if;
if a = original then -- also an uncompressed data file to delete
Delete_File (Temp_name (False, a));
end if;
end if;
end loop;
--
-- Report total files per approach.
--
Put (summary, "<tr><td></td><td><b>T<small>OTAL FILES (of chosen optimal approach)</small></b></td>");
for a in Approach loop
if consider_a_priori (a) then
Put (summary, "<td bgcolor=" & Webcolor (a) & '>' & total (a).count'Image & "</td>");
end if;
end loop;
Put
(summary,
"<td></td><td></td><td></td><td bgcolor=" & color_for_winner & "><b>" &
total_choice.count'Image &
"</b></td>" &
"<td>");
Put_Line (summary, "</td><td></td><td></td><td></td></tr>");
--
-- Report total compressed bytes.
--
Put (summary, "<tr><td></td><td><b>T<small>OTAL COMPRESSED BYTES</small></b></td>");
for a in Approach loop
if consider_a_priori (a) then
Put
(summary,
"<td bgcolor=" & Webcolor (a) & ">" &
Image_1000 (total (a).size) & "</td>");
end if;
end loop;
Put
(summary,
"<td></td><td></td><td></td><td bgcolor=" & color_for_winner & "><b>" &
Image_1000 (total_choice.size) &
"</b></td><td>");
if total (original).size > 0 then
Put (summary,
100.0 * Float (total_choice.size) / Float (total (original).size),
3, 2, 0
);
Put (summary, "%");
end if;
Put (summary, "</td><td>");
if total_choice.uncomp_size > 0 then
Put (summary,
100.0 * Float (total_choice.size) / Float (total_choice.uncomp_size),
3, 2, 0
);
Put (summary, "%");
end if;
Put_Line (summary, "</td><td></td><td></td></tr>");
--
-- Report total saved bytes per approach.
--
Put (summary, "<tr><td></td><td><b>T<small>OTAL BYTES SAVED (by chosen optimal approach)</small></b></td>");
for a in Approach loop
if consider_a_priori (a) then
Put (summary, "<td bgcolor=" & Webcolor (a) & '>' & Image_1000 (total (a).saved) & "</td>");
end if;
end loop;
Put
(summary,
"<td></td><td></td><td></td><td" &
(if total_choice.saved > 0 then
" bgcolor=" & color_for_winner
elsif total_choice.saved < 0 then
" bgcolor=" & lightred
else
"") &
"><b>" & Image_1000 (total_choice.saved) & "</b></td>" &
"<td>");
if total (original).size > 0 then
Put
(summary,
100.0 * Float (total_choice.saved) / Float (total (original).size),
3, 2, 0);
Put (summary, "%");
end if;
Put (summary, "</td><td>");
if total_choice.uncomp_size > 0 then
Put (summary,
100.0 * Float (total_choice.saved) / Float (total_choice.uncomp_size),
3, 2, 0
);
Put (summary, "%");
end if;
Put_Line (summary, "</td><td></td><td></td></tr>");
--
-- Report total saved bytes per approach, *including ex-aequos*.
--
Put
(summary,
"<tr><td></td><td><b>T<small>OTAL BYTES SAVED (by chosen or " &
"ex-aequo optimal approach)</small></b></td>");
for a in Approach loop
if consider_a_priori (a) then
Put
(summary,
"<td bgcolor=" & Webcolor (a) & ">" &
Image_1000 (total (a).saved_ex_aequo) & "</td>");
end if;
end loop;
Put (summary, "<td></td><td></td><td></td><td></td><td></td><td>");
Put_Line (summary, "</td><td></td><td></td></tr>");
Put_Line (summary, "</table></div><div><br><br>");
Put_Line (summary, "<dt>Options used for ReZip</dt>");
Put_Line (summary, "<dd>Randomized_stable =" & Integer'Image (randomized_stable) & "<br>");
Put_Line (summary, " Formats allowed:<br><table border=1 cellpadding=1 cellspacing=1>");
for f in format_choice'Range loop
Put_Line (summary,
" <tr><td>" & Zip.Image (f) & "</td><td>" &
Boolean'Image (format_choice (f)) & "</td></tr>");
end loop;
Put_Line (summary, " </table>");
Put_Line (summary, "</dd></div>");
T1 := Clock;
repack_duration := T1 - T0;
Put (summary, "Time elapsed : ");
Put (summary, Float (repack_duration), 4, 2, 0);
Put (summary, " seconds, or");
Put (summary, Float (repack_duration) / 60.0, 4, 2, 0);
Put (summary, " minutes, or");
Put (summary, Float (repack_duration) / 3600.0, 4, 2, 0);
Put_Line (summary, " hours.</font></body></html>");
Close (summary);
Dual_IO.Put ("Time elapsed : ");
DFIO.Put (Float (repack_duration), 4, 2, 0);
Dual_IO.Put_Line (" sec");
Dual_IO.Put_Line ("All details for " & orig_name & " in " & html_report_name);
end Repack_contents;
-- This is for randomizing the above seed_iterator.
-- On GNAT the clock-based Reset is too coarse: it gives many times
-- the same seed when called with small time intervals.
--
subtype Seed_Range is Integer range 1 .. 1_000_000;
package Rnd_seed is new Ada.Numerics.Discrete_Random (Seed_Range);
gen_seed : Rnd_seed.Generator;
begin
Rnd_seed.Reset (gen_seed); -- 1x clock-based randomization
seed_iterator := Rnd_seed.Random (gen_seed);
if alt_tmp_file_radix = "" then
Flexible_temp_files.Initialize;
end if;
Dual_IO.Create_Log (log_file);
Repack_contents (from_zip_file, to_zip_file, html_report);
Dual_IO.Close_Log;
if alt_tmp_file_radix = "" then
Flexible_temp_files.Finalize;
end if;
exception
when External_Tool_Failed =>
Dual_IO.Put_Line (" Is that tool callable through the ""path"" ?");
Dual_IO.Put_Line (" In doubt, re-run ReZip with the ""-int"" (internal only) option.");
Dual_IO.Close_Log;
raise;
end Rezip;
procedure Show_external_packer_list is
procedure Display (p : Zipper_Specification) is
fix : String (1 .. 8) := (others => ' ');
begin
Insert (fix, fix'First, S (p.title));
Ada.Text_IO.Put (" " & fix);
fix := (others => ' ');
Insert (fix, fix'First, S (p.name));
Ada.Text_IO.Put_Line (" Executable: " & fix & " URL: " & S (p.URL));
end Display;
name_is_new : Boolean;
begin
for e in External loop
name_is_new := True;
for ee in External'First .. External'Pred (e) loop
name_is_new := name_is_new and ext (e).name /= ext (ee).name;
end loop;
if name_is_new then
Display (ext (e));
end if;
end loop;
Display (defl_opt);
end Show_external_packer_list;
end Rezip_lib;
Zip-Ada: Ada library for zip archive files (.zip).
Ada programming.
Some news about Zip-Ada and other Ada projects
on Gautier's blog.