Пост восстановлен после переезда сайта на новый хостинг. Работа с zip в delphi появилась ещё в Delphi XE2. Однако это событие осталось практически незамеченным на фоне других нововведений, которыми пестрила Delphi XE2: разработка под MacOS и, соответственно, библиотеки Firemonkey, механизм LiveBinding и прочие «фичи», лежащие на поверхности. А про модуль delphi System.Zip особенно никто подробно и не рассказывал, разве, что здесь была небольшая заметка про zip в delphi. Сегодня я буду рассматривать работу с zip в delphi с позиции использования System.Zip в Delphi 10.3 Rio.
Введение
Для работы с ZIP-архивами в Delphi используется класс TZipFile. Этот класс предоставляет основные возможности для работы с zip, такие как чтение содержимого архива, запись архива, чтение заголовка архивного файла, упаковка и распаковка zip в delphi. И, несмотря на то, что TZipFile не содержит методов и свойств, непосредственно используемых для запароленных архивов, этот класс может предоставлять пользователю информацию о том, что файл зашифрован для того, чтобы использовать другие библиотеки delphi для распаковки зашифрованных zip-архивов.
Работу с модулем System.Zip и, в частности, с классом TZipFile, рассмотрим на примерах.
Чтение содержимого zip архива в Delphi
Для демонстрации возможностей чтения zip в delphi попробуем прочитать архив test.zip, содержащий три файла — текстовый и две схемы XSD для KML:
Для начала, прочитаем имена файлов, содержащихся в zip-архиве. Делается это так:
var Zip: TZipFile; ArchiveFile: String; begin Zip:=TZipFile.Create; try Zip.Open('test.zip',zmRead); for ArchiveFile in Zip.FileNames do ShowMessage(ArchiveFile); Zip.Close; finally Zip.Free; end; end;
Здесь мы создаем объект zip архива, затем открываем архив для чтения, используя метод Open и, используя свойство TZipFile.FileNames, получаем имена файлов в архиве. На что стоит обратить внимание:
- Путь к файлу в TZipFile.FileNames определяется относительно zip архива;
- В свойстве TZipFile.FileNames содержатся как имена файлов, так и папок архива.
Так, в приведенном выше примере программа выдаст следующим список:
- kml/2.3.0/
- kml/2.3.0/atom-author-link.xsd
- kml/2.3.0/ogckml23.xsd
- kml/2.3.0/ReadMe.txt
Метод Open имеет две версии:
procedure Open(const ZipFileName: string; OpenMode: TZipMode); overload; procedure Open(ZipFileStream: TStream; OpenMode: TZipMode); overload;
В одной версии мы передаем в качестве zip архива имя файла, а во втором — поток TStream, содержащий zip архив. При этом, второй параметр — OpenMode может принимать следующие значения:
TZipMode = (zmClosed, zmRead, //открываем zip архив для чтения zmReadWrite, //открываем zip архив для чтения и добавления новых файлов zmWrite);//создаем новый архив
Кроме имен файлов в архиве мы также можем получить по нему такую информацию как размер в упакованном и неупакованном виде, метод сжатия, комментарий к файлу в архиве и так далее. Получается эта информация с использованием свойств:
property FileInfos: TArray read GetFileInfos; property FileInfo[Index: Integer]: TZipHeader read GetFileInfo;
При этом тип TZipHeader имеет следующее описание:
TZipHeader = packed record MadeByVersion: UInt16; // Start of Central Header RequiredVersion: UInt16; // Start of Local Header Flag: UInt16; CompressionMethod: UInt16; ModifiedDateTime: UInt32; CRC32: UInt32; CompressedSize: UInt32; UncompressedSize: UInt32; FileNameLength: UInt16; ExtraFieldLength: UInt16; // End of Local Header FileCommentLength: UInt16; DiskNumberStart: UInt16; InternalAttributes: UInt16; ExternalAttributes: UInt32; LocalHeaderOffset: UInt32; // End of Central Header FileName: TBytes; ExtraField: TBytes; FileComment: TBytes; function GetUTF8Support: Boolean; procedure SetUTF8Support(value: Boolean); property UTF8Support: Boolean read GetUTF8Support write SetUTF8Support; end;
Попробуем получить информацию по файлам и папкам в нашем тестовом архиве. В коде ниже считывается информация о файле из архива: комментарий, метод сжатия, размер файла в сжатом и несжатом состоянии, контрольная сумма и дата последнего изменения файла.
var Header: TZipHeader; Date: TDateTime; begin Memo1.Lines.Clear; Header := Zip.FileInfos[ListBox1.ItemIndex]; Memo1.Lines.Add('FileComment ' + StringOf(Header.FileComment)); Memo1.Lines.Add('Method ' + TZipCompressionToString(TZipCompression(Header.CompressionMethod))); Memo1.Lines.Add('CompressedSize ' + Header.CompressedSize.ToString); Memo1.Lines.Add('UncompressedSize ' + Header.UncompressedSize.ToString); Memo1.Lines.Add('CRC32 ' + Header.CRC32.ToString); WinFileDateToDateTime(Header.ModifiedDateTime, Date); Memo1.Lines.Add('ModifiedDateTime ' + DateTimeToStr(Date)); end;
Здесь мы использовали два новых метода.
function TZipCompressionToString(Compression: TZipCompression): string;
Возвращает «человекопонятный» метод сжатия файла по значению, полученному из заголовка. TZipCompression — это перечислитель, содержащий все доступные методы сжатия файла.
Второй метод (WinFileDateToDateTime) переводит значение даты/времени из формата Windows в обычный TDateTime. Этот метод в System.Zip почему-то не публичный, в отличие от TZipCompressionToString, поэтому пришлось его «вытащить наружу», метод выглядит следующим образом:
function WinFileDateToDateTime(FileDate: UInt32; out DateTime: TDateTime): Boolean; var LDate: TDateTime; LTime: TDateTime; begin Result := TryEncodeDate( LongRec(FileDate).Hi shr 9 + 1980, LongRec(FileDate).Hi shr 5 and 15, LongRec(FileDate).Hi and 31, LDate); if Result then begin Result := TryEncodeTime( LongRec(FileDate).Lo shr 11, LongRec(FileDate).Lo shr 5 and 63, LongRec(FileDate).Lo and 31 shl 1, 0, LTime); if Result then DateTime := LDate + LTime; end; end;
Распаковка файлов из ZIP в Delphi
Следующий момент — распаковка архива. Здесь TZipFile предоставляет возможность распаковки как отдельного файла, так и сразу всех файлов, содержащихся в архиве.
Для того, чтобы распаковать отдельный файл используется одна из версий метода:
procedure Extract(const FileName: string; const Path: string = ''; CreateSubdirs: Boolean = True); overload; procedure Extract(Index: Integer; const Path: string = ''; CreateSubdirs: Boolean = True); overload;
Здесь:
- FileName — файл, который необходимо распаковать (см. свойство TZipFile.FileNames)
- Index — индекс файла в TZipFile.FileNames
- Path — путь по которому необходимо разместить распакованный файл. По умолчанию файл размещается в директории рядом с exe-файлом
- CreateSubdirs — указывает на то, следует ли воссоздать полный путь к файлу (True) или нет (False)
Для распаковки всего zip архива в delphi используется следующий метод:
procedure ExtractAll(const Path: string = '');
Если же вам необходимо содержимое какого-либо файла для дальнейшей работы в программе, то можно воспользоваться следующими методами:
procedure Read(const FileName: string; out Bytes: TBytes); overload; procedure Read(Index: Integer; out Bytes: TBytes); overload;
Первый параметр в этих процедурах имеет тот же смысл, что и в методах Extract, а второй — массив байтов в который необходимо сохранить содержимое файла. Например, так можно вывести в Memo текст xsd-файла из нашего тестового архива:
var Bytes: TBytes; begin Zip.Read(ListBox1.Items[ListBox1.ItemIndex],//файл из zip архива, выбранный в списке ListBox1 Bytes); Memo1.Lines.Text:=StringOf(Bytes); end;
Создание zip архива в Delphi
Для создания zip архива в delphi необходимо:
- Создать экземпляр TZipFile с режимом доступа zmReadWrite
- Добавить в архив файлы, используя метод TZipFile.Add
- Выполнить метод TZipFile.Close
- Уничтожить экземпляр TZipFile
Метод Add имеет следующее описание:
procedure Add(const FileName: string; const ArchiveFileName: string = ''; Compression: TZipCompression = zcDeflate); overload; procedure Add(Data: TBytes; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate); overload; procedure Add(Data: TStream; const ArchiveFileName: string; Compression: TZipCompression = zcDeflate; AExternalAttributes: TFileAttributes = []); overload; procedure Add(Data: TStream; LocalHeader: TZipHeader; CentralHeader: PZipHeader = nil); overload;
Здесь
- FileName — имя внешнего файла, который необходимо добавить в архив
- Data — содержимое внешнего файла, который необходимо добавить в архив
- ArchiveFileName — имя файла в архиве
- Compression — метод сжатия файла
Например, напишем консольное приложение, которое будет находить все файлы в папке archive рядом с exe-файлом и упаковывать их без всякой структуры в архив с названием test.zip:
program Archivator; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Zip, System.IOUtils; const cZipName = 'test.zip'; cZipFolder ='archive'; var ZipFile: TZipFile; zName: string; searchPath: string; ArchiveFiles: TArray; AFile: string; begin //задаем имя zip архива zName:=TPath.Combine(ExtractFilePath(ParamStr(0)), cZipName); //задаем путь к папке с файлами для упаковки searchPath:=TPath.Combine(ExtractFilePath(ParamStr(0)), cZipFolder); ZipFile:=TZipFile.Create; try ZipFile.Open(zName,zmWrite); //ищем все файлы в директории ArchiveFiles:=TDirectory.GetFiles(searchPath); //добавляем все найденные файлы в архив for AFile in ArchiveFiles do begin Writeln('Добавляем в архив файл '+AFile); ZipFile.Add(AFile, ExtractFileName(AFile), zcDeflate); end; //закрываем файл ZipFile.Close; finally //уничтожаем TZipFile FreeAndNil(ZipFile); end; readln; end.
Соответственно, если вам необходимо создать сложную структуру zip архива в delphi с папками, подпапками и так далее, то просто изменяете параметр ArchiveFileName, добавляя необходимые пути.
Как работать с запароленными zip архивами в Delphi
Как я писал выше, к сожалению в TZipFile нет как таковых механизмов и методов работы с zip архивами с паролем. Однако, можно проверить наличие шифрования у архива и переадресовать работу с таким архивом другой библиотеке. Для того, чтобы определить, что файл зашифрован, необходимо определить обработчик события OnCreateDecompressStream или определить callback-функцию CreateDecompressStreamCallBack.
Например, определить, что zip архив защищен паролем можно так:
Назначаем обработчик OnCreateDecompressStream
var ZipFile: TZipFile; begin ZipFile:=TZipFile.Create; ZipFile.OnCreateDecompressStream := OnCreateDecompressStream; ZipFile.Open('Password.zip',zmRead); try ZipFile.ExtractAll(); finally ZipFile.Free; end; end;
Сам обработчик выглядит следующим образом:
function TForm13.OnCreateDecompressStream(const InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader; IsEncrypted: Boolean): TStream; begin try if IsEncrypted then begin ShowMessage('Зашифровано'); end else Result := InStream; except on E: Exception do begin Result := InStream; end; end; end;
В принципе, этот подход был показан давным давно в официальной документации Embarcadero. Однако, при тестировании TZipFile обнаружилось, что, если файл зашифрован одним из методов AES (AES128, AES192, AES256), то вызывается исключение при создании потока TZDecompressionStream и, следовательно, до вызова события OnCreateDecompressStream дело не доходит. Методом AES позволяет шифровать файлы, например, тот же Total Commander:
Однако, если выбрать способ ZIP 2.0, то все работает как надо.
Отображение прогресса создания или распаковки zip архива в Delphi
При добавлении в zip архив большого файла или, наоборот, распаковки большого zip архива в delphi хотелось бы, чтобы наша программа не «висла». TZipFile предоставляет такую возможность с помощью события OnProgress:
TZipProgressEvent = procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64) of object;
Здесь
- FileName — имя файла, который в данный момент обрабатывается
- Header — информация о файле
- Position — количество обработанный байт файла
Например, чтобы получить текущий прогресс упаковки файла в zip архив в delphi можно использовать простую формулу:
Position / Header.UnCompressedSize)*100
Ну и, конечно, если вы работаете с TZipFile не в отдельном потоке, то стоит в обработчике также указать:
Application.ProcessMessages
Вот, в принципе и вся основная информация по работе delphi с zip архивами «из коробки». Остается только добавить, что помимо рассмотренных выше методов и свойств у TZipFile также имеются классовые методы, позволяющие работать с zip архивами без создания экземпляра TZipFile.
Интересная тема, но для новичков розобратся будет трудновато. К тому же автор наверное забросил сайт что очень печально.