//////////////////////////////////////////////////////////////////////////////// // // **************************************************************************** // * Project : FWZip // * Unit Name : FWZipStream // * Purpose : Вспомогательные стримы для поддержки шифрования на лету, // * : и усеченного заголовка ZLib, // * : для поддержки разбитых на тома архивов и прочее утилитарные // * : стримы для проверки целостности архива // * 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/ // // Описание идеи TFWZipItemStream: // При помещении в архив сжатого блока данных методом Deflate у него // отрезается двухбайтный заголовок в котором указаны параметры сжатия. // Т.е. в архив помещаются сами данные в чистом виде. // Для распаковки необходимо данный заголовок восстановить. // TFWZipItemStream позволяет добавить данный заголовок "на лету" // абсолютно прозрачно для внешнего кода. // Сам заголовок генерируется в конструкторе и подставляется в методе Read. // Так-же класс, выступая посредником между двумя стримами, // позволяет производить шифрование и дешифровку передаваемых данных. // Шифрование производится в методе Write, в этот момент класс является // посредником между TCompressionStream и результирующим стримом. // Дешифрование осуществляется в методе Read, в этот момент класс является // посредником между стримом со сжатыми и // пошифрованными данными и TDecompressionStream. // unit FWZipStream; {$IFDEF FPC} {$MODE Delphi} {$H+} {$ENDIF} interface {$I fwzip.inc} uses {$IFNDEF FPC} Windows, // для инлайн RenameFile {$ENDIF} Classes, SysUtils, Math, FWZipCrypt, FWZipZLib, {$IFDEF USE_AUTOGENERATED_ZLIB_HEADER} FWZipConsts, {$ENDIF} FWZipUtils; const NO_STREAM = -1; MinPartSize = {$IFDEF UNIT_TEST}100{$ELSE}$10000{$ENDIF}; type TFWZipItemStream = class(TStream) private FOwner: TStream; FCryptor: TFWZipCryptor; FDecryptor: TFWZipDecryptor; FSize, FStart, FPosition: Int64; {$IFDEF USE_AUTOGENERATED_ZLIB_HEADER} FHeader: Word; {$ENDIF} protected function GetSize: Int64; override; public constructor Create(AOwner: TStream; Cryptor: TFWZipCryptor; Decryptor: TFWZipDecryptor; {%H-}CompressLevel: Byte; ASize: Int64); function Seek(Offset: Longint; Origin: Word): Longint; overload; override; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override; function Read(var Buffer; Count: Longint): Longint; override; function Write(const {%H-}Buffer; Count: Longint): Longint; override; end; EFWZipItemItemUnpackedStreamException = class(Exception); // Виртуальный стрим данных. // Используется для более привычной работы с незапакованным блоком данных, // расположенного в архиве TFWZipItemItemUnpackedStream = class(TStream) private FOwnerStream: TStream; FOffset: Int64; FSize, FPosition: Integer; protected function GetSize: Int64; override; procedure SetSize({%H-}NewSize: Longint); override; public constructor Create; overload; constructor Create(Owner: TStream; Offset: Int64; ASize: Integer); overload; function Read(var Buffer; Count: Longint): Longint; override; function Write(const {%H-}Buffer; {%H-}Count: Longint): Longint; override; function Seek(Offset: Longint; Origin: Word): Longint; override; end; // TFakeStream предназначен для проверки архива на целостность TFakeStream = class(TStream) private FSize: Int64; FPosition: Int64; protected procedure SetSize(const NewSize: Int64); override; public function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override; function Write(const {%H-}Buffer; Count: Longint): Longint; override; function Read(var {%H-}Buffer; {%H-}Count: Longint): Longint; override; end; TFWMultiStreamMode = (msmRead, msmWrite); EFWMultiStreamException = class(Exception) public constructor Create(ADiskNumber: Integer); overload; constructor Create(const AMessage: string); overload; end; TFWMultiStreamClass = class of TFWAbstractMultiStream; TFWLastVolumesType = (lvtLastPart, lvtCentralDirectory); // Данный стрим используется при работе с архивом разбитым на тома TFWAbstractMultiStream = class(TStream) private FMode: TFWMultiStreamMode; FCurrentDiskData: TStream; FPosition: Int64; procedure CheckMode(AMode: TFWMultiStreamMode); function CurrentDiskNumber: Integer; function CalcOffset(DiskNumber: Integer): Int64; function UpdateCurrentDiskData: Integer; protected function GetNextWriteVolume: TStream; virtual; abstract; procedure GetStream(DiskNumber: Integer; var DiskData: TStream); virtual; abstract; function GetTotalSize: Int64; virtual; abstract; function GetVolumeSizeByIndex(Index: Integer): Int64; virtual; abstract; procedure TrimFromDiskNumber(Index: Integer); virtual; abstract; property VolumeSize[Index: Integer]: Int64 read GetVolumeSizeByIndex; procedure UpdateVolumeSize; virtual; abstract; /// /// Метод должен вызываться только для режима msmWrite после окончания /// записи архива. Применяется для закрытия последнего дома и его переименования. /// procedure FinallyWrite; virtual; protected procedure SetSize(const NewSize: Int64); override; public constructor Create(AMode: TFWMultiStreamMode); reintroduce; procedure GetRelativeInfo(out DiskNumber: Integer; out RealtiveOffset: Int64); function GetDiskCount: Integer; virtual; abstract; function GetWriteVolumeSize: Int64; virtual; abstract; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override; function Seek(DiskNumber: Integer; Offset: Int64): Int64; overload; /// /// Начинает новый том архива даже если предыдущий был заполнен не до конца /// Работает только в режиме msmWrite /// procedure StartNewVolume; property Mode: TFWMultiStreamMode read FMode; end; EFWFileMultiStreamException = class(Exception); TReadSizeMode = (rsmQuick, rsmFull); // Используется для работы с томами архива доступным из файловой системы TFWFileMultiStream = class(TFWAbstractMultiStream) private FCurrentStreamNumber: Integer; FCurrentStream: TFileStream; FFilePath: string; FVolumesPath: TStringList; FTotalSize, FVolumeSize: Int64; FReadVolumesSize, FWriteVolumesSize: array of Int64; function AddNewVolume: TStream; procedure FillFilesList(const FilePath: string; ReadSizeMode: TReadSizeMode); procedure FillFilesSize(ReadSizeMode: TReadSizeMode); protected {%H-}constructor Create(const FilePath: string; AMode: TFWMultiStreamMode; ReadSizeMode: TReadSizeMode; PartSize: Int64); function GetNextWriteVolume: TStream; override; procedure GetStream(DiskNumber: Integer; var DiskData: TStream); override; function GetTotalSize: Int64; override; function GetVolumeSizeByIndex(Index: Integer): Int64; override; procedure TrimFromDiskNumber(Index: Integer); override; procedure UpdateVolumeSize; override; procedure FinallyWrite; override; protected function GetVolumeExt(Index: Integer): string; virtual; // если имена файлов не .zХХ то перекрываем эту процедуру в наследнике public constructor CreateRead(const FilePath: string; ReadSizeMode: TReadSizeMode = rsmFull); constructor CreateWrite(const FilePath: string; PartSize: Int64 = MinPartSize); destructor Destroy; override; function GetDiskCount: Integer; override; function GetWriteVolumeSize: Int64; override; end; implementation const E_READONLY = 'TFWZipItemItemUnpackedStream работает только в режиме ReadOnly'; { TFWZipItemStream } constructor TFWZipItemStream.Create(AOwner: TStream; Cryptor: TFWZipCryptor; Decryptor: TFWZipDecryptor; CompressLevel: Byte; ASize: Int64); begin inherited Create; FOwner := AOwner; FCryptor := Cryptor; FDecryptor := Decryptor; FSize := ASize; FStart := AOwner.Position; FPosition := 0; // Rouse_ 30.10.2013 // Устаревший код {$IFDEF USE_AUTOGENERATED_ZLIB_HEADER} // Rouse_ 17.03.2011 // Размерчик все-же нужно править увеличивая на размер заголовка Inc(FSize, 2); // Восстанавливаем пропущенный заголовок ZLib стрима // см. deflate.c - int ZEXPORT deflate (strm, flush) // uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; FHeader := (Z_DEFLATED + (7 {32k Window size} shl 4)) shl 8; // if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) // level_flags = 0; // else if (s->level < 6) // level_flags = 1; // else if (s->level == 6) // level_flags = 2; // else // level_flags = 3; // // сам CompressLevel (level_flags) // берется из уже заполненного GeneralPurposeBitFlag // здесь мы из битовой маски восстанавливаем оригинальные значения case CompressLevel of PBF_COMPRESS_SUPERFAST: CompressLevel := 0; PBF_COMPRESS_FAST: CompressLevel := 1; PBF_COMPRESS_NORMAL: CompressLevel := 2; PBF_COMPRESS_MAXIMUM: CompressLevel := 3; end; // header |= (level_flags << 6); FHeader := FHeader or (CompressLevel shl 6); // if (s->strstart != 0) header |= PRESET_DICT; // словарь не используется - оставляем без изменений // header += 31 - (header % 31); Inc(FHeader, 31 - (FHeader mod 31)); // putShortMSB(s, header); FHeader := (FHeader shr 8) + (FHeader and $FF) shl 8; {$ENDIF} end; function TFWZipItemStream.GetSize: Int64; begin Result := FSize; end; function TFWZipItemStream.Read(var Buffer; Count: Integer): Longint; var P: PByte; DecryptBuff: Pointer; begin // Rouse_ 30.10.2013 // Устаревший код {$IFDEF USE_AUTOGENERATED_ZLIB_HEADER} if FPosition = 0 then begin // если зачитываются данные с самого начала // необходимо перед ними разместить заголовок ZLib P := @FHeader; Move(P^, Buffer, 2); FOwner.Position := FStart; P := @Buffer; Inc(P, 2); if Count > Size then Count := Size; FOwner.Position := FStart; if FDecryptor <> nil then begin // в случае если файл зашифрован, производим расшифровку блока GetMem(DecryptBuff, Count - 2); try Result := FOwner.Read(DecryptBuff^, Count - 2); FDecryptor.DecryptBuffer(DecryptBuff, Result); Move(DecryptBuff^, P^, Result); finally FreeMem(DecryptBuff); end; end else Result := FOwner.Read(P^, Count - 2); Inc(Result, 2); Inc(FPosition, Result); end else begin FOwner.Position := FStart + Position - 2; {$ELSE} begin FOwner.Position := FStart + Position; {$ENDIF} if Count > Size - Position then Count := Size - Position; if FDecryptor <> nil then begin // в случае если файл зашифрован, производим расшифровку блока GetMem(DecryptBuff, Count); try Result := FOwner.Read(DecryptBuff^, Count); FDecryptor.DecryptBuffer(DecryptBuff, Result); P := @Buffer; Move(DecryptBuff^, P^, Result); finally FreeMem(DecryptBuff); end; end else Result := FOwner.Read(Buffer, Count); Inc(FPosition, Result); end; end; function TFWZipItemStream.Seek(Offset: Integer; Origin: Word): Longint; begin Result := Seek(Int64(Offset), TSeekOrigin(Origin)); end; function TFWZipItemStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; begin case Origin of soBeginning: FPosition := Offset; soCurrent: Inc(FPosition, Offset); soEnd: FPosition := Size + Offset; end; Result := FPosition; end; function TFWZipItemStream.Write(const Buffer; Count: Integer): Longint; var EncryptBuffer: PByte; begin if FCryptor = nil then Result := FOwner.Write(Buffer, Count) else begin // криптуем буфер GetMem(EncryptBuffer, Count); try Move(Buffer, EncryptBuffer^, Count); // Rouse_ 31.10.2013 // Устаревший код {$IFDEF USE_AUTOGENERATED_ZLIB_HEADER} // Шифровать блок нужно пропустив двубайтный заголовок ZLib if FPosition = 0 then begin Inc(EncryptBuffer, 2); FCryptor.EncryptBuffer(EncryptBuffer, Count - 2); Dec(EncryptBuffer, 2); end else {$ENDIF} FCryptor.EncryptBuffer(EncryptBuffer, Count); Result := FOwner.Write(EncryptBuffer^, Count); finally FreeMem(EncryptBuffer); end; end; Inc(FPosition, Result); end; { TFWZipItemItemUnpackedStream } constructor TFWZipItemItemUnpackedStream.Create; begin raise EFWZipItemItemUnpackedStreamException.Create( 'Неверный вызов конструктора'); end; constructor TFWZipItemItemUnpackedStream.Create(Owner: TStream; Offset: Int64; ASize: Integer); begin FOwnerStream := Owner; FOffset := Offset; FSize := ASize; end; function TFWZipItemItemUnpackedStream.GetSize: Int64; begin Result := FSize; end; function TFWZipItemItemUnpackedStream.Read(var Buffer; Count: Longint): Longint; begin if FPosition + Count > FSize then Count := FSize - FPosition; FOwnerStream.Position := FOffset + FPosition; Result := FOwnerStream.Read(Buffer, Count); Inc(FPosition, Result); end; function TFWZipItemItemUnpackedStream.Seek(Offset: Longint; Origin: Word): Longint; begin case Origin of soFromBeginning: FPosition := Offset; soFromCurrent: Inc(FPosition, Offset); soFromEnd: FPosition := Size + Offset; end; if FPosition < 0 then FPosition := 0; if FPosition > FSize then FPosition := FSize; Result := FPosition; end; procedure TFWZipItemItemUnpackedStream.SetSize(NewSize: Longint); begin raise EFWZipItemItemUnpackedStreamException.Create(E_READONLY); end; function TFWZipItemItemUnpackedStream.{%H-}Write(const Buffer; Count: Longint): Longint; begin raise EFWZipItemItemUnpackedStreamException.Create(E_READONLY); end; { TFakeStream } function TFakeStream.{%H-}Read(var Buffer; Count: Longint): Longint; begin raise Exception.Create('TFakeStream.Read'); end; function TFakeStream.Write(const Buffer; Count: Longint): Longint; begin FSize := FSize + Count; Result := Count; end; function TFakeStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; begin case Origin of soBeginning: FPosition := Offset; soCurrent: Inc(FPosition, Offset); soEnd: FPosition := FSize + Offset; end; Result := FPosition; end; procedure TFakeStream.SetSize(const NewSize: Int64); begin FSize := NewSize; end; { EFWMultiStreamException } constructor EFWMultiStreamException.Create(ADiskNumber: Integer); begin inherited CreateFmt('Can not find disk image №%d', [ADiskNumber]); end; constructor EFWMultiStreamException.Create(const AMessage: string); begin inherited Create(AMessage); end; { TFWAbstractMultiStream } function TFWAbstractMultiStream.CalcOffset(DiskNumber: Integer): Int64; begin Result := FPosition - VolumeSize[DiskNumber]; end; procedure TFWAbstractMultiStream.CheckMode(AMode: TFWMultiStreamMode); begin if FMode <> AMode then if FMode = msmRead then raise EFWMultiStreamException.Create('Can`t write data on read.') else raise EFWMultiStreamException.Create('Can`t read data on write.'); end; constructor TFWAbstractMultiStream.Create(AMode: TFWMultiStreamMode); begin FMode := AMode; end; function TFWAbstractMultiStream.CurrentDiskNumber: Integer; var I: Integer; begin Result := 0; for I := GetDiskCount - 1 downto 0 do begin if VolumeSize[I] <= FPosition then begin Result := I; Break; end; end; end; procedure TFWAbstractMultiStream.FinallyWrite; begin CheckMode(msmWrite); end; procedure TFWAbstractMultiStream.GetRelativeInfo(out DiskNumber: Integer; out RealtiveOffset: Int64); begin DiskNumber := CurrentDiskNumber; RealtiveOffset := CalcOffset(DiskNumber); end; function TFWAbstractMultiStream.Read(var Buffer; Count: Longint): Longint; var PartialRead: Longint; P: PByte; begin CheckMode(msmRead); Result := 0; while Result < Count do begin P := PByte(@Buffer); Inc(P, Result); PartialRead := FCurrentDiskData.Read(P^, Count - Result); if PartialRead = 0 then raise EFWMultiStreamException.Create('Ошибка чтения данных.'); Inc(Result, PartialRead); Inc(FPosition, PartialRead); if FCurrentDiskData.Position = FCurrentDiskData.Size then begin GetStream(CurrentDiskNumber, FCurrentDiskData); if FCurrentDiskData = nil then Break; end; end; end; function TFWAbstractMultiStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; var DiskNumber: Integer; begin case TSeekOrigin(Origin) of soBeginning: FPosition := Offset; soCurrent: Inc(FPosition, Offset); soEnd: FPosition := GetTotalSize + Offset; end; if FPosition < 0 then FPosition := 0; if FPosition > GetTotalSize then FPosition := GetTotalSize; DiskNumber := UpdateCurrentDiskData; FCurrentDiskData.Seek(CalcOffset(DiskNumber), soBeginning); Result := FPosition; end; function TFWAbstractMultiStream.Seek(DiskNumber: Integer; Offset: Int64): Int64; begin if (DiskNumber < 0) or (DiskNumber >= GetDiskCount) then raise EFWMultiStreamException.Create(DiskNumber); Offset := VolumeSize[DiskNumber] + Offset; Result := Seek(Offset, soBeginning); end; procedure TFWAbstractMultiStream.SetSize(const NewSize: Int64); var TotalRemain, MaxVolumeSize, TotalSize: Int64; begin CheckMode(msmWrite); TotalSize := GetTotalSize; // Если изменения размера нет, то и нечего делать if TotalSize = NewSize then Exit; // Размер стрима уменьшается if TotalSize > NewSize then begin Position := NewSize; FCurrentDiskData.Size := CalcOffset(CurrentDiskNumber); TrimFromDiskNumber(CurrentDiskNumber); Exit; end; // В противном случае увеличивать будем с самого последнего тома GetStream(GetDiskCount - 1, FCurrentDiskData); // Которого может и не быть if FCurrentDiskData = nil then FCurrentDiskData := GetNextWriteVolume; TotalRemain := NewSize - TotalSize; MaxVolumeSize := GetWriteVolumeSize; while TotalRemain > 0 do begin if FCurrentDiskData.Size + TotalRemain <= MaxVolumeSize then begin FCurrentDiskData.Size := FCurrentDiskData.Size + TotalRemain; UpdateVolumeSize; Exit; end; Dec(TotalRemain, MaxVolumeSize - FCurrentDiskData.Size); FCurrentDiskData.Size := MaxVolumeSize; UpdateVolumeSize; FCurrentDiskData := GetNextWriteVolume; end; end; procedure TFWAbstractMultiStream.StartNewVolume; begin CheckMode(msmWrite); if Position <> Size then raise EFWMultiStreamException.Create('Нельзя завершать текущий том находясь в середине архива.'); if FCurrentDiskData <> nil then if FCurrentDiskData.Size > 0 then // Rouse_ 01.09.2023 // Фикс критической ошибки, не обновлялся внутренний стрим обьекта FCurrentDiskData := GetNextWriteVolume; end; function TFWAbstractMultiStream.UpdateCurrentDiskData: Integer; begin Result := CurrentDiskNumber; GetStream(Result, FCurrentDiskData); if FCurrentDiskData = nil then raise EFWMultiStreamException.Create(Result); end; function TFWAbstractMultiStream.Write(const Buffer; Count: Longint): Longint; var PartialWrite: LongInt; WriteSize: Int64; P: PByte; begin CheckMode(msmWrite); Result := 0; WriteSize := GetWriteVolumeSize; if FCurrentDiskData = nil then FCurrentDiskData := GetNextWriteVolume; while Result < Count do begin PartialWrite := {%H-}Min(Count - Result, WriteSize - FCurrentDiskData.Position); P := PByte(@Buffer); Inc(P, Result); if FCurrentDiskData.Write(P^, PartialWrite) <> PartialWrite then raise EFWMultiStreamException.Create('Ошибка записи данных.'); Inc(Result, PartialWrite); Inc(FPosition, PartialWrite); UpdateVolumeSize; if FCurrentDiskData.Position = WriteSize then FCurrentDiskData := GetNextWriteVolume; end; end; { TFWFileMultiStream } function TFWFileMultiStream.AddNewVolume: TStream; var NewVolumePath: string; begin FCurrentStreamNumber := FVolumesPath.Count; NewVolumePath := ChangeFileExt(FFilePath, GetVolumeExt(FCurrentStreamNumber + 1)); FVolumesPath.Add(NewVolumePath); SetLength(FReadVolumesSize, FVolumesPath.Count); SetLength(FWriteVolumesSize, FVolumesPath.Count); FreeAndNil(FCurrentStream); ForceDirectoriesEx(ExtractFilePath(NewVolumePath)); FCurrentStream := TFileStream.Create(NewVolumePath, fmCreate or fmShareDenyWrite); UpdateVolumeSize; Result := FCurrentStream; end; constructor TFWFileMultiStream.Create(const FilePath: string; AMode: TFWMultiStreamMode; ReadSizeMode: TReadSizeMode; PartSize: Int64); begin FCurrentStreamNumber := NO_STREAM; FFilePath := PathCanonicalize(FilePath); FVolumesPath := TStringList.Create; if AMode = msmRead then FillFilesList(FFilePath, ReadSizeMode) else begin if PartSize < MinPartSize then raise EFWFileMultiStreamException.CreateFmt( 'Указан слишком маленький размер тома (%d), минимальный размер = %d', [PartSize, MinPartSize]); FVolumeSize := PartSize; end; inherited Create(AMode); end; constructor TFWFileMultiStream.CreateRead(const FilePath: string; ReadSizeMode: TReadSizeMode); begin Create(FilePath, msmRead, ReadSizeMode, 0); end; constructor TFWFileMultiStream.CreateWrite(const FilePath: string; PartSize: Int64); begin Create(FilePath, msmWrite, rsmQuick, PartSize); end; destructor TFWFileMultiStream.Destroy; begin if Mode = msmWrite then FinallyWrite else FreeAndNil(FCurrentStream); FVolumesPath.Free; inherited; end; procedure TFWFileMultiStream.FillFilesList( const FilePath: string; ReadSizeMode: TReadSizeMode); var I: Integer; SplitFilePath: string; begin FVolumesPath.Clear; if not FileExists(FilePath) then raise EFWFileMultiStreamException.CreateFmt('File not found: "%s"', [FilePath]); I := 1; SplitFilePath := ChangeFileExt(FilePath, GetVolumeExt(I)); while FileExists(SplitFilePath) do begin FVolumesPath.Add(SplitFilePath); Inc(I); SplitFilePath := ChangeFileExt(FilePath, GetVolumeExt(I)); end; FVolumesPath.Add(FilePath); FillFilesSize(ReadSizeMode); end; procedure TFWFileMultiStream.FillFilesSize(ReadSizeMode: TReadSizeMode); var F: TFileStream; I, FirstVolumeSize, Tmp: Integer; begin FTotalSize := 0; SetLength(FReadVolumesSize, FVolumesPath.Count); if ReadSizeMode = rsmFull then begin for I := 0 to FVolumesPath.Count - 1 do begin F := TFileStream.Create(FVolumesPath[I], fmShareDenyWrite); try // Каждая запись содержит размер с которого она начинается в плоском массиве FReadVolumesSize[I] := FTotalSize; Inc(FTotalSize, F.Size); finally F.Free; end; end; Exit; end; F := TFileStream.Create(FVolumesPath[0], fmShareDenyWrite); try FirstVolumeSize := F.Size; finally F.Free; end; I := FVolumesPath.Count; repeat Dec(I); F := TFileStream.Create(FVolumesPath[I], fmShareDenyWrite); try FReadVolumesSize[I] := F.Size; finally F.Free; end; until FReadVolumesSize[I] = FirstVolumeSize; for I := 0 to FVolumesPath.Count - 1 do begin Tmp := FReadVolumesSize[I]; FReadVolumesSize[I] := FTotalSize; if Tmp = 0 then Inc(FTotalSize, FirstVolumeSize) else Inc(FTotalSize, Tmp); end; end; procedure TFWFileMultiStream.FinallyWrite; var LastDiskIndex: Integer; begin inherited; FreeAndNil(FCurrentStream); FCurrentStreamNumber := NO_STREAM; LastDiskIndex := GetDiskCount - 1; while LastDiskIndex >= 0 do begin if FWriteVolumesSize[LastDiskIndex] = 0 then begin DeleteFile(FVolumesPath[LastDiskIndex]); Dec(LastDiskIndex); end else Break; end; if LastDiskIndex >= 0 then begin // Rouse_ 21.10.2020 // Если файл с именем архива уже существует, то RenameFile не сможет // переименовать последний том архива. // Спасибо Владиславу Нечепоренко за найденую ошибку. DeleteFile(FFilePath); RenameFile(FVolumesPath[LastDiskIndex], FFilePath); end; SetLength(FReadVolumesSize, 0); SetLength(FWriteVolumesSize, 0); FVolumesPath.Clear; end; function TFWFileMultiStream.GetDiskCount: Integer; begin Result := FVolumesPath.Count; end; function TFWFileMultiStream.GetNextWriteVolume: TStream; begin if (FCurrentStreamNumber < 0) or (FCurrentStreamNumber >= FVolumesPath.Count - 1) then Result := AddNewVolume else GetStream(FCurrentStreamNumber + 1, Result); end; procedure TFWFileMultiStream.GetStream(DiskNumber: Integer; var DiskData: TStream); const OpenMode: array [TFWMultiStreamMode] of Word = (fmShareDenyWrite, fmOpenReadWrite or fmShareExclusive); begin if FCurrentStreamNumber = DiskNumber then begin DiskData := FCurrentStream; Exit; end; FCurrentStreamNumber := DiskNumber; FreeAndNil(FCurrentStream); DiskData := nil; if (DiskNumber < 0) or (DiskNumber >= FVolumesPath.Count) then begin if FMode = msmRead then Exit; if DiskNumber > FVolumesPath.Count then Exit; DiskData := AddNewVolume; Exit; end; if FileExists(FVolumesPath[DiskNumber]) then begin FCurrentStream := TFileStream.Create(FVolumesPath[DiskNumber], OpenMode[FMode]); DiskData := FCurrentStream; end; end; function TFWFileMultiStream.GetTotalSize: Int64; begin Result := FTotalSize; end; function TFWFileMultiStream.GetVolumeExt(Index: Integer): string; var Tmp, CharCount: Integer; begin if Index < 100 then Result := Format('.z%.2d', [Index]) else begin Tmp := Index div 100; CharCount := 2; while Tmp > 0 do begin Inc(CharCount); Tmp := Tmp div 10; end; Result := Format('.z%.' + IntToStr(CharCount) + 'd', [Index]); end; end; function TFWFileMultiStream.GetVolumeSizeByIndex(Index: Integer): Int64; begin Result := FReadVolumesSize[Index]; end; function TFWFileMultiStream.GetWriteVolumeSize: Int64; begin Result := FVolumeSize; end; procedure TFWFileMultiStream.TrimFromDiskNumber(Index: Integer); var I: Integer; begin Inc(Index); SetLength(FReadVolumesSize, Index); SetLength(FWriteVolumesSize, Index); for I := FVolumesPath.Count - 1 downto Index do begin DeleteFile(PChar(FVolumesPath[I])); FVolumesPath.Delete(I); end; UpdateVolumeSize; end; procedure TFWFileMultiStream.UpdateVolumeSize; var I: Integer; begin if FCurrentStream = nil then Exit; if FCurrentStreamNumber < 0 then Exit; FWriteVolumesSize[FCurrentStreamNumber] := FCurrentStream.Size; FTotalSize := 0; for I := 0 to Length(FReadVolumesSize) - 1 do begin FReadVolumesSize[I] := FTotalSize; Inc(FTotalSize, FWriteVolumesSize[I]); end; end; end.