Pergunta

When you try to read a TZipFile.FileComment the compiler reports that this will not compile: [dcc64 Error] Unit1.pas(451): E2010 Incompatible types: 'string' and 'System.TArray'

var
  iFileComment: string;
...
iFileComment := iZipFile.FileInfo[i].FileComment;

So... how do you read a TZipFile comment and assign it to a string? This seems strange because I can successfully write the zipfile comment as follows:

iZipFile.FileComment[ListView1.ItemIndex] := FileComment1.Text;

If you try to read the comment with:

iFileComment := iZipFile.FileComment[i];

every file in the zip file has the same comment, even though only one file actually has a filecomment.

The comments are added by this:

procedure TForm1.SetFileComment1Click(Sender: TObject); var  
iZipFile: TZipFile;   iListItem: TlistItem; 
begin   
  if ListView1.ItemIndex <> -1 then   
  begin
     iZipFile := TZipFile.Create;
     try
       { Open zip file for writing }
       iZipFile.Open(ZipFilename1.Text, zmReadWrite);
       iZipFile.FileComment[ListView1.ItemIndex] := FileComment1.Text;
       { Close the zip file }
       iZipFile.Close;
       { Update the listview }
       ListView1.Items.BeginUpdate;
       try
         iListItem := ListView1.Items.Item[ListView1.ItemIndex];
         iListItem.SubItems[5] := FileComment1.Text;
       finally
         ListView1.Items.EndUpdate;
       end;
     finally
       iZipFile.Free;
     end;   
  end   
  else   
  begin
    MessageBox(0, 'A filename is not selected. Please select a filename.',
       'Warning', MB_ICONINFORMATION or MB_OK);
  end; 
end;

EDIT

I am now setting and writing the filecomments with iZipFile.FileComment[Index] as suggested. When I set one file's comment and open the zip file in a working zip app only the single file that I set appears with the comment... In this TZipFile project when I load the same zip file, every file has the same comment but I can not see why. All the other fields operate as you would expect except for the filecomment field.

Here is the code that opens the zip file:

procedure TForm1.Open1Click(Sender: TObject);
{ Open zip file. }
var
  i: integer;
  iZipFile: TZipFile;
  iFilename: string;
  iDateTime: TDateTime;
  iCompressedSize: cardinal;
  iUnCompressedSize: cardinal;
  iCRC32: cardinal;
  iCompressionMethod: word;
  iFileComment: string;
  iListItem: TlistItem;
begin
  if OpenDialog1.Execute then
  begin
    if FileExists(OpenDialog1.FileName) then
    begin
      iZipFile := TZipFile.Create;
      try
        ListView1.Items.Clear;
        ZipFilename1.Text := OpenDialog1.FileName;
        try
          { Open zip file for reading }
          iZipFile.Open(ZipFilename1.Text, zmReadWrite);
          for i := 0 to iZipFile.FileCount - 1 do
          begin
            iFilename := iZipFile.FileNames[i];
            iListItem := ListView1.Items.Add;
            iListItem.Caption := iFilename;
            iDateTime := FileDateToDateTime
              (iZipFile.FileInfo[i].ModifiedDateTime);
            iListItem.SubItems.Add(DateTimeToStr(iDateTime)); { 0 }
            iCompressedSize := iZipFile.FileInfo[i].CompressedSize;
            iListItem.SubItems.Add(FormatByteSize(iCompressedSize)); { 1 }
            iUnCompressedSize := iZipFile.FileInfo[i].UncompressedSize;
            iListItem.SubItems.Add(FormatByteSize(iUnCompressedSize)); { 2 }
            iCRC32 := iZipFile.FileInfo[i].CRC32;
            iListItem.SubItems.Add(IntToStr(iCRC32)); { 3 }
            iCompressionMethod := iZipFile.FileInfo[i].CompressionMethod;
            iListItem.SubItems.Add
              (ZipCompressionToStr(iCompressionMethod)); { 4 }
            iFileComment := iZipFile.FileComment[i];
            iListItem.SubItems.Add(iFileComment); { 5 }
          end;
          { Close the zip file }
          iZipFile.Close;
        except
          on E: Exception do
          begin
            ShowMessage(E.ClassName + #10#13 + E.Message);
          end;
        end;
      finally
        iZipFile.Free;
      end;
    end;
  end;
end;
Foi útil?

Solução

You read it back the same way you write it, in reverse:

// Write the comment
iZipFile.FileComment[Index] := 'This is a zip file comment';

// Read it back
sComment := iZipFile.FileComment[Index];
WriteLn(sComment);             // Outputs "This is a zip file comment"

Index would match the index of the individual file within the zip archive. In other words, the first file would be at index 0, the second at index 1, etc., wit the last being at index FileCount - 1.

Here's a working example. I created a dummy zip file named Test.zip containing a few old .png files I've used in answers here (as seen in the image below):

List of files in zip displayed in Windows Explorer

I then used the below code to add comments to each file and read them back, writing them to the console:

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Zip;

var
  Zip: TZipFile;
  i: Integer;
begin
  Zip := TZipFile.Create;
  Zip.Open('D:\TempFiles\Test.zip', zmReadWrite);
  for i := 0 to Zip.FileCount - 1 do
    Zip.FileComment[i] := Format('This is file %d', [i]);
  Zip.Free;  // Flushes changes to disk automatically

  // Create a new instance, just to make it clear.
  Zip := TZipFile.Create;
  Zip.Open('D:\TempFiles\Test.zip', zmReadWrite);
  for i := 0 to Zip.FileCount - 1 do
    WriteLn(Zip.FileNames[i] + ':'#9 + Zip.FileComment[i]);
  Zip.Free;
  ReadLn;
end.

Here's the output it produced:

Screen capture of console output displaying file comments from code above

Here's the zip file comments it added, as displayed in 7Zip:

Screen capture of 7Zip window with zip file names and comments

Outras dicas

In XE2, TZipHeader.FileComment was declared as a RawByteString, which could be assigned directly to a String (using a typecast to avoid a compiler warning):

iFileComment := String(iZipFile.FileInfo[i].FileComment);

However, in XE4, TZipHeader.FileComment is declared as a TBytes instead. You cannot directly assign a TBytes to a String (and vice versa), hense the compiler error.

If you look at the implementation of TZipFile.GetFileComment() in XE4, it uses TZipFile.TBytesToString() to convert the TBytes to a String. TZipFile.TBytesToString() uses SysUtils.TEncoding.GetString() to do the actual conversion, using either codepage 65001 or 437 depending on the value of the TZipFile.UTF8Support property.

The correct way to get/set each file's comment is to use the TZipFile.FileComments[] property, like Ken demonstrates. Not only does it handle the string<->byte conversions for you, but it also checks to make sure the zip is open before accessing the data.

Sorry to revive this old question but none of the answers actually addresses the every file in the zip file has the same comment problem.

The cause is a bug in TZipFile.ReadCentralHeader

When a zip file is opened ReadCentralHeader reads all the zip directory entries into an internal list of records but fails to clear the temporary record, being used to populate the list, between each entry.

In the case of the file comment ReadCentralHeader first reads the FileCommentLength value but if this value is zero then it doesn't set the value of the FileComment value which therefore retains the value of the previous entry.

A possible work around is to take the FileCommentLength value into account when reading the FileComment[] property:

Comment := Copy(ZipFile.FileComment[Index], 1, ZipFile.FileInfo[Index].FileCommentLength);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top