2059 lines
71 KiB
ObjectPascal

////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Project : FWZip
// * Unit Name : FWZipWriter
// * Purpose : Êëàññ äëÿ ñîçäàíèÿ ZIP àðõèâà
// * Author : Àëåêñàíäð (Rouse_) Áàãåëü
// * Copyright : © Fangorn Wizards Lab 1998 - 2023.
// * Version : 2.0.1
// * Home Page : http://rouse.drkb.ru
// * Home Blog : http://alexander-bagel.blogspot.ru
// ****************************************************************************
// * Stable Release : http://rouse.drkb.ru/components.php#fwzip
// * Latest Source : https://github.com/AlexanderBagel/FWZip
// ****************************************************************************
//
// Èñïîëüçóåìûå èñòî÷íèêè:
// ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
// https://zlib.net/zlib-1.2.13.tar.gz
// http://www.base2ti.com/
//
unit FWZipWriter;
{$IFDEF FPC}
{$MODE Delphi}
{$H+}
{$ENDIF}
interface
{$I fwzip.inc}
uses
SysUtils,
Classes,
Contnrs,
Masks,
FWZipConsts,
FWZipCrc32,
FWZipCrypt,
FWZipStream,
FWZipZLib,
FWZipUtils;
type
TFWZipWriter = class;
TFWZipWriterItem = class
private
FOwner: TFWZipWriter;
FComment: string; // êîìåíòàðèé ê ýëåìåíòó
FFilePath: string; // ïóòü ê âíåøíåìó ôàéëó (èëè ñì. TFWZipWriterItemEx.Data)
FFileName: string; // èìÿ ýëåìåíòà â àðõèâå - íå ìîæåò áûòü ïóñòûì
FPassword: string; // ïàðîëü
FCmpLevel: TCompressionLevel; // óðîâåíü ñæàòèÿ
FNeedDescriptor: Boolean; // ôëàã îïðåäåëÿåò ñïîñîá õðàíåíèÿ äàííûõ
// î ýëåìåíòå, (äåñêðèïòîð èëè ëîêàëüíûé çàãîëîâîê)
FSize: Int64;
FData: TStream; // Äàííûå ýëåìåíòà â ñëó÷àå åñëè ôàéë
// îòñóòñòâóåò íà äèñêå
FAttributes: TFileAttributeData; // Âíåøíèå àòòðèáóòû ôàéëà
FTag: Integer;
FUseUTF8String: Boolean;
FUseExternalData: Boolean; // Ôëàã óêàçûâàþùèé íà òî ÷òî äàííûå áóäóò ïåðåäàâàòüñÿ ñíàðóæè
procedure SetBool(const Value: Boolean);
procedure SetCmpLevel(const Value: TCompressionLevel);
procedure SetString(const Index: Integer; const Value: string);
protected
property Data: TStream read FData;
property UseExternalData: Boolean read FUseExternalData write FUseExternalData;
public
constructor Create(Owner: TFWZipWriter;
const InitFilePath: string;
InitAttributes: TFileAttributeData;
const InitFileName: string = ''); virtual;
destructor Destroy; override;
procedure ChangeDataStream(Value: TStream;
AOwnerShip: TStreamOwnership = soReference);
procedure ChangeAttributes(Value: TFileAttributeData);
function IsFolder: Boolean;
property Comment: string index 0 read FComment write SetString;
property FilePath: string index 1 read FFilePath write SetString;
property FileName: string index 2 read FFileName write SetString;
property Password: string index 3 read FPassword write SetString;
property CompressionLevel: TCompressionLevel read FCmpLevel write SetCmpLevel;
property NeedDescriptor: Boolean read FNeedDescriptor write SetBool;
property Size: Int64 read FSize;
property Attributes: TFileAttributeData read FAttributes;
property Tag: Integer read FTag write FTag;
property UseUTF8String: Boolean read FUseUTF8String write FUseUTF8String;
end;
TFWZipWriterItemClass = class of TFWZipWriterItem;
// Ðåçóëüòàò ñîçäàíèÿ àðõèâà
TBuildZipResult =
(
brDone, // àðõèâ ñîçäàí óñïåøíî
brFailed, // îøèáêà ñîçäàíèÿ àðõèâà
brAborted, // ñîçäàíèå àðõèâà îòìåíåíî ïîëüçîâàòåëåì
brPartialBuild // íåêîòîðûå ýëåìåíòû ïðîïóùåíû èç-çà âîçíèêøèõ îøèáîê
);
{ TFWZipWriter }
TFWZipWriter = class
private
FDefaultDescryptorState: Boolean;
FDefaultCompressionLevel: TCompressionLevel;
FDefaultPassword: string;
FItems: TObjectList;
FCD: array of TCentralDirectoryFileHeaderEx;
FVersionToExtract: Word;
FcdfhOffset, FTotalProgress, FTotalSizeCount,
FTotalProcessedCount, FSizeOfCentralDir: Int64;
FCompressedStream: TStream;
FProcessedItemIndex, FcdfhDiskNumber, FcdfhRecordOnDisk: Integer;
FComment: string;
FOnProgress: TZipProgressEvent;
FBuildState: Boolean;
FSaveExData: TZipSaveExDataEvent;
FExceptionCount: Integer;
FBuidException: TZipBuildExceptionEvent;
FTrimPackedStreamSize: Boolean;
FAlwaysAddEmptyFolder: Boolean;
FUseUTF8String: Boolean;
function GetItem(Index: Integer): TFWZipWriterItem;
protected
function GetItemClass: TFWZipWriterItemClass; virtual;
function AddNewItem(Value: TFWZipWriterItem): Integer;
function IsMultiPartZip(AStream: TStream): Boolean;
procedure FillItemCDFHeader(CurrentItem: TFWZipWriterItem;
var Value: TCentralDirectoryFileHeaderEx); virtual;
procedure CompressItem(CurrentItem: TFWZipWriterItem;
Index: Integer; StreamSizeBeforeCompress: Int64; Stream: TStream); virtual;
procedure FillExData(Stream: TStream; Index: Integer); virtual;
protected
procedure DoProgress(ProgressState: TProgressState);
procedure CompressorOnProcess(Sender: TObject);
protected
function CheckFileNameSlashes(const Value: string): string;
function CreateItemFromStream(const FileName: string;
Value: TStream; AOwnerShip: TStreamOwnership): TFWZipWriterItem;
function GetVersionToExtract(Index: Integer): Word;
function GetCurrentFileTime: TFileTime;
procedure SaveItemToStream(Stream: TStream; Index: Integer); virtual;
procedure SaveCentralDirectory(Stream: TStream);
procedure SaveEndOfCentralDirectory(Stream: TStream);
procedure SaveString(Stream: TStream; const Value: string;
AUseUTF8String: Boolean);
procedure UpdateLocalHeaders(Stream: TStream);
property BuildState: Boolean read FBuildState;
function StringLength(const Value: string; AUseUTF8String: Boolean): Integer;
public
constructor Create; overload;
constructor Create(CompressionLevel: TCompressionLevel); overload;
constructor Create(UseDescryptors: Boolean;
CompressionLevel: TCompressionLevel;
const DefaultPassword: string); overload;
destructor Destroy; override;
function AddEmptyFolder(const FolderRelativeName: string): Integer; overload;
function AddEmptyFolder(const FolderRelativeName: string;
Attributes: TFileAttributeData): Integer; overload;
function AddFile(const FilePath: string;
const FileName: string = ''): Integer; overload;
function AddFile(const FilePath: string;
Attributes: TFileAttributeData;
const FileName: string = ''): Integer; overload;
function AddStream(const FileName: string; Value: TStream;
AOwnerShip: TStreamOwnership = soReference): Integer;
function AddFiles(Value: TStringList): Integer;
function AddFilesAndFolders(Value: TStringList; SubFolders: Boolean = True): Integer;
function AddFolder(const Path: string;
SubFolders: Boolean = True): Integer; overload;
function AddFolder(const RelativePath, Path, Mask: string;
SubFolders: Boolean = True): Integer; overload;
function BuildZip(ZipFilePath: string): TBuildZipResult; overload;
function BuildZip(Stream: TStream): TBuildZipResult; overload;
function Count: Integer;
procedure Clear;
procedure DeleteItem(Index: Integer);
/// <summary>
/// Ôóíêöèÿ èùåò çàïèñü îá ýëåìåíòå ïî èìåíè
/// </summary>
function Find(const Value: string; out AItem: TFWZipWriterItem;
IgnoreCase: Boolean = True): Boolean; overload;
function InsertStream(const FileName: string; Index: Integer;
Value: TStream; AOwnerShip: TStreamOwnership = soReference): Integer;
// Ñâîéñòâî îòâå÷àåò çà äîáàâëåíèå ïàïêè â âèäå TFWZipWriterItem
// íåïîñðåäñòâåííî ïåðåä äîáàâëåíèåì äàííûõ èç óêàçàíîé ïàïêè
property AlwaysAddEmptyFolder: Boolean read FAlwaysAddEmptyFolder
write FAlwaysAddEmptyFolder;
property Item[Index: Integer]: TFWZipWriterItem read GetItem; default;
property Comment: string read FComment write FComment;
// Ñâîéñòâî ðàáîòàåò òîëüêî ïðè âêëþ÷åííîé äèðåêòèâå USE_AUTOGENERATED_ZLIB_HEADER
//  îñòàëüíûõ ñëó÷àÿõ íå âëèÿåò íà àðõèâ è îñòàâëåíî äëÿ ñîâìåñòèìîñòè
property TrimPackedStreamSize: Boolean read FTrimPackedStreamSize
write FTrimPackedStreamSize; // deprecated;
property OnException: TZipBuildExceptionEvent read FBuidException write FBuidException;
property OnProgress: TZipProgressEvent read FOnProgress write FOnProgress;
property OnSaveExData: TZipSaveExDataEvent read FSaveExData write FSaveExData;
// Ñâîéñòâî äëÿ àâòîìàòè÷åñêîãî âûñòàâëåíèÿ êîäèðîâêè ó äîáàâëÿåìûõ ýëåìåíòîâ
// Íå âëèÿåò íà êîäèðîâêó êîìåíòàðèÿ ê àðõèâó (îí âñåãäà Ansi)
property UseUTF8String: Boolean read FUseUTF8String write FUseUTF8String;
end;
EZipWriterItem = class(Exception);
EZipWriter = class(Exception);
EZipWriterWrite = class(Exception);
implementation
{ TFWZipWriterItem }
//
// Ïðîöåäóðà èçìåíÿåò àòòðèáóòû ýëåìåíòà àðõèâà
// =============================================================================
procedure TFWZipWriterItem.ChangeAttributes(Value: TFileAttributeData);
begin
if not FOwner.BuildState then
FAttributes := Value;
end;
//
// Ïðîöåäóðà èçìåíÿåò áëîê äàííûõ îá ýëåìåíòå. Àâòîìàòè÷åñêè ÷èñòèòñÿ èìÿ ê ôàéëó.
// Äàííûå ïðè ñæàòèè áóäóò áðàòüñÿ èç ïîëÿ FData
// =============================================================================
procedure TFWZipWriterItem.ChangeDataStream(Value: TStream;
AOwnership: TStreamOwnership);
begin
if not FOwner.BuildState then
if Value.Size <> 0 then
begin
FreeAndNil(FData);
if AOwnership = soOwned then
FData := Value
else
begin
FData := TMemoryStream.Create;
FData.CopyFrom(Value, 0);
end;
FSize := FData.Size;
FFilePath := '';
end;
end;
//
// Ñòàíäàðòíûé êîíñòðóêòîð êëàññà
// =============================================================================
constructor TFWZipWriterItem.Create(Owner: TFWZipWriter;
const InitFilePath: string;
InitAttributes: TFileAttributeData; const InitFileName: string);
begin
inherited Create;
FData := nil;
FOwner := Owner;
FFilePath := InitFilePath;
FAttributes := InitAttributes;
FSize :=
FileSizeToInt64(FAttributes.nFileSizeLow, FAttributes.nFileSizeHigh);
FFileName := InitFileName;
UseUTF8String := Owner.UseUTF8String;
end;
//
// Ñòàíäàðòíûé äåñòðóêòîð êëàññà
// =============================================================================
destructor TFWZipWriterItem.Destroy;
begin
FData.Free;
inherited;
end;
//
// Ôóíêöèÿ ïðîâåðÿåò - ÿâëÿåòñÿ ëè ýëåìåíò ïàïêîé?
// =============================================================================
function TFWZipWriterItem.IsFolder: Boolean;
begin
Result := Attributes.dwFileAttributes and faDirectory <> 0;
if not Result then
Result := FileName[Length(FileName)] = ZIP_SLASH;
end;
//
// Ïðîöåäóðà èçìåíÿåò ôëàã äåñêðèïòîðà ýëåìåíòà
// =============================================================================
procedure TFWZipWriterItem.SetBool(const Value: Boolean);
begin
if not FOwner.BuildState then
FNeedDescriptor := Value;
end;
//
// Ïðîöåäóðà èçìåíÿåò ñòåïåíü ñæàòèÿ ýëåìåíòà
// =============================================================================
procedure TFWZipWriterItem.SetCmpLevel(const Value: TCompressionLevel);
begin
if not FOwner.BuildState then
FCmpLevel := Value;
end;
//
// Ïðîöåäóðà èçìåíÿåò ñòðîêîâûå ñâîéñòâà ýëåìåíòà.
// Ïðè èçìåíåíèè ïóòè ê ôàéëó, àâòîìàòè÷åñêè ðàññ÷èòûâàþòñÿ
// íîâûå àòòðèáóòû ôàéëà è î÷èøàåòñÿ ñòðèì FData çà íåíàäîáíîñòüþ
// =============================================================================
procedure TFWZipWriterItem.SetString(const Index: Integer;
const Value: string);
var
Attributes: TFileAttributeData;
begin
if not FOwner.BuildState then
case Index of
0: FComment := Value;
1:
begin
if FileExists(Value) then
begin
// Èçìåíÿåì òîëüêî â òîì ñëó÷àå åñëè îáúåêò äîñòóïåí
// è ìû ñìîãëè ñíÿòü åãî àòòðèáóòû
if GetFileAttributes(Value, Attributes) then
begin
FAttributes := Attributes;
FSize :=
FileSizeToInt64(Attributes.nFileSizeLow, Attributes.nFileSizeHigh);
FFilePath := Value;
FreeAndNil(FData);
end;
end;
end;
2:
// Rouse_ 20.12.2019
// Äàåì âîçìîæíîñòü ðàáîòû ñ äëèííûìè ïóòÿìè
// if Length(Value) >= MAX_PATH then
// raise EZipWriterItem.Create('Ñëèøêîì äëèííûé ïóòü.')
// else
FFileName := Value;
3: FPassword := Value;
end;
end;
{ TFWZipWriter }
//
// Ôóíêöèÿ äîáàâëÿåò î÷åðåäíîé ôàéë â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// FilePath - ïóòü ê ôàéëó
// FileName - íàèìåíîâàíèå ôàéëà â àðõèâå
// (âêëþ÷àÿ îòíîñèòåëüíûé ïóòü îò êîðíÿ àðõèâà)
// =============================================================================
function TFWZipWriter.AddFile(const FilePath: string; const FileName: string
): Integer;
var
Attributes: TFileAttributeData;
FullFilePath: string;
begin
Result := -1;
FullFilePath := PathCanonicalize(FilePath);
// Äîáàâëÿåì òîëüêî â òîì ñëó÷àå åñëè îáúåêò äîñòóïåí
// è ìû ñìîãëè ñíÿòü åãî àòòðèáóòû
if GetFileAttributes(FullFilePath, Attributes) then
Result := AddFile(FullFilePath, Attributes, FileName);
end;
//
// Ôóíêöèÿ äîáàâëÿåò ïóñòóþ ïàïêó â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// FolderRelativeName - íàèìåíîâàíèå ïàïêè â àðõèâå
// (âêëþ÷àÿ îòíîñèòåëüíûé ïóòü îò êîðíÿ àðõèâà)
// =============================================================================
function TFWZipWriter.AddEmptyFolder(const FolderRelativeName: string): Integer;
var
Attributes: TFileAttributeData;
begin
ZeroMemory(@Attributes, SizeOf(TFileAttributeData));
Attributes.dwFileAttributes := faDirectory;
Result := AddEmptyFolder(FolderRelativeName, Attributes);
end;
//
// Ôóíêöèÿ äîáàâëÿåò ïóñòóþ ïàïêó â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// FolderRelativeName - íàèìåíîâàíèå ïàïêè â àðõèâå
// (âêëþ÷àÿ îòíîñèòåëüíûé ïóòü îò êîðíÿ àðõèâà)
// Attributes - àòòðèáóòû ïàïêè
// =============================================================================
function TFWZipWriter.AddEmptyFolder(const FolderRelativeName: string;
Attributes: TFileAttributeData): Integer;
var
FolderPath: string;
Item: TFWZipWriterItem;
begin
Result := -1;
if FolderRelativeName = '' then Exit;
FolderPath := CheckFileNameSlashes(FolderRelativeName);
if FolderPath[Length(FolderPath)] <> ZIP_SLASH then
FolderPath := FolderPath + ZIP_SLASH;
Item := GetItemClass.Create(Self, FolderPath, Attributes, FolderPath);
Result := AddNewItem(Item);
end;
//
// Ôóíêöèÿ äîáàâëÿåò î÷åðåäíîé ôàéë â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// FilePath - ïóòü ê ôàéëó
// Attributes - àòòðèáóòû ôàéëà
// FileName - íàèìåíîâàíèå ôàéëà â àðõèâå
// (âêëþ÷àÿ îòíîñèòåëüíûé ïóòü îò êîðíÿ àðõèâà)
// =============================================================================
function TFWZipWriter.AddFile(const FilePath: string;
Attributes: TFileAttributeData; const FileName: string): Integer;
var
Item: TFWZipWriterItem;
InitFileName, FullFilePath: string;
begin
// Ïðîâåðêà ÷òî íàì ïåðåäàëè. ïàïêó èëè ôàéë?
Result := -1;
FullFilePath := PathCanonicalize(FilePath);
if not FileExists(FullFilePath) then Exit;
if FileName = '' then
InitFileName := ExtractFileName(ExcludeTrailingPathDelimiter(FullFilePath))
else
InitFileName := CheckFileNameSlashes(FileName);
Item := GetItemClass.Create(Self, FullFilePath, Attributes, InitFileName);
Item.CompressionLevel := FDefaultCompressionLevel;
Item.Password := FDefaultPassword;
// â ñëó÷àå íàëè÷èÿ äåñêðèïòîðà ìû ìîæåì
// ïðîèçâîäèòü ðàñ÷åò êîíòðîëüíîé ñóììû íà ëåòó, ò.ê. ïðè âêëþ÷åííîì
// ðåæèìå øèôðîâàíèÿ îíà íå ó÷àñòâóåò â ãåíåðàöèè çàãîëîâêà èíèöèàëèçàöèè
Item.NeedDescriptor := FDefaultDescryptorState;
Result := AddNewItem(Item);
end;
//
// Ôóíêöèÿ äîáàâëÿåò íàáîð ôàéëîâ â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// Value - ïóòè ê ôàéëàì
// Value.ValueFromIndex[I] ñîäåðæèò ïîëíûé ïóòü ê ôàéëó
// Value.Names[I] ñîäåðæèò èìÿ ôàéëà ñ êîòîðûì îí áóäåò
// ïîìåùåí â àðõèâ (íåîáÿçàòåëüíûé ïàðàìåòð)
// =============================================================================
function TFWZipWriter.AddFiles(Value: TStringList): Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to Value.Count - 1 do
if AddFile(Value.ValueFromIndex[I], Value.Names[I]) >= 0 then
Inc(Result);
end;
//
// Ôóíêöèÿ äîáàâëÿåò íàáîð ôàéëîâ è ïàïîê â ñïèñîê.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// Value - ïóòè ê ôàéëàì è ïàïêàì èç êîòîðûõ áóäóò äîáàâëÿòüñÿ äàííûå
// Value.ValueFromIndex[I] äëÿ ïàïêè ñîäåðæèò ïîëíûé ïóòü ê ïàïêå
// Value.Names[I] äëÿ ïàïêè ñîäåðæèò ïóòü îòíîñèòåëüíî êîðíÿ àðõèâà
// (íåîáÿçàòåëüíûé ïàðàìåòð)
// Value.ValueFromIndex[I] äëÿ ôàéëà ñîäåðæèò ïîëíûé ïóòü ê ôàéëó
// Value.Names[I] äëÿ ôàéëà ñîäåðæèò èìÿ ôàéëà ñ êîòîðûì îí áóäåò
// ïîìåùåí â àðõèâ (íåîáÿçàòåëüíûé ïàðàìåòð)
// SubFolders - äîáàâëÿòü äàííûå èç ïîäïàïîê èëè íåò.
// =============================================================================
function TFWZipWriter.AddFilesAndFolders(Value: TStringList;
SubFolders: Boolean): Integer;
var
I: Integer;
Path: string;
begin
Result := 0;
for I := 0 to Value.Count - 1 do
begin
Path := PathCanonicalize(Value.ValueFromIndex[I]);
if DirectoryExists(Path) then
Inc(Result, AddFolder(Value.Names[I], Path, '', SubFolders))
else
if AddFile(Path, Value.Names[I]) >= 0 then
Inc(Result);
end;
end;
//
// Ôóíêöèÿ äîáàâëÿåò ôàéëû èç óêàçàíîé ïàïêè
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// Path - ïóòü ê ïàïêå èç êîòîðîé áóäóò äîáàâëÿòüñÿ äàííûå
// SubFolders - äîáàâëÿòü äàííûå èç ïîäïàïîê èëè íåò.
// =============================================================================
function TFWZipWriter.AddFolder(const Path: string; SubFolders: Boolean): Integer;
begin
Result := AddFolder(ExtractFileName(
ExcludeTrailingPathDelimiter(PathCanonicalize(Path))),
Path, EmptyStr, SubFolders);
end;
//
// Ðàñøèðåííûé âàðèàíò AddFolder
// Ôóíêöèÿ äîáàâëÿåò ôàéëû èç óêàçàíîé ïàïêè
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// RelativePath - ïóòü ê ýëåìåíòó â àðõèâå îòíîñèòåëüíî êîðíÿ.
// Path - ïóòü ê ïàïêå èç êîòîðîé áóäóò äîáàâëÿòüñÿ äàííûå
// Mask - ìàñêà îòáîðà ôàéëîâ (÷åðåç òî÷êó ñ çàïÿòîé). Íàïðèìåð "*.txt;*.bin;12???6.dat"
// SubFolders - äîáàâëÿòü äàííûå èç ïîäïàïîê èëè íåò.
// =============================================================================
function TFWZipWriter.AddFolder(const RelativePath, Path, Mask: string;
SubFolders: Boolean): Integer;
const
AllDataMask = '*';
var
SR: TSearchRec;
TrailingPath, TrailingRelativePath, ResultMask: string;
Attributes: TFileAttributeData;
Masks: TStringList;
I: Integer;
Matched: Boolean;
begin
Result := 0;
// îáû÷íîå ðåêóðñèâíîå ñêàíèðîâàíèå ïàïêè
// åäèíñòâåííûé íþàíñ - ïàðàìåòð RelativePath
// â êîòîðîì ïåðåäàåòñÿ ïóòü ê ôàéëó èëè ïàïêå îòíîñèòåëüíî êîðíåâîé ïàïêè
if RelativePath = '' then
TrailingRelativePath := ''
else
TrailingRelativePath := IncludeTrailingPathDelimiter(RelativePath);
TrailingPath := IncludeTrailingPathDelimiter(PathCanonicalize(Path));
Masks := nil;
if Mask = EmptyStr then
ResultMask := AllDataMask
else
ResultMask := AnsiLowerCase(Mask);
if ResultMask <> AllDataMask then
begin
Masks := TStringList.Create;
Masks.Delimiter := ';';
Masks.DelimitedText := ResultMask;
end;
try
if FindFirst(TrailingPath + AllDataMask, faAnyFile, SR) = 0 then
try
repeat
if SR.Name = '..' then Continue;
{$IFDEF LINUX}
GetFileAttributes(TrailingPath + SR.Name, Attributes);
{$ELSE}
{$WARN SYMBOL_PLATFORM OFF}
Attributes.dwFileAttributes := SR.FindData.dwFileAttributes;
Attributes.ftCreationTime := SR.FindData.ftCreationTime;
Attributes.ftLastAccessTime := SR.FindData.ftLastAccessTime;
Attributes.ftLastWriteTime := SR.FindData.ftLastWriteTime;
Attributes.nFileSizeHigh := SR.FindData.nFileSizeHigh;
Attributes.nFileSizeLow := SR.FindData.nFileSizeLow;
{$WARN SYMBOL_PLATFORM ON}
{$ENDIF}
if SR.Name = '.' then
begin
// Rouse_ 14.02.2013
// Åñëè âêëþ÷åíî äîáàâëåíèå ïóñòûõ ïàïîê, òî äîáàâëÿåì ñíà÷àëà òðåáóåìóþ çàïèñü
if AlwaysAddEmptyFolder then
AddEmptyFolder(TrailingRelativePath, Attributes);
Continue;
end;
if SR.Attr and faDirectory <> 0 then
begin
if SubFolders then
Inc(Result, AddFolder(TrailingRelativePath + SR.Name,
TrailingPath + SR.Name, ResultMask, SubFolders));
end
else
begin
if Assigned(Masks) then
begin
Matched := False;
for I := 0 to Masks.Count - 1 do
if MatchesMask(SR.Name, Masks[I]) then
begin
Matched := True;
Break;
end;
end
else
Matched := True;
if not Matched then Continue;
if AddFile(TrailingPath + SR.Name, Attributes,
TrailingRelativePath + SR.Name) >= 0 then
Inc(Result);
end;
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
finally
Masks.Free;
end;
end;
//
// Ôóíêöèÿ äîáàâëÿåò â àðõèâ äàííûå èç ïåðåäàííîãî ñòðèìà.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå
// =============================================================================
function TFWZipWriter.AddStream(const FileName: string;
Value: TStream; AOwnerShip: TStreamOwnership): Integer;
var
Item: TFWZipWriterItem;
begin
Item := CreateItemFromStream(FileName, Value, AOwnerShip);
Result := AddNewItem(Item);
end;
//
// Âñïîìîãàòåëüíàÿ ôóíêöèÿ äëÿ äîñòóïà ê ëèñòó èç íàñëåäíèêîâ
// =============================================================================
function TFWZipWriter.AddNewItem(Value: TFWZipWriterItem): Integer;
begin
Result := FItems.Add(Value);
end;
//
// Ïðîöåäóðà ôîðìèðóåò àðõèâ è ñîõðàíÿåò åãî â óêàçàííûé ñòðèì.
// =============================================================================
function TFWZipWriter.BuildZip(Stream: TStream): TBuildZipResult;
var
I, TotalCount: Integer;
BeforeExceptPosition: Int64;
ExceptAction: TExceptionAction;
OldPathName, NewFilePath: string;
NewFileData: TMemoryStream;
DeletePackedFile: Boolean;
begin
FVersionToExtract := 0;
FTotalProgress := 0;
FBuildState := True;
try
Result := brFailed;
if Count > 0 then
begin
// Ñîãëàñòíî ñïåöèôèêàöèè â ñàìûé ïåðâûé òîì ìíîãîòîìíîãî àðõèâà
// ïåðâîé äîáàâëÿåòñÿ SPAN_DESCRIPTOR_SIGNATURE
// ñì. zip_format.txt 8.5.4
if IsMultiPartZip(Stream) then
begin
I := SPAN_DESCRIPTOR_SIGNATURE;
Stream.WriteBuffer(I, 4);
end;
// Âûñòàâëÿåì ðàçìåðû CentralDirectory
SetLength(FCD, Count);
// Ðàññ÷èòûâàåì îáùèé ðàçìåð ýëåìåíòîâ äëÿ îòîáðàæåíèÿ ïðîãðåññà
FTotalSizeCount := 0;
FTotalProcessedCount := 0;
for I := 0 to Count - 1 do
begin
Inc(FTotalSizeCount, Item[I].Size);
// Rouse_ 12.05.2020
// Íå çàáûâàåì ñêèíóòü ôëàã îøèáêè ñ ïðåäûäóùåé ñáîðêè àðõèâà
FCD[I].ExceptOnWrite := False;
end;
// Ñæèìàåì âñå ôàéëû àðõèâà è ïîìåùàåì èõ â ôèíàëüíûé ñòðèì
// ïðè ýòîì ðåçåðâèðóåòñÿ ìåñòî ïîä LocalHeader è DataDescryptor
// ò.ê. ðàçìåð çàïàêîâàííîãî ôàéëà áóäåò èçâåñòåí òîëüêî ïîñëå ýòàïà
// àðõèâàöèè è íå ïîíÿòíî êóäà ïîìåùàòü çíà÷åíèå:
// â LocalHeader, DataDescryptor èëè â ZIP64 áëîê äàííûõ â CentralDirectory
FExceptionCount := 0;
BeforeExceptPosition := 0;
TotalCount := 0;
Result := brDone;
I := 0;
DeletePackedFile := False;
OldPathName := '';
while I < Count do
begin
try
BeforeExceptPosition := Stream.Position;
SaveItemToStream(Stream, I);
Inc(TotalCount);
Inc(I);
// â ñëó÷àå åñëè ýòî ïîâòîðíàÿ ïîïûòêà ñæàòèÿ è áûë ïðèìåíåí
// àòòðèáóò acUseNewFilePathAndDel, òî íåîáõîäèìî óäàëèòü ôàéë.
if DeletePackedFile then
begin
DeletePackedFile := False;
NewFilePath := Item[I - 1].FilePath;
SetNormalFileAttributes(NewFilePath);
DeleteFile(NewFilePath);
end;
// åñëè ïóòü ê ôàéëó áûë âðåìåííî èçìåíåí èç-çà ïðèìåíåíèÿ àòòðèáóòîâ
// acUseNewFilePath èëè acUseNewFilePathAndDel,
// íåîáõîäèìî âîññòàíîâèòü ñòàðîå çíà÷åíèå
if OldPathName <> '' then
begin
FBuildState := False;
try
Item[I - 1].FilePath := OldPathName;
finally
OldPathName := '';
FBuildState := True;
end;
end;
except
// Åñëè ïîëüçîâàòåëü îòìåíèë ñîçäàíèå àðõèâà, âûõîäèì èç öèêëà
on E: EAbort do
begin
Result := brAborted;
Exit;
end;
on E: Exception do
begin
// âîçâðàùàåì ïîçèöèþ â ñòðèìå íà ñòàðîå ìåñòî
Stream.Position := BeforeExceptPosition;
// åñëè ýëåìåíò èñïîëüçîâàë êàñòîìíûå äàííûå,
// òî ñíèìàåì ýòîò âàðèàíò çàïîëíåíèÿ è ïóñòü èçâíå ðåøàþò
// ÷òî äåëàòü â ýòîì ñëó÷àå
Item[I].UseExternalData := False;
// çàïðàøèâàåì ïîëüçîâàòåëÿ, ÷òî äåëàòü ñ èñêëþ÷åíèåì?
ExceptAction := eaSkip;
NewFilePath := '';
NewFileData := TMemoryStream.Create;
try
if Assigned(FBuidException) then
FBuidException(Self, E, I, ExceptAction,
NewFilePath, NewFileData);
// îáðàáàòûâàåì âûáîð ïîëüòçîâàòåëÿ
case ExceptAction of
// ïîâòîðèòü ïîïûòêó
eaRetry:
Continue;
// ïðîïóñòèòü ýëåìåíò
eaSkip:
begin
// òî ïîìå÷àåì ýëåìåíò öåíòðàëüíîé äèðåêòîðèè.
// Îí íå áóäåò îáðàáàòûâàòüñÿ ïðè ñîõðàíåíèèè.
FCD[I].ExceptOnWrite := True;
// Òàêæå óâåëè÷èì ÷èñëî èñêëþ÷åíèé, äëÿ ïðàâèëüíîé çàïèñè
// êîëè÷åñòâà ýëåìåíòîâ àðõèâà â êîðíåâîé äèðåêòîðèè
Inc(FExceptionCount);
Inc(I);
Result := brPartialBuild;
end;
// îñòàíîâèòü ñîçäàíèå àðõèâà
eaAbort:
begin
Result := brAborted;
Exit;
end;
// èñïîëüçîâàòü äàííûå èç äðóãîãî ôàéëà
eaUseNewFilePath, eaUseNewFilePathAndDel:
begin
// çàïîìèíàåì òåêóùèé ïóòü ê ôàéëó,
// äëÿ ïîñëåäóþùåãî âîññòàíîâëåíèÿ
OldPathName := Item[I].FilePath;
FBuildState := False;
try
Item[I].FilePath := NewFilePath;
finally
FBuildState := True;
end;
// âûñòàâëÿåì ôëàã, ÷òî ïðè çàâåðøåíè ñæàòèÿ,
// ôàéë ñëóäåò óäàëèòü
DeletePackedFile := ExceptAction = eaUseNewFilePathAndDel;
Continue;
end;
// èñïîëüçîâàòü äàííûå èç ñòðèìà
eaUseNewFileData:
begin
FBuildState := False;
try
Item[I].ChangeDataStream(NewFileData);
finally
FBuildState := True;
end;
Continue;
end;
end;
finally
NewFileData.Free;
end;
end;
end;
end;
// Åñëè â àðõèâ íå äîáàâëåí íè îäèí èç ýëåìåíòîâ,
// òî âîçâðàùàåì ðàçìåð ñòðèìà íà ïðåæíåå çíà÷åíèå è âûõîäèì ñ îøèáêîé
if TotalCount = 0 then
begin
Stream.Size := Stream.Position;
Result := brFailed;
Exit;
end;
// Òåïåðü ðàçìåðû ñæàòûõ ôàéëîâ èçâåñòíû,
// îáíîâëÿåì LocalHeader è DataDescryptor
UpdateLocalHeaders(Stream);
// Çàïèñûâàåì CentralDirectory
SaveCentralDirectory(Stream);
// Ïèøåì ñòðóêòóðó EndOfCentralDirectory ïðè ýòîì ïðè íåîáõîäèìîñòè
// ôîðìèðóþòñÿ è ïèøóòñÿ òàêæå ñòðóêòóðû Zip64EOFCentralDirectoryRecord
// è Zip64EOFCentralDirectoryLocator
SaveEndOfCentralDirectory(Stream);
end;
finally
FBuildState := False;
end;
end;
//
// Ïðîöåäóðà ôîðìèðóåò àðõèâ è ñîõðàíÿåò åãî â óêàçàííûé ôàéë.
// =============================================================================
function TFWZipWriter.BuildZip(ZipFilePath: string): TBuildZipResult;
var
ZIP: TFileStream;
begin
Result := brFailed;
if Count = 0 then Exit;
ZipFilePath := PathCanonicalize(ZipFilePath);
ForceDirectoriesEx(ExtractFilePath(ZipFilePath));
ZIP := TFileStream.Create(ZipFilepath, fmCreate);
try
Result := BuildZip(ZIP);
FinallyFileBuffers(ZIP.Handle);
finally
ZIP.Free;
end;
if Result in [brFailed, brAborted] then
DeleteFile(ZipFilePath);
end;
//
// Ôóíêöèÿ ïðîâîäèò ïðîâåðêó ïðàâèëüíîñòè íàèìåíîâàíèÿ ôàéëà â àðõèâå
// =============================================================================
function TFWZipWriter.CheckFileNameSlashes(const Value: string): string;
begin
{
The name of the file, with optional relative path.
The path stored should not contain a drive or
device letter, or a leading slash. All slashes
should be forward slashes '/' as opposed to
backwards slashes '\' for compatibility with Amiga
and Unix file systems etc.
}
Result := StringReplace(Value, '\', ZIP_SLASH, [rfReplaceAll]);
end;
//
// Î÷èùàåì âñå äîáàâëåííûå â àðõèâ ýëåìåíòû
// =============================================================================
procedure TFWZipWriter.Clear;
begin
FItems.Clear;
end;
//
// Ïðîöåäóðà ñæèìåò äàííûå
// =============================================================================
procedure TFWZipWriter.CompressItem(CurrentItem: TFWZipWriterItem;
Index: Integer; StreamSizeBeforeCompress: Int64; Stream: TStream);
function CopyWithProgress(Src, Dst: TStream;
Cryptor: TFWZipCryptor): Cardinal;
var
Buff: Pointer;
Size, TotalSize: Integer;
begin
Result := $FFFFFFFF;
GetMem(Buff, MAXWORD);
try
Src.Position := 0;
FCompressedStream := Src;
DoProgress(psInitialization);
try
TotalSize := 0;
while True do
begin
Size := Src.Read(Buff^, MAXWORD);
Result := CRC32Calc(Result, Buff, Size);
if Size <> 0 then
begin
Inc(TotalSize, Size);
if Cryptor <> nil then
Cryptor.EncryptBuffer(Buff, Size);
Dst.WriteBuffer(Buff^, Size);
DoProgress(psInProgress);
end
else
Break;
end;
if TotalSize <> Src.Size then
raise EZipWriterWrite.CreateFmt(
'Îøèáêà çàïèñè äàííûõ ýëåìåíòà ¹%d "%s".', [Index, Item[Index].FileName]);
except
DoProgress(psException);
raise;
end;
DoProgress(psFinalization);
finally
FreeMem(Buff);
end;
Result := Result xor $FFFFFFFF;
end;
var
F: TFileStream;
Compressor: TZCompressionStream;
Cryptor: TFWZipCryptor;
ZipItemStream: TFWZipItemStream;
CRC32Stream: TFWZipCRC32Stream;
EncryptedHeaderStream: TMemoryStream;
begin
Cryptor := nil;
try
EncryptedHeaderStream := TMemoryStream.Create;
try
if CurrentItem.Password <> '' then
begin
Cryptor := TFWZipCryptor.Create(AnsiString(CurrentItem.Password));
Cryptor.GenerateEncryptionHeader(EncryptedHeaderStream,
CurrentItem.NeedDescriptor,
FCD[Index].Header.Crc32,
FCD[Index].Header.LastModFileTimeTime +
FCD[Index].Header.LastModFileTimeDate shl 16);
// ðåçåðâèðóåì ìåñòî ïîä EncryptedHeaderStream
Stream.Size := StreamSizeBeforeCompress + EncryptedHeaderSize;
Stream.Position := Stream.Size;
end;
// ïèøåì ñàì ñæàòûé ôàéë
case FCD[Index].Header.CompressionMethod of
Z_NO_COMPRESSION:
begin
if CurrentItem.Data <> nil then
FCD[Index].Header.Crc32 :=
CopyWithProgress(CurrentItem.Data, Stream, Cryptor)
else
begin
try
F := TFileStream.Create(CurrentItem.FilePath,
fmOpenRead or fmShareDenyWrite);
try
FCD[Index].Header.Crc32 :=
CopyWithProgress(F, Stream, Cryptor)
finally
F.Free;
end;
except
on E: Exception do
raise EZipWriterWrite.CreateFmt(string(
'Îøèáêà äîñòóïà ê äàííûì ýëåìåíòà ¹%d "%s".' +
sLineBreak) + ExceptionMessage(E),
[Index, CurrentItem.FileName]);
end;
end;
// Ïîëó÷àåì ðàçìåð ñæàòûõ äàííûõ
//  ñëó÷àå åñëè èñïîëüçîâàëîñü øèôðîâàíèå â ðàçìåðå ñðóçó áóäåò
// ó÷òåí 12-òè áàéòíûé çàãîëîâîê èíèöèàëèçàöèè êëþ÷à ðàñøèôðîâêè
FCD[Index].CompressedSize := Stream.Size - StreamSizeBeforeCompress;
end;
Z_DEFLATED:
begin
{$IFDEF USE_AUTOGENERATED_ZLIB_HEADER}
// ïîçèöèþ ñäâèãàåì íà äâà áàéòà âëåâî,
// òàêèì îáðàçîì ìû çàòðåì íåíóæíûé íàì çàãîëîâîê ZLib
Stream.Position := Stream.Position - 2;
{$ENDIF}
if CurrentItem.Data <> nil then
begin
// ñîõðàíÿåì ññûëêó íà ñòðèì ñ äàííûìè äëÿ ðàññ÷åòà ïðîãðåññà
FCompressedStream := CurrentItem.Data;
ZipItemStream := TFWZipItemStream.Create(Stream, Cryptor, nil,
0, CurrentItem.Size);
try
Compressor := TZCompressionStream.Create(
ZipItemStream, CurrentItem.CompressionLevel,
defaultWindowBits, 8, zsDefault);
try
Compressor.OnProgress := CompressorOnProcess;
DoProgress(psInitialization);
try
CRC32Stream := TFWZipCRC32Stream.Create(CurrentItem.Data);
try
Compressor.CopyFrom(CRC32Stream, 0);
FCD[Index].Header.Crc32 := CRC32Stream.CRC32;
finally
CRC32Stream.Free;
end;
except
DoProgress(psException);
raise;
end;
DoProgress(psFinalization);
finally
Compressor.Free;
end;
finally
ZipItemStream.Free;
end;
end
else
begin
// TFWZipItemStream âûñòóïàåò êàê ïîñðåäíèê ìåæäó ðåçóëüòèðóþùèì
// ñòðèìîì è TCompressionStream.
// Åãî çàäà÷à çàøèôðîâàòü âñå ïðîõîäÿùèå ÷åðåç íåøãî äàííûå
ZipItemStream := TFWZipItemStream.Create(Stream, Cryptor, nil,
0, CurrentItem.Size);
try
Compressor := TZCompressionStream.Create(
ZipItemStream, CurrentItem.CompressionLevel,
defaultWindowBits, 8, zsDefault);
try
try
F := TFileStream.Create(CurrentItem.FilePath,
fmOpenRead or fmShareDenyWrite);
try
// ñîõðàíÿåì ññûëêó íà ñòðèì ñ äàííûìè äëÿ ðàññ÷åòà ïðîãðåññà
FCompressedStream := F;
F.Position := 0;
Compressor.OnProgress := CompressorOnProcess;
// TFWZipCRC32Stream âûñòóïàåò êàê ïîñðåäíèê ìåæäó
// íåðàñïàêîâàííûìè äàííûìè è TCompressionStream,
// â êîòîðîì ïðîèñõîäèò ñæàòèå äàííûõ.
// Åãî çàäà÷à îòñëåäèòü âñå ïåðåäàííûå ÷åðåç íåãî
// áëîêè äàííûõ è ðàññ÷èòàòü èõ êîíòðîëüíóþ ñóììó
// äî òîãî êàê îíè áóäóò ñæàòû
DoProgress(psInitialization);
try
CRC32Stream := TFWZipCRC32Stream.Create(F);
try
Compressor.CopyFrom(CRC32Stream, 0);
FCD[Index].Header.Crc32 := CRC32Stream.CRC32;
finally
CRC32Stream.Free;
end;
except
DoProgress(psException);
raise;
end;
DoProgress(psFinalization);
finally
F.Free;
end;
except
on E: Exception do
raise EZipWriterWrite.CreateFmt(string(
'Îøèáêà äîñòóïà ê äàííûì ýëåìåíòà ¹%d "%s".' +
sLineBreak) + ExceptionMessage(E),
[Index, CurrentItem.FileName]);
end;
finally
Compressor.Free;
end;
finally
ZipItemStream.Free;
end;
end;
{$IFDEF USE_AUTOGENERATED_ZLIB_HEADER}
// Rouse_ 14.02.2013
// Íå çíàþ ïî÷åìó, íî îïûòíûì ïóòåì óñòàíîâëåíî,
// ÷òî ðàçìåð çàïàêîâàíûõ äàííûõ äîëæåí áûòü ìåíüøå íà 4 áàéòà
// ïðè èñïîëüçîâàíèè àâòîãåíåðèðóåìîãî çàãîëîâêà.
// Ýòîò ìîìåíò ó÷èòûâàåòñÿ â ICSharpCode.SharpZipLibrary
// Ðàñïàêîâêà â ëþáîì ñëó÷àå ïðîèñõîäèò íîðìàëüíî
if TrimPackedStreamSize then
Stream.Size := Stream.Size - 4;
{$ENDIF}
// Ïîëó÷àåì ðàçìåð ñæàòûõ äàííûõ
//  ñëó÷àå åñëè èñïîëüçîâàëîñü øèôðîâàíèå â ðàçìåðå ñðóçó áóäåò
// ó÷òåí 12-òè áàéòíûé çàãîëîâîê èíèöèàëèçàöèè êëþ÷à ðàñøèôðîâêè
FCD[Index].CompressedSize := Stream.Size - StreamSizeBeforeCompress;
end;
end;
// åñëè ôàéë çàøèôðîâàí,
// çàïèñûâàåì çàãîëîâîê èíèöèàëèçàöèè êëþ÷à ðàñøèôðîâêè
if EncryptedHeaderStream.Size > 0 then
begin
Stream.Position := StreamSizeBeforeCompress;
Stream.CopyFrom(EncryptedHeaderStream, 0);
end;
finally
EncryptedHeaderStream.Free;
end;
finally
Cryptor.Free;
end;
end;
//
// Ïðîöåäóðà âûçûâàåò ñîáûòèå OnProcess
// =============================================================================
procedure TFWZipWriter.CompressorOnProcess(Sender: TObject);
begin
DoProgress(psInProgress);
end;
//
// Ôóíêöèÿ âîçâðàùàåò êîëè÷åñòâî äîáàâëåííûõ ýëåìåíòîâ àðõèâà
// =============================================================================
function TFWZipWriter.Count: Integer;
begin
Result := FItems.Count;
end;
//
// Ñòàíäàðòíûé êîíñòðóêòîð êëàññà
// =============================================================================
constructor TFWZipWriter.Create;
begin
Create(False, clDefault, '');
end;
//
// Ðàñøèðåííûé êîíñòðóêòîð êëàññà,
// â êîòîðîì ìîæíî óêàçàòü ñòåïåíü ñæàòèÿ,
// èñïîëüçóåìóþ äëÿ âñåõ ýëåìåíòîâ ïî óìîë÷àíèþ.
// =============================================================================
constructor TFWZipWriter.Create(CompressionLevel: TCompressionLevel);
begin
Create(False, CompressionLevel, '');
end;
//
// Ðàñøèðåííûé êîíñòðóêòîð êëàññà,
// â êîòîðîì ìîæíî èçìåíèòü íàñòðîéêè ýëåìåíòîâ áóäóþùåãî àðõèâà ïî óìîë÷íèþ.
// =============================================================================
constructor TFWZipWriter.Create(UseDescryptors: Boolean;
CompressionLevel: TCompressionLevel; const DefaultPassword: string);
begin
inherited Create;
FDefaultDescryptorState := UseDescryptors;
FDefaultCompressionLevel := CompressionLevel;
FDefaultPassword := DefaultPassword;
FItems := TObjectList.Create;
FItems.Capacity := 100000;
FTrimPackedStreamSize := True;
end;
//
// Ôóíêöèÿ ñîçäàåò íî íå äîáàâëÿåò â ìàñèâ çàïèñåé íîâûé ýëåìåíò
// =============================================================================
function TFWZipWriter.CreateItemFromStream(const FileName: string;
Value: TStream; AOwnerShip: TStreamOwnership): TFWZipWriterItem;
var
Size: Int64;
InitFileName: string;
Attributes: TFileAttributeData;
begin
// ïðîâåðêà íà äóáëè
InitFileName := CheckFileNameSlashes(FileName);
Size := Value.Size;
ZeroMemory(@Attributes, SizeOf(TFileAttributeData));
Attributes.ftCreationTime := GetCurrentFileTime;
Attributes.ftLastAccessTime := Attributes.ftCreationTime;
Attributes.ftLastWriteTime := Attributes.ftCreationTime;
Attributes.nFileSizeLow := Size and MAXDWORD;
Attributes.nFileSizeHigh := Size shr 32;
Result := GetItemClass.Create(Self, '', Attributes, InitFileName);
Result.CompressionLevel := FDefaultCompressionLevel;
Result.Password := FDefaultPassword;
// â ñëó÷àå íàëè÷èÿ äåñêðèïòîðà ìû ìîæåì
// ïðîèçâîäèòü ðàñ÷åò êîíòðîëüíîé ñóììû íà ëåòó, ò.ê. ïðè âêëþ÷åííîì
// ðåæèìå øèôðîâàíèÿ îíà íå ó÷àñòâóåò â ãåíåðàöèè çàãîëîâêà èíèöèàëèçàöèè
Result.NeedDescriptor := FDefaultDescryptorState;
Result.ChangeDataStream(Value, AOwnerShip);
end;
//
// Ïðîöåäóðà óäàëÿåò íåíóæíûé ýëåìåíò àðõèâà
// =============================================================================
procedure TFWZipWriter.DeleteItem(Index: Integer);
begin
FItems.Delete(Index);
end;
//
// Ñòàíäàðòíûé äåñòðóêòîð êëàññà
// =============================================================================
destructor TFWZipWriter.Destroy;
begin
FItems.Free;
inherited;
end;
//
// Âûçîâ âíåøíåãî ñîáûòèÿ î ïðîãðåññå ñæàòèÿ
// =============================================================================
procedure TFWZipWriter.DoProgress(ProgressState: TProgressState);
var
CurrentProgress: Byte;
Cancel: Boolean;
begin
if Assigned(FOnProgress) then
begin
case ProgressState of
psInProgress:
begin
if FCompressedStream.Size = 0 then
CurrentProgress := 100
else
CurrentProgress :=
Round(FCompressedStream.Position / (FCompressedStream.Size / 100));
if FTotalSizeCount = 0 then
FTotalProgress := 100
else
FTotalProgress :=
Round((FTotalProcessedCount + FCompressedStream.Position) /
(FTotalSizeCount / 100));
end;
psFinalization, psEnd: CurrentProgress := 100;
else
CurrentProgress := 0;
end;
Cancel := False;
FOnProgress(Self, Item[FProcessedItemIndex].FileName,
CurrentProgress, FTotalProgress, Cancel, ProgressState);
if Cancel then Abort;
end;
end;
//
// Ðàññ÷èòûâàåì äëèíó ñòðîêè ñ ó÷åòîì UTF8
// =============================================================================
function TFWZipWriter.StringLength(const Value: string; AUseUTF8String: Boolean): Integer;
begin
if AUseUTF8String then
Result := Length(UTF8Encode(Value))
else
Result := Length(ConvertToOemString(AnsiString(Value)));
end;
//
// Ôóíêöèÿ âîçâðàùàåò òåêóùåå âðåìÿ â ôîðìàòå TFileTime
// =============================================================================
function TFWZipWriter.GetCurrentFileTime: TFileTime;
begin
Result := DateTimeToFileTime(Now);
end;
//
// Îáðàáîò÷èê ñâîéñòâà Items
// =============================================================================
function TFWZipWriter.GetItem(Index: Integer): TFWZipWriterItem;
begin
Result := TFWZipWriterItem(FItems[Index]);
end;
//
// Çàïðàøèâàåì äàííûå î ðàñøèðåííûõ áëîêàõ äàííûõ äëÿ êàæäîé çàïèñè
// =============================================================================
procedure TFWZipWriter.FillExData(Stream: TStream; Index: Integer);
var
ExDataStream: TMemoryStream;
EmptyExData: Boolean;
UserExDataBlockCount, {%H-}ExDataSize: Integer;
ExDataHeaderTag: Word;
begin
if Assigned(FSaveExData) then
begin
ExDataStream := TMemoryStream.Create;
try
EmptyExData := False;
UserExDataBlockCount := 0;
while not EmptyExData do
begin
ExDataHeaderTag := 0;
ExDataStream.Clear;
FSaveExData(Self, Index, UserExDataBlockCount,
ExDataHeaderTag, ExDataStream);
Inc(UserExDataBlockCount);
EmptyExData := ExDataStream.Size = 0;
if not EmptyExData then
begin
// Rouse_ 18.05.2020
// Ôèêñ îøèáêè. Îáùèé ðàçìåð áëîêà EXDATA îãðàíè÷åí MAXWORD
// À íå êàæäàÿ çàïèñü â íåì!
if Stream.Size + ExDataStream.Size + 4 > MAXWORD then
raise EZipWriter.Create(
'Ðàçìåð áëîêà ðàñøèðåííûõ äàííûõ' +
' íå ìîæåò ïðåâûøàòü 65535 áàéò.')
else
ExDataSize := ExDataStream.Size;
if ExDataHeaderTag in
[0, SUPPORTED_EXDATA_ZIP64, SUPPORTED_EXDATA_NTFSTIME] then
raise EZipWriter.Create(
'Íåëüçÿ èñïîëüçîâàòü çàðåçåðâèðîâàííûå òýãè' +
' áëîêà ðàñøèðåííûõ äàííûõ.');
Stream.WriteBuffer(ExDataHeaderTag, 2);
Stream.WriteBuffer(ExDataSize, 2);
Stream.CopyFrom(ExDataStream, 0);
end;
end;
finally
ExDataStream.Free;
end;
end;
end;
//
// Èíèöèàëèçèðóåì CentralDirectoryFileHeader ýëåìåíòà
// =============================================================================
procedure TFWZipWriter.FillItemCDFHeader(CurrentItem: TFWZipWriterItem;
var Value: TCentralDirectoryFileHeaderEx);
var
FileDate: Cardinal;
Buff: array of Byte;
begin
Value.Header.CentralFileHeaderSignature := CENTRAL_FILE_HEADER_SIGNATURE;
Value.Header.VersionMadeBy := CurrentVersionMadeBy;
Value.Header.VersionNeededToExtract := 0; // Ðàññ÷èòûâàåòñÿ ïîçäíåå
Value.Header.GeneralPurposeBitFlag := 0;
if CurrentItem.Password <> '' then
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_CRYPTED;
{%H-}case CurrentItem.CompressionLevel of
clNone:; // äàííûé ðåæèì êîìïðåññèè íå ïîääåðæèâàåòñÿ, ñðàçó ìåíÿåì íà Store
clFastest:
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_COMPRESS_SUPERFAST;
clDefault:
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_COMPRESS_NORMAL;
clMax:
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_COMPRESS_MAXIMUM;
end;
if CurrentItem.NeedDescriptor then
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_DESCRIPTOR;
if CurrentItem.CompressionLevel = clNone then
Value.Header.CompressionMethod := Z_NO_COMPRESSION
else
Value.Header.CompressionMethod := Z_DEFLATED;
if not CurrentItem.IsFolder then
if not CurrentItem.NeedDescriptor then
if CurrentItem.Password <> '' then
begin
// â ñëó÷àå åñëè äåñêðèïòîðû îòêëþ÷åíû è âêëþ÷åíî øèôðîâàíèå ýëåìåíòà
// òî íåîáõîäèìî ðàññ÷èòàòü åãî êîíòðîëüíóþ ñóììó ïåðåä
// ãåíåðàöèåé çàãîëîâêà èíèöèàëèçàöèè êëþ÷à øèôðîâàíèÿ
if CurrentItem.Data = nil then
Value.Header.Crc32 := FileCRC32(CurrentItem.FilePath)
else
begin
CurrentItem.Data.Position := 0;
SetLength(Buff{%H-}, CurrentItem.Data.Size);
CurrentItem.Data.Read(Buff[0], CurrentItem.Data.Size);
Value.Header.Crc32 :=
CRC32Calc(@Buff[0], CurrentItem.Data.Size);
end;
end;
Value.UncompressedSize := CurrentItem.Size;
FileDate := FileTimeToLocalFileDate(CurrentItem.Attributes.ftLastWriteTime);
Value.Header.LastModFileTimeTime := FileDate and $FFFF;
Value.Header.LastModFileTimeDate := FileDate shr 16;
Value.Filename := CurrentItem.FileName;
Value.Header.FilenameLength :=
StringLength(CurrentItem.Filename, CurrentItem.UseUTF8String);
Value.Header.ExtraFieldLength := 0;
Value.FileComment := CurrentItem.Comment;
Value.Header.FileCommentLength :=
StringLength(CurrentItem.Comment, CurrentItem.UseUTF8String);
Value.Header.InternalFileAttributes := 0;
Value.Header.ExternalFileAttributes :=
CurrentItem.Attributes.dwFileAttributes;
Value.Attributes := CurrentItem.Attributes;
if CurrentItem.UseUTF8String then
Value.Header.GeneralPurposeBitFlag :=
Value.Header.GeneralPurposeBitFlag or PBF_UTF8;
end;
function TFWZipWriter.Find(const Value: string; out AItem: TFWZipWriterItem;
IgnoreCase: Boolean): Boolean;
var
I: Integer;
AItemText: string;
begin
Result := False;
AItem := nil;
for I := 0 to Count - 1 do
begin
AItemText := Item[I].FileName;
if IgnoreCase then
Result := AnsiSameText(Value, AItemText)
else
Result := AnsiSameStr(Value, AItemText);
if Result then
begin
AItem := Item[I];
Break;
end;
end;
end;
//
// Äîáàâëÿåì âîçìîæíîñòü ñîçäàâàòü íàñëåäíèêîâ îò áàçîâîãî êëàññà
// =============================================================================
function TFWZipWriter.GetItemClass: TFWZipWriterItemClass;
begin
Result := TFWZipWriterItem;
end;
//
// Ôóíêöèÿ ðàññ÷èòûâàåò ìèíèìàëüíóþ âåðñèþ äëÿ èçâëå÷åíèÿ
// óêàçàííîãî ýëåìåíòà àðõèâà
// =============================================================================
function TFWZipWriter.GetVersionToExtract(Index: Integer): Word;
begin
{
Current minimum feature versions are as defined below:
1.0 - Default value
1.1 - File is a volume label
2.0 - File is a folder (directory)
2.0 - File is compressed using Deflate compression
2.0 - File is encrypted using traditional PKWARE encryption
2.1 - File is compressed using Deflate64(tm)
2.5 - File is compressed using PKWARE DCL Implode
2.7 - File is a patch data set
4.5 - File uses ZIP64 format extensions
4.6 - File is compressed using BZIP2 compression*
5.0 - File is encrypted using DES
5.0 - File is encrypted using 3DES
5.0 - File is encrypted using original RC2 encryption
5.0 - File is encrypted using RC4 encryption
5.1 - File is encrypted using AES encryption
5.1 - File is encrypted using corrected RC2 encryption**
5.2 - File is encrypted using corrected RC2-64 encryption**
6.1 - File is encrypted using non-OAEP key wrapping***
6.2 - Central directory encryption
}
// TFWZIPWriter ïîääåðæèâàåò ñëåäóþùèå ðàñøèðåíèÿ ñòàíäàðòà:
// 1. èñïîëüçîâàíèå äèðåêòîðèé (âåðñèÿ äëÿ èçâëå÷åíèÿ - 2.0)
// 2. èñïîëüçîâàíèå ZIP64 ðàñøèðåíèÿ (âåðñèÿ äëÿ èçâëå÷åíèÿ - 4.5)
// Äëÿ îïðåäåëåíèÿ, íóæíî ëè íàì èñïîëüçîâàòü ZIP64 íåîáõîäèìî ïðîâåðèòü
// ñëåäóþùèå ïàðàìåòðû:
// ðàçìåð êàæäîãî ýëåìåíòà àðõèâà ñæàòîãî è íå ñæàòîãî,
// îôôñåò íà íà÷àëî áëîêà äàííûõ äëÿ êàæäîãî ýëåìåíòà
// åñëè ëþáîå èç ýòèõ çíà÷åíèé âûõîäèò çà çíà÷åíèå MAXDWORD,
// èëè êîëè÷åñòâî ýëåìåíòîâ àðõèâà âûõîäèò çà çíà÷åíèå MAXWORD,
// íàì íåîáõîäèìî ïðèìåíÿòü ZIP64
Result := 20;
if (FCD[Index].UncompressedSize >= MAXDWORD) or
(FCD[Index].CompressedSize >= MAXDWORD) or
(FCD[Index].RelativeOffsetOfLocalHeader >= MAXDWORD) or
(FCD[Index].DiskNumberStart >= MAXWORD) then
Result := 45;
end;
//
// Ôóíêöèÿ äîáàâëÿåò â àðõèâ äàííûå èç ïåðåäàííîãî ñòðèìà.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå
// =============================================================================
function TFWZipWriter.InsertStream(const FileName: string; Index: Integer;
Value: TStream; AOwnerShip: TStreamOwnership): Integer;
var
Item: TFWZipWriterItem;
begin
Item := CreateItemFromStream(FileName, Value, AOwnerShip);
FItems.Insert(Index, Item);
Result := Index;
end;
//
// Ôóíêöèÿ äëÿ ïåðåêëþ÷åíèÿ ëîãèêè ðàáîòû ñ ìíîãîòîìíûìè ìàñèâàìè
// =============================================================================
function TFWZipWriter.IsMultiPartZip(AStream: TStream): Boolean;
begin
Result := AStream is TFWAbstractMultiStream;
end;
//
// Ïðîöåäóðà ïðîâîäèò ñîõðàíåíèå ñåêöèè CentralDirectory
// =============================================================================
procedure TFWZipWriter.SaveCentralDirectory(Stream: TStream);
var
I, DiskNumber: Integer;
RealtiveOffset, VolumeSize: Int64;
{%H-}ExDataHeader: TExDataHeaderAndSize;
ExDataNTFS: TExDataNTFS;
ZIP64Data: TMemoryStream;
TotalExDataStream: TMemoryStream;
FirstDisk: Boolean;
begin
FirstDisk := True;
FcdfhRecordOnDisk := 0;
FSizeOfCentralDir := FcdfhOffset;
if IsMultiPartZip(Stream) then
TFWAbstractMultiStream(Stream).GetRelativeInfo(FcdfhDiskNumber, FcdfhOffset)
else
FcdfhDiskNumber := 0;
for I := 0 to Count - 1 do
begin
// ïðîïóñêàåì ýëåìåíòû ïðè çàïèñè êîòîðûõ ïðîèçîøëî èñêëþ÷åíèå
if FCD[I].ExceptOnWrite then Continue;
// ïåðåä çàïèñüþ êàæäîãî ýëåìåíòà CentralDirectory
// íåîáõîäèìî ïîäãîòîâèòü áóôôåðû ñ ðàñøèðåííûìè äàííûìè
// è óêàçàòü èõ ðàñìåð
ZIP64Data := TMemoryStream.Create;
try
// ïîäãîòàâèâàåì áóôôåð ñ ZIP64 äàííûìè
{
The order of the fields in the ZIP64 extended
information record is fixed, but the fields will
only appear if the corresponding Local or Central
directory record field is set to 0xFFFF or 0xFFFFFFFF.
}
if FCD[I].UncompressedSize >= MAXDWORD then
ZIP64Data.WriteBuffer(FCD[I].UncompressedSize, 8);
if FCD[I].CompressedSize >= MAXDWORD then
ZIP64Data.WriteBuffer(FCD[I].CompressedSize, 8);
if FCD[I].RelativeOffsetOfLocalHeader >= MAXDWORD then
ZIP64Data.WriteBuffer(FCD[I].RelativeOffsetOfLocalHeader, 8);
if FCD[I].DiskNumberStart >= MAXWORD then
ZIP64Data.WriteBuffer(FCD[I].DiskNumberStart, 4);
ZeroMemory(@ExDataNTFS, SizeOf(TExDataNTFS));
if IsAttributesPresent(FCD[I].Attributes) then
begin
// ïîäãîòàâëèâàåì áóôôåð ñ NTFS âðåìåíåì
FCD[I].Header.ExtraFieldLength := SizeOf(TExDataNTFS);
// (NTFS) 0x000a Short Tag for this "extra" block type
ExDataNTFS.HS.Header := SUPPORTED_EXDATA_NTFSTIME;
{
In the current implementations, this field has
a fixed total data size of 32 bytes and is only stored as local
extra field
}
ExDataNTFS.HS.Size := 32;
// Reserved Long for future use
ExDataNTFS.Reserved := 0;
// Tag1 Short NTFS attribute tag value #1
ExDataNTFS.Tag := 1;
//Size1 2 bytes Size of attribute #1, in bytes (24)
ExDataNTFS.RecordSize := 24;
ExDataNTFS.Data.Mtime := FCD[I].Attributes.ftLastWriteTime;
ExDataNTFS.Data.Atime := FCD[I].Attributes.ftLastAccessTime;
ExDataNTFS.Data.Ctime := FCD[I].Attributes.ftCreationTime;
end;
if ZIP64Data.Size > 0 then
Inc(FCD[I].Header.ExtraFieldLength,
ZIP64Data.Size + SizeOf(TExDataHeaderAndSize));
TotalExDataStream := TMemoryStream.Create;
try
// Çàïðàøèâàåì áëîêè ExData îò ïîëüçîâàòåëÿ
FillExData(TotalExDataStream, I);
// ïðàâèì îáùèé ðàçìåð ðàñøèðåííûõ áëîêîâ
Inc(FCD[I].Header.ExtraFieldLength, TotalExDataStream.Size);
// Äëÿ MultiPart àðõèâà íàäî ñëåäèòü ÷òîáû CentralDirectoryFileHeader
// öåëèêîì âëàçèëî â òîì è íåáûëî ðàçáèòèÿ ìåæäó ãðàíèö äâóõ òîìîâ
if IsMultiPartZip(Stream) then
begin
VolumeSize := TFWAbstractMultiStream(Stream).GetWriteVolumeSize;
TFWAbstractMultiStream(Stream).GetRelativeInfo(
DiskNumber, RealtiveOffset);
if VolumeSize < RealtiveOffset + SizeOf(TCentralDirectoryFileHeader) +
FCD[I].Header.FilenameLength + FCD[I].Header.FileCommentLength +
FCD[I].Header.ExtraFieldLength then
begin
TFWAbstractMultiStream(Stream).StartNewVolume;
if FcdfhRecordOnDisk = 0 then
begin
Inc(FcdfhDiskNumber);
FcdfhOffset := 0;
end
else
FirstDisk := False;
end;
end;
// Ïèøåì ñòðóêòóðó TCentralDirectoryFileHeader, îïèñûâàþùóþ ýëåìåíò
Stream.WriteBuffer(FCD[I].Header, SizeOf(TCentralDirectoryFileHeader));
// Ïèøåì íàèìåíîâàíèå ýëåìåíòà
SaveString(Stream, FCD[I].FileName,
FCD[I].Header.GeneralPurposeBitFlag and PBF_UTF8 = PBF_UTF8);
// åñëè íóæíî - äîï èíôîðìàöèþ â ôîðìàòå ZIP64
if ZIP64Data.Size > 0 then
begin
ExDataHeader.Header := SUPPORTED_EXDATA_ZIP64;
ExDataHeader.Size := ZIP64Data.Size;
Stream.WriteBuffer(ExDataHeader, SizeOf(TExDataHeaderAndSize));
Stream.CopyFrom(ZIP64Data, 0);
end;
// ïîòîì èíôîðìàöèþ î NTFSTime
if ExDataNTFS.HS.Header = SUPPORTED_EXDATA_NTFSTIME then
Stream.WriteBuffer(ExDataNTFS, SizeOf(TExDataNTFS));
// è ðàñøèðåííóþ èíôîðìàöèþ ïîëó÷åííóþ îò ïîëüçîâàòåëÿ
if TotalExDataStream.Size > 0 then
Stream.CopyFrom(TotalExDataStream, 0);
finally
TotalExDataStream.Free;
end;
// â çàâåðøåíèå, ïèøåì êîìåíòàðèé ê ýëåìåíòó
SaveString(Stream, FCD[I].FileComment,
FCD[I].Header.GeneralPurposeBitFlag and PBF_UTF8 = PBF_UTF8);
if FirstDisk then
Inc(FcdfhRecordOnDisk);
finally
ZIP64Data.Free;
end;
end;
FSizeOfCentralDir := Stream.Position - FSizeOfCentralDir;
end;
//
// Ïðîöåäóðà ïðîâîäèò ñîõðàíåíèå ñåêöèè EndOfCentralDirectory
// =============================================================================
procedure TFWZipWriter.SaveEndOfCentralDirectory(Stream: TStream);
var
eo64cd: TZip64EOFCentralDirectoryRecord;
{%H-}locator: TZip64EOFCentralDirectoryLocator;
eocd: TEndOfCentralDir;
eo64cdOffset: Int64;
DiskNumber: Integer;
VolumeSize: Int64;
begin
eocd.ZipfileCommentLength := StringLength(FComment, False);
if IsMultiPartZip(Stream) then
begin
VolumeSize := TFWAbstractMultiStream(Stream).GetWriteVolumeSize;
TFWAbstractMultiStream(Stream).GetRelativeInfo(DiskNumber, eo64cdOffset);
if VolumeSize < eo64cdOffset + SizeOf(TZip64EOFCentralDirectoryRecord) +
SizeOf(TZip64EOFCentralDirectoryLocator) + SizeOf(TEndOfCentralDir) +
eocd.ZipfileCommentLength then
begin
TFWAbstractMultiStream(Stream).StartNewVolume;
Inc(DiskNumber);
eo64cdOffset := 0;
end;
locator.TotalNumberOfDisks := TFWAbstractMultiStream(Stream).GetDiskCount;
end
else
begin
DiskNumber := 0;
eo64cdOffset := Stream.Position;
locator.TotalNumberOfDisks := 1;
end;
// êîëè÷åñòâî çàïèñåé â êîíêðåòíîì òîìå
eo64cd.TotalNumberOfEntriesOnThisDisk := FcdfhRecordOnDisk;
// Èñêëþ÷àåì èç îáùåãî êîëè÷åñòâà ýëåìåíòîâ àðõèâà êîëè÷åñòâî èñêëþ÷åíèé
eo64cd.TotalNumberOfEntries {%H-}:= Count - FExceptionCount;
// ôîðìàò ZIP64 èñïîëüçóåòñÿ â ñëó÷àå åñëè êîëè÷åñòâî ýëåìåíòîâ
// àðõèâà ïðåâûøàåò MAXWORD, èëè ñìåùåíèå íà íà÷àëî öåíòðàëüíîé äèðåêòîðèè
// ïðåâûøàåò MAXDWORD èëè åå ðàçìåð ïðåâûøàåò MAXDWORD
if (FcdfhOffset > MAXDWORD) or (FSizeOfCentralDir > MAXDWORD) or
(eo64cd.TotalNumberOfEntriesOnThisDisk > MAXWORD) or
(eo64cd.TotalNumberOfEntries > MAXWORD) or (DiskNumber > MAXWORD) or
(FcdfhDiskNumber > MAXWORD) then
begin
//  ñëó÷àå èñïîëüçîâàíèÿ ôîðìàòà ZIP64
// íåîáõîäèìî çàïèñàòü äîïîëíèòåëüíûå ñòðóêòóðû
// TZip64EOFCentralDirectoryRecord
eo64cd.Zip64EndOfCentralDirSignature := ZIP64_END_OF_CENTRAL_DIR_SIGNATURE;
// Rouse_ 20.07.2013
// â ñïåöèôèêàöèè 6.3.0 îò September 29, 2006 îãîâîðåíî
{
The value stored into the "size of zip64 end of central
directory record" should be the size of the remaining
record and should not include the leading 12 bytes.
Size = SizeOfFixedFields + SizeOfVariableData - 12.
}
// FWZip ðàçðàáàòûâàëñÿ íà îñíîâå áîëåå ðàííèõ ñïåöèôèêàöèé
// (èçíà÷àëüíî íà 6.0 ïîòîì íà 6.2) è íå ó÷èòûâàë ýòîò ìîìåíò
// Ïîýòîìó âìåñòî
// oe64cd.SizeOfZip64EOFCentralDirectoryRecord :=
// SizeOf(TZip64EOFCentralDirectoryRecord);
// ïèøåì:
eo64cd.SizeOfZip64EOFCentralDirectoryRecord :=
SizeOf(TZip64EOFCentralDirectoryRecord) - 12;
eo64cd.VersionMadeBy := CurrentVersionMadeBy;
eo64cd.VersionNeededToExtract := FVersionToExtract;
eo64cd.NumberOfThisDisk := DiskNumber;
eo64cd.DiskNumberStart := FcdfhDiskNumber;
eo64cd.SizeOfTheCentralDirectory := FSizeOfCentralDir;
eo64cd.RelativeOffsetOfCentralDirectory := FcdfhOffset;
Stream.WriteBuffer(eo64cd, SizeOf(TZip64EOFCentralDirectoryRecord));
// TZip64EOFCentralDirectoryLocator
locator.Signature := ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE;
locator.DiskNumberStart := DiskNumber;
locator.RelativeOffset := eo64cdOffset;
Stream.WriteBuffer(locator, SizeOf(TZip64EOFCentralDirectoryLocator));
end;
eocd.EndOfCentralDirSignature := END_OF_CENTRAL_DIR_SIGNATURE;
if DiskNumber > MAXWORD then
eocd.NumberOfThisDisk := MAXWORD
else
eocd.NumberOfThisDisk := DiskNumber;
if FcdfhDiskNumber > MAXWORD then
eocd.DiskNumberStart := MAXWORD
else
eocd.DiskNumberStart := FcdfhDiskNumber;
if eo64cd.TotalNumberOfEntriesOnThisDisk > MAXWORD then
eocd.TotalNumberOfEntriesOnThisDisk := MAXWORD
else
eocd.TotalNumberOfEntriesOnThisDisk := eo64cd.TotalNumberOfEntriesOnThisDisk;
if eo64cd.TotalNumberOfEntries > MAXWORD then
eocd.TotalNumberOfEntries := MAXWORD
else
eocd.TotalNumberOfEntries := eo64cd.TotalNumberOfEntries;
if FSizeOfCentralDir > MAXDWORD then
eocd.SizeOfTheCentralDirectory := MAXDWORD
else
eocd.SizeOfTheCentralDirectory := FSizeOfCentralDir;
if FcdfhOffset > MAXDWORD then
eocd.RelativeOffsetOfCentralDirectory := MAXDWORD
else
eocd.RelativeOffsetOfCentralDirectory := FcdfhOffset;
Stream.WriteBuffer(eocd, SizeOf(TEndOfCentralDir));
if eocd.ZipfileCommentLength > 0 then
// Rouse_ 26.06.2023
//  ñïåöèôèêàöèè âîîáùå íè÷åãî íå ñêàçàíî ïðî ôîðìàò êîìåíòàðèÿ
// è íåò ïîëÿ ãäå õðàíèëñÿ áû ôëàã êîäèðîâêè îáùåãî êîìåíòàðèÿ ê àðõèâó
// ïîýòîìó êîìåíòàðèé äîëæåí ñîõðàíÿòüñÿ ÒÎËÜÊÎ êàê Ansi ñòðîêà
SaveString(Stream, FComment, False);
end;
//
// Ïðîöåäóðà ïðîâîäèò âñå ïðîöåäóðû ïîäãîòîâêè, ñæàòèÿ è ñîõðàíåíèÿ óêàçàííîãî ýëåìåíòà àðõèâà
// =============================================================================
procedure TFWZipWriter.SaveItemToStream(Stream: TStream; Index: Integer);
var
CurrentItem: TFWZipWriterItem;
FileNameOffset, StreamSizeBeforeCompress, RealtiveOffset: Int64;
DiskNumber: Integer;
begin
CurrentItem := Item[Index];
// ïðîâåðêà íà äóðàêîóñòîé÷èâîñòü
if not CurrentItem.UseExternalData then
if (CurrentItem.FilePath = '') and (CurrentItem.Data = nil) then
raise EZipWriter.CreateFmt('Äàííûå ýëåìåíòà ¹%d "%s" îòñóòñòâóþò',
[Index, CurrentItem.FileName]);
FProcessedItemIndex := Index;
// Rouse_ 25.10.2013
// Ãåíåðèðóåì ñîáûòèå íà÷àëà ðàñïàêîâêè ïåðåä òåì êàê ðåçóëüòèðóþùèé ôàéë áóäåò çàëî÷åí
DoProgress(psStart);
try
// Çàïîëíÿåì èíôîðìàöèþ â CentralDirectory
// ===========================================================================
FillItemCDFHeader(CurrentItem, FCD[Index]);
if IsMultiPartZip(Stream) then
begin
TFWAbstractMultiStream(Stream).GetRelativeInfo(DiskNumber, RealtiveOffset);
FCD[Index].DiskNumberStart := DiskNumber;
FCD[Index].RelativeOffsetOfLocalHeader := RealtiveOffset;
end
else
begin
FCD[Index].DiskNumberStart := 0;
FCD[Index].RelativeOffsetOfLocalHeader := Stream.Position;
end;
// Ïîìåùàåì äàííûå â ðåçóëüòèðóþùèé ôàéë
// ===========================================================================
// Çàïîìèíàåì îôôñåò ïî êîòîðîìó íåîáõîäèìî áóäåò ïèñàòü èìÿ ôàéëà
FileNameOffset := Stream.Position + SizeOf(TLocalFileHeader);
// ðàññ÷èòûâàåì ðàçìåð ðåçåðâèðóåìîãî ìåñòà ïîä
// LocalFileHeader è èìÿ ôàéëà
StreamSizeBeforeCompress := FileNameOffset +
FCD[Index].Header.FilenameLength;
// ðåçåðâèðóåì ìåñòî ïîä ZIP64
if FCD[Index].UncompressedSize >= MAXDWORD then
Inc(StreamSizeBeforeCompress, SizeOf(TExDataInfo64));
// âûäåëÿåì áëîê äàííûõ ïîä LocalFileHeader è èìÿ ôàéëà
Stream.Size := StreamSizeBeforeCompress;
Stream.Position := Stream.Size;
// ñæèìàåì äàííûå
if not CurrentItem.IsFolder then
CompressItem(CurrentItem, Index, StreamSizeBeforeCompress, Stream);
Inc(FTotalProcessedCount, CurrentItem.Size);
// ïèøåì èìÿ ôàéëà
Stream.Position := FileNameOffset;
SaveString(Stream, FCD[Index].Filename, CurrentItem.UseUTF8String);
// ðåçåðâèðóåì ìåñòî ïîä äåñêðèïòîð
if CurrentItem.NeedDescriptor then
Stream.Size := Stream.Size + SizeOf(TDataDescriptor);
Stream.Position := Stream.Size;
finally
// Rouse_ 25.10.2013
// Ðåçóëüòèðóþùèé ôàéë îñâîáîæäåí, ãåíåðèðóåì ñîáûòèå
DoProgress(psEnd);
end;
end;
//
// Ïðîöåäóðà ïðîâîäèò ïðåîáðàçîâàíèå ïåðåäàííîé ñòðîêè â OEM è åå ñîõðàíåíèå
// =============================================================================
procedure TFWZipWriter.SaveString(Stream: TStream; const Value: string;
AUseUTF8String: Boolean);
var
OemString: AnsiString;
begin
if Value <> '' then
begin
if AUseUTF8String then
OemString := UTF8Encode(Value)
else
OemString := ConvertToOemString(AnsiString(Value));
// äëèíà ñòðîêè ïîñëå êîíâåðòàöèè áóäåò ïðàâèëüíîé, ò.å. äëÿ
// 'Ìîé òåñòîâûé êîììåíòàðèé' ó UTF8 áóäåò ðàâíà 46 è 24 â ïðîòèâíîì ñëó÷àå
Stream.WriteBuffer(OemString[1], Length(OemString));
end;
end;
//
// Ïðîöåäóðà îáíîâëÿåò ñåêöèè LocalFileHeader
// =============================================================================
procedure TFWZipWriter.UpdateLocalHeaders(Stream: TStream);
var
I: Integer;
lfh: TLocalFileHeader;
dd: TDataDescriptor;
UseDescriptor: Boolean;
{%H-}Info64: TExDataInfo64;
begin
FcdfhOffset := Stream.Position;
for I := 0 to Count - 1 do
begin
// ïðîïóñêàåì ýëåìåíòû ïðè çàïèñè êîòîðûõ ïðîèçîøëî èñêëþ÷åíèå
if FCD[I].ExceptOnWrite then Continue;
// Èìåÿ íà ðóêàõ âñå äàííûå ïåðåçàïèñûâàåì âñå LocalFileHeader
// è DataDescriptor (åñëè òðåáóåòñÿ)
lfh.LocalFileHeaderSignature := LOCAL_FILE_HEADER_SIGNATURE;
// ðàññ÷èòûâàåì âåðñèþ íåîáõîäèìóþ äëÿ ðàñïàêîâêè ýëåìåíòà àðõèâà
lfh.VersionNeededToExtract := GetVersionToExtract(I);
lfh.GeneralPurposeBitFlag := FCD[I].Header.GeneralPurposeBitFlag;
UseDescriptor := lfh.GeneralPurposeBitFlag and PBF_DESCRIPTOR <> 0;
lfh.CompressionMethod := FCD[I].Header.CompressionMethod;
lfh.LastModFileTimeTime := FCD[I].Header.LastModFileTimeTime;
lfh.LastModFileTimeDate := FCD[I].Header.LastModFileTimeDate;
if UseDescriptor then
begin
dd.DescriptorSignature := DATA_DESCRIPTOR_SIGNATURE;
// õîòü â ñòàíäàðòå ñêàçàíî ÷òî ïðè èñïîëüçîâàíèè äåñêðèïòîðîâ
// ïîëÿ Crc32, CompressedSize è UncompressedSize äîëæíû áûëü óñòàíîâëåíû
// â íîëü, íî áîëüøèíñòâî àðõèâàòîðîâ ýòîãî íå äåëàþò,
// ïîýòîìó óïîäîáèìñÿ èì :)
lfh.Crc32 := FCD[I].Header.Crc32;
dd.Crc32 := lfh.Crc32;
if FCD[I].CompressedSize > MAXDWORD then
dd.CompressedSize := MAXDWORD
else
dd.CompressedSize := FCD[I].CompressedSize;
lfh.CompressedSize := dd.CompressedSize;
if FCD[I].UncompressedSize > MAXDWORD then
dd.UncompressedSize := MAXDWORD
else
dd.UncompressedSize := FCD[I].UncompressedSize;
lfh.UncompressedSize := dd.UncompressedSize;
end
else
begin
lfh.Crc32 := FCD[I].Header.Crc32;
if FCD[I].CompressedSize > MAXDWORD then
lfh.CompressedSize := MAXDWORD
else
lfh.CompressedSize := FCD[I].CompressedSize;
if FCD[I].UncompressedSize > MAXDWORD then
lfh.UncompressedSize := MAXDWORD
else
lfh.UncompressedSize := FCD[I].UncompressedSize;
end;
lfh.FilenameLength := FCD[I].Header.FilenameLength;
if (FCD[I].UncompressedSize >= MAXDWORD) or
(FCD[I].CompressedSize >= MAXDWORD) then
lfh.ExtraFieldLength := SizeOf(TExDataInfo64)
else
lfh.ExtraFieldLength := 0;
if IsMultiPartZip(Stream) then
TFWAbstractMultiStream(Stream).Seek(
FCD[I].DiskNumberStart, FCD[I].RelativeOffsetOfLocalHeader)
else
Stream.Position := FCD[I].RelativeOffsetOfLocalHeader;
Stream.WriteBuffer(lfh, SizeOf(TLocalFileHeader));
// Rouse_ 20.03.2015
// Ïèøåì äàííûå äëÿ ïîääåðæêè ZIP64
// à òî WinRar, WinZip è 7Zip íå áóäóò ðàñïàêîâûâàòü òàêîé àðõèâ
// (íå ïîíÿòíî ïðàâäà, ïî÷åìó îíè íå ÷èòàþò ýòó èíôîðìàöèþ èç CentralDirectory)
if lfh.ExtraFieldLength > 0 then
begin
Stream.Position := Stream.Position + lfh.FilenameLength;
Info64.HS.Header := SUPPORTED_EXDATA_ZIP64;
Info64.HS.Size := SizeOf(TExDataInfo64) - SizeOf(TExDataHeaderAndSize);
Info64.UncompressedSize := FCD[I].UncompressedSize;
// åñëè CompressedSize ìåíüøå MAXDWORD åãî ïèñàòü íå íàäî ïî ñòàíäàðòó,
// íî ìåñòî ïîä íåãî óæå çàðåçåðâèðîâàííî, ïîýòîìó ïðèäåòñÿ ïèñàòü åãî â ëþáîì ñëó÷àå
// êàê îáîéòè ýòî ðàñõîæäåíèå ñî ñòàíäàðòîì õîðîøèì ñïîñîáîì - ÿ ïîêà íå çíàþ
Info64.CompressedSize := FCD[I].CompressedSize;
Stream.WriteBuffer(Info64, SizeOf(TExDataInfo64));
end;
if UseDescriptor then
begin
// äåñêðèïòîð ïèøåòñÿ ïîñëå ñæàòîãî áëîêà äàííûõ
if IsMultiPartZip(Stream) then
begin
TFWAbstractMultiStream(Stream).Seek(
FCD[I].DiskNumberStart, FCD[I].RelativeOffsetOfLocalHeader);
Stream.Seek(SizeOf(TLocalFileHeader) + lfh.FilenameLength +
lfh.ExtraFieldLength {%H-}+ FCD[I].CompressedSize, soCurrent);
end
else
Stream.Position := FCD[I].RelativeOffsetOfLocalHeader +
SizeOf(TLocalFileHeader) + lfh.FilenameLength +
lfh.ExtraFieldLength + FCD[I].CompressedSize;
Stream.WriteBuffer(dd, SizeOf(TDataDescriptor));
end;
// îáíîâëÿåì èíôîðìàöèþ â ìàññèâå CentralDirectoryFileHeader
// Rouse_ 14.02.2013
// Íåâåðíî âûñòàâëÿëàñü âåðñèÿ äëÿ ðàñïàêîâêè â CentralDirectoryFileHeader
//FCD[I].Header.VersionNeededToExtract := FVersionToExtract;
FCD[I].Header.VersionNeededToExtract := lfh.VersionNeededToExtract;
if UseDescriptor then
FCD[I].Header.CompressedSize := dd.CompressedSize
else
FCD[I].Header.CompressedSize := lfh.CompressedSize;
if UseDescriptor then
FCD[I].Header.UncompressedSize := dd.UncompressedSize
else
FCD[I].Header.UncompressedSize := lfh.UncompressedSize;
if FCD[I].RelativeOffsetOfLocalHeader > MAXDWORD then
FCD[I].Header.RelativeOffsetOfLocalHeader := MAXDWORD
else
FCD[I].Header.RelativeOffsetOfLocalHeader :=
FCD[I].RelativeOffsetOfLocalHeader;
if FCD[I].DiskNumberStart > MAXWORD then
FCD[I].Header.DiskNumberStart := MAXWORD
else
FCD[I].Header.DiskNumberStart := FCD[I].DiskNumberStart;
// Rouse_ 14.02.2013
// îáíîâëÿåì ãëîáàëüíóþ ìåòêó âåðñèè
if FVersionToExtract < lfh.VersionNeededToExtract then
FVersionToExtract := lfh.VersionNeededToExtract;
end;
Stream.Position := FcdfhOffset;
end;
end.