////////////////////////////////////////////////////////////////////////////////
//
//  ****************************************************************************
//  * Unit Name : GSStorage
//  * Purpose   : Обертк?на?структурированны?хранилищем данных (Com Storage)
//  * Author    : Александ?Багель
//  * Copyright : Цент?Гран?2001 - 2005 ?
//  * Version   : 1.34
//  ****************************************************************************
//

unit EM.GSStorage;

interface

uses
  WinApi.Windows, Classes, WinApi.ActiveX, AxCtrls, SysUtils, WinAPi.ShlObj;

const
  CLRF = #13#10;

type
  TStatStgEx = record
    pwcsName: String[30];
    dwType: Longint;
  end;

  TStatStgArray = array of TStatStgEx;
  TGSStorageEnum = packed record
    Count: Cardinal;
    ElementEnum: TStatStgArray;
  end;

  GSCustomStorageException = class(Exception);
  TGSStorageCursors = class;
  TGSCustomStorage = class;
  TGSStorage = class;

  TGSStorageCursor = class(TCollectionItem)
  private
    FLockCount: Integer;
    FID: ShortString;
    FStorageInterface: IStorage;
    FSubStorage: TGSStorageCursors;
    FMode: Longint;
    FOwner: TGSCustomStorage;
    FParent: TGSStorageCursor;
    procedure SetParam(const AOwner: TGSCustomStorage;
      AParent: TGSStorageCursor);
    function GetPath: String;
    function GetLock: Boolean;
    function GetLockCount: Integer;
    procedure Lock;
    procedure UnLock;
    property Locked: Boolean read GetLock;
    {$IFDEF CLASS_INSTANCE}
      class procedure AddInstance;
      class procedure ReleaseInstance;
    {$ENDIF}
  protected
    procedure Garbage;
    function ExistsObject(const AName: String; const AType: Longint;
      var Exists: Boolean): Boolean;
    function GetName: String;
  public
    {$IFDEF CLASS_INSTANCE}
      class function NumOfInstances: Integer;
    {$ENDIF}
    function Backward(var ACursor: TGSStorageCursor): Boolean;
    constructor Create(Collection: TCollection); override;
    function CreateStream(const AName: String): HRESULT;
    function CreateStorage(const AName: String;
      var ACursor: TGSStorageCursor): HRESULT;
    function Copy(const AName: String;
      const ACursor: TGSStorageCursor): HRESULT;
    function DeleteStream(const AName: String): HRESULT;
    function DeleteStorage(const AName: String): HRESULT;
    destructor Destroy; override;
    function Enumerate(var AData: TGSStorageEnum): HRESULT;
    procedure FreeMemAfterEnum(var AData: TGSStorageEnum);
    function FlushBuffer: HRESULT;
    function MoveTo(const AName: String;
      const ACursor: TGSStorageCursor): HRESULT;
    function OpenStorage(const AName: String;
      var ACursor: TGSStorageCursor; Verify: Boolean = True): HRESULT;
    function StreamExists(const AName: String): Boolean;
    function StorageExists(const AName: String): Boolean;
//    function ReadStream(const AName: String;
//      var AStream: TStream; Verify: Boolean = True): HRESULT;
    function ReadStream(const AName: String;
      AStream: TStream; Verify: Boolean = True): HRESULT;
    function Rename(const AName, ANewName: String): HRESULT;
    function WriteStream(const AName: String; const AStream: TMemoryStream): HRESULT;
    procedure Release;
    property Path: String read GetPath;
    property Storages: TGSStorageCursors read FSubStorage;
  end;

  TGSStorageCursors = class(TCollection)
  private
    function GetItem(Index: Integer): TGSStorageCursor;
    procedure SetItem(Index: Integer; const Value: TGSStorageCursor);
    function Add: TGSStorageCursor;
    {$IFDEF CLASS_INSTANCE}
      class procedure AddInstance;
      class procedure ReleaseInstance;
    {$ENDIF}
  protected
    function FindStorageByName(
      const AName: String; var Index: Cardinal): Boolean;
  public
    {$IFDEF CLASS_INSTANCE}
      class function NumOfInstances: Integer;
    {$ENDIF}
    constructor Create;
    destructor Destroy; override;
    property Items[Index: Integer]: TGSStorageCursor read GetItem write SetItem; default;
  end;

  TGSCustomStorage = class(TPersistent)
  private
    FRootStorage: TGSStorageCursor;
    FMode: Longint;
    FFileName: String;
    FCanCreate: Boolean;
    function GetActive: Boolean;
    {$IFDEF CLASS_INSTANCE}
      class procedure AddInstance;
      class procedure ReleaseInstance;
    {$ENDIF}
  protected
    function OpenFileEx(const AFileName: String;
      const CanCreate: Boolean; const AMode: LongInt;
      var ACursor: TGSStorageCursor): HRESULT;
    procedure Garbage;
    class function ClassSupport(const AFileName: String; Change: Boolean): HRESULT;
  public
    {$IFDEF CLASS_INSTANCE}
      class function NumOfInstances: Integer;
      constructor Create;
    {$ENDIF}
    destructor Destroy; override;
    function CreateCursor: TGSStorageCursor;
    function ForceStorage(APath: String; var ACursor: TGSStorageCursor): HRESULT;
    function OpenFileReadOnly(const AFileName: String;
      var ACursor: TGSStorageCursor): HRESULT;
    function OpenFile(const AFileName: String;
      const CanCreate: Boolean; var ACursor: TGSStorageCursor): HRESULT;
    function OpenStorage(APath: String; var ACursor: TGSStorageCursor): HRESULT;
    procedure CloseFile;
    function IsBussy: Boolean;
    function ReConnect(var AStorage: TGSStorage;
      var ACursor: TGSStorageCursor): HRESULT;
    class function Compress(const AFileName: String): HRESULT;
    class function IsStgValidBinaryFmt(const AFileName: String): HRESULT;
    property Active: Boolean read GetActive;
  end;

  TGSStorage = class(TGSCustomStorage);

 { TGInfoStorage = class(TGSCustomStorage)
  public
    function WriteInteger
  end; }

implementation

uses Variants, System.Win.ComObj;

{$IFDEF CLASS_INSTANCE}
var
  TGSCustomStorage_Instance: Integer = 0;
  TGSStorageCursor_Instance: Integer = 0;
  TGSStorageCursors_Instance: Integer = 0;
{$ENDIF}

{ TGSStorage }

//  Закрытие всех дочерних Storage
// =============================================================================
procedure TGSCustomStorage.CloseFile;
begin
  if Assigned(FRootStorage) then
  begin
    FreeAndNil(FRootStorage);
    {$IFDEF CREATE_FREE_LOG}
      CreateFreeLog(False, 'TGSCustomStorage.FRootStorage');
    {$ENDIF}
  end;
end;

function TGSCustomStorage.CreateCursor: TGSStorageCursor;
begin
  if FRootStorage = nil then
  begin
    Result := nil;
    Exit;
  end;
  Result := FRootStorage;
end;

destructor TGSCustomStorage.Destroy;
begin
  if Assigned(FRootStorage) then
    FRootStorage.Free;
  {$IFDEF CREATE_FREE_LOG}
    CreateFreeLog(False, 'TGSCustomStorage.FRootStorage');
  {$ENDIF}

  {$IFDEF CLASS_INSTANCE}
    ReleaseInstance;
  {$ENDIF}
  inherited;
end;


function TGSCustomStorage.IsBussy: Boolean;
var
  LocCount: Integer;
begin
  LocCount := 0;
  Inc(LocCount, FRootStorage.GetLockCount);
  Result := LocCount > 1;
end;


function TGSCustomStorage.ReConnect(var AStorage: TGSStorage;
  var ACursor: TGSStorageCursor): HRESULT;
begin
  AStorage.CloseFile;
  Result := AStorage.OpenFileEx(FFileName, FCanCreate, FMode, ACursor);
end;


//  Аналог ForceDirectory
// =============================================================================
function TGSCustomStorage.ForceStorage(APath: String;
  var ACursor: TGSStorageCursor): HRESULT;
var
  S: TStringList;
  I: Integer;
begin
  Result := S_FALSE;
  if APath = '' then
    raise Exception.Create(SysErrorMessage(ERROR_INVALID_PARAMETER));
  S := TStringList.Create;
  try
    ACursor := FRootStorage;
    if ACursor.GetName + '\' <> Copy(APath, 1, Length(ACursor.GetName) + 1) then
    begin
      Result := ERROR_PATH_NOT_FOUND;
      Exit;
    end;
    Delete(APath, 1, Length(ACursor.GetName) + 1);
    S.Text := StringReplace(APath, '\', CLRF, [rfReplaceAll]);
    for I := 0 to S.Count - 1 do
    begin
      if ACursor.StorageExists(S.Strings[I]) then
        Result := ACursor.OpenStorage(S.Strings[I], ACursor)
      else
        Result := ACursor.CreateStorage(S.Strings[I], ACursor);
      if Result <> S_OK then
      begin
        FRootStorage.Garbage;
        Exit;
      end;
    end;
  finally
    S.Free;
  end;
end;      

procedure TGSCustomStorage.Garbage;
begin
  if FRootStorage <> nil then
    FRootStorage.Garbage;
end;


//  открытие хранилищ?(?перезапись?ил?не?
// =============================================================================
function TGSCustomStorage.OpenFile(const AFileName: String;
  const CanCreate: Boolean; var ACursor: TGSStorageCursor): HRESULT;
begin
  Result := OpenFileEx(AFileName, CanCreate,
    STGM_READWRITE or STGM_SHARE_EXCLUSIVE, ACursor);
end;


//  Реализац? открыт? файл?
// =============================================================================
function TGSCustomStorage.OpenFileEx(const AFileName: String;
  const CanCreate: Boolean; const AMode: LongInt;
  var ACursor: TGSStorageCursor): HRESULT;
begin
  FMode := AMode;
  FCanCreate := CanCreate;
  FFileName := AFileName;
  CloseFile;
  FRootStorage := TGSStorageCursor.Create(nil);
  {$IFDEF CREATE_FREE_LOG}
    CreateFreeLog(True, 'TGSCustomStorage.FRootStorage');
  {$ENDIF}
  if CanCreate then
  begin
    ACursor := FRootStorage;
    ACursor.SetParam(Self, nil);
    Result := StgCreateDocfile(StringToOleStr(AFileName), FMode or STGM_CREATE, 0,
      ACursor.FStorageInterface);
    {$IFDEF CREATE_FREE_LOG}
      CreateFreeLog(True, 'TGSCustomStorage.ACursor.FStorageInterface = ' + IntToStr(Integer(ACursor.FStorageInterface)));
    {$ENDIF}
    ACursor.Lock;
  end
  else
  begin
    if not FileExists(AFileName) then
    begin
      Result := ERROR_FILE_NOT_FOUND;
      Exit;
    end;
    Result := StgIsStorageFile(StringToOleStr(AFileName));
    if Result <> S_OK then Exit;
    ACursor := FRootStorage;
    ACursor.SetParam(Self, nil);
    Result := StgOpenStorage(StringToOleStr(AFileName),
      nil, FMode, nil, 0, ACursor.FStorageInterface);
    {$IFDEF CREATE_FREE_LOG}
      CreateFreeLog(True, 'TGSCustomStorage.ACursor.FStorageInterface = ' + IntToStr(Integer(ACursor.FStorageInterface)));
    {$ENDIF}      
    ACursor.Lock;
  end;
end;


function TGSCustomStorage.OpenStorage(APath: String;
  var ACursor: TGSStorageCursor): HRESULT;
var
  S: TStringList;
  I: Integer;
begin
  Result := S_FALSE;
  if APath = '' then
    raise Exception.Create(SysErrorMessage(ERROR_INVALID_PARAMETER));
  S := TStringList.Create;
  try
    ACursor := FRootStorage;
    if APath[Length(APath)] <> '\' then APath := APath + '\';
    if ACursor.GetName + '\' <> Copy(APath, 1, Length(ACursor.GetName) + 1) then
    begin
      Result := ERROR_PATH_NOT_FOUND;
      Exit;
    end;
    Delete(APath, 1, Length(ACursor.GetName) + 1);
    S.Text := StringReplace(APath, '\', CLRF, [rfReplaceAll]);
    for I := 0 to S.Count - 1 do
      if ACursor.StorageExists(S.Strings[I]) then
      begin
        if ACursor.OpenStorage(S.Strings[I], ACursor) <> S_OK then Exit
      end
      else
        raise Exception.Create(SysErrorMessage(ERROR_PATH_NOT_FOUND));
    Result := S_OK;
  finally
    S.Free;
  end;
end;


//  Открытие хранилищ??режиме чтен?
// =============================================================================
function TGSCustomStorage.OpenFileReadOnly(
  const AFileName: String; var ACursor: TGSStorageCursor): HRESULT;
begin
  Result := OpenFileEx(AFileName, False, STGM_READ or STGM_SHARE_EXCLUSIVE,
    ACursor);
end;

{ TGSStorageCursors }

function TGSStorageCursors.Add: TGSStorageCursor;
begin
  Result := TGSStorageCursor(inherited Add);
end;

{$IFDEF CLASS_INSTANCE}
class procedure TGSStorageCursors.AddInstance;
begin
  Inc(TGSStorageCursors_Instance);
end;
{$ENDIF}

constructor TGSStorageCursors.Create;
begin
  inherited Create(TGSStorageCursor);
  {$IFDEF CLASS_INSTANCE}
    AddInstance;
  {$ENDIF}
end;

destructor TGSStorageCursors.Destroy;
begin
  {$IFDEF CLASS_INSTANCE}
    ReleaseInstance;
  {$ENDIF}
  inherited;
end;


//  Поис?открытог?Storage по имен?
// =============================================================================
function TGSStorageCursors.FindStorageByName(
  const AName: String; var Index: Cardinal): Boolean;
var
  I: Cardinal;
begin
  Result := False;
  Index := 0;
  if Count = 0 then Exit;
  for I := 0 to Count - 1 do
    if Items[I].GetName = AName then
    begin
      Result := True;
      Index := I;
      Break;
    end;
end;

function TGSStorageCursors.GetItem(Index: Integer): TGSStorageCursor;
begin
  Result := TGSStorageCursor(inherited GetItem(Index));
end;

{$IFDEF CLASS_INSTANCE}
class function TGSStorageCursors.NumOfInstances: Integer;
begin
  Result := TGSStorageCursors_Instance;
end;

class procedure TGSStorageCursors.ReleaseInstance;
begin
  Dec(TGSStorageCursors_Instance);
end;
{$ENDIF}

procedure TGSStorageCursors.SetItem(Index: Integer;
  const Value: TGSStorageCursor);
begin
  inherited SetItem(Index, Value);
end;

{ TGSStorageCursor }

{$IFDEF CLASS_INSTANCE}
class procedure TGSStorageCursor.AddInstance;
begin
  Inc(TGSStorageCursor_Instance);
end;
{$ENDIF}

//
// =============================================================================
function TGSStorageCursor.Backward(var ACursor: TGSStorageCursor): Boolean;
begin
  Result := FParent <> nil;
  if Result then
  begin
    UnLock;
    ACursor := FParent;
    ACursor.Lock;
    if not Locked then
      Self.Free;
  end;
end;

function TGSStorageCursor.Copy(const AName: String;
  const ACursor: TGSStorageCursor): HRESULT;
begin
  if ACursor = nil then
    raise EComponentError.Create('TGSStorageCursor.Copy() >> "ACursor = nil"');//SysErrorMessage(E_INVALIDARG));
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.Copy() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  //Result := FStorageInterface.CopyTo(0, nil, nil, ACursor.FStorageInterface);
  Result := FStorageInterface.MoveElementTo(StringToOleStr(AName),
    ACursor.FStorageInterface, StringToOleStr(AName), STGMOVE_COPY);
end;

constructor TGSStorageCursor.Create(Collection: TCollection);
var
  ID: TGUID;
begin
  inherited;
  {$IFDEF CLASS_INSTANCE}
    AddInstance;
  {$ENDIF}
  FSubStorage := TGSStorageCursors.Create;
  (*{$IFDEF CREATE_FREE_LOG}
    CreateFreeLog(True, 'TGSStorageCursor - конструкто?);
    CreateFreeLog(True, 'TGSStorageCursor.ACursor.FSubStorage');
  {$ENDIF}*)
  FLockCount := 0;
  if CreateGUID(ID) <> S_OK then
    raise EComponentError.Create('TGSStorageCursor.Create() >> "CreateGUID(ID) <> S_OK"');//SysErrorMessage(E_UNEXPECTED));
  FID := GUIDToString(ID);
end;

function TGSStorageCursor.CreateStorage(const AName: String;
  var ACursor: TGSStorageCursor): HRESULT;
var
  Index: Cardinal;
begin
{  if ACursor = nil then
    raise EComponentError.Create(SysErrorMessage(E_INVALIDARG));}
  if FSubStorage.FindStorageByName(AName, Index) then
  begin
    Result := ERROR_ALREADY_EXISTS;
    Exit;
  end;
  ACursor := FSubStorage.Add;
  ACursor.SetParam(FOwner, Self);
  Result := FStorageInterface.CreateStorage(StringToOleStr(AName),
    FMode, 0, 0, ACursor.FStorageInterface);
  {$IFDEF CREATE_FREE_LOG}
    CreateFreeLog(True, 'TGSStorageCursor.ACursor.FStorageInterface = ' + IntToStr(Integer(ACursor.FStorageInterface)));
  {$ENDIF}
  ACursor.Lock;
end;

function TGSStorageCursor.CreateStream(const AName: String): HRESULT;
var
  TmpStream:IStream;
begin
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.CreateStream( >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  Result := FStorageInterface.CreateStream(StringToOleStr(AName),
    FMode, 0, 0, TmpStream);
  TmpStream := nil;
end;

function TGSStorageCursor.DeleteStorage(const AName: String): HRESULT;
var
  Index: Cardinal;
begin
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.DeleteStorage() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  if FSubStorage.FindStorageByName(AName, Index) then
  begin
    { TODO : Думать - нужнал?ту?блокировка? }
    {if FSubStorage.Items[Index].Locked then
     raise EComponentError.Create(SysErrorMessage(ERROR_ACCESS_DENIED)); }
    FSubStorage.Delete(Index);
  end;
  Result := FStorageInterface.DestroyElement(StringToOleStr(AName));
end;

function TGSStorageCursor.DeleteStream(const AName: String): HRESULT;
begin
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.DeleteStream() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  Result := FStorageInterface.DestroyElement(StringToOleStr(AName));
end;

destructor TGSStorageCursor.Destroy;
begin
  {$IFDEF CREATE_FREE_LOG}
    //CreateFreeLog(False, 'TGSStorageCursor - деструктор');
    //CreateFreeLog(False, 'TGSStorageCursor.ACursor.FSubStorage');
    CreateFreeLog(False, 'TGSStorageCursor.FStorageInterface = ' + IntToStr(Integer(FStorageInterface)));
  {$ENDIF}
  FStorageInterface := nil;
  FreeAndNil(FSubStorage);
  {$IFDEF CLASS_INSTANCE}
    ReleaseInstance;
  {$ENDIF}
  inherited;
end;

function TGSStorageCursor.Enumerate(var AData: TGSStorageEnum): HRESULT;
var
  Enum: IEnumStatStg;
  TmpElement: TStatStg;
  ShellMalloc: IMalloc;
  Fetched: Int64;
begin
  if (CoGetMalloc(1, ShellMalloc) <> S_OK) or (ShellMalloc = nil) then
    raise EComponentError.Create('CoGetMalloc failed.');
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.Enumerate() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  Result := FStorageInterface.EnumElements(0, nil, 0, Enum);
  if Result = S_OK then
  begin
    AData.Count := 0;
    ZeroMemory(@AData, SizeOf(AData));
    Fetched := 1;
    while Fetched > 0 do
      if Enum.Next(1, TmpElement, @Fetched) = S_OK then
        if ShellMalloc.DidAlloc(TmpElement.pwcsName) = 1 then
        begin
          Inc(AData.Count);
          SetLength(AData.ElementEnum, AData.Count);
          AData.ElementEnum[AData.Count - 1].pwcsName := String(TmpElement.pwcsName);
          ShellMalloc.Free(TmpElement.pwcsName);
          AData.ElementEnum[AData.Count - 1].dwType := TmpElement.dwType;
        end;
  end;
  Enum := nil;
end;

function TGSStorageCursor.ExistsObject(const AName: String; const AType: Longint;
  var Exists: Boolean): Boolean;
var
  I: Integer;
  Data: TGSStorageEnum;
begin
  Data.Count := 0;
  Result := Enumerate(Data) = S_OK;
  try
    Exists := False;
    if Result then
      if Data.Count > 0 then
        for I := 0 to Data.Count - 1 do
        begin
          if Data.ElementEnum[I].pwcsName = AName then
          begin
            Exists := Data.ElementEnum[I].dwType = AType;
            Exit;
          end;
        end;
  finally
    //FreeMemAfterEnum(Data);
  end;
end;

function TGSStorageCursor.FlushBuffer: HRESULT;
begin
  Result := FStorageInterface.Commit(STGC_DEFAULT);
end;

procedure TGSStorageCursor.FreeMemAfterEnum(var AData: TGSStorageEnum);
{var
  I: Integer;}
begin
//  for I := 0 to AData.Count - 1 do
//    FreeMem(AData.ElementEnum[I].pwcsName);
  AData.Count := 0;
  SetLength(AData.ElementEnum, 0);
end;

procedure TGSStorageCursor.Garbage;
var
  I: Integer;
begin
  if FSubStorage = nil then Exit;
  if FSubStorage.Count > 0 then
    for I := 0 to FSubStorage.Count - 1 do
    begin
      FSubStorage.Items[I].Garbage;
      if not FSubStorage.Items[I].Locked then
        FSubStorage.Items[I].Free;
    end;
end;

function TGSStorageCursor.GetLock: Boolean;
var
  I: Integer;
begin
  Result := FLockCount > 0;
  if not Result then
    if FSubStorage.Count > 0 then
      for I := 0 to FSubStorage.Count - 1 do
        if FSubStorage.Items[I].Locked then
        begin
          Result := True;
          Break;
        end;
end;

function TGSStorageCursor.GetLockCount: Integer;
var
  I: Integer;
begin
  Result := FLockCount;
  if FSubStorage.Count > 0 then
    for I := 0 to FSubStorage.Count - 1 do
      Inc(Result, FSubStorage.Items[I].GetLockCount);
end;

function TGSStorageCursor.GetName: String;
var
  Error: HRESULT;
  StatStg: TStatStg;
  ShellMalloc: IMalloc;
begin
  Result := '';
  if (CoGetMalloc(1, ShellMalloc) <> S_OK) or (ShellMalloc = nil) then
    raise EComponentError.Create('CoGetMalloc failed.');
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.GetName() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  Error := FStorageInterface.Stat(StatStg, STATFLAG_DEFAULT);
  if Error <> S_OK then
    raise Exception.Create('TGSStorageCursor.GetName() >> "Error <> S_OK"');//SysErrorMessage(Error));
  if ShellMalloc.DidAlloc(StatStg.pwcsName) = 1 then
  begin
    Result := String(StatStg.pwcsName);
    ShellMalloc.Free(StatStg.pwcsName);
  end;
end;

function TGSStorageCursor.GetPath: String;
begin
  if FParent = nil then
    Result := GetName
  else
    Result := FParent.GetPath + '\' + GetName;
end;

procedure TGSStorageCursor.Lock;
begin
  Inc(FLockCount);
end;

function TGSStorageCursor.MoveTo(const AName: String;
  const ACursor: TGSStorageCursor): HRESULT;
var
  Index: Cardinal;
begin
  if ACursor = nil then
    raise EComponentError.Create('TGSStorageCursor.MoveTo() >> "ACursor = nil"');//SysErrorMessage(E_INVALIDARG));
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.MoveTo() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  if FSubStorage.FindStorageByName(AName, Index) then
    Result := E_ACCESSDENIED
  else
    Result := FStorageInterface.MoveElementTo(StringToOleStr(AName),
        ACursor.FStorageInterface, StringToOleStr(AName), STGMOVE_MOVE);
end;

{$IFDEF CLASS_INSTANCE}
class function TGSStorageCursor.NumOfInstances: Integer;
begin
  Result := TGSStorageCursor_Instance;
end;
{$ENDIF}

function TGSStorageCursor.OpenStorage(const AName: String;
  var ACursor: TGSStorageCursor; Verify: Boolean = True): HRESULT;
var
  Index: Cardinal;
begin
  //if ACursor = nil then
    //raise EComponentError.Create(SysErrorMessage(E_INVALIDARG));
  if fSubStorage.FindStorageByName(AName, Index) then
  begin
    Result := S_OK;
    ACursor := FSubStorage.Items[Index];
    ACursor.Lock;
    Exit;
  end;

  if Verify then
    if not StorageExists(AName) then
      raise EComponentError.Create(SysErrorMessage(ERROR_PATH_NOT_FOUND));

  if ACursor <> nil then
    if ACursor.FID = FID then UnLock;

  ACursor := FSubStorage.Add;
  ACursor.SetParam(FOwner, Self);

  Result := FStorageInterface.OpenStorage(StringToOleStr(AName),
    nil, FMode, nil, 0, ACursor.FStorageInterface);
  {$IFDEF CREATE_FREE_LOG}
    CreateFreeLog(True, 'TGSCustomStorage.ACursor.FStorageInterface = ' + IntToStr(Integer(ACursor.FStorageInterface)));
  {$ENDIF}
  ACursor.Lock; 
end;

// ?данной функци?проверять = если результа?не S_OK то смотреть AStream <> nil
//function TGSStorageCursor.ReadStream(const AName: String;
//  var AStream: TStream; Verify: Boolean = True): HRESULT;

// ЅєЖ®ёІА» АМ ЗФјцїЎј­ »эјєЗПБц ѕК°Ф јцБ¤ 18_1123 15:53:21 sunk
function TGSStorageCursor.ReadStream(const AName: String;
  AStream: TStream; Verify: Boolean = True): HRESULT;
var
  TmpStream:IStream;
  OS:TOleStream;
  Buff: array of Byte;
  I, Err: Integer;

  procedure DoReadAnything;
  begin
    { DONE : Возможна утечка па?ти, та?ка?AStream уж?буде?создан!!! }
    // Ха?сами проверяют


    // Попытк?вытащить хоть чт?нибудь...
    SetLength(Buff, OS.Size);
    Err := 0;
    I := 0;
    OS.Position := 0;
    while I < OS.Size do
    try
      OS.Read(Buff[I - Err], 1);
      Inc(I);
    except
      Break;
    end;
    AStream.Write(Buff[0], I);
  end;

begin
//  AStream := nil;
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.ReadStream() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  if Verify then
  begin
    if StreamExists(AName) then
      Result := FStorageInterface.OpenStream(StringToOleStr(AName),
        nil, FMode, 0, TmpStream)
    else
      raise EComponentError.Create('TGSStorageCursor.ReadStream() >> "StreamExists(AName) = false"');//SysErrorMessage(ERROR_PATH_NOT_FOUND));
  end
  else
    Result := FStorageInterface.OpenStream(StringToOleStr(AName),
      nil, FMode, 0, TmpStream);
  if Result = S_OK then
  begin
//    AStream := TMemoryStream.Create;
    AStream.Position := 0;
    OS := TOleStream.Create(TmpStream);
    try
      OS.Position := 0;
      try
        AStream.CopyFrom(OS, OS.Size);
      except
        on E: EOleException do
        begin
          Result := E.ErrorCode;
          DoReadAnything;
          Exit;
        end;
        on E: Exception do
        begin
          Result := S_FALSE;
          DoReadAnything;
        end;
      end;
      AStream.Position := 0;
    finally
      OS.Free;
      TmpStream := nil;
    end;
  end;
end;

procedure TGSStorageCursor.Release;
begin
  if FID = FOwner.FRootStorage.FID then Exit;
  Free;
  FOwner.Garbage;
end;

{$IFDEF CLASS_INSTANCE}
class procedure TGSStorageCursor.ReleaseInstance;
begin
  Dec(TGSStorageCursor_Instance);
end;
{$ENDIF}

function TGSStorageCursor.Rename(const AName, ANewName: String): HRESULT;
begin
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.Rename() >> "FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  Garbage;
  Result := FStorageInterface.RenameElement(StringToOleStr(AName),
    StringToOleStr(ANewName));
end;

procedure TGSStorageCursor.SetParam(const AOwner: TGSCustomStorage;
  AParent: TGSStorageCursor);
begin
  FMode := AOwner.FMode;
  FOwner := AOwner;
  FParent := AParent;
end;

function TGSStorageCursor.StorageExists(const AName: String): Boolean;
var
  Exists: Boolean;
begin
  Result := False;
  if ExistsObject(AName, STGTY_STORAGE, Exists) then
    Result := Exists;
end;

function TGSStorageCursor.StreamExists(const AName: String): Boolean;
var
  Exists: Boolean;
begin
  Result := False;
  if ExistsObject(AName, STGTY_STREAM, Exists) then
    Result := Exists;
end;

procedure TGSStorageCursor.UnLock;
begin
  Dec(FLockCount);
end;

function TGSStorageCursor.WriteStream(const AName: String;
  const AStream: TMemoryStream): HRESULT;
var
  TmpStream:IStream;
  OS:TOleStream;
begin
  if FStorageInterface = nil then
    raise EComponentError.Create('TGSStorageCursor.WriteStream() >> " FStorageInterface = nil"');//SysErrorMessage(E_UNEXPECTED));
  if StreamExists(AName) then
    Result := FStorageInterface.OpenStream(StringToOleStr(AName),
      nil, FMode, 0, TmpStream)
  else
  begin
    // Правим глюк когд?стри?присутствует но ег?не?пр?енумерации
    // Тупо создае?ег? если не создал? - удаляем ?опять создае?
    Result := FStorageInterface.CreateStream(StringToOleStr(AName),
      FMode, 0, 0, TmpStream);
    if Result <> S_OK then
    begin
      FStorageInterface.DestroyElement(StringToOleStr(AName));
      Result := FStorageInterface.CreateStream(StringToOleStr(AName),
        FMode, 0, 0, TmpStream);
    end;
  end;
  if Result = S_OK then
  begin
    { TODO : ?TmpStream удалять нужн? }
    OS := TOleStream.Create(TmpStream);
    try
      AStream.Position := 0;
      OS.CopyFrom(AStream, AStream.Size);
    finally
      OS.Free;
      TmpStream := nil;
    end;
  end;
end;

class function TGSCustomStorage.Compress(const AFileName: String): HRESULT;
begin
  Result := ClassSupport(AFileName, True);
end;

class function TGSCustomStorage.IsStgValidBinaryFmt(
  const AFileName: String): HRESULT;
begin
  Result := ClassSupport(AFileName, False);
end;

class function TGSCustomStorage.ClassSupport(const AFileName: String;
  Change: Boolean): HRESULT;
var
  Src, Dest: IStorage;
begin
  Result := S_FALSE;
  if not FileExists(AFileName) then Exit;
  Result := StgOpenStorage(StringToOleStr(AFileName), nil,
    STGM_READ or STGM_SHARE_EXCLUSIVE, nil, 0, Src);
  if Result = S_OK then
  try
    Result :=  StgCreateDocfile(StringToOleStr(String(AFileName) + '~'),
      STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_CREATE, 0, Dest);
    if Result = S_OK then
    try
      Result := Src.CopyTo(0, nil, nil, Dest);
    finally
      Dest := nil;
    end;
  finally
    Src := nil;
    if Result = S_OK then
    begin
      if Change then
      begin
        if Result = S_OK then
          if not DeleteFile(AFileName) then
            Result := S_FALSE
          else
            RenameFile(AFileName + '~', AFileName);
      end else begin
        if Result = S_OK then
          if not DeleteFile(AFileName + '~') then
            Result := S_FALSE
      end;
    end else
      DeleteFile(AFileName + '~'); // »иБ¦ ГЯ°Ў 18_0726 15:11:38 sunk
  end;
end;

function TGSCustomStorage.GetActive: Boolean;
begin
  Result := FRootStorage.FStorageInterface <> nil;
end;

{$IFDEF CLASS_INSTANCE}
class procedure TGSCustomStorage.AddInstance;
begin
  Inc(TGSCustomStorage_Instance);
end;

constructor TGSCustomStorage.Create;
begin
  AddInstance;
end;

class function TGSCustomStorage.NumOfInstances: Integer;
begin
  Result := TGSCustomStorage_Instance;
end;

class procedure TGSCustomStorage.ReleaseInstance;
begin
  Dec(TGSCustomStorage_Instance);
end;
{$ENDIF}

{$IFDEF CLASS_INSTANCE}

initialization

finalization

  if TGSCustomStorage_Instance > 0 then
    raise GSCustomStorageException.Create(Format('%d instances of TGSCustomStorage active', [TGSCustomStorage_Instance]));

  if TGSStorageCursor_Instance > 0 then
    raise GSCustomStorageException.Create(Format('%d instances of TGSStorageCursor active', [TGSStorageCursor_Instance]));

  if TGSStorageCursors_Instance > 0 then
    raise GSCustomStorageException.Create(Format('%d instances of TGSStorageCursors active', [TGSStorageCursors_Instance]));

{$ENDIF}

end.

