403 lines
17 KiB
ObjectPascal
403 lines
17 KiB
ObjectPascal
////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// ****************************************************************************
|
||
// * Project : FWZip
|
||
// * Unit Name : FWZipModifier
|
||
// * Purpose : Класс для модификации созданного ранее ZIP архива
|
||
// * Author : Александр (Rouse_) Багель
|
||
// * Copyright : © Fangorn Wizards Lab 1998 - 2015.
|
||
// * Version : 1.0.11
|
||
// * 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
|
||
// http://zlib.net/zlib-1.2.5.tar.gz
|
||
// http://www.base2ti.com/
|
||
//
|
||
|
||
unit FWZipModifier;
|
||
{$mode delphi}
|
||
{$codepage UTF8}
|
||
|
||
interface
|
||
|
||
uses
|
||
LCLIntf, LCLType, LMessages, windows,
|
||
Classes,
|
||
SysUtils,
|
||
FWZipConsts,
|
||
FWZipReader,
|
||
FWZipWriter,
|
||
FWZipZLib;
|
||
|
||
type
|
||
TReaderIndex = Integer;
|
||
|
||
TFWZipModifierItem = class(TFWZipWriterItem)
|
||
private
|
||
FReaderIndex: TReaderIndex; // индекс TFWZipReader в массиве TFWZipModifier.FReaderList
|
||
FOriginalItemIndex: Integer; // оригинальный индекс элемента в изначальном архиве
|
||
protected
|
||
property ReaderIndex: TReaderIndex read FReaderIndex write FReaderIndex;
|
||
property OriginalItemIndex: Integer read FOriginalItemIndex write FOriginalItemIndex;
|
||
public
|
||
constructor Create(Owner: TFWZipWriter;
|
||
const InitFilePath: string;
|
||
InitAttributes: TWin32FileAttributeData;
|
||
const InitFileName: string = ''); override;
|
||
end;
|
||
|
||
EFWZipModifier = class(Exception);
|
||
|
||
// Данная структура хранит все блоки ExData из подключаемых архивов
|
||
TExDataRecord = record
|
||
Index: Integer;
|
||
Tag: Word;
|
||
Stream: TMemoryStream;
|
||
end;
|
||
TExDataRecords = array of TExDataRecord;
|
||
|
||
// структура для хранения подключенного архива и его блоков ExData
|
||
TReaderData = record
|
||
Reader: TFWZipReader;
|
||
ExDataRecords: TExDataRecords;
|
||
end;
|
||
|
||
TReaderList = array of TReaderData;
|
||
|
||
TFWZipModifier = class(TFWZipWriter)
|
||
private
|
||
FReaderList: TReaderList;
|
||
function CheckZipFileIndex(Value: TReaderIndex): TReaderIndex;
|
||
function AddItemFromZip(AReader: TFWZipReader;
|
||
ReaderIndex: TReaderIndex; ItemIndex: Integer): Integer;
|
||
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 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; ItemsList: TStringList): Integer; overload;
|
||
end;
|
||
|
||
implementation
|
||
|
||
type
|
||
TFWZipReaderFriendly = class(TFWZipReader);
|
||
TFWZipReaderItemFriendly = class(TFWZipReaderItem);
|
||
|
||
{ TFWZipModifierItem }
|
||
|
||
//
|
||
// В конструкторе производим первичную инициализацию полей
|
||
// Сами поля ReaderIndex и OriginalItemIndex будут инициализироваться только
|
||
// при добавлении их посредством класса TFWZipModifier
|
||
// =============================================================================
|
||
constructor TFWZipModifierItem.Create(Owner: TFWZipWriter;
|
||
const InitFilePath: string; InitAttributes: TWin32FileAttributeData;
|
||
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;
|
||
var
|
||
Reader: TFWZipReader;
|
||
begin
|
||
CheckZipFileIndex(ReaderIndex);
|
||
Reader := FReaderList[ReaderIndex].Reader;
|
||
Result :=
|
||
AddItemFromZip(Reader, ReaderIndex, Reader.GetElementIndex(ItemPath));
|
||
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) >= 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])) >= 0 then
|
||
Inc(Result);
|
||
end;
|
||
|
||
//
|
||
// Функция переносит элемент в финальный архив из ранее добавленного архива.
|
||
// В качестве результата возвращает индекс элемента в списке.
|
||
// =============================================================================
|
||
function TFWZipModifier.AddItemFromZip(AReader: TFWZipReader;
|
||
ReaderIndex: TReaderIndex; ItemIndex: Integer): Integer;
|
||
var
|
||
OldItem: TFWZipReaderItemFriendly;
|
||
NewItem: TFWZipModifierItem;
|
||
begin
|
||
Result := -1;
|
||
if ItemIndex < 0 then Exit;
|
||
// Получаем указатель на элемент из уже существующего архива
|
||
OldItem := TFWZipReaderItemFriendly(AReader.Item[ItemIndex]);
|
||
// создаем новый элемент, который будем добавлять к новому архиву
|
||
NewItem := TFWZipModifierItem(
|
||
GetItemClass.Create(Self, '', OldItem.Attributes, OldItem.FileName));
|
||
// переключаем его в режим получения данных вручную
|
||
NewItem.UseExternalData := True;
|
||
// инициализируем ему индексы, дабы потом понять, откуда брать о нем данные
|
||
NewItem.ReaderIndex := ReaderIndex;
|
||
NewItem.OriginalItemIndex := ItemIndex;
|
||
// инициализируем внешние и рассчитываемые поля
|
||
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
|
||
Result := Length(FReaderList);
|
||
SetLength(FReaderList, Result + 1);
|
||
AReader := TFWZipReader.Create;
|
||
AReader.OnLoadExData := OnLoadExData;
|
||
AReader.LoadFromStream(FileStream, SFXOffset, ZipEndOffset);
|
||
FReaderList[Result].Reader := AReader;
|
||
end;
|
||
|
||
//
|
||
// Функция добавляет новый архив из которого можно брать готовые данные.
|
||
// В качестве результата возвращает индекс архива в списке добавленных.
|
||
// Параметры:
|
||
// FilePath - путь к добавляемому архиву
|
||
// SFXOffset и ZipEndOffset - его границы
|
||
// =============================================================================
|
||
function TFWZipModifier.AddZipFile(const FilePath: string;
|
||
SFXOffset, ZipEndOffset: Integer): TReaderIndex;
|
||
var
|
||
AReader: TFWZipReader;
|
||
begin
|
||
Result := Length(FReaderList);
|
||
SetLength(FReaderList, Result + 1);
|
||
AReader := TFWZipReader.Create;
|
||
AReader.OnLoadExData := OnLoadExData;
|
||
AReader.LoadFromFile(FilePath, SFXOffset, ZipEndOffset);
|
||
FReaderList[Result].Reader := AReader;
|
||
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]);
|
||
// рассчитываем его позицию в архиве
|
||
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
|
||
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;
|
||
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;
|
||
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;
|
||
end;
|
||
|
||
//
|
||
// Расширяем коллекцию
|
||
// =============================================================================
|
||
function TFWZipModifier.GetItemClass: TFWZipWriterItemClass;
|
||
begin
|
||
Result := TFWZipModifierItem;
|
||
end;
|
||
|
||
//
|
||
// Задача процедуры собрать все ExData в локальное хранилище,
|
||
// чтобы их можно было присоединить к структуре архива на этапе ребилда
|
||
// =============================================================================
|
||
procedure TFWZipModifier.OnLoadExData(Sender: TObject; ItemIndex: Integer;
|
||
Tag: Word; Data: TStream);
|
||
var
|
||
ReaderCount, ExDataCount: Integer;
|
||
ExData: TExDataRecord;
|
||
begin
|
||
ExData.Index := ItemIndex;
|
||
ExData.Tag := Tag;
|
||
ExData.Stream := TMemoryStream.Create;
|
||
ExData.Stream.CopyFrom(Data, 0);
|
||
ReaderCount := Length(FReaderList);
|
||
ExDataCount := Length(FReaderList[ReaderCount - 1].ExDataRecords);
|
||
SetLength(FReaderList[ReaderCount - 1].ExDataRecords, ExDataCount + 1);
|
||
FReaderList[ReaderCount - 1].ExDataRecords[ExDataCount] := ExData;
|
||
end;
|
||
|
||
end.
|