unit _vdsk;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, INIFiles, StdCtrls;

{$WARN UNIT_PLATFORM OFF}
{$WARN SYMBOL_PLATFORM OFF}

type
  TvdskIndex=record
    Filename:string;
    hash:word;
    pos,size:dword;
  end;

  THashBuf=record
    Count:integer;
    idx:array of dword;
  end;

type
  Tvdsk = class
  private
    { Private 宣言 }
    MemSync:TMultiReadExclusiveWriteSynchronizer;
    BasePath,vdskPath:string;
    INIFilename,IndexFilename,DataFilename:string;
    procedure CreateHashBufs;
  public
    { Public 宣言 }
    DatabaseVer:string;
    IndexCount:dword;
    Index:array of TvdskIndex;
    HashBufs:array[0..$ffff] of THashBuf;
    Datafs:TFileStream;
    function Init(_BasePath,_vdskPath:string):boolean;
    procedure FreeMemory;
    procedure LoadIndex;
    function GetIndexFilesCount:dword;
    function GetFindIndex(Filename:string):dword;
    function GetFilename(vdskidx:dword):string;
    function GetDataSize(const vdskidx:dword):integer;
    function GetData(const vdskidx:dword;const buf:Pointer;const bufpos:integer):boolean;
    function GetDataStr(const vdskidx:dword;var buf:string;const bufpos:integer):boolean;
    function GetDataBuf(const vdskidx:dword;var buf:array of byte;const bufpos:integer):boolean;
    function GetFileSize(const Filename:string):integer;
    function GetFile(const Filename:string;const buf:Pointer;const bufpos:integer):boolean;
    function GetFileStr(const Filename:string;var buf:string;const bufpos:integer):boolean;
    function GetFileBuf(const Filename:string;var buf:array of byte;const bufpos:integer):boolean;
  end;

implementation

uses _m_Tools, _filebuf;

function Tvdsk.Init(_BasePath,_vdskPath:string):boolean;
var
  INI:TINIFile;
begin
  MemSync:=TMultiReadExclusiveWriteSynchronizer.Create;

  MemSync.BeginWrite;

  BasePath:=_BasePath;
  vdskPath:=_vdskPath;

  if DirectoryExists(BasePath+vdskPath)=True then begin
    INIFilename:=BasePath+vdskPath+'virtualdisk.ini';
    IndexFilename:=BasePath+vdskPath+'virtualdisk.vdi';
    DataFilename:=BasePath+vdskPath+'virtualdisk.vdd';
    end else begin
    INIFilename:=BasePath+'virtualdisk.ini';
    IndexFilename:=BasePath+'virtualdisk.vdi';
    DataFilename:=BasePath+'virtualdisk.vdd';
  end;

  if (FileExists(INIFilename)=False) or (FileExists(IndexFilename)=False) or (FileExists(DataFilename)=False) then begin
    Result:=False;
    exit;
  end;

  INI:=TINIFile.Create(INIFilename);
  DataBaseVer:=INI.ReadString('DataBase','Version','');
  INI.Free;

  IndexCount:=0;
  SetLength(Index,IndexCount);
  LoadIndex;
  CreateHashBufs;

  Datafs:=TFileStream.Create(DataFilename,fmOpenRead or fmShareDenyWrite);

  MemSync.EndWrite;

  Result:=True;
end;

procedure Tvdsk.FreeMemory;
begin
  MemSync.BeginWrite;
  IndexCount:=0;
  SetLength(Index,IndexCount);
  Datafs.Free;
  MemSync.EndWrite;

  MemSync.Free;
end;

procedure Tvdsk.LoadIndex;
var
  Indexfs:TReadFileBuf;
  cnt:dword;
begin
  MemSync.BeginWrite;

  Indexfs:=TReadFileBuf.Create;
  Indexfs.Init(IndexFilename);
  Indexfs.LoadFile;

  Indexfs.GetVDWord(IndexCount);
  SetLength(Index,IndexCount);
  for cnt:=0 to IndexCount-1 do begin
    with Index[cnt] do begin
      Indexfs.GetVMString(Filename);
      Indexfs.GetVWord(hash);
      Indexfs.GetVDWord(pos);
      Indexfs.GetVDWord(size);
    end;
  end;
  Indexfs.Free;

  MemSync.EndWrite;
end;

procedure Tvdsk.CreateHashBufs;
var
  cnt:integer;
begin
  MemSync.BeginWrite;

  for cnt:=0 to $ffff do begin
    with HashBufs[cnt] do begin
      Count:=0;
      SetLength(idx,Count);
    end;
  end;

  for cnt:=0 to IndexCount-1 do begin
    with HashBufs[Index[cnt].hash] do begin
      SetLength(idx,Count+1);
      idx[Count]:=cnt;
      inc(Count);
    end;
  end;

  MemSync.EndWrite;
end;

function Tvdsk.GetIndexFilesCount:dword;
begin
  MemSync.BeginRead;
  Result:=IndexCount;
  MemSync.EndRead;
end;

function Tvdsk.GetFindIndex(Filename:string):dword;
var
  _Filename:string;
  _Hash:word;
  cnt:integer;
begin
  MemSync.BeginRead;

  _Filename:=AnsiLowerCase(Filename);
  if copy(_Filename,1,length(vdskPath))<>vdskPath then begin
    Result:=$ffffffff;
    MemSync.EndRead;
    exit;
  end;
  _Filename:=copy(_Filename,length(vdskPath)+1,length(_Filename));
  _Hash:=GetStrHash(_Filename);

  with HashBufs[_Hash] do begin
    if Count<>0 then begin
      for cnt:=0 to Count-1 do begin
        if AnsiLowerCase(Index[idx[cnt]].Filename)=_Filename then begin
          Result:=idx[cnt];
          MemSync.EndRead;
          exit;
        end;
      end;
    end;
  end;

  Result:=$ffffffff;
  MemSync.EndRead;
end;

function Tvdsk.GetFilename(vdskidx:dword):string;
begin
  MemSync.BeginRead;
  if IndexCount<vdskidx then begin
    Result:='';
    end else begin
    Result:=Index[vdskidx].Filename;
  end;
  MemSync.EndRead;
end;

function Tvdsk.GetDataSize(const vdskidx:dword):integer;
begin
  MemSync.BeginRead;
  if IndexCount<vdskidx then begin
    Result:=0;
    end else begin
    Result:=Index[vdskidx].size;
  end;
  MemSync.EndRead;
end;

function Tvdsk.GetData(const vdskidx:dword;const buf:Pointer;const bufpos:integer):boolean;
var
  _pos,_size:dword;
begin
  MemSync.BeginRead;
  if IndexCount<vdskidx then begin
    _pos:=0;
    _size:=0;
    end else begin
    _pos:=Index[vdskidx].pos;
    _size:=Index[vdskidx].size;
  end;
  MemSync.EndRead;

  Result:=True;
  if (_size<>0) then begin
    try
      Datafs.Position:=_pos;
      if _size>(2*1024*1024) then begin // 2MB以上のファイルは転送しない。
        Result:=False;
        end else begin
        Datafs.ReadBuffer(PByteArray(buf)[bufpos],_size);
      end;
      except else Result:=False;
    end;
  end;
end;

function Tvdsk.GetDataStr(const vdskidx:dword;var buf:string;const bufpos:integer):boolean;
begin
  Result:=GetData(vdskidx,addr(buf[1]),bufpos);
end;

function Tvdsk.GetDataBuf(const vdskidx:dword;var buf:array of byte;const bufpos:integer):boolean;
begin
  Result:=GetData(vdskidx,addr(buf[0]),bufpos);
end;

function Tvdsk.GetFileSize(const Filename:string):integer;
var
  fs:TFileStream;
begin
  if FileExists(Filename)=False then begin
    Result:=0;
    end else begin
    fs:=nil;
    try
      fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
      Result:=fs.Size;
      except else Result:=0;
    end;
    fs.Free;
  end;
end;

function Tvdsk.GetFile(const Filename:string;const buf:Pointer;const bufpos:integer):boolean;
var
  fs:TFileStream;
begin
  if FileExists(Filename)=False then begin
    Result:=False;
    end else begin
    Result:=True;
    fs:=nil;
    try
      fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
      if fs.Size>(8*1024*1024) then begin // 8MB以上のファイルは転送しない。
        Result:=False;
        end else begin
        fs.ReadBuffer(PByteArray(buf)[bufpos],fs.Size);
      end;
      except else Result:=False;
    end;
    fs.Free;
  end;
end;

function Tvdsk.GetFileStr(const Filename:string;var buf:string;const bufpos:integer):boolean;
begin
  Result:=GetFile(Filename,addr(buf[1]),bufpos);
end;

function Tvdsk.GetFileBuf(const Filename:string;var buf:array of byte;const bufpos:integer):boolean;
begin
  Result:=GetFile(Filename,addr(buf[0]),bufpos);
end;

end.
