Search code examples
delphizipdelphi-xe2delphi-xe3

Delphi XE2 TZipFile: replace a file in zip archive


I'd like to replace a file (= delete old and add new) in a zip archive with the Delphi XE2/XE3 standard System.Zip unit. But there are no replace/delete methods. Does anybody have an idea how it could be achieved without needing to extract all files and add them to a new archive?

I have this code, but it adds the "document.txt" once more if it's already present:

var
  ZipFile: TZipFile;
  SS: TStringStream;
const
  ZipDocument = 'E:\document.zip';
begin
  ZipFile := TZipFile.Create; //Zipfile: TZipFile
  SS := TStringStream.Create('hello');
  try
    if FileExists(ZipDocument) then
      ZipFile.Open(ZipDocument, zmReadWrite)
    else
      ZipFile.Open(ZipDocument, zmWrite);

    ZipFile.Add(SS, 'document.txt');

    ZipFile.Close;
  finally
    SS.Free;
    ZipFile.Free;
  end;
end;

Note: I used TPAbbrevia before (that did the job), but I'd like to use Delphi's Zip unit now. So please do not answer something like "use another library". Thank you.


Solution

  • I'd recommend Abbrevia because I'm biased :), you already know it, and it doesn't require any hacks. Barring that, here's your hack:

    type
      TZipFileHelper = class helper for TZipFile
        procedure Delete(FileName: string);
      end;
    
    { TZipFileHelper }
    
    procedure TZipFileHelper.Delete(FileName: string);
    var
      i, j: Integer;
      StartOffset, EndOffset, Size: UInt32;
      Header: TZipHeader;
      Buf: TBytes;
    begin
      i := IndexOf(FileName);
      if i <> -1 then begin
        // Find extents for existing file in the file stream
        StartOffset := Self.FFiles[i].LocalHeaderOffset;
        EndOffset := Self.FEndFileData;
        for j := 0 to Self.FFiles.Count - 1 do begin
          if (Self.FFiles[j].LocalHeaderOffset > StartOffset) and
             (Self.FFiles[j].LocalHeaderOffset <= EndOffset) then
            EndOffset := Self.FFiles[j].LocalHeaderOffset;
        end;
        Size := EndOffset - StartOffset;
        // Update central directory header data
        Self.FFiles.Delete(i);
        for j := 0 to Self.FFiles.Count - 1 do begin
          Header := Self.FFiles[j];
          if Header.LocalHeaderOffset > StartOffset then begin
            Header.LocalHeaderOffset := Header.LocalHeaderOffset - Size;
            Self.FFiles[j] := Header;
          end;
        end;
        // Remove existing file stream
        SetLength(Buf, Self.FEndFileData - EndOffset);
        Self.FStream.Position := EndOffset;
        if Length(Buf) > 0 then
          Self.FStream.Read(Buf[0], Length(Buf));
        Self.FStream.Size := StartOffset;
        if Length(Buf) > 0 then
          Self.FStream.Write(Buf[0], Length(Buf));
        Self.FEndFileData := Self.FStream.Position;
      end;
    end;
    

    Usage:

    ZipFile.Delete('document.txt');
    ZipFile.Add(SS, 'document.txt');