Subversion Repositories autosfx

Rev

Blame | Last modification | View Log | RSS feed

  1. unit ZMZipFile19;
  2.  
  3. (*
  4.   ZMZipFile19.pas - Represents the 'Directory' of a Zip file
  5.     Copyright (C) 2009, 2010  by Russell J. Peters, Roger Aelbrecht,
  6.       Eric W. Engler and Chris Vleghert.
  7.  
  8.         This file is part of TZipMaster Version 1.9.
  9.  
  10.     TZipMaster is free software: you can redistribute it and/or modify
  11.     it under the terms of the GNU Lesser General Public License as published by
  12.     the Free Software Foundation, either version 3 of the License, or
  13.     (at your option) any later version.
  14.  
  15.     TZipMaster is distributed in the hope that it will be useful,
  16.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.     GNU Lesser General Public License for more details.
  19.  
  20.     You should have received a copy of the GNU Lesser General Public License
  21.     along with TZipMaster.  If not, see <http://www.gnu.org/licenses/>.
  22.  
  23.     contact: problems@delphizip.org (include ZipMaster in the subject).
  24.     updates: http://www.delphizip.org
  25.     DelphiZip maillist subscribe at http://www.freelists.org/list/delphizip
  26.  
  27.   modified 2010-06-20
  28. ---------------------------------------------------------------------------*)
  29. interface
  30.  
  31. uses
  32.   Classes, Windows, ZipMstr19, ZMCore19, ZMIRec19, ZMHash19, ZMWorkFile19, ZMCompat19, ZMEOC19;
  33.  
  34. type
  35.   TZCChanges     = (zccNone, zccBegin, zccCount, zccAdd, zccEdit, zccDelete,
  36.     zccEnd, zccCheckNo);
  37.   TZCChangeEvent = procedure(Sender: TObject; idx: Integer;
  38.     change: TZCChanges) of object;
  39.  
  40. type
  41.   TVariableData = array of Byte;
  42.  
  43. type
  44.   TZMCXFields = (zcxUncomp, zcxComp, zcxOffs, zcxStart);
  45.  
  46.  
  47.  
  48. type
  49.   TZMZipFile = class(TZMEOC)
  50.   private
  51.     FAddOptions: TZMAddOpts;
  52.     fCheckNo:     Cardinal;
  53.     FEncodeAs: TZMEncodingOpts;
  54.     fEncoding: TZMEncodingOpts;
  55.     fEncoding_CP: Cardinal;
  56.     fEntries:     TList;
  57.     fEOCFileTime: TFileTime;
  58.     FFirst: Integer;
  59.     FIgnoreDirOnly: boolean;
  60.     fOnChange:    TZCChangeEvent;
  61.     fOpenRet:     Integer;
  62.     FSelCount: integer;
  63.     fSFXOfs:      Cardinal;
  64.     fShowAll:     Boolean;
  65.     fStub:        TMemoryStream;
  66.     fUseSFX:      Boolean;
  67.     FWriteOptions: TZMWriteOpts;
  68.     function GetCount: Integer;
  69.     function GetItems(Idx: Integer): TZMIRec;
  70.     function SelectEntry(t: TZMIRec; How: TZipSelects): Boolean;
  71.     procedure SetCount(const Value: Integer);
  72.     procedure SetEncoding(const Value: TZMEncodingOpts);
  73.     procedure SetEncoding_CP(const Value: Cardinal);
  74.     procedure SetItems(Idx: Integer; const Value: TZMIRec);
  75.     procedure SetShowAll(const Value: Boolean);
  76.     procedure SetStub(const Value: TMemoryStream);
  77.   protected
  78.     fHashList: TZMDirHashList;
  79.     function BeforeCommit: Integer; virtual;
  80.     function CalcSizes(var NoEntries: Integer; var ToProcess: Int64;
  81.       var CenSize: Cardinal): Integer;
  82.     procedure ClearCachedNames;
  83.     procedure ClearEntries;
  84.     function EOCSize(Is64: Boolean): Cardinal;
  85.     procedure InferNumbering;
  86.     function Load: Integer;
  87.     procedure MarkDirty;
  88.     function Open1(EOConly: Boolean): Integer;
  89.     function WriteCentral: Integer;
  90.     property Entries: TList Read fEntries;
  91.   public
  92.     constructor Create(Wrkr: TZMCore); override;
  93.     function Add(rec: TZMIRec): Integer;
  94.     procedure AssignFrom(Src: TZMWorkFile); override;
  95.     procedure AssignStub(from: TZMZipFile);
  96.     procedure BeforeDestruction; override;
  97.     procedure ClearSelection;
  98.     function Commit(MarkLatest: Boolean): Integer;
  99.     function CommitAppend(Last: Integer; MarkLatest: Boolean): Integer;
  100.     procedure Replicate(Src: TZMZipFile; LastEntry: Integer);
  101.     function Entry(Chk: Cardinal; Idx: Integer): TZMIRec;
  102.     function FindName(const pattern: TZMString; var idx: Integer): TZMIRec;
  103.         overload;
  104.     function FindName(const pattern: TZMString; var idx: Integer; const myself:
  105.         TZMIRec): TZMIRec; overload;
  106.     function FindNameEx(const pattern: TZMString; var idx: Integer; IsWild:
  107.         boolean): TZMIRec;
  108.     function HasDupName(const rec: TZMIRec): Integer;
  109.     //1 Returns the number of duplicates
  110.     function HashContents(var HList: TZMDirHashList; what: integer): Integer;
  111.     //1 Mark as Contents Invalid
  112.     procedure Invalidate;
  113.     function Next(Current: Integer): integer;
  114.     function NextSelected(Current: Integer): integer;
  115.     function Open(EOConly, NoLoad: Boolean): Integer;
  116.     function PrepareWrite(typ: TZipWrites): Boolean;
  117.     function Reopen(Mode: Cardinal): integer;
  118.     function Select(const Pattern: TZMString; How: TZipSelects): Integer;
  119.     function Select1(const Pattern, reject: TZMString; How: TZipSelects): Integer;
  120.     function SelectFiles(const want, reject: TStrings; skipped: TStrings): Integer;
  121.     function VerifyOpen: Integer;
  122.     property AddOptions: TZMAddOpts read FAddOptions write FAddOptions;
  123.     property CheckNo: Cardinal Read fCheckNo;
  124.     property Count: Integer Read GetCount Write SetCount;
  125.     // how new/modified entries will be encoded
  126.     property EncodeAs: TZMEncodingOpts read FEncodeAs write FEncodeAs;
  127.     // how to interpret entry strings
  128.     property Encoding: TZMEncodingOpts read fEncoding write SetEncoding;
  129.     property Encoding_CP: Cardinal Read fEncoding_CP Write SetEncoding_CP;
  130.     property EOCFileTime: TFileTime Read fEOCFileTime;
  131.     property First: Integer read FFirst;
  132.     property IgnoreDirOnly: boolean read FIgnoreDirOnly write FIgnoreDirOnly;
  133.     property Items[Idx: Integer]: TZMIRec Read GetItems Write SetItems; default;
  134.     property OpenRet: Integer Read fOpenRet Write fOpenRet;
  135.     property SelCount: integer read FSelCount;
  136.     property SFXOfs: Cardinal Read fSFXOfs Write fSFXOfs;
  137.     property ShowAll: Boolean Read fShowAll Write SetShowAll;
  138.     property Stub: TMemoryStream Read fStub Write SetStub;
  139.     property UseSFX: Boolean Read fUseSFX Write fUseSFX;
  140.     property WriteOptions: TZMWriteOpts read FWriteOptions write FWriteOptions;
  141.     property OnChange: TZCChangeEvent Read fOnChange Write fOnChange;
  142.   end;
  143.  
  144. type
  145.   TZMCopyRec = class(TZMIRec)
  146.   private
  147.     fLink: TZMIRec;
  148.     procedure SetLink(const Value: TZMIRec);
  149.   public
  150.     constructor Create(theOwner: TZMWorkFile);
  151.     procedure AfterConstruction; override;
  152.     function Process: Int64; override;
  153.     function ProcessSize: Int64; override;
  154.     property Link: TZMIRec Read fLink Write SetLink;
  155.   end;
  156.  
  157. type
  158.   TZMZipCopy = class(TZMZipFile)
  159.   protected
  160.     function AffixZippedFile(rec: TZMIRec): Integer;
  161.   public
  162.     constructor Create(Wrkr: TZMCore); override;
  163.     function AffixZippedFiles(Src: TZMZipFile; All: Boolean): Integer;
  164.     function WriteFile(InZip: TZMZipFile; All: Boolean): Int64;
  165.   end;
  166.  
  167. const
  168.   BadIndex = -HIGH(Integer);
  169.  
  170. const
  171.   zfi_Loaded: cardinal = $1000;     // central loaded
  172.   zfi_DidLoad: cardinal = $2000;    // central loaded
  173.   zfi_Invalid: Cardinal = $8000;    // needs reload
  174.  
  175. implementation
  176.  
  177. uses
  178.   SysUtils, ZMMsg19, ZMXcpt19, ZMMsgStr19, ZMStructs19, ZMDelZip19,
  179.   ZMUtils19, ZMMatch19, ZMUTF819;
  180.  
  181. {$INCLUDE '.\ZipVers19.inc'}
  182.  
  183. const
  184.   AllSpec: String = '*.*';
  185.   AnySpec: String = '*';
  186.  
  187. constructor TZMZipFile.Create(Wrkr: TZMCore);
  188. begin
  189.   inherited;
  190.   fEntries  := TList.Create;
  191.   fHashList := TZMDirHashList.Create;
  192. {$IFNDEF UNICODE}
  193.   fHashList.Worker := Worker;
  194. {$ENDIF}
  195.   fEncoding := Wrkr.Encoding;
  196.   fAddOptions := wrkr.AddOptions;
  197.   fEncodeAs := wrkr.EncodeAs;
  198.   fEncoding_CP := wrkr.Encoding_CP;
  199.   fIgnoreDirOnly := Wrkr.IgnoreDirOnly;
  200.   FWriteOptions := Wrkr.WriteOptions;
  201. end;
  202.  
  203. function TZMZipFile.Add(rec: TZMIRec): Integer;
  204. begin
  205.   Result := fEntries.Add(rec);
  206.   if fHashList.Empty then
  207.     fHashList.Add(rec);
  208. end;
  209.  
  210. procedure TZMZipFile.AssignFrom(Src: TZMWorkFile);
  211. begin
  212.   inherited;
  213.   if (Src is TZMZipFile) and (Src <> Self) then
  214.   begin
  215.     Replicate(TZMZipFile(Src), -1);  // copy all entries
  216.   end;
  217. end;
  218.  
  219. procedure TZMZipFile.AssignStub(from: TZMZipFile);
  220. begin
  221.   FreeAndNil(fStub);
  222.   fStub := from.Stub;
  223.   from.fStub := nil;
  224. end;
  225.  
  226. function TZMZipFile.BeforeCommit: Integer;
  227. begin
  228.   Result := 0;
  229. end;
  230.  
  231. procedure TZMZipFile.BeforeDestruction;
  232. begin
  233.   ClearEntries;
  234.   FreeAndNil(fEntries);
  235.   FreeAndNil(fStub);
  236.   FreeAndNil(fHashList);
  237.   inherited;
  238. end;
  239.  
  240. function TZMZipFile.CalcSizes(var NoEntries: Integer; var ToProcess: Int64;
  241.   var CenSize: Cardinal): Integer;
  242. var
  243.   i: Integer;
  244.   rec: TZMIRec;
  245. begin
  246.   Result := 0;
  247.   for i := 0 to Count - 1 do
  248.   begin
  249.     rec := Items[i];
  250.     ToProcess := ToProcess + rec.ProcessSize;
  251.     CenSize := CenSize + rec.CentralSize;
  252.     Inc(NoEntries);
  253.   end;
  254. end;
  255.  
  256. procedure TZMZipFile.ClearCachedNames;
  257. var
  258.   i: Integer;
  259.   tmp: TObject;
  260. begin
  261.   for i := 0 to Count - 1 do
  262.   begin
  263.     tmp := fEntries[i];
  264.     if tmp is TZMIRec then
  265.       TZMIRec(tmp).ClearCachedName;
  266.   end;
  267.   fHashList.Clear;
  268. end;
  269.  
  270. procedure TZMZipFile.ClearEntries;
  271. var
  272.   i: Integer;
  273.   tmp: TObject;
  274. begin
  275.   for i := 0 to pred(fEntries.Count) do
  276.   begin
  277.     tmp := fEntries.Items[i];
  278.     if tmp <> nil then
  279.     begin
  280.       fEntries.Items[i] := nil;
  281.       tmp.Free;
  282.     end;
  283.   end;
  284.   fEntries.Clear;
  285.   fHashList.Clear;
  286.   FFirst := -1;
  287.   fSelCount := 0;
  288. end;
  289.  
  290. procedure TZMZipFile.ClearSelection;
  291. var
  292.   i: Integer;
  293.   t: TZMIRec;
  294. begin
  295.   FSelCount := 0;
  296.   for i := 0 to fEntries.Count - 1 do
  297.   begin
  298.     t := fEntries[i];
  299.     t.Selected := False;
  300.   end;
  301. end;
  302.  
  303. function TZMZipFile.Commit(MarkLatest: Boolean): Integer;
  304. var
  305.   i: Integer;
  306.   latest: Cardinal;
  307.   NoEntries: Integer;
  308.   ToDo: Int64;
  309.   r: Integer;
  310.   rec: TZMIRec;
  311.   s: Cardinal;
  312.   ToProcess: Int64;
  313.   TotalProcess: Int64;
  314.   w64: Int64;
  315.   wrote: Int64;
  316. begin
  317.   Diag('Commit file');
  318.   latest := 0;
  319.   wrote  := 0;
  320.   Result := BeforeCommit;
  321.   if Result < 0 then
  322.     exit;
  323.   // calculate sizes
  324.   NoEntries := 0;
  325.   ToProcess := 0;
  326.   for i := 0 to Count - 1 do
  327.   begin
  328.     Boss.CheckCancel;
  329.     rec := TZMIRec(Items[i]);
  330.     Assert(assigned(rec), ' no rec');
  331.     ToProcess := ToProcess + rec.ProcessSize;
  332.     Inc(NoEntries);
  333.     if MarkLatest and (rec.ModifDateTime > Latest) then
  334.         Latest := rec.ModifDateTime;
  335.   end;
  336.   // mostly right ToProcess = total compressed sizes
  337.   TotalProcess := ToProcess;
  338.   if UseSFX and assigned(Stub) and (Stub.size > 0) then
  339.     TotalProcess := TotalProcess + Stub.Size;
  340.   ProgReport(zacCount, PR_Writing, '', NoEntries + 1);
  341.   ProgReport(zacSize, PR_Writing, '', TotalProcess);
  342.   Diag(' to process ' + IntToStr(NoEntries) + ' entries');
  343.   Diag(' size = ' + IntToStr(TotalProcess));
  344.   Result := 0;
  345.   if MarkLatest then
  346.   begin
  347. //    Diag(' latest date = ' + DateTimeToStr(FileDateToLocalDateTime(latest)));
  348.     StampDate := latest;
  349.   end;
  350.   try
  351.     // if out is going to split should write proper signal
  352.     if IsMultiPart then
  353.     begin
  354.       s := ExtLocalSig;
  355.       Result := Write(s, -4);
  356.       if (Result <> 4) and (Result > 0) then
  357.         Result := -DS_NoWrite;
  358.       Sig := zfsMulti;
  359.     end
  360.     else   // write stub if required
  361.     if UseSFX and assigned(Stub) and (Stub.size > 0) then
  362.     begin
  363.       // write the sfx stub
  364.       ProgReport(zacItem, PR_SFX, '', Stub.Size);
  365.       Stub.Position := 0;
  366.       Result := WriteFrom(Stub, Stub.Size);
  367.       if Result > 0 then
  368.       begin
  369.         wrote := Stub.Size;
  370.         ProgReport(zacProgress, PR_SFX, '', Stub.Size);
  371.         if ShowProgress = zspFull then
  372.           Boss.ProgDetail.Written(wrote);
  373.         Sig := zfsDOS; // assume correct
  374.       end;
  375.     end
  376.     else
  377.       Sig := zfsLocal;
  378.     if (Result >= 0) and (ToProcess > 0) then
  379.     begin
  380.       for i := 0 to Count - 1 do
  381.       begin
  382.         Boss.CheckCancel;
  383.         rec := TZMIRec(Items[i]);
  384.         ToDo := rec.ProcessSize;
  385.         if ToDo > 0 then
  386.         begin
  387.           w64 := rec.Process;
  388.           if w64 < 0 then
  389.           begin
  390.             Result := w64;
  391.             Break;
  392.           end;
  393.           wrote := wrote + w64;
  394.           if ShowProgress = zspFull then
  395.             Boss.TotalWritten := wrote;
  396.         end;
  397.       end;
  398.     end;
  399.     // finished locals and data
  400.     if Result >= 0 then
  401.     begin
  402.       // write central
  403.       Boss.ReportMsg(GE_Copying, [Boss.ZipLoadStr(DS_CopyCentral)]);
  404.       r := WriteCentral;  // uses XProgress
  405.       if r >= 0 then
  406.         wrote := wrote + r;
  407.       Diag(' wrote = ' + IntToStr(wrote));
  408.       if r > 0 then
  409.       begin
  410.         Result := FinishWrite;
  411.         if r >= 0 then
  412.         begin
  413.           Result := 0;
  414.           File_Size := wrote;
  415.           Diag('  finished ok');
  416.         end;
  417.       end;
  418.     end;
  419.   finally
  420.     ProgReport(zacEndOfBatch, 7, '', 0);
  421.   end;
  422. end;
  423.  
  424. function TZMZipFile.CommitAppend(Last: Integer; MarkLatest: Boolean): Integer;
  425. var
  426.   i: Integer;
  427.   latest: Cardinal;
  428.   NoEntries: Integer;
  429.   ToDo: Int64;
  430.   r: Integer;
  431.   rec: TZMIRec;
  432.   ToProcess: Int64;
  433.   TotalProcess: Int64;
  434.   w64: Int64;
  435.   wrote: Int64;
  436. begin
  437.   Diag('CommitAppend file');
  438.   latest := 0;
  439.   wrote := 0;
  440.   // calculate sizes
  441.   NoEntries := 0;
  442.   ToProcess := 0;
  443.   for i := 0 to Count - 1 do
  444.   begin
  445.     Boss.CheckCancel;
  446.     rec := TZMIRec(Items[i]);
  447.     Assert(assigned(rec), ' no rec');
  448.     if i >= Last then
  449.     begin
  450.       ToProcess := ToProcess + rec.ProcessSize;
  451.       Inc(NoEntries);
  452.     end;
  453.     if MarkLatest and (rec.ModifDateTime > latest) then
  454.       latest := rec.ModifDateTime;
  455.   end;
  456.   // mostly right ToProcess = total compressed sizes
  457.   TotalProcess := ToProcess;
  458.   if UseSFX and assigned(Stub) and (Stub.size > 0) and (First < 0) then
  459.     TotalProcess := TotalProcess + Stub.size;
  460.   ProgReport(zacCount, PR_Writing, '', NoEntries + 1);
  461.   ProgReport(zacSize, PR_Writing, '', TotalProcess);
  462.   Diag(' to process ' + IntToStr(NoEntries) + ' entries');
  463.   Diag(' size = ' + IntToStr(TotalProcess));
  464.   Result := 0;
  465.   if MarkLatest then
  466.   begin
  467.     // Diag(' latest date = ' + DateTimeToStr(FileDateToLocalDateTime(latest)));
  468.     StampDate := latest;
  469.   end;
  470.   try
  471.     // write stub if required
  472.     if UseSFX and assigned(Stub) and (Stub.size > 0) and (First < 0) then
  473.     begin
  474.       // write the sfx stub
  475.       ProgReport(zacItem, PR_SFX, '', Stub.size);
  476.       Stub.Position := 0;
  477.       Result := WriteFrom(Stub, Stub.size);
  478.       if Result > 0 then
  479.       begin
  480.         wrote := Stub.size;
  481.         ProgReport(zacProgress, PR_SFX, '', Stub.size);
  482.         if ShowProgress = zspFull then
  483.           Boss.ProgDetail.Written(wrote);
  484.         Sig := zfsDOS; // assume correct
  485.       end;
  486.     end
  487.     else
  488.       Sig := zfsLocal;
  489.     if (Result >= 0) and (ToProcess > 0) then
  490.     begin
  491.       for i := Last to Count - 1 do
  492.       begin
  493.         Boss.CheckCancel;
  494.         rec := TZMIRec(Items[i]);
  495.         ToDo := rec.ProcessSize;
  496.         if ToDo > 0 then
  497.         begin
  498.           w64 := rec.Process;
  499.           if w64 < 0 then
  500.           begin
  501.             Result := w64;
  502.             Break;
  503.           end;
  504.           wrote := wrote + w64;
  505.           if ShowProgress = zspFull then
  506.             Boss.TotalWritten := wrote;
  507.         end;
  508.       end;
  509.     end;
  510.     // finished locals and data
  511.     if Result >= 0 then
  512.     begin
  513.       // write central
  514.       Boss.ReportMsg(GE_Copying, [Boss.ZipLoadStr(DS_CopyCentral)]);
  515.       r := WriteCentral; // uses XProgress
  516.       if r >= 0 then
  517.         wrote := wrote + r;
  518.       Diag(' wrote = ' + IntToStr(wrote));
  519.       if r > 0 then
  520.       begin
  521.         Result := 0;
  522.         File_Size := wrote;
  523.         Diag('  finished ok');
  524.       end;
  525.     end;
  526.   finally
  527.     ProgReport(zacEndOfBatch, 7, '', 0);
  528.   end;
  529. end;
  530.  
  531. function TZMZipFile.Entry(Chk: Cardinal; Idx: Integer): TZMIRec;
  532. begin
  533.   Result := nil;
  534.   if (Chk = CheckNo) and (Idx >= 0) and (Idx < Count) then
  535.     Result := Items[Idx];
  536. end;
  537.  
  538. // Zip64 size aproximate only
  539. function TZMZipFile.EOCSize(Is64: Boolean): Cardinal;
  540. begin
  541.   Result := Cardinal(sizeof(TZipEndOfCentral) + Length(ZipComment));
  542.   if Is64 then
  543.     Result := Result + sizeof(TZip64EOCLocator) + sizeof(TZipEOC64) +
  544.       (3 * sizeof(Int64));
  545. end;
  546.  
  547. function TZMZipFile.FindName(const pattern: TZMString; var idx: Integer):
  548.     TZMIRec;
  549. begin
  550.   Result := FindNameEx(pattern, idx, CanHash(pattern));
  551. end;
  552.  
  553. function TZMZipFile.FindName(const pattern: TZMString; var idx: Integer; const
  554.     myself: TZMIRec): TZMIRec;
  555. begin
  556.   if myself = nil then
  557.     Result := FindNameEx(pattern, idx, CanHash(pattern))
  558.   else
  559.   begin
  560.     myself.SetStatusBit(zsbIgnore);  // prevent 'finding' myself
  561.     Result := FindNameEx(pattern, idx, CanHash(pattern));
  562.     myself.ClearStatusBit(zsbIgnore);
  563.   end;
  564. end;
  565.  
  566. function TZMZipFile.FindNameEx(const pattern: TZMString; var idx: Integer;
  567.     IsWild: boolean): TZMIRec;
  568. var
  569.   found: Boolean;
  570.   hash: Cardinal;
  571. begin
  572.   found := False;
  573.   Result := nil;   // keep compiler happy
  574.   hash := 0;       // keep compiler happy
  575.   if (pattern <> '') then
  576.   begin
  577.     // if it wild or multiple we must try to match - else only if same hash
  578.     if (not IsWild) and (idx < 0) and (fHashList.Size > 0) then
  579.       Result := fHashList.Find(pattern)  // do it quick
  580.     else
  581.     Begin
  582.       if not IsWild then
  583.         hash := HashFunc(pattern);
  584.       repeat
  585.         idx := Next(idx);
  586.         if idx < 0 then
  587.           break;
  588.         Result := Entries[idx];
  589.         if IsWild or (Result.Hash = hash) then
  590.         begin
  591.           found := Worker.FNMatch(pattern, Result.Filename);
  592.           if Result.StatusBit[zsbIgnore] <> 0 then
  593.             found := false;
  594.         end;
  595.       until (found);
  596.       if not found then
  597.         Result := nil;
  598.     End;
  599.   end;
  600.   if Result = nil then
  601.     idx := BadIndex;
  602. end;
  603.  
  604. function TZMZipFile.GetCount: Integer;
  605. begin
  606.   Result := fEntries.Count;
  607. end;
  608.  
  609. function TZMZipFile.GetItems(Idx: Integer): TZMIRec;
  610. begin
  611.   if Idx >= Count then
  612.     Result := nil
  613.   else
  614.     Result := Entries[Idx];
  615. end;
  616.  
  617. // searches for record with same name
  618. function TZMZipFile.HasDupName(const rec: TZMIRec): Integer;
  619. var
  620.   nrec: TZMIRec;
  621. begin
  622.   Result := -1;
  623.   if fHashList.Size = 0 then
  624.     HashContents(fHashList, 0);
  625.   nrec := fHashList.Add(rec);
  626.   if nrec <> nil then// exists
  627.   begin
  628.     Diag('Duplicate FileName: ' + rec.FileName);
  629.     for Result := 0 to Count - 1 do
  630.     begin
  631.       if nrec = TZMIRec(Items[Result]) then
  632.         break;
  633.     end;
  634.   end;
  635. end;
  636.  
  637. //  zsbDirty    = $1;
  638. //  zsbSelected = $2;
  639. //  zsbSkipped  = $4;
  640. //  zsbIgnore   = $8;
  641. //  zsbDirOnly  = $10;
  642. //  zsbInvalid  = $20;
  643. // what = -1 _ all
  644. //  else ignore rubbish
  645. // what = 0 _ any non rubbish
  646. function TZMZipFile.HashContents(var HList: TZMDirHashList; what: integer):
  647.     Integer;
  648. const
  649.   Skip = zsbInvalid or zsbIgnore or zsbSkipped;
  650. var
  651.   I: Integer;
  652.   rec: TZMIRec;
  653.   use: boolean;
  654. begin
  655.   Result := 0;
  656.   HList.AutoSize(Count);   // make required size
  657.   for I := 0 to Count - 1 do
  658.   begin
  659.     rec := Entries[i];
  660.     if rec = nil then
  661.       continue;
  662.     use := what = -1;
  663.     if (not use) then
  664.     begin
  665.       if (rec.StatusBit[Skip] <> 0) then
  666.         continue;
  667.       use := (what = 0) or (rec.StatusBit[what] <> 0);
  668.     end;
  669.     if use then
  670.     begin
  671.       if HList.Add(Entries[I]) <> nil then
  672.         Inc(Result);  // count duplicates
  673.     end;
  674.   end;
  675. end;
  676.  
  677. // Use after EOC found and FileName is last part
  678. // if removable has proper numbered volume name we assume it is numbered volume
  679. procedure TZMZipFile.InferNumbering;
  680. var
  681.   fname: string;
  682.   num: Integer;
  683.   numStr: string;
  684. begin
  685.   // only if unknown
  686.   if (Numbering = znsNone) and (TotalDisks > 1) then
  687.   begin
  688.     if WorkDrive.DriveIsFloppy and AnsiSameText(WorkDrive.DiskName, VolName(DiskNr)) then
  689.       Numbering := znsVolume
  690.     else
  691.     begin
  692.       numStr := '';
  693.       fname := ExtractNameOfFile(FileName);
  694.       Numbering := znsExt;
  695.       if Length(fname) > 3 then
  696.       begin
  697.         numStr := Copy(fname, length(fname) - 2, 3);
  698.         num := StrToIntDef(numStr, -1);
  699.         if num = (DiskNr + 1) then
  700.         begin
  701.           // ambiguous conflict
  702.           if WorkDrive.DriveIsFixed then
  703.           begin
  704.             if HasSpanSig(ChangeNumberedName(FileName, 1, True)) then
  705.               Numbering := znsName; // unless there is an orphan
  706.           end;
  707.         end;
  708.       end;
  709.     end;
  710.   end;
  711. end;
  712.  
  713. procedure TZMZipFile.Invalidate;
  714. begin
  715.   info := info or zfi_Invalid;
  716. end;
  717.  
  718. function TZMZipFile.Load: Integer;
  719. var
  720.   i: Integer;
  721.   LiE: Integer;
  722.   OffsetDiff: Int64;
  723.   r: Integer;
  724.   rec: TZMIRec;
  725.   sgn: Cardinal;
  726.   SOCOfs: Int64;
  727. begin
  728.   if not IsOpen then
  729.   begin
  730.     Result := DS_FileOpen;
  731.     exit;
  732.   end;
  733.   Result := -LI_ErrorUnknown;
  734.   if (info and zfi_EOC) = 0 then
  735.     exit; // should not get here if eoc has not been read
  736.   LiE := 1;
  737.   OffsetDiff := 0;
  738.   ClearEntries;
  739.   fCheckNo := TZMCore(Worker).NextCheckNo;
  740.   if Assigned(OnChange) then
  741.     OnChange(Self, CheckNo, zccBegin);
  742.   SOCOfs := CentralOffset;
  743.   try
  744.     OffsetDiff := CentralOffset;
  745.     // Do we have to request for a previous disk first?
  746.     if DiskNr <> CentralDiskNo then
  747.     begin
  748.       SeekDisk(CentralDiskNo);
  749.       File_Size := Seek(0, 2);
  750.     end
  751.     else
  752.     if not Z64 then
  753.     begin
  754.       // Due to the fact that v1.3 and v1.4x programs do not change the archives
  755.       // EOC and CEH records in case of a SFX conversion (and back) we have to
  756.       // make this extra check.
  757.       OffsetDiff := File_Size - (Integer(CentralSize) +
  758.         SizeOf(TZipEndOfCentral) + ZipCommentLen);
  759.     end;
  760.     SOCOfs := OffsetDiff;
  761.     // save the location of the Start Of Central dir
  762.     SFXOfs := Cardinal(OffsetDiff);
  763.     if SFXOfs <> SOCOfs then
  764.       SFXOfs := 0;
  765.     // initialize this - we will reduce it later
  766.     if File_Size = 22 then
  767.       SFXOfs := 0;
  768.  
  769.     if CentralOffset <> OffsetDiff then
  770.     begin
  771.       // We need this in the ConvertXxx functions.
  772.       Boss.ShowZipMessage(LI_WrongZipStruct, '');
  773.       CheckSeek(CentralOffset, 0, LI_ReadZipError);
  774.       CheckRead(sgn, 4, DS_CEHBadRead);
  775.       if sgn = CentralFileHeaderSig then
  776.       begin
  777.         SOCOfs := CentralOffset;
  778.         // TODO warn - central size error
  779.       end;
  780.     end;
  781.  
  782.     // Now we can go to the start of the Central directory.
  783.     CheckSeek(SOCOfs, 0, LI_ReadZipError);
  784.     ProgReport(zacItem, PR_Loading, '', TotalEntries);
  785.     // Read every entry: The central header and save the information.
  786. {$IFDEF DEBUG}
  787.       if Boss.Verbosity >= zvTrace then
  788.         Diag(Format('List - expecting %d files', [TotalEntries]));
  789. {$ENDIF}
  790.     fEntries.Capacity := TotalEntries;
  791.     rec := nil;
  792.     if Assigned(OnChange) then
  793.       OnChange(Self, TotalEntries, zccCount);
  794.     fHashList.AutoSize(TotalEntries);
  795.     for i := 0 to (TotalEntries - 1) do
  796.     begin
  797.       FreeAndNil(rec);
  798.       rec := TZMIRec.Create(Self);
  799.       r := rec.Read(Self);
  800.       if r < 0 then
  801.       begin
  802.         FreeAndNil(rec);
  803.         raise EZipMaster.CreateResDisp(r, True);
  804.       end;
  805.       if r > 0 then
  806.         Z64 := True;
  807. {$IFDEF DEBUG}
  808.         if Boss.Verbosity >= zvTrace then //Trace then
  809.           Diag(Format('List - [%d] "%s"', [i, rec.FileName]));
  810. {$ENDIF}
  811.       fEntries.Add(rec);
  812.       fHashList.Add(rec);
  813.       // Notify user, when needed, of the NextSelected entry in the ZipDir.
  814.       if Assigned(OnChange) then
  815.         OnChange(Self, i, zccAdd);   // change event to give TZipDirEntry
  816.  
  817.       // Calculate the earliest Local Header start
  818.       if SFXOfs > rec.RelOffLocal then
  819.         SFXOfs := rec.RelOffLocal;
  820.       rec := nil; // used
  821.       ProgReport(zacProgress, PR_Loading, '', 1);
  822.       Boss.CheckCancel;
  823.     end;  // for
  824.     LiE := 0;                             // finished ok
  825.     Result := 0;
  826.     info := (info and not (zfi_MakeMask)) or zfi_Loaded;
  827.   finally
  828.     ProgReport(zacEndOfBatch, PR_Loading, '', 0);
  829.     if LiE = 1 then
  830.     begin
  831.       FileName := '';
  832.       SFXOfs := 0;
  833.       File_Close;
  834.     end
  835.     else
  836.     begin
  837.       CentralOffset := SOCOfs;  // corrected
  838.       // Correct the offset for v1.3 and 1.4x
  839.       SFXOfs := SFXOfs + Cardinal(OffsetDiff - CentralOffset);
  840.     end;
  841.  
  842.     // Let the user's program know we just refreshed the zip dir contents.
  843.     if Assigned(OnChange) then
  844.       OnChange(Self, Count, zccEnd);
  845.   end;
  846. end;
  847.  
  848. procedure TZMZipFile.MarkDirty;
  849. begin
  850.   info := info or zfi_Dirty;
  851. end;
  852.  
  853. // allow current = -1 to get first
  854. // get next index, if IgnoreDirOnly = True skip DirOnly entries
  855. function TZMZipFile.Next(Current: Integer): Integer;
  856. var
  857.   cnt: Integer;
  858. begin
  859.   Result := BadIndex;
  860.   if Current >= -1 then
  861.   begin
  862.     cnt := Entries.Count;
  863.     if IgnoreDirOnly then
  864.     begin
  865.       repeat
  866.         Inc(Current);
  867.       until (Current >= cnt) or ((TZMIRec(Entries[Current]).StatusBits and zsbDirOnly) = 0);
  868.     end
  869.     else
  870.       Inc(Current);
  871.     if Current < cnt then
  872.       Result := Current;
  873.   end;
  874. end;
  875.  
  876. // return BadIndex when no more
  877. function TZMZipFile.NextSelected(Current: Integer): integer;
  878. var
  879.   k: Cardinal;
  880.   mask: cardinal;
  881.   rec: TZMIRec;
  882. begin
  883.   Result := BadIndex;
  884.   mask := zsbSkipped or zsbSelected;
  885.   if IgnoreDirOnly then
  886.      mask := mask or zsbDirOnly;
  887.   if Current >= -1 then
  888.   begin
  889.     while Current < Entries.Count -1 do
  890.     begin
  891.       inc(Current);
  892.       rec := TZMIRec(Entries[Current]);
  893.       if rec <> nil then
  894.       begin
  895.         k := rec.StatusBit[mask];
  896.         if k = zsbSelected then
  897.         begin
  898.           Result := Current;
  899.           break;
  900.         end;
  901.       end;
  902.     end;
  903.   end;
  904. end;
  905.  
  906. function TZMZipFile.Open(EOConly, NoLoad: Boolean): Integer;
  907. var
  908.   r: Integer;
  909. begin
  910.   // verify disk loaded
  911.   ClearFileInformation;
  912.   info := (info and zfi_MakeMask) or zfi_Loading;
  913.   if WorkDrive.DriveIsFixed or WorkDrive.HasMedia(False) then
  914.   begin
  915.     Result := Open1(EOConly);
  916.     if (Result >= 0) then
  917.     begin
  918.       LastWriteTime(fEOCFileTime);
  919.       InferNumbering;
  920.       if not (EOConly or NoLoad) then
  921.       begin
  922.         info := info or zfi_EOC;
  923.         if (Result and EOCBadComment) <> 0 then
  924.           Boss.ShowZipMessage(DS_CECommentLen, '');
  925.         if (Result and EOCBadStruct) <> 0 then
  926.           Boss.ShowZipMessage(LI_WrongZipStruct, '');
  927.         r := Load;
  928.         if r <> 0 then
  929.           Result := r
  930.         else
  931.         begin
  932.           info := info or zfi_Loaded or zfi_DidLoad;
  933.           SaveFileInformation;  // get details
  934.         end;
  935.       end;
  936.     end;
  937.   end
  938.   else
  939.     Result := -DS_NoInFile;
  940.   OpenRet := Result;
  941.   if Boss.Verbosity >= zvTrace then
  942.   begin
  943.     if Result < 0 then
  944.       Diag('Open = ' + Boss.ZipLoadStr(-Result))
  945.     else
  946.       Diag('Open = ' + IntToStr(Result));
  947.   end;
  948. end;
  949.  
  950. function TZMZipFile.Open1(EOConly: Boolean): Integer;
  951. var
  952.   fn: string;
  953.   SfxType: Integer;
  954.   size: Integer;
  955. begin
  956.   SfxType := 0;   // keep compiler happy
  957.   ReqFileName := FileName;
  958.   fn := FileName;
  959.   Result := OpenEOC(EOConly);
  960.   if (Result >= 0) and (Sig = zfsDOS) then
  961.   begin
  962.     stub := nil;
  963.     SfxType := CheckSFXType(handle, fn, size);
  964.     if SfxType >= cstSFX17 then
  965.     begin
  966.       if Seek(0, 0) <> 0 then
  967.         exit;
  968.       stub := TMemoryStream.Create;
  969.       try
  970.         if ReadTo(stub, size) <> size then
  971.         begin
  972.           stub := nil;
  973.         end;
  974.       except
  975.         stub := nil;
  976.       end;
  977.     end;
  978.   end;
  979.   if not (spExactName in SpanOptions) then
  980.   begin
  981.     if (Result >= 0) and (SfxType >= cstDetached) then
  982.     begin    //  it is last part of detached sfx
  983.       File_Close;
  984.       // Get proper path and name
  985.       FileName := IncludeTrailingBackslash(ExtractFilePath(ReqFileName)) + fn;
  986.       // find last part
  987.       Result := -DS_NoInFile;
  988.     end;
  989.     if Result < 0 then
  990.       Result := OpenLast(EOConly, Result);
  991.   end;
  992. end;
  993.  
  994. function TZMZipFile.PrepareWrite(typ: TZipWrites): Boolean;
  995. begin
  996.   case typ of
  997.     zwSingle:
  998.       Result := false;
  999.     zwMultiple:
  1000.       Result := True;
  1001.   else
  1002.     Result := zwoDiskSpan in WriteOptions;
  1003.   end;
  1004.   IsMultiPart := Result;
  1005.   if Result then
  1006.   begin
  1007.     DiskNr := 0;
  1008.     File_Close;
  1009.   end
  1010.   else
  1011.   begin
  1012.     DiskNr := -1;
  1013.   end;
  1014. end;
  1015.  
  1016. function TZMZipFile.Reopen(Mode: Cardinal): integer;
  1017. begin
  1018.   Result := 0;
  1019.   if (not IsOpen) or (OpenMode <> Mode) then
  1020.   begin
  1021.     File_Close;
  1022.     if Boss.Verbosity >= zvTrace then
  1023.       Diag('Trace: Reopening ' + RealFileName);
  1024.     if not File_Open(Mode) then
  1025.     begin
  1026.       Diag('Could not reopen: ' + RealFileName);
  1027.       Result := -DS_FileOpen;
  1028.     end;
  1029.   end;
  1030.   if (Result = 0) and ((info and zfi_Loaded) <> 0) and
  1031.     not VerifyFileInformation then
  1032.   begin
  1033.     Worker.Diag('File has changed! ' + RealFileName);
  1034.     // close it?
  1035.     Result := GE_FileChanged; // just complain at moment
  1036.   end;
  1037. end;
  1038.  
  1039. procedure TZMZipFile.Replicate(Src: TZMZipFile; LastEntry: Integer);
  1040. var
  1041.   I: Integer;
  1042.   rec: TZMIRec;
  1043. begin
  1044.   if (Src <> nil) and (Src <> Self) then
  1045.   begin
  1046.     inherited AssignFrom(Src);
  1047.     fCheckNo := Worker.NextCheckNo;
  1048. //    FAddOptions := Src.FAddOptions;
  1049. //    FEncodeAs := Src.FEncodeAs;
  1050. //    fEncoding := Src.fEncoding;
  1051. //    fEncoding_CP := Src.fEncoding_CP;
  1052. //    FIgnoreDirOnly := Src.FIgnoreDirOnly;
  1053.     fEOCFileTime := Src.fEOCFileTime;
  1054.     FFirst := Src.FFirst;
  1055.     fOnChange := Src.fOnChange;
  1056.     fOpenRet := Src.fOpenRet;
  1057.     FSelCount := Src.FSelCount;
  1058.     fSFXOfs := Src.fSFXOfs;
  1059.     fShowAll := Src.fShowAll;
  1060.     fStub := nil;
  1061.     fUseSFX := False;
  1062.     if Src.UseSFX and Assigned(Src.fStub) then
  1063.     begin
  1064.       fStub := TMemoryStream.Create;
  1065.       Src.fStub.Position := 0;
  1066.       if fStub.CopyFrom(Src.fStub, Src.fStub.Size) = Src.fStub.Size then
  1067.         fUseSFX := True
  1068.       else
  1069.         FreeAndNil(fStub);
  1070.     end;
  1071.     // add records from Src
  1072.     if (LastEntry < 0) or (LastEntry > Src.Count) then
  1073.       LastEntry := Src.Count - 1;
  1074.     for I := 0 to LastEntry do
  1075.     begin
  1076.       rec := TZMIRec.Create(self);
  1077.       rec.AssignFrom(Src[I]);
  1078.       Add(rec);
  1079.     end;
  1080.   end;
  1081. end;
  1082.  
  1083. // select entries matching external pattern - return number of selected entries
  1084. function TZMZipFile.Select(const Pattern: TZMString; How: TZipSelects): Integer;
  1085. var
  1086.   i: Integer;
  1087.   srch: Integer;
  1088.   t: TZMIRec;
  1089.   wild: Boolean;
  1090. begin
  1091.   Result := 0;
  1092.   // if it wild or multiple we must try to match - else only if same hash
  1093.   wild := not CanHash(pattern);
  1094.   if (Pattern = '') or (wild and ((Pattern = AllSpec) or (Pattern = AnySpec))) then
  1095.   begin
  1096.     // do all
  1097.     for i := 0 to fEntries.Count - 1 do
  1098.     begin
  1099.       t := fEntries[i];
  1100.       if SelectEntry(t, How) then
  1101.         Inc(Result);
  1102.     end;
  1103.   end
  1104.   else
  1105.   begin
  1106.     // select specific pattern
  1107.     i := -1;
  1108.     srch := 1;
  1109.     while srch <> 0 do
  1110.     begin
  1111.       t := FindNameEx(Pattern, i, wild);
  1112.       if t = nil then
  1113.         break;
  1114.       if SelectEntry(t, How) then
  1115.         Inc(Result);
  1116.       if srch > 0 then
  1117.       begin
  1118.         if wild then
  1119.           srch := -1  // search all
  1120.         else
  1121.           srch := 0;  // done
  1122.       end;
  1123.     end;
  1124.   end;
  1125. end;
  1126.  
  1127. // Select1 entries matching external pattern
  1128. function TZMZipFile.Select1(const Pattern, reject: TZMString;
  1129.     How: TZipSelects): Integer;
  1130. var
  1131.   args: string;
  1132.   i: Integer;
  1133.   exc: string;
  1134.   ptn: string;
  1135.   aRec: TZMIRec;
  1136.   wild: Boolean;
  1137. begin
  1138.   Result := 0;
  1139.   args := '';     // default args - empty
  1140.   exc := reject;  // default excludes
  1141.   ptn := Pattern; // need to remove switches
  1142.   // split Pattern into pattern and switches
  1143.   // if it wild or multiple we must try to match - else only if same hash
  1144.   wild := not CanHash(ptn);
  1145.   if (ptn = '') or (wild and ((ptn = AllSpec) or (ptn = AnySpec))) then
  1146.   begin
  1147.     // do all
  1148.     for i := 0 to fEntries.Count - 1 do
  1149.     begin
  1150.       aRec := fEntries[i];
  1151.       if (exc <> '') and (Worker.FNMatch(exc, aRec.Filename)) then
  1152.         Continue;
  1153.       if SelectEntry(aRec, How) then
  1154.       begin
  1155.         // set SelectArgs
  1156.         aRec.SelectArgs := args;
  1157.       end;
  1158.       Inc(Result);
  1159.     end;
  1160.   end
  1161.   else
  1162.   begin
  1163.     // Select1 specific pattern
  1164.     i := -1;
  1165.     while True do
  1166.     begin
  1167.       aRec := FindNameEx(ptn, i, wild);
  1168.       if aRec = nil then
  1169.         break;        // no matches
  1170.       if (exc = '') or not (Worker.FNMatch(exc, aRec.Filename)) then
  1171.       begin
  1172.         if SelectEntry(aRec, How) then
  1173.         begin
  1174.           // set SelectArgs
  1175.           aRec.SelectArgs := args;
  1176.         end;
  1177.         Inc(Result);
  1178.       end;
  1179.       if not wild then
  1180.         Break;    // old find first
  1181.     end;
  1182.   end;
  1183. end;
  1184.  
  1185. function TZMZipFile.SelectEntry(t: TZMIRec; How: TZipSelects): Boolean;
  1186. begin
  1187.   Result := t.Select(How);
  1188.   if Result then
  1189.     inc(FSelCount)
  1190.   else
  1191.     dec(FSelCount);
  1192. end;
  1193.  
  1194. function TZMZipFile.SelectFiles(const want, reject: TStrings; skipped:
  1195.     TStrings): Integer;
  1196. var
  1197.   a:  Integer;
  1198.   SelectsCount: Integer;
  1199.   exc: string;
  1200.   I: Integer;
  1201.   NoSelected:  Integer;
  1202.   spec: String;
  1203. begin
  1204.   Result := 0;
  1205.   ClearSelection; // clear all
  1206.   SelectsCount := want.Count;
  1207.   if (SelectsCount < 1) or (Count < 1) then
  1208.     exit;
  1209.   exc := '';
  1210.   // combine rejects into a string
  1211.   if (reject <> nil) and (reject.Count > 0) then
  1212.   begin
  1213.     exc := reject[0];
  1214.     for I := 1 to reject.Count - 1 do
  1215.       exc := exc + ZSwitchFollows + reject[I];
  1216.   end;
  1217.   // attempt to select each wanted spec
  1218.   for a := 0 to SelectsCount - 1 do
  1219.   begin
  1220.     spec := want[a];
  1221.     NoSelected := Select1(spec, exc, zzsSet);
  1222.     if NoSelected < 1 then
  1223.     begin
  1224.       // none found
  1225.       if Boss.Verbosity >= zvVerbose then
  1226.         Diag('Skipped filespec ' + spec);
  1227.       if assigned(skipped) then
  1228.         skipped.Add(spec);
  1229.     end;
  1230.     if NoSelected > 0 then
  1231.       Result := Result + NoSelected;
  1232.     if NoSelected >= Count then
  1233.       break;  // all have been done
  1234.   end;
  1235. end;
  1236.  
  1237. procedure TZMZipFile.SetCount(const Value: Integer);
  1238. begin
  1239.   // not allowed
  1240. end;
  1241.  
  1242. procedure TZMZipFile.SetEncoding(const Value: TZMEncodingOpts);
  1243. begin
  1244.   if fEncoding <> Value then
  1245.   begin
  1246.     ClearCachedNames;
  1247.     fEncoding := Value;
  1248.   end;
  1249. end;
  1250.  
  1251. procedure TZMZipFile.SetEncoding_CP(const Value: Cardinal);
  1252. begin
  1253.   if fEncoding_CP <> Value then
  1254.   begin
  1255.     ClearCachedNames;
  1256.     fEncoding_CP := Value;
  1257.   end;
  1258. end;
  1259.  
  1260. procedure TZMZipFile.SetItems(Idx: Integer; const Value: TZMIRec);
  1261. var
  1262.   tmp: TObject;
  1263. begin
  1264.   tmp := fEntries[Idx];
  1265.   if tmp <> Value then
  1266.   begin
  1267.     fEntries[Idx] := Value;
  1268.     tmp.Free;
  1269.   end;
  1270. end;
  1271.  
  1272. procedure TZMZipFile.SetShowAll(const Value: Boolean);
  1273. begin
  1274.   fShowAll := Value;
  1275. end;
  1276.  
  1277. procedure TZMZipFile.SetStub(const Value: TMemoryStream);
  1278. begin
  1279.   if fStub <> Value then
  1280.   begin
  1281.     if assigned(fStub) then
  1282.       fStub.Free;
  1283.     fStub := Value;
  1284.   end;
  1285. end;
  1286.  
  1287. function TZMZipFile.VerifyOpen: Integer;
  1288. var
  1289.   ft: TFileTime;
  1290. begin
  1291.   Result := DS_FileOpen;
  1292.   if not IsOpen and not File_Open(fmOpenRead or fmShareDenyWrite) then
  1293.     exit;
  1294.   if LastWriteTime(ft) then
  1295.   begin
  1296.     Result := 0;
  1297.  
  1298.     LastWriteTime(fEOCFileTime);
  1299.     if CompareFileTime(EOCFileTime, ft) <> 0 then
  1300.       Result := -DS_FileChanged;
  1301.   end;
  1302. end;
  1303.  
  1304. // returns bytes written or <0 _ error
  1305. function TZMZipFile.WriteCentral: Integer;
  1306. var
  1307.   i: Integer;
  1308.   rec: TZMIRec;
  1309.   wrote: Integer;
  1310. begin
  1311.   Result := 0;
  1312.   wrote  := 0;
  1313.   CentralOffset := Position;
  1314.   CentralDiskNo := DiskNr;
  1315.   TotalEntries := 0;
  1316.   CentralEntries := 0;
  1317.   CentralSize := 0;
  1318.   ProgReport(zacXItem, PR_CentrlDir, '', Count);
  1319.   for i := 0 to Count - 1 do
  1320.   begin
  1321.     rec := TZMIRec(Items[i]);
  1322.     if rec.StatusBit[zsbError] = 0 then
  1323.     begin
  1324.       // no processing error
  1325.       if Boss.Verbosity >= zvTrace then
  1326.         Diag('Writing central [' + IntToStr(i) + '] ' + rec.FileName);
  1327.       // check for deleted?
  1328.       Result := rec.Write;
  1329.       if Result < 0 then
  1330.         break;      // error
  1331.       if Position <= Result then    // started new part
  1332.         CentralEntries := 0;
  1333.       wrote := wrote + Result;
  1334.       CentralSize  := CentralSize + Cardinal(Result);
  1335.       TotalEntries := TotalEntries + 1;
  1336.       CentralEntries := CentralEntries + 1;
  1337.       ProgReport(zacXProgress, PR_CentrlDir, '', 1);
  1338.     end
  1339.     else
  1340.       Diag('skipped Writing central ['+ IntToStr(i) + '] ' + rec.FileName);
  1341.   end;
  1342.   // finished Central
  1343.   if Result >= 0 then
  1344.   begin
  1345.     Result := WriteEOC;
  1346.     if Result >= 0 then
  1347.     begin
  1348.       ProgReport(zacXProgress, PR_CentrlDir, '', 1);
  1349.       Result := wrote + Result;
  1350.       if Result > 0 then
  1351.       begin
  1352.         Diag('  finished ok');
  1353.       end;
  1354.     end;
  1355.   end;
  1356. end;
  1357.  
  1358. constructor TZMCopyRec.Create(theOwner: TZMWorkFile);
  1359. begin
  1360.   inherited Create(theOwner);
  1361. end;
  1362.  
  1363. procedure TZMCopyRec.AfterConstruction;
  1364. begin
  1365.   inherited;
  1366.   fLink := nil;
  1367. end;
  1368.  
  1369. // process record, return bytes written; <0 = -error
  1370. function TZMCopyRec.Process: Int64;
  1371. var
  1372.   did:  Int64;
  1373.   InRec: TZMIRec;
  1374.   InWorkFile: TZMWorkFile;
  1375.   stNr: Integer;
  1376.   stt:  Int64;
  1377.   ToWrite: Int64;
  1378.   wrt:  Int64;
  1379. begin
  1380.   //  ASSERT(assigned(Owner), 'no owner');
  1381.   if Owner.Boss.Verbosity >= zvVerbose then
  1382.     Owner.Boss.ReportMsg(GE_Copying, [FileName]);
  1383.   InRec := Link;
  1384.   InWorkFile := InRec.Owner;
  1385.   if Owner.Boss.Verbosity >= zvVerbose then
  1386.     Diag('Copying local');
  1387.   Result := InRec.SeekLocalData;
  1388.   if Result < 0 then
  1389.     exit;   // error
  1390.   stNr := Owner.DiskNr;
  1391.   stt  := Owner.Position;
  1392.   Result := WriteAsLocal1(ModifDateTime, CRC32);
  1393.   if Result < 0 then
  1394.     exit;   // error
  1395.   wrt := Result;
  1396.   Owner.ProgReport(zacProgress, PR_Copying, '', wrt);
  1397.   //  Diag('  finished copy local');
  1398.   // ok so update positions
  1399.   RelOffLocal := stt;
  1400.   DiskStart := stNr;
  1401.   ToWrite := CompressedSize;
  1402.   //    Diag('copying zipped data');
  1403.   Owner.ProgReport(zacItem, zprCompressed, FileName, ToWrite);
  1404.   did := Owner.CopyFrom(InWorkFile, ToWrite);
  1405.   if did <> ToWrite then
  1406.   begin
  1407.     if did < 0 then
  1408.       Result := did // write error
  1409.     else
  1410.       Result := -DS_DataCopy;
  1411.     exit;
  1412.   end;
  1413.   wrt := wrt + did;
  1414.   if (Flag and 8) <> 0 then
  1415.   begin
  1416.     did := WriteDataDesc(Owner);
  1417.     if did < 0 then
  1418.     begin
  1419.       Result := did;  // error
  1420.       exit;
  1421.     end;
  1422.     wrt := wrt + did;
  1423.     Owner.ProgReport(zacProgress, PR_Copying, '', did);
  1424.   end;
  1425.   Result := wrt;
  1426. end;
  1427.  
  1428. // return bytes to be processed
  1429. function TZMCopyRec.ProcessSize: Int64;
  1430. begin
  1431.   Result := CompressedSize + LocalSize;
  1432.   if (Flag and 8) <> 0 then
  1433.     Result := Result + sizeof(TZipDataDescriptor);
  1434. end;
  1435.  
  1436. procedure TZMCopyRec.SetLink(const Value: TZMIRec);
  1437. begin
  1438.   if fLink <> Value then
  1439.   begin
  1440.     fLink := Value;
  1441.   end;
  1442. end;
  1443.  
  1444. constructor TZMZipCopy.Create(Wrkr: TZMCore);
  1445. begin
  1446.   inherited Create(Wrkr);
  1447. end;
  1448.  
  1449. // Add a copy of source record if name is unique
  1450. function TZMZipCopy.AffixZippedFile(rec: TZMIRec): Integer;
  1451. var
  1452.   nrec: TZMCopyRec;
  1453. begin
  1454.   Result := -1;
  1455.   if HasDupName(rec) < 0 then
  1456.   begin
  1457.     // accept it
  1458.     nrec := TZMCopyRec.Create(self); // make a copy
  1459.     nrec.AssignFrom(rec);
  1460.     // clear unknowns ?
  1461.     nrec.Link := rec;  // link to original
  1462.     Result := Add(nrec);
  1463.   end;
  1464. end;
  1465.  
  1466. // return >=0 number added <0 error
  1467. function TZMZipCopy.AffixZippedFiles(Src: TZMZipFile; All: Boolean): Integer;
  1468. var
  1469.   i:  Integer;
  1470.   r:  Integer;
  1471.   rec: TZMIRec;
  1472. begin
  1473.   Result := 0;
  1474.   for i := 0 to Src.Count - 1 do
  1475.   begin
  1476.     rec := Src[i];
  1477.     if not assigned(rec) then
  1478.       continue;
  1479.     if All or rec.TestStatusBit(zsbSelected) then
  1480.     begin
  1481.       Diag('including: ' + rec.FileName);
  1482.       r := AffixZippedFile(rec);
  1483.       if (r >= 0) then
  1484.         Inc(Result) // added
  1485.       else
  1486.       begin
  1487.         // error
  1488.         if r < 0 then
  1489.           Result := r;
  1490.       end;
  1491.     end
  1492.     else
  1493.       Diag('ignoring: ' + rec.FileName);
  1494.   end;
  1495. end;
  1496.  
  1497.  
  1498. // copies selected files from InZip
  1499. function TZMZipCopy.WriteFile(InZip: TZMZipFile; All: Boolean): Int64;
  1500. begin
  1501.   ASSERT(assigned(InZip), 'no input');
  1502.   Diag('Write file');
  1503.   Result := InZip.VerifyOpen;  // verify unchanged and open
  1504.   if Result < 0 then
  1505.     exit;
  1506.   ZipComment := InZip.ZipComment;
  1507.   Result := AffixZippedFiles(InZip, All);
  1508.   if Result >= 0 then
  1509.     Result := Commit(zwoZipTime in {Worker.}WriteOptions);
  1510. end;
  1511.  
  1512.  
  1513. end.
  1514.