Back to... Zip-Ada

Source file : rezip_lib.adb



   1  ------------------------------------------------------------------------------
   2  --  File:            rezip_lib.adb
   3  --  Description:     Recompression tool to make archives smaller.
   4  --                   Core moved from Rezip (main). Still Q&D !
   5  --  Author:          Gautier de Montmollin
   6  ------------------------------------------------------------------------------
   7  --
   8  --  To do:
   9  --    * In order to facilitate customization, ReZip could have a config file
  10  --      ( http://sf.net/projects/ini-files/ ) to store external packer
  11  --      program names. See ZipMax as an example...
  12  --
  13  --  External programs used (feel free to customize/add/remove):
  14  --    7-Zip, KZip, Zip (info-zip), AdvZip, DeflOpt
  15  --    Web URL's: see Zipper_specification below or run ReZip without arguments.
  16  
  17  with Zip.Create,
  18       Zip.Compress,
  19       Zip.Headers;
  20  
  21  with Flexible_temp_files;
  22  with UnZip;
  23  with Zip_Streams;
  24  with Zip_Console_IO;
  25  
  26  with Ada.Calendar,
  27       Ada.Characters.Handling,
  28       Ada.Directories,
  29       Ada.Float_Text_IO,
  30       Ada.Integer_Text_IO,
  31       Ada.IO_Exceptions,
  32       Ada.Numerics.Discrete_Random,
  33       Ada.Numerics.Elementary_Functions,
  34       Ada.Numerics.Float_Random,
  35       Ada.Streams.Stream_IO,
  36       Ada.Strings.Fixed,
  37       Ada.Strings.Unbounded,
  38       Ada.Text_IO,
  39       Ada.Unchecked_Deallocation;
  40  
  41  with Dual_IO;
  42  
  43  with Interfaces;
  44  
  45  with GNAT.OS_Lib;
  46  
  47  package body Rezip_lib is
  48  
  49    function S (Source : Ada.Strings.Unbounded.Unbounded_String) return String
  50      renames Ada.Strings.Unbounded.To_String;
  51    function U (Source : String) return Ada.Strings.Unbounded.Unbounded_String
  52      renames Ada.Strings.Unbounded.To_Unbounded_String;
  53  
  54    use Ada.Strings.Unbounded;
  55    use Interfaces;
  56  
  57    --  This info might be better read from a config file...
  58    --
  59    type Zipper_Specification is record
  60      name, title, URL, options : Unbounded_String;
  61      expanded_options    : Unbounded_String;
  62      --  ^ Options with dynamically expanded tokens
  63      made_by_version     : Unsigned_16;
  64      pkzm                : Zip.PKZip_method;
  65      limit               : Zip.Zip_64_Data_Size_Type;
  66      --  ^ Compression is considered too slow or unefficient beyond limit (if not 0).
  67      --    E.g., kzip's algorithm might be O(N^2) or worse; on large files,
  68      --    deflate_e or other methods are better anyway
  69      randomized          : Boolean;
  70    end record;
  71  
  72    NN : constant Unbounded_String := Null_Unbounded_String;
  73  
  74    --  Give up recompression above a certain data size for some external packers like KZip
  75    --  or Zopfli.
  76    --
  77    kzip_zopfli_limit : constant := 2_000_000;
  78  
  79    type Approach is
  80      (original,
  81       presel_2, presel_1,
  82       shrink,
  83       reduce_4,
  84       deflate_3,
  85       deflate_r,
  86       bzip2_3, bzip2_2, bzip2_1,
  87       lzma_3, lzma_2,
  88       external_01, external_02, external_03, external_04,
  89       external_05, external_06, external_07, external_08,
  90       external_09, external_10, external_11, external_12,
  91       external_13, external_14, external_15);
  92  
  93    subtype Internal is Approach
  94      range Approach'Succ (Approach'First) .. Approach'Pred (external_01);
  95    subtype External is Approach
  96      range external_01 .. Approach'Last;
  97  
  98    ext : array (External) of Zipper_Specification :=
  99      ( --  Zip 2.32 or later:
 100        (U ("zip"), U ("Zip"), U ("http://info-zip.org/"),
 101           U ("-9"), NN, 20, Zip.deflate, 0, False),
 102        --  7-Zip 4.64 or later; Deflate:
 103        (U ("7z"),
 104           U ("7-Zip"), U ("http://7-zip.org/"),
 105           U ("a -tzip -mm=deflate -mfb=258 -mpass=#RAND#(7,15) -mmc=10000"),
 106           NN, 20, Zip.deflate, 0, True),
 107        (U ("7z"),
 108           U ("7-Zip"), NN,
 109           U ("a -tzip -mm=deflate64 -mfb=257 -mpass=15 -mmc=10000"),
 110           NN, 21, Zip.deflate_e, 0, False),
 111        --  KZip:
 112        (U ("kzip"), U ("KZIP"), U ("http://www.advsys.net/ken/utils.htm"),
 113           U ("/rn /b0"), NN, 20, Zip.deflate, kzip_zopfli_limit, True),
 114        (U ("kzip"), U ("KZIP"), NN,
 115           U ("/rn /b#RAND_EXP#(1,2048)"), NN, 20, Zip.deflate, kzip_zopfli_limit, True),
 116        --  Zip 3.0 or later; BZip2:
 117        (U ("zip"), U ("Zip"), NN,
 118           U ("-#RAND#(1,9) -Z bzip2"), NN, 46, Zip.bzip2_meth, 0, True),
 119        --  7z:
 120        (U ("7z"), U ("7-Zip"), NN,
 121           U ("a -tzip -mm=BZip2:d=#RAND#(1,9)00k:pass=7"), NN, 46, Zip.bzip2_meth, 0, True),
 122        --  7-Zip 9.20 or later; LZMA:
 123        (U ("7z"), U ("7-Zip"), NN,
 124           U ("a -tzip -mm=LZMA -mx=9"), NN, 63, Zip.lzma_meth, 0, False),
 125        (U ("7z"), U ("7-Zip"), NN, --  LZ77: BT3 or BT4, dictionary size 2**19 = 512 KiB
 126           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),
 127        (U ("7z"), U ("7-Zip"), NN, --  LZ77: BT3 or BT4, dictionary size 2**25 = 32 MiB
 128           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),
 129        (U ("7z"), U ("7-Zip"), NN, --  LZ77: BT3 or BT4, dictionary size 2**26 = 64 MiB
 130           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),
 131        (U ("7z"), U ("7-Zip"), NN, --  Super-randomized version
 132           U ("a -tzip -mm=LZMA:a=2:d=#RAND_EXP#(1,65535)k:mf=bt#RAND#(2,5):fb=#RAND#(128,273):" &
 133              "lc=#RAND#(0,8):lp#RAND#(0,4):pb#RAND#(0,4)"),
 134           NN, 63, Zip.lzma_meth, 0, True),
 135        --  AdvZip: advancecomp v1.19+ interesting for the Zopfli algorithm
 136        (U ("advzip"), U ("AdvZip"), U ("http://advancemame.sf.net/comp-readme.html"),
 137           U ("-a -2"), NN, 20, Zip.deflate, 0, False),
 138        (U ("advzip"), U ("AdvZip"), NN,
 139           U ("-a -3"), NN, 20, Zip.deflate, 0, False),
 140        (U ("advzip"), U ("AdvZip"), NN,
 141           U ("-a -4"), NN, 20, Zip.deflate, kzip_zopfli_limit, False));
 142  
 143    defl_opt : constant Zipper_Specification :=
 144      (U ("deflopt"), U ("DeflOpt"), U ("http://www.walbeehm.com/download/"),
 145       NN, NN, 0, Zip.deflate, 0, False);
 146  
 147    use Ada.Strings.Fixed, Ada.Strings;
 148  
 149    procedure Rezip (
 150      from_zip_file      : String;
 151      to_zip_file        : String;
 152      format_choice      : Zip_format_set := all_formats;  --  force output into selected format set
 153      touch              : Boolean        := False;        --  set time stamps to now
 154      lower              : Boolean        := False;        --  set full file names to lower case
 155      delete_comment     : Boolean        := False;        --  delete zip comment
 156      randomized_stable  : Positive       := 1;
 157      log_file           : String         := "";
 158      html_report        : String         := "";
 159      alt_tmp_file_radix : String         := "";           --  e.g. "X:\temp\rz_"
 160      internal_only      : Boolean        := False         --  Zip-Ada algorithms only, no ext. call
 161    )
 162    is
 163  
 164      use Zip.Create;
 165      use Zip_Streams;
 166  
 167      use Ada.Calendar, Ada.Characters.Handling, Ada.Directories, Ada.Text_IO;
 168  
 169      package DFIO is new Dual_IO.Float_IO (Float);
 170  
 171      procedure Rip_data (
 172        archive      : Zip.Zip_Info; -- from this archive...
 173        input        : in out Root_Zipstream_Type'Class;
 174        data_name    : String;       -- extract this data
 175        rip_rename   : String;       -- to this file (compressed)
 176        unzip_rename : String;       -- and this one (uncompressed)
 177        header       : out Zip.Headers.Local_File_Header
 178      )
 179      is
 180        file_index     : Zip_Streams.ZS_Index_Type;
 181        comp_size      : Zip.Zip_64_Data_Size_Type;
 182        uncomp_size    : Zip.Zip_64_Data_Size_Type;
 183        file_out       : Ada.Streams.Stream_IO.File_Type;
 184        dummy_encoding : Zip.Zip_Name_Encoding;
 185        dummy_crc      : Unsigned_32;
 186        mem            : Zip_Streams.ZS_Index_Type;
 187        head_extra     : Zip.Headers.Local_File_Header_Extension;
 188        dummy_offset   : Unsigned_64 := 0;  --  Initialized for avoiding random value = 16#FFFF_FFFF#
 189  
 190        use UnZip, Ada.Streams.Stream_IO;
 191      begin
 192        Zip.Find_Offset (
 193          info           => archive,
 194          name           => data_name,
 195          name_encoding  => dummy_encoding,
 196          file_index     => file_index,
 197          comp_size      => comp_size,
 198          uncomp_size    => uncomp_size,
 199          crc_32         => dummy_crc
 200        );
 201        Set_Index (input, file_index);
 202        Zip.Headers.Read_and_Check (input, header);
 203        --  Skip name
 204        Set_Index (input,
 205          Index (input) + Zip_Streams.ZS_Size_Type (header.filename_length)
 206        );
 207        mem := Index (input);
 208        if header.extra_field_length >= 4 then
 209          Zip.Headers.Read_and_Check (input, head_extra);
 210          Zip.Headers.Interpret
 211            (head_extra,
 212             header.dd.uncompressed_size,
 213             header.dd.compressed_size,
 214             dummy_offset);
 215        end if;
 216        --  Skip extra field
 217        Set_Index (input, mem + Zip_Streams.ZS_Size_Type (header.extra_field_length));
 218        --  * Get the data, compressed
 219        Ada.Streams.Stream_IO.Create (file_out, Out_File, rip_rename);
 220        Zip.Copy_Chunk (input, Stream (file_out).all, Integer (comp_size));
 221        Close (file_out);
 222        if unzip_rename /= "" then
 223          --  * Get the data, uncompressed
 224          Extract (
 225            from    => archive,
 226            what    => data_name,
 227            rename  => unzip_rename,
 228            options =>
 229               (test_only => False,
 230                junk_directories => False,
 231                case_sensitive_match => True,
 232                extract_as_text => False
 233            )
 234          );
 235        end if;
 236      end Rip_data;
 237  
 238      Approach_to_Method : constant array (Internal) of Zip.Compress.Compression_Method :=
 239        (shrink    => Zip.Compress.Shrink,
 240         reduce_4  => Zip.Compress.Reduce_4,
 241         deflate_3 => Zip.Compress.Deflate_3,
 242         deflate_r => Zip.Compress.Deflate_R,
 243         bzip2_1   => Zip.Compress.BZip2_1,
 244         bzip2_2   => Zip.Compress.BZip2_2,
 245         bzip2_3   => Zip.Compress.BZip2_3,
 246         lzma_2    => Zip.Compress.LZMA_2,
 247         lzma_3    => Zip.Compress.LZMA_3,
 248         presel_1  => Zip.Compress.Preselection_1,
 249         presel_2  => Zip.Compress.Preselection_2);
 250  
 251      type Packer_info is record
 252        size             : Zip.Zip_64_Data_Size_Type;
 253        zfm              : Unsigned_16;
 254        count            : Natural;
 255        saved            : Integer_64;  --  Number of bytes saved by chosen method
 256        --  NB: can be negative if -defl chosen: suboptimal recompression,
 257        --      but compatible method.
 258        saved_ex_aequo   : Integer_64;  --  Number of bytes saved if method is as good as
 259                                        --  the winning method.
 260        uncomp_size      : Unsigned_64;
 261        --  summed uncompressed sizes might be more than 2**32
 262        expanded_options : Unbounded_String;
 263        iter             : Positive; -- iterations needed
 264        LZMA_EOS         : Boolean;
 265      end record;
 266  
 267      type Packer_info_array is array (Approach) of Packer_info;
 268  
 269      type Dir_entry;
 270      type p_Dir_entry is access Dir_entry;
 271      --
 272      type Dir_entry is record
 273        head : Zip.Headers.Central_File_Header;
 274        name : Unbounded_String;
 275        next : p_Dir_entry := null;
 276        chosen_approach : Approach := original;
 277        info : Packer_info_array;
 278      end record;
 279  
 280      function Radix return String is
 281      begin
 282        if alt_tmp_file_radix = "" then
 283          return Flexible_temp_files.Radix;
 284        else
 285          return alt_tmp_file_radix;
 286        end if;
 287      end Radix;
 288  
 289      function Temp_name (
 290        is_compressed : Boolean;
 291        appr          : Approach
 292      )
 293        return String
 294      is
 295        initial : constant array (Boolean) of Character := ('u', 'c');
 296      begin
 297        return
 298          Radix &
 299          "_!" & initial (is_compressed) &
 300          '!' & Trim (Integer'Image (Approach'Pos (appr)), Left) &
 301          "!_.tmp";
 302      end Temp_name;
 303  
 304      function Img (a : Approach; html : Boolean) return String is
 305        function Repl (s : String) return String is
 306          t : String := s;
 307        begin
 308          for i in t'Range loop
 309            if html and t (i) = ':' then t (i) := ' '; end if;  --  Break too long texts within a cell.
 310          end loop;
 311          return t;
 312        end Repl;
 313      begin
 314        if a in External then
 315          return "External: " & S (ext (a).title) & ", " & Repl (S (ext (a).expanded_options));
 316        else
 317          declare
 318            s : constant String := Approach'Image (a);
 319          begin
 320            return s (s'First) & To_Lower (s (s'First + 1 .. s'Last) & (Approach'Width - s'Length + 1) * ' ');
 321          end;
 322        end if;
 323      end Img;
 324  
 325      --  From AZip_Common...
 326      function Image_1000 (r : Zip.Zip_64_Data_Size_Type; separator : Character := ''') return String is
 327        s : constant String := Zip.Zip_64_Data_Size_Type'Image (r);
 328        t : String (s'First .. s'First + (s'Length * 4) / 3);
 329        j, c : Natural;
 330      begin
 331        --  For signed integers
 332        --  if r < 0 then
 333        --    return '-' & Image_1000(abs r, separator);
 334        --  end if;
 335        --
 336        --  We build result string t from right to left
 337        j := t'Last + 1;
 338        c := 0;
 339        for i in reverse s'First .. s'Last loop
 340          exit when s (i) = ' ' or s (i) = '-';
 341          if c > 0 and then c mod 3 = 0 then
 342            j := j - 1;
 343            t (j) := separator;
 344          end if;
 345          j := j - 1;
 346          t (j) := s (i);
 347          c := c + 1;
 348        end loop;
 349        return t (j .. t'Last);
 350      end Image_1000;
 351  
 352      function Image_1000 (r : Integer_64; separator : Character := ''') return String is
 353        s : constant String := Integer_64'Image (r);
 354        t : String (s'First .. s'First + (s'Length * 4) / 3);
 355        j, c : Natural;
 356      begin
 357        --  For signed integers
 358        if r < 0 then
 359          return '-' & Image_1000 (abs r, separator);
 360        end if;
 361        --  We build result string t from right to left
 362        j := t'Last + 1;
 363        c := 0;
 364        for i in reverse s'First .. s'Last loop
 365          exit when s (i) = ' ' or s (i) = '-';
 366          if c > 0 and then c mod 3 = 0 then
 367            j := j - 1;
 368            t (j) := separator;
 369          end if;
 370          j := j - 1;
 371          t (j) := s (i);
 372          c := c + 1;
 373        end loop;
 374        return t (j .. t'Last);
 375      end Image_1000;
 376  
 377      procedure Call_External
 378        (packer         : String;
 379         args           : String;
 380         is_tool_needed : Boolean)
 381      is
 382        use GNAT.OS_Lib;
 383        procedure Dispose is
 384          new Ada.Unchecked_Deallocation (Argument_List, Argument_List_Access);
 385        list : Argument_List_Access;
 386        ok : Boolean;
 387      begin
 388        Dual_IO.Put_Line (packer & " [" & args & ']');
 389        list := Argument_String_To_List (args);
 390        GNAT.OS_Lib.Spawn (packer, list.all, ok);
 391        Dispose (list);
 392        if not ok then
 393          declare
 394            msg : constant String := " cannot call external tool """ & packer &
 395                 """, or it has returned an error.";
 396          begin
 397            Dual_IO.New_Line;
 398            Dual_IO.Put_Line ("**************");
 399            if is_tool_needed then
 400              Dual_IO.Put_Line ("ReZip ERROR:" & msg);
 401              raise External_Tool_Failed;
 402            else
 403              Dual_IO.Put_Line ("ReZip warning:" & msg);
 404            end if;
 405          end;
 406        end if;
 407      end Call_External;
 408  
 409      seed_iterator : Natural;
 410  
 411      procedure Call_External_Expanded
 412        (packer     :        String;
 413         options    :        String;
 414         other_args :        String;
 415         expand     : in out Unbounded_String)  --  expanded arguments
 416      is
 417        type Token is (rand, rand_exp);
 418      begin
 419        expand := U (options);
 420        for t in Token loop
 421          --  Replace all tokens:  #<t>#(a,b)
 422          loop
 423            declare
 424              tok : constant String := '#' & Token'Image (t) & '#';
 425              idx : constant Natural := Index (expand, tok);
 426              par : Natural;
 427              replace_by : Unbounded_String;
 428            begin
 429              --  put_line("Token: " & Token'Image(t) & "   " & S(expand));
 430              exit when idx = 0;  --  No more of token t to replace
 431              par := Index (expand, ")", idx);
 432              declare
 433                opt : constant String := S (expand);  --  partially processed option string
 434                curr : constant String := opt (idx + 1 .. opt'Last);  --  current option
 435                par_a : constant Natural := Index (curr, "(");
 436                par_z : constant Natural := Index (curr, ")");
 437                comma : constant Natural := Index (curr, ",");
 438                n1, n2, n : Integer;
 439              begin
 440                n1 := Integer'Value (curr (par_a + 1 .. comma - 1));
 441                n2 := Integer'Value (curr (comma + 1 .. par_z - 1));
 442                case t is
 443                  when rand =>
 444                    --  Replace #RAND#(n1,n2) by a number between n1 and n2.
 445                    --  Uniform distribution: U(n1,n2).
 446                    declare
 447                      subtype rng is Integer range n1 .. n2;
 448                      package Rnd is new Ada.Numerics.Discrete_Random (rng);
 449                      gen : Rnd.Generator;
 450                    begin
 451                      Rnd.Reset (gen, seed_iterator);  --  seed_iterator is itself randomized.
 452                      seed_iterator := seed_iterator + 1;
 453                      n := Rnd.Random (gen);
 454                    end;
 455                    replace_by := U (Trim (Integer'Image (n), Left));
 456                  when rand_exp =>
 457                    --  Replace #RAND_EXP#(n1,n2) by a number between n1 and n2.
 458                    --  Strong bias towards small numbers (rather close to n1 than to n2).
 459                    --
 460                    --  Example (k=1, n1=1, n2=100): P(X in [1;10]) = 1/2; P(X in [10;100]) = 1/2.
 461                    --
 462                    --  The CDF is:  F(x) = ((log x - log n1) / (log n2 - log n1)) ^ (1/k).
 463                    --
 464                    declare
 465                      use Ada.Numerics.Float_Random, Ada.Numerics.Elementary_Functions;
 466                      gen : Generator;
 467                      l1, l2, l, u : Float;
 468                      k : constant := 2;
 469                    begin
 470                      Reset (gen, seed_iterator);  --  seed_iterator is itself randomized.
 471                      seed_iterator := seed_iterator + 1;
 472                      u := Random (gen);  --  u is Uniform in [0;1]
 473                      l1 := Log (Float (n1));
 474                      l2 := Log (Float (n2));
 475                      l := l1 + (l2 - l1) * (u ** k);
 476                      n := Integer (Exp (l));
 477                    end;
 478                    replace_by := U (Trim (Integer'Image (n), Left));
 479                end case;
 480                Replace_Slice (expand, idx, par, S (replace_by));
 481              end;
 482            end;
 483          end loop;
 484        end loop;
 485        Call_External (packer, S (expand) & ' ' & other_args, is_tool_needed => True);
 486      end Call_External_Expanded;
 487  
 488      function Temp_Zip_Name return String is
 489      begin
 490        return Simple_Name (Radix) & "_$temp$.zip";
 491      end Temp_Zip_Name;
 492  
 493      procedure Try_deleting_Temp_Zip_File is
 494      begin
 495        if Exists (Temp_Zip_Name) then
 496          Delete_File (Temp_Zip_Name);
 497        end if;
 498      exception
 499        when Ada.IO_Exceptions.Use_Error =>
 500          null;
 501      end Try_deleting_Temp_Zip_File;
 502  
 503      procedure Process_External
 504        (packer     : String;
 505         options    : String;
 506         out_name   : String;
 507         is_rand    : Boolean;
 508         is_deflate : Boolean;
 509         info       : out Packer_info)
 510      is
 511        rand_winner : constant String := Simple_Name (Radix) & "_$rand$.tmp";
 512        options_winner : Unbounded_String;
 513        data_name : constant String := Simple_Name (Temp_name (False, original));
 514        header : Zip.Headers.Local_File_Header;
 515        MyStream   : aliased File_Zipstream;
 516        cur_dir : constant String := Current_Directory;
 517        size_memory : array (1 .. randomized_stable) of Zip.Zip_64_Data_Size_Type := (others => 0);
 518        current_size : Zip.Zip_64_Data_Size_Type := 0;
 519        zfm : Unsigned_16;
 520        attempt : Positive := 1;
 521        dummy_exp_opt : Unbounded_String;
 522        zi_ext : Zip.Zip_Info;
 523      begin
 524        --  We jump into the TEMP directory, to avoid putting pathes into the
 525        --  temporary zip file.
 526        Set_Directory (Containing_Directory (Radix));
 527        loop
 528          Try_deleting_Temp_Zip_File;  --  remove (eventually broken) zip
 529          Call_External_Expanded (
 530            packer,
 531            options,
 532            Temp_Zip_Name & ' ' & data_name,
 533            info.expanded_options
 534          );
 535          if (not Exists (Temp_Zip_Name)) and then Ada.Directories.Size (data_name) = 0 then
 536            --  ADVZip 1.19 doesn't create a zip file for a 0-size entry; we call Zip instead...
 537            Call_External_Expanded ("zip", "", Temp_Zip_Name & ' ' & data_name, dummy_exp_opt);
 538          end if;
 539          if is_deflate then
 540            --  Post processing of "deflated" entry with DeflOpt:
 541            Call_External (S (defl_opt.name), Temp_Zip_Name, is_tool_needed => False);
 542          end if;
 543          --  Now, rip
 544          Set_Name (MyStream, Temp_Zip_Name);
 545          Open (MyStream, In_File);
 546          Zip.Load (zi_ext, MyStream, True);
 547          Rip_data (
 548            archive      => zi_ext,
 549            input        => MyStream,
 550            data_name    => data_name,
 551            rip_rename   => out_name,
 552            unzip_rename => "",
 553            header       => header
 554          );
 555          Close (MyStream);
 556          Try_deleting_Temp_Zip_File;
 557          --
 558          if randomized_stable = 1 or not is_rand then  --  normal behaviour (1 attempt)
 559            current_size := header.dd.compressed_size;
 560            zfm := header.zip_type;
 561            info.iter := 1;
 562            exit;
 563          end if;
 564          --
 565          --  Here, we process the cases where compressed sizes need
 566          --  to be reduced and we expect a stable size over n=randomized_stable
 567          --  attempts.
 568          --
 569          if attempt = 1 or else
 570            header.dd.compressed_size < current_size  --  better size
 571          then
 572            current_size := header.dd.compressed_size;
 573            zfm := header.zip_type;
 574            if Exists (rand_winner) then
 575              Delete_File (rand_winner);
 576            end if;
 577            Rename (out_name, rand_winner);
 578            options_winner := info.expanded_options;
 579          end if;
 580          --
 581          --  Manage the array of last n=randomized_stable sizes
 582          --
 583          if attempt > size_memory'Last then
 584            for i in size_memory'First + 1 .. size_memory'Last loop
 585              size_memory (i - 1) := size_memory (i);
 586            end loop;
 587            size_memory (size_memory'Last) := current_size;
 588          else
 589            size_memory (attempt) := current_size;
 590          end if;
 591          --
 592          --  Check stability over n=randomized_stable attempts
 593          --
 594          if attempt >= randomized_stable then
 595            if size_memory (randomized_stable) = size_memory (1) then
 596              if Exists (out_name) then
 597                Delete_File (out_name);
 598              end if;
 599              Rename (rand_winner, out_name);
 600              info.expanded_options := options_winner;
 601              info.iter := attempt;
 602              exit;
 603            end if;
 604          end if;
 605          attempt := attempt + 1;
 606        end loop;
 607        info.size        := current_size;
 608        info.uncomp_size := header.dd.uncompressed_size;
 609        --  uncomp_size should not matter (always the same).
 610        info.zfm        := zfm;
 611        info.LZMA_EOS   := (zfm = 14) and (header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
 612        --  We jump back to the startup directory.
 613        Set_Directory (cur_dir);
 614      end Process_External;
 615  
 616      --  Compress data as raw compressed data
 617      procedure Process_Internal_Raw (a : Approach; e : in out Dir_entry) is
 618        File_in       : aliased File_Zipstream;
 619        File_out      : aliased File_Zipstream;
 620      begin
 621        Set_Name (File_in, Temp_name (False, original));
 622        Open (File_in, In_File);
 623        Set_Name (File_out, Temp_name (True, a));
 624        Create (File_out, Out_File);
 625        Zip.Compress.Compress_Data
 626        (
 627          input            => File_in,
 628          output           => File_out,
 629          input_size_known => True,
 630          input_size       => e.head.short_info.dd.uncompressed_size,
 631          method           => Approach_to_Method (a),
 632          feedback         => Zip_Console_IO.My_feedback'Access,
 633          password         => "",
 634          content_hint     => Zip.Compress.Guess_Type_from_Name (S (e.name)),
 635          CRC              => e.head.short_info.dd.crc_32,
 636          --  we take the occasion to compute the CRC if not
 637          --  yet available (e.g. JAR)
 638          output_size      => e.info (a).size,
 639          zip_type         => e.info (a).zfm
 640        );
 641        e.info (a).LZMA_EOS := e.info (a).zfm = 14;
 642        Close (File_in);
 643        Close (File_out);
 644      end Process_Internal_Raw;
 645  
 646      --  Compress data as a temp Zip archive (like external methods), then call post-processing.
 647      --  Currently, only the DeflOpt post-processor is considered.
 648      --
 649      procedure Process_Internal_as_Zip (a : Approach; e : in out Dir_entry) is
 650        zip_file : aliased File_Zipstream;
 651        archive : Zip_Create_Info;
 652        data_name : constant String := Simple_Name (Temp_name (False, original));
 653        zi_ext : Zip.Zip_Info;
 654        header : Zip.Headers.Local_File_Header;
 655        MyStream   : aliased File_Zipstream;
 656        cur_dir : constant String := Current_Directory;
 657      begin
 658        Set_Directory (Containing_Directory (Radix));
 659        Create_Archive (archive, zip_file'Unchecked_Access, Temp_Zip_Name);
 660        Set (archive, Approach_to_Method (a));
 661        Add_File (archive, data_name);
 662        Finish (archive);
 663        --  Post processing of "deflated" entry with DeflOpt:
 664        Call_External (S (defl_opt.name), Temp_Zip_Name, is_tool_needed => False);
 665        --  Now, rip
 666        Set_Name (MyStream, Temp_Zip_Name);
 667        Open (MyStream, In_File);
 668        Zip.Load (zi_ext, MyStream, True);
 669        Rip_data (
 670          archive      => zi_ext,
 671          input        => MyStream,
 672          data_name    => data_name,
 673          rip_rename   => Temp_name (True, a),
 674          unzip_rename => "",
 675          header       => header
 676        );
 677        e.info (a).size := header.dd.compressed_size;
 678        e.info (a).zfm := header.zip_type;
 679        e.info (a).LZMA_EOS :=
 680          (header.zip_type = 14) and (header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
 681        Close (MyStream);
 682        Try_deleting_Temp_Zip_File;
 683        Set_Directory (cur_dir);
 684      end Process_Internal_as_Zip;
 685  
 686      time_0 : constant Ada.Calendar.Time := Clock;
 687  
 688      procedure Repack_contents (orig_name, repacked_name, html_report_name : String)
 689      is
 690        zi : Zip.Zip_Info;
 691        MyStream   : aliased File_Zipstream;
 692  
 693        list, e, curr : p_Dir_entry := null;
 694        repacked_zip_file   : aliased File_Zipstream;
 695        null_packer_info : constant Packer_info := (0, 0, 0, 0, 0, 0, NN, 1, False);
 696        total : Packer_info_array := (others => null_packer_info);
 697        --  total(a).count counts the files where approach 'a' was optimal
 698        --  total(a).saved counts the saved bytes when approach 'a' was optimal
 699        total_choice : Packer_info := null_packer_info;
 700        summary : Ada.Text_IO.File_Type;
 701        T0, T1 : Ada.Calendar.Time;
 702        repack_duration : Duration;
 703        --
 704        type Approach_Filtering is array (Approach) of Boolean;
 705        consider_a_priori : Approach_Filtering;
 706        --
 707        lightred    : constant String := "#ff8696";
 708        lightorange : constant String := "#ffe0b0";
 709  
 710        color_for_original : constant String := lightorange;
 711        color_for_winner   : constant String := "lightgreen";
 712  
 713        use Ada.Float_Text_IO, Ada.Integer_Text_IO;
 714  
 715        procedure Process_one (unique_name : String) is
 716          comp_size   :  Zip.Zip_64_Data_Size_Type;
 717          uncomp_size :  Zip.Zip_64_Data_Size_Type;
 718          choice : Approach := original;
 719          deco : constant String := "-->-->-->" & (20 + unique_name'Length) * '-';
 720          mth : Zip.PKZip_method;
 721          consider : Approach_Filtering;
 722          gain, gain_a : Integer_64;
 723          --
 724          procedure Winner_Color is
 725          begin
 726            if e.info (choice).size < e.info (original).size then
 727              Put (summary, "<td bgcolor=" & color_for_winner & "><b>");
 728              --  We were able to reduce the size. :-)
 729            elsif e.info (choice).size = e.info (original).size then
 730              if choice = original then
 731                Put (summary, "<td bgcolor=" & color_for_original & "><b>");
 732              else
 733                --  Something else with exactly the same size as the
 734                --  original was chosen.
 735                --  Happens only if we force another format.
 736                Put (summary, "<td bgcolor=lightblue><b>");
 737              end if;
 738              --  Original was already the best.
 739            else
 740              Put (summary, "<td bgcolor=" & lightred & "><b>");
 741              --  Forced to a format with a less efficient compression. :-(
 742            end if;
 743          end Winner_Color;
 744          --
 745          use Zip;
 746          needs_zip64 : Boolean;
 747          fh_extra : Zip.Headers.Local_File_Header_Extension;
 748          ex_aequo : Boolean;
 749        begin
 750          --  Start with the set of approaches that has been decided for all entries.
 751          consider := consider_a_priori;
 752          if unique_name = "" or else
 753               (unique_name (unique_name'Last) = '\'
 754             or unique_name (unique_name'Last) = '/'
 755            )
 756          then
 757            return; -- directories are useless entries!
 758          end if;
 759          total_choice.count := total_choice.count + 1;
 760          Dual_IO.Close_and_Append_Log; -- have an up to date copy on file system
 761          Dual_IO.Put_Line (deco);
 762          Dual_IO.Put_Line (
 763            ' ' &
 764            Integer'Image ((100 * total_choice.count) / Zip.Entries (zi)) &
 765            "% - Processing " &
 766            unique_name & ',' &
 767            Integer'Image (total_choice.count) &
 768            " of" &
 769            Integer'Image (Zip.Entries (zi))
 770          );
 771          Dual_IO.Put_Line (deco);
 772          Dual_IO.New_Line;
 773          --
 774          e := new Dir_entry;
 775          if curr = null then
 776            curr := e;
 777            list := e;
 778          else
 779            curr.next := e;
 780            curr := e;
 781          end if;
 782          e.name := U (unique_name);
 783          e.head.made_by_version     := 20; -- version 2.0
 784          e.head.comment_length      := 0;
 785          e.head.disk_number_start   := 0;
 786          e.head.internal_attributes := 0; -- 0: seems binary; 1, text
 787          e.head.external_attributes := 0;
 788          --
 789          Dual_IO.Put ("    Phase 1:  dump & unzip -");
 790          Rip_data (
 791            archive      => zi,
 792            input        => MyStream,
 793            data_name    => unique_name,
 794            rip_rename   => Temp_name (True, original),
 795            unzip_rename => Temp_name (False, original),
 796            header       => e.head.short_info
 797          );
 798          --
 799          if touch then
 800            e.head.short_info.file_timedate := Zip.Convert (time_0);
 801          end if;
 802          if lower then
 803            e.name := U (To_Lower (S (e.name)));
 804          end if;
 805          --  Get reliable data from zi
 806          Zip.Get_Sizes (
 807            info           => zi,
 808            name           => unique_name,
 809            comp_size      => comp_size,
 810            uncomp_size    => uncomp_size
 811          );
 812          Dual_IO.Put_Line (" done");
 813          --
 814          --  Apply limitations: skip some methods if certain conditions are met.
 815          --  For instance:
 816          --    Shrink may in rare cases be better, but only for tiny files.
 817          --    KZip and Zopfli are excellent but really too slow on large files.
 818          --
 819          for a in Approach loop
 820            case a is
 821              when original =>
 822                null;
 823              when shrink =>
 824                consider (a) := consider (a) and uncomp_size <= 6000;
 825              when reduce_4 =>
 826                consider (a) := consider (a) and uncomp_size <= 9000;
 827              when External =>
 828                consider (a) := consider (a) and (ext (a).limit = 0 or uncomp_size <= ext (a).limit);
 829              when others =>
 830                null;
 831            end case;
 832          end loop;
 833          Dual_IO.Put_Line ("    Phase 2:  try different tactics...");
 834          --
 835          Try_all_approaches :
 836          --
 837          for a in Approach loop
 838            if consider (a) then
 839              Dual_IO.Put ("              -o-> " & Img (a, html => False));
 840              e.info (a).iter := 1;
 841              case a is
 842                --
 843                when original =>
 844                  --  This is from the original .zip - just record size and method
 845                  e.info (a).size := comp_size;
 846                  e.info (a).zfm  := e.head.short_info.zip_type;
 847                  e.info (a).LZMA_EOS :=
 848                    (e.info (a).zfm = 14) and
 849                    (e.head.short_info.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0;
 850                  mth := Zip.Method_from_Code (e.info (a).zfm);
 851                  --
 852                when Internal =>
 853                  if Approach_to_Method (a) in Zip.Compress.Deflation_Method
 854                    and not internal_only
 855                  then
 856                    --  We will post-process our internal Deflate with DeflOpt.
 857                    Process_Internal_as_Zip (a, e.all);
 858                  else
 859                    Process_Internal_Raw (a, e.all);
 860                  end if;
 861                when External =>
 862                  Dual_IO.New_Line;
 863                  Process_External (
 864                    S (ext (a).name),
 865                    S (ext (a).options),
 866                    Temp_name (True, a),
 867                    ext (a).randomized,
 868                    ext (a).pkzm = Zip.deflate,
 869                    e.info (a)
 870                  );
 871                  e.head.made_by_version := ext (a).made_by_version;
 872                  ext (a).expanded_options := e.info (a).expanded_options;
 873                  --
 874              end case;
 875              total (a).size := total (a).size + e.info (a).size;
 876              if e.info (a).size < e.info (choice).size then
 877                --  Hurra, we found a smaller size than previous choice!
 878                choice := a;
 879              end if;
 880              if choice = original and not format_choice (mth) then
 881                --  This occurs if we want to make an archive with only a certain set of formats,
 882                --  for instance deflate_or_store, which is the most compatible.
 883                --  Since approach _a_ uses a format in the desired set, the choice will be
 884                --  forced out of original, even with a worse size.
 885                choice := a;
 886              end if;
 887              Dual_IO.New_Line;
 888            end if;
 889          end loop Try_all_approaches;
 890          --
 891          total_choice.size := total_choice.size + e.info (choice).size;
 892          total (choice).count := total (choice).count + 1;
 893          total_choice.uncomp_size :=
 894            total_choice.uncomp_size + Unsigned_64 (uncomp_size);
 895          gain := Integer_64 (e.info (original).size) - Integer_64 (e.info (choice).size);
 896          total (choice).saved := total (choice).saved + gain;
 897          --  We award now the ex-aequo's. Caution: multiple counting if you take the sum of totals
 898          --  over all approachs, but it is good for knowing the strength of an individual approach.
 899          for a in Approach loop
 900            if consider (a) then
 901              gain_a := Integer_64 (e.info (original).size) - Integer_64 (e.info (a).size);
 902              if gain_a = gain then
 903                total (a).saved_ex_aequo := total (a).saved_ex_aequo + gain;
 904              end if;
 905            end if;
 906          end loop;
 907          total_choice.saved := total_choice.saved + gain;
 908          --
 909          Dual_IO.New_Line;
 910          Dual_IO.Put (
 911            "    Phase 3:  Winner is " & Img (choice, html => False) &
 912            "; gain in bytes:" & Integer_64'Image (gain) &
 913            "; writing data -"
 914          );
 915          --  * Summary outputs
 916          Put (summary,
 917            "<tr><td>" &
 918            Trim (Integer'Image (total_choice.count), Left) &
 919            --  '/' &
 920            --  Trim(Integer'Image(Zip.Entries(zi)),Left) &
 921            "</td>" &
 922            "<td bgcolor=lightgrey><tt>" & unique_name & "</tt>, " &
 923            Image_1000 (uncomp_size) & "</td>");
 924          for a in Approach loop
 925            if consider_a_priori (a) then
 926              ex_aequo := e.info (a).size = e.info (choice).size;
 927              if not consider (a) then
 928                Put (summary, "<td bgcolor=lightgray>skipped");
 929              elsif a = choice then
 930                Winner_Color;
 931              elsif ex_aequo then
 932                Put (summary, "<td bgcolor=lightblue><b>");
 933              elsif a = original then
 934                Put (summary, "<td bgcolor=" & color_for_original & '>');
 935              else
 936                Put (summary, "<td>");
 937              end if;
 938              if consider (a) then
 939                Put (summary, Image_1000 (e.info (a).size));
 940              end if;
 941              if ex_aequo then
 942                Put (summary, "</b>");
 943              end if;
 944              Put (summary, "</td>");
 945            end if;
 946          end loop;
 947          --  Recall winner approach:
 948          Put
 949            (summary,
 950             "<td" &
 951             (if choice = original then " bgcolor=" & color_for_original else "") & '>' &
 952             Img (choice, html => True) & "</td>");
 953          --  Recall winner format:
 954          Put
 955            (summary,
 956             "<td" &
 957             (if choice = original then
 958               " bgcolor=" & color_for_original
 959              elsif e.info (choice).size < e.info (original).size then
 960                " bgcolor=" & color_for_winner
 961              elsif e.info (choice).size > e.info (original).size then
 962                " bgcolor=" & lightred
 963              else
 964                "") &
 965             '>' & Zip.Image (Zip.Method_from_Code (e.info (choice).zfm)) & "</td>");
 966          --  Recall original format:
 967          Put
 968            (summary,
 969             "<td bgcolor=" & color_for_original & '>' &
 970             Zip.Image (Zip.Method_from_Code (e.info (original).zfm)) & "</td>");
 971          --  Recall winner size:
 972          Winner_Color;
 973          Put (summary, Image_1000 (e.info (choice).size));
 974          Put (summary, "</b></td><td>");
 975          if e.info (original).size > 0 then
 976            Put (
 977              summary,
 978              100.0 * Float (e.info (choice).size) / Float (e.info (original).size),
 979              3, 2, 0
 980            );
 981            Put (summary, "%");
 982          end if;
 983          Put (summary, "</td><td>");
 984          if uncomp_size > 0 then
 985            Put (
 986              summary,
 987              100.0 * Float (e.info (choice).size) / Float (uncomp_size),
 988              3, 2, 0
 989            );
 990            Put (summary, "%");
 991          end if;
 992          Put (summary, "</td><td>");
 993          Put (summary, Image_1000 (uncomp_size));
 994          Put (summary, "</td><td>");
 995          Put (summary, Integer'Image (e.info (choice).iter));
 996          Put_Line (summary, "</td></tr>");
 997          --
 998          --  Write winning data:
 999          --
1000          e.head.short_info.extra_field_length := 0;  --  We choose to ignore it...
1001          --  No data descriptor after data (bit 3); no EOS for LZMA (bit 1):
1002          e.head.short_info.bit_flag :=
1003            e.head.short_info.bit_flag and (2#1111_1111_1111_0101#);
1004          --  Set the LZMA EOS flag if present in winner entry (checked by 7-Zip v.17.01):
1005          if e.info (choice).LZMA_EOS then
1006            e.head.short_info.bit_flag := e.head.short_info.bit_flag or Zip.Headers.LZMA_EOS_Flag_Bit;
1007          end if;
1008          --  Set or adjust the pre-data data descriptor:
1009          --  NB: even if missing pre-data, CRC will have been computed
1010          --     at least with one internal method
1011          e.head.short_info.dd.uncompressed_size := uncomp_size;
1012          --  Put the winning size and method
1013          e.head.short_info.dd.compressed_size := e.info (choice).size;
1014          e.head.short_info.zip_type := e.info (choice).zfm;
1015          e.head.local_header_offset := Unsigned_64 (Index (repacked_zip_file)) - 1;
1016          needs_zip64 :=
1017            Zip.Headers.Needs_Local_Zip_64_Header_Extension
1018              (e.head.short_info, e.head.local_header_offset);
1019          Zip.Headers.Write
1020            (repacked_zip_file, e.head.short_info,
1021             (if needs_zip64 then Zip.Headers.force_zip_64 else Zip.Headers.force_empty));
1022          String'Write (repacked_zip_file'Access, S (e.name));
1023          if needs_zip64 then
1024            fh_extra.tag  := 1;
1025            fh_extra.size := Zip.Headers.local_header_extension_short_length - 4;
1026            fh_extra.value_64 (1) := e.head.short_info.dd.uncompressed_size;
1027            fh_extra.value_64 (2) := e.head.short_info.dd.compressed_size;
1028            fh_extra.value_64 (3) := e.head.local_header_offset;  --  Not actually written.
1029            Zip.Headers.Write (repacked_zip_file, fh_extra, True);
1030          end if;
1031          --  Copy the compressed data
1032          Zip.Copy_File (Temp_name (True, choice), repacked_zip_file);
1033          Dual_IO.Put_Line (" done");
1034          Dual_IO.New_Line;
1035        end Process_one;
1036  
1037        procedure Process_all is new Zip.Traverse (Process_one);
1038  
1039        ed : Zip.Headers.End_of_Central_Dir;
1040  
1041        function Webcolor (a : Approach) return String is
1042          v : Float;
1043          sr, sg, sb : String (1 .. 10);
1044        begin
1045          if a = original then
1046            return color_for_original;
1047          end if;
1048          if total_choice.saved > 0 and
1049            --  with options like -defl ot -fast_dec, we may have
1050            --  negative values or other strange things:
1051             total (a).saved >= 0
1052          then
1053            v := Float (total (a).saved) / Float (total_choice.saved);
1054            --   ^ contribution of approach 'a'
1055          else
1056            v := 0.0;
1057          end if;
1058          Put (sr, 512 + Integer (144.0 + 111.0 * (1.0 - v)), 16);
1059          sb := sr;
1060          Put (sg, 512 + Integer (238.0 + 17.0 * (1.0 - v)), 16);
1061          return
1062            '#' &
1063            sr (sr'Last - 2 .. sr'Last - 1) &
1064            sg (sg'Last - 2 .. sg'Last - 1) &
1065            sb (sb'Last - 2 .. sb'Last - 1);
1066        end Webcolor;
1067  
1068        meth : Zip.Compress.Compression_Method;
1069  
1070      begin  --  Repack_contents
1071        T0 := Clock;
1072        for a in Approach loop
1073          case a is
1074            when original =>
1075              consider_a_priori (a) := True;
1076            when Internal =>
1077              meth := Approach_to_Method (a);
1078              case meth is
1079                when Zip.Compress.Single_Method =>
1080                  consider_a_priori (a) := format_choice (Zip.Compress.Method_to_Format (meth));
1081                when Zip.Compress.Multi_Method =>
1082                  --  For the sake of simplicity, we consider the Multi_Method's
1083                  --  only when all formats are admitted.
1084                  consider_a_priori (a) := format_choice = all_formats;
1085              end case;
1086            when External =>
1087              consider_a_priori (a) := format_choice (ext (a).pkzm) and not internal_only;
1088          end case;
1089        end loop;
1090        Set_Name (MyStream, orig_name);
1091        Open (MyStream, In_File);
1092        Zip.Load (zi, MyStream, True);
1093  
1094        Set_Name (repacked_zip_file, repacked_name);
1095        Create (repacked_zip_file, Out_File);
1096        Create (summary, Out_File, html_report_name);
1097        --
1098        --  HTML Report begins here.
1099        --
1100        Put_Line (summary,
1101          "<html><head><title>ReZip summary for file "
1102           & orig_name & "</title></head>"
1103        );
1104        Put_Line (summary, "<style>.container { overflow-y: auto; height: 87%; }");
1105        Put_Line (summary, "td_approach { width:115px; }");
1106        Put_Line (summary, "</style><body>");
1107        Put_Line (summary, "<font face=""Calibri, Arial, Tahoma""> <!-- Set font for the whole page !-->");
1108        Put_Line (summary,
1109          "<h2><a target=_blank href=" & Zip.web &
1110          ">ReZip</a> summary for file " & orig_name & "</h2>"
1111        );
1112        Put_Line (summary,
1113          "ReZip - Zip-Ada Library version " & Zip.version & " dated " & Zip.reference
1114        );
1115        if format_choice /= all_formats then
1116          Put_Line (summary,
1117            "<br><table border=0 cellpadding=0 cellspacing=0>" &
1118            "<tr bgcolor=" & lightred &
1119            "><td><b>An option that filters methods is on, " &
1120            "result(s) may be sub-optimal - see details at bottom.</b></td></tr></table><br>"
1121          );
1122        end if;
1123        Put_Line (summary, "<div class=""container""><table border=1 cellpadding=1 cellspacing=1>");
1124        Put (summary,
1125          "<tr bgcolor=lightyellow><td></td>" &
1126          "<td align=right valign=top><b>Approach:</b></td>"
1127        );
1128        for a in Approach loop
1129          if consider_a_priori (a) then
1130            if a in External then
1131              ext (a).expanded_options := ext (a).options;
1132            end if;
1133            Put
1134              (summary,
1135               "<td valign=top class=""td_approach""" &
1136               (if a = original then " bgcolor=" & color_for_original else "") & '>' &
1137               Img (a, html => True) & "</td>");
1138          end if;
1139        end loop;
1140        Put_Line (summary, "</tr>");
1141        Put (summary,
1142          "<tr bgcolor=lightyellow><td></td>" &
1143          "<td bgcolor=lightgrey valign=bottom><b>File name, uncompressed size:</b></td>"
1144        );
1145        --  Additionally, we show a row with the Approach's Compression_Method's output format (the
1146        --  Zip.PKZip_method). If it is not unique, we mention it.
1147        for a in Approach loop
1148          if consider_a_priori (a) then
1149            case a is
1150              when original =>
1151                Put (summary, "<td align=right bgcolor=#dddd00 class=""td_approach"">Approach's<br>format &rarr;</td>");
1152              when Internal =>
1153                Put (summary, "<td bgcolor=#fafa64>");
1154                meth := Approach_to_Method (a);
1155                case meth is
1156                  when Zip.Compress.Single_Method =>
1157                    Put (summary, Zip.Image (Zip.Compress.Method_to_Format (meth)));
1158                  when Zip.Compress.Multi_Method =>
1159                    Put (summary, "(Various formats)");
1160                end case;
1161                Put (summary, "</td>");
1162              when External =>
1163                Put (summary, "<td bgcolor=#fafa64>" & Zip.Image (ext (a).pkzm) & "</td>");
1164            end case;
1165          end if;
1166        end loop;
1167        Put_Line (summary,
1168          "<td><b>Choice</b></td>" &
1169          "<td bgcolor=#dddd00>Choice's<br>method/<br>format</td>" &
1170          "<td>Original<br>method/<br>format</td>" &
1171          "<td>Smallest<br>size</td>" &
1172          "<td>% of<br>original</td>" &
1173          "<td>% of<br>uncompressed</td>" &
1174          "<td>Uncompressed<br>size</td>" &
1175          "<td>Iterations</td></tr>"
1176        );
1177        --
1178        --  1/ Recompress each file into the new archive:
1179        --
1180        Process_all (zi);
1181        --
1182        --  2/ Almost done - write Central Directory:
1183        --
1184        ed.central_dir_offset := Unsigned_64 (Index (repacked_zip_file)) - 1;
1185        ed.total_entries := 0;
1186        ed.central_dir_size := 0;
1187        ed.main_comment_length := 0;
1188        declare
1189          comment : constant String := Zip.Zip_Comment (zi);
1190          needs_64, needs_local_zip64 : Boolean;
1191          fh_extra : Zip.Headers.Local_File_Header_Extension;
1192          ed64l    : Zip.Headers.Zip64_End_of_Central_Dir_Locator;
1193          ed64     : Zip.Headers.Zip64_End_of_Central_Dir;
1194        begin
1195          if not delete_comment then
1196            ed.main_comment_length := comment'Length;
1197          end if;
1198          --  Restart at the beginning of the list
1199          e := list;
1200          needs_64 := False;
1201          while e /= null loop
1202            ed.total_entries := ed.total_entries + 1;
1203            needs_local_zip64 :=
1204              Zip.Headers.Needs_Local_Zip_64_Header_Extension
1205                (e.head.short_info, e.head.local_header_offset);
1206            if needs_local_zip64 then
1207              e.head.short_info.extra_field_length := Zip.Headers.local_header_extension_length;
1208              fh_extra.tag  := 1;
1209              fh_extra.size := Zip.Headers.local_header_extension_length - 4;
1210              fh_extra.value_64 (1) := e.head.short_info.dd.uncompressed_size;
1211              fh_extra.value_64 (2) := e.head.short_info.dd.compressed_size;
1212              fh_extra.value_64 (3) := e.head.local_header_offset;
1213              e.head.short_info.dd.uncompressed_size := 16#FFFF_FFFF#;
1214              e.head.short_info.dd.compressed_size   := 16#FFFF_FFFF#;
1215              e.head.local_header_offset             := 16#FFFF_FFFF#;
1216              needs_64 := True;
1217            end if;
1218            Zip.Headers.Write (repacked_zip_file, e.head);
1219            String'Write (repacked_zip_file'Access, S (e.name));
1220            if needs_local_zip64 then
1221              Zip.Headers.Write (repacked_zip_file, fh_extra, False);
1222            end if;
1223            ed.central_dir_size :=
1224              ed.central_dir_size +
1225              Zip.Headers.central_header_length +
1226              Unsigned_64 (e.head.short_info.filename_length) +
1227              Unsigned_64 (e.head.short_info.extra_field_length);
1228            e := e.next;
1229          end loop;
1230          ed.disknum := 0;
1231          ed.disknum_with_start := 0;
1232          ed.disk_total_entries := ed.total_entries;
1233          if needs_64 then
1234            ed64l.number_of_the_disk_with_the_start_of_the_zip64_end_of_central_dir := 0;
1235            ed64l.relative_offset_of_the_zip64_end_of_central_dir_record :=
1236              Unsigned_64 (Index (repacked_zip_file) - 1);
1237            ed64l.total_number_of_disks := 1;
1238            --
1239            ed64.size := 44;
1240            ed64.version_made_by           := 16#2D#;
1241            ed64.version_needed_to_extract := 16#2D#;
1242            ed64.number_of_this_disk                                        := ed.disknum;
1243            ed64.number_of_the_disk_with_the_start_of_the_central_directory := ed.disknum_with_start;
1244            ed64.total_number_of_entries_in_the_central_directory_on_this_disk := ed.disk_total_entries;
1245            ed64.total_number_of_entries_in_the_central_directory              := ed.total_entries;
1246            ed64.size_of_the_central_directory        := ed.central_dir_size;
1247            ed64.offset_of_start_of_central_directory := ed.central_dir_offset;
1248            Zip.Headers.Write (repacked_zip_file, ed64);
1249            --
1250            Zip.Headers.Write (repacked_zip_file, ed64l);
1251            --
1252            ed.disk_total_entries := 16#FFFF#;
1253            ed.total_entries      := 16#FFFF#;
1254            ed.central_dir_size   := 16#FFFF_FFFF#;
1255            ed.central_dir_offset := 16#FFFF_FFFF#;
1256          end if;
1257          Zip.Headers.Write (repacked_zip_file, ed);
1258          if not delete_comment then
1259            String'Write (repacked_zip_file'Access, comment);
1260          end if;
1261        end;
1262        Close (repacked_zip_file);
1263        Close (MyStream);
1264        --
1265        --  Cleanup.
1266        --
1267        for a in Approach loop
1268          if consider_a_priori (a) then
1269            if Exists (Temp_name (True, a)) then
1270              Delete_File (Temp_name (True, a));
1271            end if;
1272            if a = original then -- also an uncompressed data file to delete
1273              Delete_File (Temp_name (False, a));
1274            end if;
1275          end if;
1276        end loop;
1277        --
1278        --  Report total files per approach.
1279        --
1280        Put (summary, "<tr><td></td><td><b>T<small>OTAL FILES (of chosen optimal approach)</small></b></td>");
1281        for a in Approach loop
1282          if consider_a_priori (a) then
1283            Put (summary, "<td bgcolor=" & Webcolor (a) & '>' & total (a).count'Image & "</td>");
1284          end if;
1285        end loop;
1286        Put
1287          (summary,
1288           "<td></td><td></td><td></td><td bgcolor=" & color_for_winner & "><b>" &
1289           total_choice.count'Image &
1290           "</b></td>" &
1291           "<td>");
1292        Put_Line (summary, "</td><td></td><td></td><td></td></tr>");
1293        --
1294        --  Report total compressed bytes.
1295        --
1296        Put (summary, "<tr><td></td><td><b>T<small>OTAL COMPRESSED BYTES</small></b></td>");
1297        for a in Approach loop
1298          if consider_a_priori (a) then
1299            Put
1300              (summary,
1301               "<td bgcolor=" & Webcolor (a) & ">" &
1302               Image_1000 (total (a).size) & "</td>");
1303          end if;
1304        end loop;
1305        Put
1306          (summary,
1307           "<td></td><td></td><td></td><td bgcolor=" & color_for_winner & "><b>" &
1308           Image_1000 (total_choice.size) &
1309           "</b></td><td>");
1310        if total (original).size > 0 then
1311          Put (summary,
1312            100.0 * Float (total_choice.size) / Float (total (original).size),
1313            3, 2, 0
1314          );
1315          Put (summary, "%");
1316        end if;
1317        Put (summary, "</td><td>");
1318        if total_choice.uncomp_size > 0 then
1319          Put (summary,
1320            100.0 * Float (total_choice.size) / Float (total_choice.uncomp_size),
1321            3, 2, 0
1322          );
1323          Put (summary, "%");
1324        end if;
1325        Put_Line (summary, "</td><td></td><td></td></tr>");
1326        --
1327        --  Report total saved bytes per approach.
1328        --
1329        Put (summary, "<tr><td></td><td><b>T<small>OTAL BYTES SAVED (by chosen optimal approach)</small></b></td>");
1330        for a in Approach loop
1331          if consider_a_priori (a) then
1332            Put (summary, "<td bgcolor=" & Webcolor (a) & '>' & Image_1000 (total (a).saved) & "</td>");
1333          end if;
1334        end loop;
1335        Put
1336          (summary,
1337           "<td></td><td></td><td></td><td" &
1338           (if total_choice.saved > 0 then
1339              " bgcolor=" & color_for_winner
1340            elsif total_choice.saved < 0 then
1341              " bgcolor=" & lightred
1342            else
1343              "") &
1344           "><b>" & Image_1000 (total_choice.saved) & "</b></td>" &
1345           "<td>");
1346        if total (original).size > 0 then
1347          Put
1348            (summary,
1349             100.0 * Float (total_choice.saved) / Float (total (original).size),
1350             3, 2, 0);
1351          Put (summary, "%");
1352        end if;
1353        Put (summary, "</td><td>");
1354        if total_choice.uncomp_size > 0 then
1355          Put (summary,
1356            100.0 * Float (total_choice.saved) / Float (total_choice.uncomp_size),
1357            3, 2, 0
1358          );
1359          Put (summary, "%");
1360        end if;
1361        Put_Line (summary, "</td><td></td><td></td></tr>");
1362        --
1363        --  Report total saved bytes per approach, *including ex-aequos*.
1364        --
1365        Put
1366          (summary,
1367           "<tr><td></td><td><b>T<small>OTAL BYTES SAVED (by chosen or " &
1368           "ex-aequo optimal approach)</small></b></td>");
1369        for a in Approach loop
1370          if consider_a_priori (a) then
1371            Put
1372              (summary,
1373               "<td bgcolor=" & Webcolor (a) & ">" &
1374               Image_1000 (total (a).saved_ex_aequo) & "</td>");
1375          end if;
1376        end loop;
1377        Put (summary, "<td></td><td></td><td></td><td></td><td></td><td>");
1378        Put_Line (summary, "</td><td></td><td></td></tr>");
1379        Put_Line (summary, "</table></div><div><br><br>");
1380        Put_Line (summary, "<dt>Options used for ReZip</dt>");
1381        Put_Line (summary, "<dd>Randomized_stable =" & Integer'Image (randomized_stable) & "<br>");
1382        Put_Line (summary, "    Formats allowed:<br><table border=1 cellpadding=1 cellspacing=1>");
1383        for f in format_choice'Range loop
1384          Put_Line (summary,
1385            "      <tr><td>" & Zip.Image (f) & "</td><td>" &
1386            Boolean'Image (format_choice (f)) & "</td></tr>");
1387        end loop;
1388        Put_Line (summary, "    </table>");
1389        Put_Line (summary, "</dd></div>");
1390        T1 := Clock;
1391        repack_duration := T1 - T0;
1392        Put (summary, "Time elapsed : ");
1393        Put (summary,  Float (repack_duration), 4, 2, 0);
1394        Put (summary,  " seconds, or");
1395        Put (summary,  Float (repack_duration) / 60.0, 4, 2, 0);
1396        Put (summary,  " minutes, or");
1397        Put (summary,  Float (repack_duration) / 3600.0, 4, 2, 0);
1398        Put_Line (summary,  " hours.</font></body></html>");
1399        Close (summary);
1400        Dual_IO.Put ("Time elapsed : ");
1401        DFIO.Put (Float (repack_duration), 4, 2, 0);
1402        Dual_IO.Put_Line (" sec");
1403        Dual_IO.Put_Line ("All details for " & orig_name & " in " & html_report_name);
1404      end Repack_contents;
1405  
1406      --  This is for randomizing the above seed_iterator.
1407      --  On GNAT the clock-based Reset is too coarse: it gives many times
1408      --  the same seed when called with small time intervals.
1409      --
1410      subtype Seed_Range is Integer range 1 .. 1_000_000;
1411      package Rnd_seed is new Ada.Numerics.Discrete_Random (Seed_Range);
1412      gen_seed : Rnd_seed.Generator;
1413  
1414    begin
1415      Rnd_seed.Reset (gen_seed);  --  1x clock-based randomization
1416      seed_iterator := Rnd_seed.Random (gen_seed);
1417      if alt_tmp_file_radix = "" then
1418        Flexible_temp_files.Initialize;
1419      end if;
1420      Dual_IO.Create_Log (log_file);
1421      Repack_contents (from_zip_file, to_zip_file, html_report);
1422      Dual_IO.Close_Log;
1423      if alt_tmp_file_radix = "" then
1424        Flexible_temp_files.Finalize;
1425      end if;
1426    exception
1427      when External_Tool_Failed =>
1428        Dual_IO.Put_Line ("  Is that tool callable through the ""path"" ?");
1429        Dual_IO.Put_Line ("  In doubt, re-run ReZip with the ""-int"" (internal only) option.");
1430        Dual_IO.Close_Log;
1431        raise;
1432    end Rezip;
1433  
1434    procedure Show_external_packer_list is
1435      procedure Display (p : Zipper_Specification) is
1436        fix : String (1 .. 8) := (others => ' ');
1437      begin
1438        Insert (fix, fix'First, S (p.title));
1439        Ada.Text_IO.Put ("  " & fix);
1440        fix := (others => ' ');
1441        Insert (fix, fix'First, S (p.name));
1442        Ada.Text_IO.Put_Line (" Executable: " & fix & " URL: " & S (p.URL));
1443      end Display;
1444      name_is_new : Boolean;
1445    begin
1446      for e in External loop
1447        name_is_new := True;
1448        for ee in External'First .. External'Pred (e) loop
1449          name_is_new := name_is_new and ext (e).name /= ext (ee).name;
1450        end loop;
1451        if name_is_new then
1452          Display (ext (e));
1453        end if;
1454      end loop;
1455      Display (defl_opt);
1456    end Show_external_packer_list;
1457  
1458  end Rezip_lib;

Web view of Ada source code generated by GNATHTML, project: ALI_Parse version 1.0.
Zip-Ada: Ada library for zip archive files (.zip). Ada programming.
Some news about Zip-Ada and other Ada projects on Gautier's blog.