lasarus_compotents/fwzip/delphi7/FWZipModifier.pas

511 lines
18 KiB
ObjectPascal

////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Project : FWZip
// * Unit Name : FWZipModifier
// * Purpose : Êëàññ äëÿ ìîäèôèêàöèè ñîçäàííîãî ðàíåå ZIP àðõèâà
// * Author : Àëåêñàíäð (Rouse_) Áàãåëü
// * Copyright : © Fangorn Wizards Lab 1998 - 2023.
// * Version : 2.0.2
// * 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 FWZipModifier;
{$IFDEF FPC}
{$MODE Delphi}
{$H+}
{$ENDIF}
interface
uses
Classes,
SysUtils,
FWZipConsts,
FWZipReader,
FWZipWriter,
FWZipStream,
FWZipZLib,
FWZipUtils;
type
TReaderIndex = Integer;
TFWZipModifierItem = class(TFWZipWriterItem)
private
FReaderIndex: TReaderIndex; // èíäåêñ TFWZipReader â ìàññèâå TFWZipModifier.FReaderList
FOriginalItemIndex: Integer; // îðèãèíàëüíûé èíäåêñ ýëåìåíòà â èçíà÷àëüíîì àðõèâå
FOverloadItemPath: string; // çàäàâàåìûé èçâíå ïóòü ýëåìåíòà â àðõèâå
protected
property ReaderIndex: TReaderIndex read FReaderIndex write FReaderIndex;
property OriginalItemIndex: Integer read FOriginalItemIndex write FOriginalItemIndex;
property OverloadItemPath: string read FOverloadItemPath write FOverloadItemPath;
public
constructor Create(Owner: TFWZipWriter;
const InitFilePath: string;
InitAttributes: TFileAttributeData;
const InitFileName: string = ''); override;
end;
EFWZipModifier = class(Exception);
// Äàííàÿ ñòðóêòóðà õðàíèò âñå áëîêè ExData èç ïîäêëþ÷àåìûõ àðõèâîâ
TExDataRecord = record
Index: Integer;
Tag: Word;
Stream: TMemoryStream;
end;
TExDataRecords = array of TExDataRecord;
// ñòðóêòóðà äëÿ õðàíåíèÿ ïîäêëþ÷åííîãî àðõèâà è åãî áëîêîâ ExData
TReaderOwnership = (roReference, roOwned);
TReaderData = record
Reader: TFWZipReader;
ExDataRecords: TExDataRecords;
Ownership: TReaderOwnership;
end;
TReaderList = array of TReaderData;
TFWZipModifier = class(TFWZipWriter)
private
FReaderList: TReaderList;
function CheckZipFileIndex(Value: TReaderIndex): TReaderIndex;
function AddItemFromZip(AReader: TFWZipReader;
ReaderIndex: TReaderIndex; ItemIndex: Integer;
OverloadItemPath: string): Integer;
function GetReader(Index: Integer): TFWZipReader;
protected
function GetItemClass: TFWZipWriterItemClass; override;
procedure FillItemCDFHeader(CurrentItem: TFWZipWriterItem;
var Value: TCentralDirectoryFileHeaderEx); override;
procedure CompressItem(CurrentItem: TFWZipWriterItem;
Index: Integer; StreamSizeBeforeCompress: Int64; Stream: TStream); override;
procedure FillExData(Stream: TStream; Index: Integer); override;
procedure OnLoadExData(Sender: TObject; ItemIndex: Integer;
Tag: Word; Data: TStream);
public
destructor Destroy; override;
function AddZipFile(const AReader: TFWZipReader;
AOwnership: TReaderOwnership = roReference): TReaderIndex; overload;
function AddZipFile(const FilePath: string; SFXOffset: Integer = -1;
ZipEndOffset: Integer = -1): TReaderIndex; overload;
function AddZipFile(FileStream: TStream; SFXOffset: Integer = -1;
ZipEndOffset: Integer = -1): TReaderIndex; overload;
function AddFromZip(ReaderIndex: TReaderIndex): Integer; overload;
function AddFromZip(ReaderIndex: TReaderIndex; const ItemPath: string): Integer; overload;
function AddFromZip(ReaderIndex: TReaderIndex; const ItemPath, NewItemPath: string): Integer; overload;
function AddFromZip(ReaderIndex: TReaderIndex; ItemsList: TStringList): Integer; overload;
function ReadersCount: Integer;
property Reader[Index: Integer]: TFWZipReader read GetReader;
end;
implementation
type
TFWZipReaderFriendly = class(TFWZipReader);
TFWZipReaderItemFriendly = class(TFWZipReaderItem);
{ TFWZipModifierItem }
//
//  êîíñòðóêòîðå ïðîèçâîäèì ïåðâè÷íóþ èíèöèàëèçàöèþ ïîëåé
// Ñàìè ïîëÿ ReaderIndex è OriginalItemIndex áóäóò èíèöèàëèçèðîâàòüñÿ òîëüêî
// ïðè äîáàâëåíèè èõ ïîñðåäñòâîì êëàññà TFWZipModifier
// =============================================================================
constructor TFWZipModifierItem.Create(Owner: TFWZipWriter;
const InitFilePath: string; InitAttributes: TFileAttributeData;
const InitFileName: string);
begin
inherited Create(Owner, InitFilePath, InitAttributes, InitFileName);
FReaderIndex := -1;
FOriginalItemIndex := -1;
end;
{ TFWZipModifier }
//
// Ôóíêöèÿ ïåðåíîñèò ýëåìåíò â ôèíàëüíûé àðõèâ èç ðàíåå äîáàâëåííîãî àðõèâà.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// ReaderIndex - èíäåêñ ðàíåå äîáàâëåííî ôóíêöèåé AddZipFile àðõèâà
// ItemPath - èìÿ ýëåìåíòà, êîòîðîå òðåáóåòñÿ äîáàâèòü
// =============================================================================
function TFWZipModifier.AddFromZip(ReaderIndex: TReaderIndex;
const ItemPath: string): Integer;
begin
Result := AddFromZip(ReaderIndex, ItemPath, EmptyStr);
end;
//
// Ôóíêöèÿ ïåðåíîñèò âñå ýëåìåíòû èç ðàíåå äîáàâëåííîãî àðõèâà â ôèíàëüíûé àðõèâ.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// ReaderIndex - èíäåêñ ðàíåå äîáàâëåííî ôóíêöèåé AddZipFile àðõèâà
// =============================================================================
function TFWZipModifier.AddFromZip(ReaderIndex: TReaderIndex): Integer;
var
I: Integer;
Reader: TFWZipReader;
begin
CheckZipFileIndex(ReaderIndex);
Result := 0;
Reader := FReaderList[ReaderIndex].Reader;
for I := 0 to Reader.Count - 1 do
if AddItemFromZip(Reader, ReaderIndex, I, EmptyStr) >= 0 then
Inc(Result);
end;
//
// Ôóíêöèÿ ïåðåíîñèò âñå ýëåìåíòû èç ðàíåå äîáàâëåííîãî àðõèâà
// ïî ñïèñêó â ôèíàëüíûé àðõèâ.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò êîëè÷åñòâî óñïåøíî äîáàâëåííûõ ýëåìåíòîâ.
// Ïàðàìåòðû:
// ReaderIndex - èíäåêñ ðàíåå äîáàâëåííî ôóíêöèåé AddZipFile àðõèâà
// ItemsList - ñïèñîê ýëåìåíòîâ ê äîáàâëåíèþ
// =============================================================================
function TFWZipModifier.AddFromZip(ReaderIndex: TReaderIndex;
ItemsList: TStringList): Integer;
var
I: Integer;
Reader: TFWZipReader;
begin
CheckZipFileIndex(ReaderIndex);
Result := 0;
Reader := FReaderList[ReaderIndex].Reader;
for I := 0 to ItemsList.Count - 1 do
if AddItemFromZip(Reader, ReaderIndex,
Reader.GetElementIndex(ItemsList[I]), EmptyStr) >= 0 then
Inc(Result);
end;
//
// Ôóíêöèÿ ïåðåíîñèò ýëåìåíò â ôèíàëüíûé àðõèâ èç ðàíåå äîáàâëåííîãî àðõèâà.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// Ïàðàìåòðû:
// ReaderIndex - èíäåêñ ðàíåå äîáàâëåííî ôóíêöèåé AddZipFile àðõèâà
// ItemPath - èìÿ ýëåìåíòà, êîòîðîå òðåáóåòñÿ äîáàâèòü
// NewItemPath - íîâîå èìÿ ýëåìåíòà â àðõèâå
// =============================================================================
function TFWZipModifier.AddFromZip(ReaderIndex: TReaderIndex; const ItemPath,
NewItemPath: string): Integer;
var
Reader: TFWZipReader;
begin
CheckZipFileIndex(ReaderIndex);
Reader := FReaderList[ReaderIndex].Reader;
Result :=
AddItemFromZip(Reader, ReaderIndex, Reader.GetElementIndex(ItemPath),
CheckFileNameSlashes( NewItemPath));
end;
//
// Ôóíêöèÿ ïåðåíîñèò ýëåìåíò â ôèíàëüíûé àðõèâ èç ðàíåå äîáàâëåííîãî àðõèâà.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ ýëåìåíòà â ñïèñêå.
// =============================================================================
function TFWZipModifier.AddItemFromZip(AReader: TFWZipReader;
ReaderIndex: TReaderIndex; ItemIndex: Integer;
OverloadItemPath: string): Integer;
const
OldItemType: array [Boolean] of string = ('file', 'folder');
var
OldItem: TFWZipReaderItemFriendly;
NewItem: TFWZipModifierItem;
begin
Result := -1;
if ItemIndex < 0 then Exit;
// Ïîëó÷àåì óêàçàòåëü íà ýëåìåíò èç óæå ñóùåñòâóþùåãî àðõèâà
OldItem := TFWZipReaderItemFriendly(AReader.Item[ItemIndex]);
// Ïðîâåðêà ñîîòâåòñòâèÿ íîâîãî èìåíè òèïó ñòàðîãî ýëåìåíòà
if OverloadItemPath <> '' then
begin
if OverloadItemPath <> OldItem.FileName then
begin
if OldItem.IsFolder <>
(OverloadItemPath[Length(OverloadItemPath)] = ZIP_SLASH) then
raise EFWZipModifier.CreateFmt(
'"%s" does not match the %s path.',
[OverloadItemPath, OldItemType[OldItem.IsFolder]]);
end
else
OverloadItemPath := EmptyStr;
end;
// ñîçäàåì íîâûé ýëåìåíò, êîòîðûé áóäåì äîáàâëÿòü ê íîâîìó àðõèâó
NewItem := TFWZipModifierItem(
GetItemClass.Create(Self, '', OldItem.Attributes, OldItem.FileName));
// ïåðåêëþ÷àåì åãî â ðåæèì ïîëó÷åíèÿ äàííûõ âðó÷íóþ
NewItem.UseExternalData := True;
// èíèöèàëèçèðóåì åìó èíäåêñû, äàáû ïîòîì ïîíÿòü, îòêóäà áðàòü î íåì äàííûå
NewItem.ReaderIndex := ReaderIndex;
NewItem.OriginalItemIndex := ItemIndex;
NewItem.OverloadItemPath := OverloadItemPath;
// èíèöèàëèçèðóåì âíåøíèå è ðàññ÷èòûâàåìûå ïîëÿ
NewItem.Comment := OldItem.Comment;
NewItem.NeedDescriptor :=
OldItem.CentralDirFileHeader.GeneralPurposeBitFlag and PBF_DESCRIPTOR <> 0;
NewItem.UseUTF8String :=
OldItem.CentralDirFileHeader.GeneralPurposeBitFlag and PBF_UTF8 <> 0;
// äîáàâëÿåì
Result := AddNewItem(NewItem);
end;
//
// Ôóíêöèÿ äîáàâëÿåò íîâûé àðõèâ èç êîòîðîãî ìîæíî áðàòü ãîòîâûå äàííûå.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ àðõèâà â ñïèñêå äîáàâëåííûõ.
// Ïàðàìåòðû:
// FileStream - ïîòîê ñ äàííûìè àðõèâà
// SFXOffset è ZipEndOffset - åãî ãðàíèöû
// =============================================================================
function TFWZipModifier.AddZipFile(FileStream: TStream; SFXOffset,
ZipEndOffset: Integer): TReaderIndex;
var
AReader: TFWZipReader;
begin
AReader := TFWZipReader.Create;
Result := AddZipFile(AReader, roOwned);
AReader.OnLoadExData := OnLoadExData;
AReader.LoadFromStream(FileStream, SFXOffset, ZipEndOffset);
end;
//
// Ôóíêöèÿ äîáàâëÿåò íîâíûé ðèäåð â ñïèñîê äîñòóïíûõ
// Çàãðóçêà äàííûõ íå ïðîèçâîäèòñÿ, ïîýòîìó äàííûé âàðèàíò äîáàâëåíèÿ
// ðèäåðà íå ïîçâîëèò ðàáîòàòü ñ áëîêîì ExData åñëè òàêîâîé ïðèñóòñòâóåò.
// =============================================================================
function TFWZipModifier.AddZipFile(const AReader: TFWZipReader;
AOwnership: TReaderOwnership): TReaderIndex;
begin
Result := Length(FReaderList);
SetLength(FReaderList, Result + 1);
FReaderList[Result].Reader := AReader;
FReaderList[Result].Ownership := AOwnership;
end;
//
// Ôóíêöèÿ äîáàâëÿåò íîâûé àðõèâ èç êîòîðîãî ìîæíî áðàòü ãîòîâûå äàííûå.
//  êà÷åñòâå ðåçóëüòàòà âîçâðàùàåò èíäåêñ àðõèâà â ñïèñêå äîáàâëåííûõ.
// Ïàðàìåòðû:
// FilePath - ïóòü ê äîáàâëÿåìîìó àðõèâó
// SFXOffset è ZipEndOffset - åãî ãðàíèöû
// =============================================================================
function TFWZipModifier.AddZipFile(const FilePath: string;
SFXOffset, ZipEndOffset: Integer): TReaderIndex;
var
AReader: TFWZipReader;
begin
AReader := TFWZipReader.Create;
Result := AddZipFile(AReader, roOwned);
AReader.OnLoadExData := OnLoadExData;
AReader.LoadFromFile(FilePath, SFXOffset, ZipEndOffset);
end;
//
// Ôóíêöèÿ ïðîâåðÿåò ïðàâèëüíîñòü ïåðåäàííîãî èíäåêñà àðõèâà â ñïèñêå
// =============================================================================
function TFWZipModifier.CheckZipFileIndex(Value: TReaderIndex): TReaderIndex;
begin
Result := Value;
if (Value < 0) or (Value >= Length(FReaderList)) then
raise EFWZipModifier.CreateFmt('Invalid index value (%d).', [Value]);
end;
//
// Ïðîöåäóðà ïåðåêðûâàåò ñæàòèå äàííûõ ýåëåìåíòà
// è áåðåò ýòè äàííûå èç ðàíåå ñôîðìèðîâàííîãî àðõèâà.
// =============================================================================
procedure TFWZipModifier.CompressItem(CurrentItem: TFWZipWriterItem;
Index: Integer; StreamSizeBeforeCompress: Int64; Stream: TStream);
var
OldItem: TFWZipReaderItemFriendly;
NewItem: TFWZipModifierItem;
Reader: TFWZipReaderFriendly;
Offset: Int64;
begin
NewItem := TFWZipModifierItem(CurrentItem);
// ïðîâåðêà, ðàáîòàåì ëè ìû ñ ýëåìåíòîì, äàííûå êîòîðîãî çàïîëíÿþòñÿ âðó÷íóþ?
if not NewItem.UseExternalData then
begin
inherited;
Exit;
end;
// ïîëó÷àåì óêàçàòåëü íà êëàññ, êîòîðûé äåðæèò äîáàâëåííûé ðàíåå àðõèâ
Reader := TFWZipReaderFriendly(FReaderList[NewItem.ReaderIndex].Reader);
// ïîëó÷àåì óêàçàòåëü íà îðèãèíàëüíûé ýëåìåíò àðõèâà
OldItem := TFWZipReaderItemFriendly(Reader.Item[NewItem.OriginalItemIndex]);
// ðàññ÷èòûâàåì åãî ïîçèöèþ â àðõèâå
if IsMultiPartZip(Reader.ZIPStream) then
begin
TFWAbstractMultiStream(Reader.ZIPStream).Seek(
OldItem.CentralDirFileHeader.DiskNumberStart,
OldItem.CentralDirFileHeader.RelativeOffsetOfLocalHeader);
Offset := Reader.ZIPStream.Position;
end
else
Offset := OldItem.CentralDirFileHeader.RelativeOffsetOfLocalHeader;
Inc(Offset, SizeOf(TLocalFileHeader));
Inc(Offset, OldItem.CentralDirFileHeader.FilenameLength);
if OldItem.CentralDirFileHeaderEx.UncompressedSize >= MAXDWORD then
Inc(Offset, SizeOf(TExDataInfo64));
Reader.ZIPStream.Position := Offset;
// êîïèðóåì äàííûå êàê åñòü, áåç ïåðåïàêîâêè
Stream.CopyFrom(Reader.ZIPStream, OldItem.CentralDirFileHeaderEx.CompressedSize);
end;
//
// Modifier ñëåãêà íå îïòèìàëüíî ðàñõîäóåò ïàìÿòü, ïîýòîìó ïîä÷èùàåì.
// =============================================================================
destructor TFWZipModifier.Destroy;
var
I, A: Integer;
begin
for I := 0 to Length(FReaderList) - 1 do
begin
if FReaderList[I].Ownership = roOwned then
FReaderList[I].Reader.Free;
for A := 0 to Length(FReaderList[I].ExDataRecords) - 1 do
FReaderList[I].ExDataRecords[A].Stream.Free;
end;
inherited;
end;
//
// Ïðîöåäóðà ïåðåêðûâàåò çàïîëíåíèå áëîêîâ ExData
// è áåðåò ýòè äàííûå èç ðàíåå ñôîðìèðîâàííîãî àðõèâà.
// =============================================================================
procedure TFWZipModifier.FillExData(Stream: TStream; Index: Integer);
var
NewItem: TFWZipModifierItem;
ReaderIndex: TReaderIndex;
I: Integer;
{%H-}ExDataSize: Word;
ExDataRecord: TExDataRecord;
begin
NewItem := TFWZipModifierItem(Item[Index]);
// ïðîâåðêà, ðàáîòàåì ëè ìû ñ ýëåìåíòîì, äàííûå êîòîðîãî çàïîëíÿþòñÿ âðó÷íóþ?
if not NewItem.UseExternalData then
begin
inherited;
Exit;
end;
// ïðîâåðÿåì ïðèâÿçêó ê àðõèâó, ñ åëåìåíòîâ êîòîðîãî ìû áóäåì äîáàâëÿòü áëîêè ExData
ReaderIndex := CheckZipFileIndex(NewItem.ReaderIndex);
for I := 0 to Length(FReaderList[ReaderIndex].ExDataRecords) - 1 do
if FReaderList[ReaderIndex].ExDataRecords[I].Index = NewItem.OriginalItemIndex then
begin
// áëîêîâ ìîæåò áûòü íåñêîëüêî, ïîýòîìó äîáàâëÿåì èõ âñå
ExDataRecord := FReaderList[ReaderIndex].ExDataRecords[I];
Stream.WriteBuffer(ExDataRecord.Tag, 2);
ExDataSize := ExDataRecord.Stream.Size;
Stream.WriteBuffer(ExDataSize, 2);
Stream.CopyFrom(ExDataRecord.Stream, 0);
end;
end;
//
// Ïðîöåäóðà ïåðåêðûâàåò çàïîëíåíèå ñòðóêòóðû TCentralDirectoryFileHeaderEx
// è áåðåò ýòè äàííûå èç ðàíåå ñôîðìèðîâàííîãî àðõèâà.
// =============================================================================
procedure TFWZipModifier.FillItemCDFHeader(CurrentItem: TFWZipWriterItem;
var Value: TCentralDirectoryFileHeaderEx);
var
OldItem: TFWZipReaderItemFriendly;
NewItem: TFWZipModifierItem;
Reader: TFWZipReader;
FileDate: Cardinal;
begin
NewItem := TFWZipModifierItem(CurrentItem);
// ïðîâåðêà, ðàáîòàåì ëè ìû ñ ýëåìåíòîì, äàííûå êîòîðîãî çàïîëíÿþòñÿ âðó÷íóþ?
if not NewItem.UseExternalData then
begin
inherited;
Exit;
end;
Reader := FReaderList[NewItem.ReaderIndex].Reader;
OldItem := TFWZipReaderItemFriendly(Reader.Item[NewItem.OriginalItemIndex]);
// ïîëíîñòüþ ïåðåçàïèñûâàåì âñå äàííûå ñòðóêòóðû
// èñêëþ÷åíèåì ÿâëÿåòñÿ ïîëå RelativeOffsetOfLocalHeader
// íî îíî ðåèíèöèàëèçèðóåòñÿ ïîñëå âûçîâà äàííîãî ìåòîäà
Value := OldItem.CentralDirFileHeaderEx;
// Rouse_ 11.11.2023
// åñëè èìÿ â àðõèâå áûëî ïåðåíàçíà÷åíî,
// òî ìåíÿåì åãî ñ ïðàâêîé âðåìåíè ïîñëåäíåãî èçìåíåíèÿ
if NewItem.OverloadItemPath <> '' then
begin
Value.Filename := NewItem.OverloadItemPath;
Value.Header.FilenameLength :=
StringLength(NewItem.OverloadItemPath, CurrentItem.UseUTF8String);
Value.Attributes.ftLastWriteTime := GetCurrentFileTime;
FileDate := FileTimeToLocalFileDate(Value.Attributes.ftLastWriteTime);
Value.Header.LastModFileTimeTime := FileDate and $FFFF;
Value.Header.LastModFileTimeDate := FileDate shr 16;
end;
end;
//
// Ðàñøèðÿåì êîëëåêöèþ
// =============================================================================
function TFWZipModifier.GetItemClass: TFWZipWriterItemClass;
begin
Result := TFWZipModifierItem;
end;
//
// Âîçâðàùàåì ñûëêó íà âíóòðåííèé ðèäåð
// =============================================================================
function TFWZipModifier.GetReader(Index: Integer): TFWZipReader;
begin
if (Index < 0) or (Index >= ReadersCount) then
raise EFWZipModifier.CreateFmt('Invalid reader index value (%d).', [Index]);
Result := FReaderList[Index].Reader;
end;
//
// Çàäà÷à ïðîöåäóðû ñîáðàòü âñå ExData â ëîêàëüíîå õðàíèëèùå,
// ÷òîáû èõ ìîæíî áûëî ïðèñîåäèíèòü ê ñòðóêòóðå àðõèâà íà ýòàïå ðåáèëäà
// =============================================================================
procedure TFWZipModifier.OnLoadExData(Sender: TObject; ItemIndex: Integer;
Tag: Word; Data: TStream);
var
Index, ExDataCount: Integer;
ExData: TExDataRecord;
begin
Index := ReadersCount - 1;
if Index >= 0 then
begin
ExData.Index := ItemIndex;
ExData.Tag := Tag;
ExData.Stream := TMemoryStream.Create;
ExData.Stream.CopyFrom(Data, 0);
ExDataCount := Length(FReaderList[Index].ExDataRecords);
SetLength(FReaderList[Index].ExDataRecords, ExDataCount + 1);
FReaderList[Index].ExDataRecords[ExDataCount] := ExData;
end;
end;
//
// Âîçâðàùàåì êîëè÷åñòâî äîñòóïíûõ ðèäåðîâ
// =============================================================================
function TFWZipModifier.ReadersCount: Integer;
begin
Result := Length(FReaderList);
end;
end.