unit DbS4Utils;

interface

uses
  {$IFDEF _ScNet}
  S4Net;
  //Sense4Net;
  {$ELSE}
  //S4Single;
  Sense4;
  {$ENDIF}

const
  // 197707074568224603070424
  Sense4DevPin  = '19770707' + '45682246' + '03070424';
  Sense4UserPin = '45682246';
  Sense4AuthenPin = '19770707' + '03070424';
  Sense4OldDevPin  = '12345678' + '12345678' + '12345678';
  Sense4OldUserPin = '12345678';
  Sense4OldAuthenPin = '12345678' + '12345678';

function OpenSense(var s4Ctx: PSENSE4_CONTEXT): Boolean;
procedure CloseSense(var s4Ctx: PSENSE4_CONTEXT);
function WriteSenseData(s4Ctx: PSENSE4_CONTEXT; AFileID: string; APData: Pointer;
  ADataLength: Integer; AIsExe: Boolean; AIsUpdate: Boolean = False): Boolean;
function WriteSenseFile(s4Ctx: PSENSE4_CONTEXT; AFileID: string; AFileName: string;
  AIsExe: Boolean; AIsUpdate: Boolean = False): Boolean;
procedure SenseLEDOn(s4Ctx: PSENSE4_CONTEXT);
procedure SenseLEDOff(s4Ctx: PSENSE4_CONTEXT);
procedure SenseLEDFlash(s4Ctx: PSENSE4_CONTEXT);

implementation

uses
  Windows, Classes, SysUtils, Variants;

var
  g_s4Ctx: PSENSE4_CONTEXT = nil;
  tmp_s4Ctx: PSENSE4_CONTEXT = nil;
  g_S4Opened: Boolean = False;

  // 深思4全局数据（特为网络版设计）
  {网络版应该一套软件占用一个授权，即三个应用程序只占用一个授权，
   因此不管启动几个应用程序，都只能使用一次S4Open，
   为此必须采用共享内存来存储深思4的数据，提供跨程序的调用。
  }

type
  TGlobalS4Data = packed record
    // 深思网络锁引用计数
    SenseNetDogRefCount: Integer;
    // 深思加密锁数据
    SenseData: array [0..91] of Byte;
  end;

  PGlobalS4Data = ^TGlobalS4Data;

const
  ScSense4ShareMemoryName = 'SmartCost_Sense4_Data_Handle';

// 返回 0：函数调用失败；1：已存在共享内存对象；2：不存在共享内存对象
function GetGlobalS4Ctx(var s4Ctx: PSENSE4_CONTEXT): Integer;
var
  // 文件映射对象句柄
  ShareHandle: THandle;
  pData: PGlobalS4Data;
  SA: TSecurityAttributes;
begin
  Result := 0;
  SA.nLength := SizeOf(SA);
  SA.lpSecurityDescriptor := nil;
  SA.bInheritHandle := False;
  ShareHandle := CreateFileMapping($FFFFFFFF, @SA, PAGE_READWRITE,
    0, SizeOf(TGlobalS4Data), PChar(ScSense4ShareMemoryName));
  try
    // 已存在共享内存
    if GetLastError = ERROR_ALREADY_EXISTS then
    begin
      pData := MapViewOfFile(ShareHandle, File_MAP_WRITE, 0, 0, 0);
      try
        if pData <> nil then
        begin
          Inc(pData^.SenseNetDogRefCount);
          CopyMemory(s4Ctx, @(pData^.SenseData[0]), SizeOf(SENSE4_CONTEXT));
          Result := 1;
        end;
      finally
      //  if pData <> nil then
      //    UnmapViewOfFile(pData);
      end;
    end
    // 尚未存在共享内存
    else
    begin
      pData := MapViewOfFile(ShareHandle, File_MAP_WRITE, 0, 0, 0);
      try
        if pData <> nil then
        begin
          ZeroMemory(pData, SizeOf(TGlobalS4Data));
          pData^.SenseNetDogRefCount := 1;
          Result := 2;
        end;
      finally
      //  if pData <> nil then
      //    UnmapViewOfFile(pData);
      end;
    end;
  finally
    //CloseHandle(ShareHandle);
  end;
end;

function SetGlobalS4Ctx(var s4Ctx: PSENSE4_CONTEXT): Boolean;
var
  // 文件映射对象句柄
  ShareHandle: THandle;
  pData: PGlobalS4Data;
begin
  Result := False;
  ShareHandle := OpenFileMapping(FILE_MAP_WRITE, False, PChar(ScSense4ShareMemoryName));
  if ShareHandle <> Null then
  begin
    try
      pData := MapViewOfFile(ShareHandle, File_MAP_WRITE, 0, 0, 0);
      try
        if pData <> nil then
        begin
          CopyMemory(@(pData^.SenseData[0]), s4Ctx, SizeOf(SENSE4_CONTEXT));
          Result := True;
        end;
      finally
        if pData <> nil then
          UnmapViewOfFile(pData);
      end;
    finally
      CloseHandle(ShareHandle);
    end;
  end;
end;

function ReleaseGlobalS4Ctx(var s4Ctx: PSENSE4_CONTEXT): Boolean;
var
  // 文件映射对象句柄
  ShareHandle: THandle;
  pData: PGlobalS4Data;
begin
  Result := False;
  ShareHandle := OpenFileMapping(FILE_MAP_WRITE, False, PChar(ScSense4ShareMemoryName));
  if ShareHandle <> Null then
  begin
    try
      pData := MapViewOfFile(ShareHandle, File_MAP_WRITE, 0, 0, 0);
      try
        if pData <> nil then
        begin
          Dec(pData^.SenseNetDogRefCount);
          if pData^.SenseNetDogRefCount = 0 then
            S4Close(s4Ctx);
          Result := True;
        end;
      finally
        if pData <> nil then
          UnmapViewOfFile(pData);
      end;
    finally
      CloseHandle(ShareHandle);
    end;
  end;
end;

function OpenSense(var s4Ctx: PSENSE4_CONTEXT): Boolean;
var
  CtxLength: Cardinal;
begin
  Result := False;
  CtxLength := 0;
  {$IFDEF _ScNET}
  { 网络版为了控制节点数，必须保持OPEN状态，
    而原来单机版的做法是OPEN后检查完就CLOSE；
    好在OpenSense, CloseSense是成对调用，所以可做如下修改：

    为兼容单机版的做法，在这里不每次打开，只是第一次打开，
    另创建一个指针，并将传入的指针保存，
    再把创建的指针传出去
    在CloseSense时，不关闭，只把指针替换为原来传进来的指针}
  if g_s4Ctx = nil then
  begin
    S4Enum(nil, CtxLength);
    if CtxLength = 0 then Exit;
    New(g_s4Ctx);
    try
      if S4Enum(g_s4Ctx, CtxLength) = S4_SUCCESS then
      begin
        if S4Open(g_s4Ctx) = S4_SUCCESS then
        begin
          g_S4Opened := True;
          Result := True;
        end;
      end;
{      case GetGlobalS4Ctx(g_s4Ctx) of
        // 失败
        0:
          Exit;
        // 已有其他版本程序打开锁
        1:
        begin
          g_S4Opened := True;
          Result := True;
        end;
        // 首次打开锁
        2:
        begin
          if S4Enum(g_s4Ctx, CtxLength) = S4_SUCCESS then
          begin
            if S4Open(g_s4Ctx) = S4_SUCCESS then
            begin
              g_S4Opened := True;
              SetGlobalS4Ctx(g_s4Ctx);
              Result := True;
            end;
          end;
        end;
      end;}
    except
      Result := False;
    end;
  end
  else
    Result := True;
  tmp_s4Ctx := s4Ctx;
  s4Ctx := g_s4Ctx;
  {$ELSE}
  S4Enum(nil, CtxLength);
  if CtxLength = 0 then Exit;
  try
    if S4Enum(s4Ctx, CtxLength) = S4_SUCCESS then
    begin
      if S4Open(s4Ctx) = S4_SUCCESS then
      begin
        Result := True;
      end;
    end;
  except
    Result := False;
  end;
  {$ENDIF}
end;

procedure CloseSense(var s4Ctx: PSENSE4_CONTEXT);
begin
  {$IFDEF _ScNet}
  { 网络版为了控制节点数，必须保持OPEN状态，
    而原来单机版的做法是OPEN后检查完就CLOSE；
    为兼容单机版的做法，在这里不关闭，只是将OPEN时传入的指针再传出去
    在程序关闭时再CLOSE}
  if Assigned(tmp_s4Ctx) then
  begin
    s4Ctx := tmp_s4Ctx;
    tmp_s4Ctx := nil;
  end
  else if s4Ctx = g_s4Ctx then
  begin
    s4Ctx := nil;
    New(s4Ctx);
  end;
  {$ELSE}
  S4Close(s4Ctx);
  {$ENDIF}
end;

function WriteSenseData(s4Ctx: PSENSE4_CONTEXT; AFileID: string; APData: Pointer;
  ADataLength: Integer; AIsExe: Boolean; AIsUpdate: Boolean): Boolean;
var
  iBytes: Cardinal;
  iFileType: Byte;
  iFlag: Cardinal;
  iResult: Cardinal;
begin
  {$IFDEF _ScNet}
  Result := True;
  {$ELSE}
  Result := False;
  if AIsExe then
    iFileType := S4_EXE_FILE
  else
    iFileType := S4_DATA_FILE;
  if AIsUpdate then
    iFlag := S4_UPDATE_FILE
  else
    iFlag := S4_CREATE_NEW;
  if S4VerifyPin(s4Ctx, Sense4DevPin, 24, S4_DEV_PIN) = S4_SUCCESS then
  begin
    iResult := S4WriteFile(s4Ctx, PChar(AFileID), 0, APData, ADataLength,
        ADataLength, iBytes, iFlag, iFileType);
    Result := iResult = S4_SUCCESS;
  end;
  {$ENDIF}
end;

function WriteSenseFile(s4Ctx: PSENSE4_CONTEXT; AFileID: string; AFileName: string;
  AIsExe: Boolean; AIsUpdate: Boolean): Boolean;
var
  fsFile: TFileStream;
  pData: Pointer;
begin
  fsFile := TFileStream.Create(AFileName, fmOpenRead);
  try
    pData := AllocMem(fsFile.Size);
    try
      ZeroMemory(pData, fsFile.Size);
      fsFile.Seek(0, soFromBeginning);
      fsFile.Read(pData^, fsFile.Size);
      Result := WriteSenseData(s4Ctx, AFileID, pData, fsFile.Size, AIsExe, AIsUpdate);
    finally
      FreeMem(pData);
    end;
  finally
    fsFile.Free;
  end;
end;

procedure SenseLEDOn(s4Ctx: PSENSE4_CONTEXT);
var
  iReturn: Cardinal;
begin
  {$IFNDEF _ScNet}
  S4Control(s4Ctx, S4_LED_UP, nil, 0, nil, 0, iReturn);
  {$ENDIF}
end;

procedure SenseLEDOff(s4Ctx: PSENSE4_CONTEXT);
var
  iReturn: Cardinal;
begin
  {$IFNDEF _ScNet}
  S4Control(s4Ctx, S4_LED_DOWN, nil, 0, nil, 0, iReturn);
  {$ENDIF}
end;

procedure SenseLEDFlash(s4Ctx: PSENSE4_CONTEXT);
var
  iReturn: Cardinal;
begin
  {$IFNDEF _ScNet}
  S4Control(s4Ctx, S4_LED_WINK, nil, 0, nil, 0, iReturn);
  {$ENDIF}
end;

initialization

finalization
  if g_S4Opened and (g_s4Ctx <> nil) then
    //ReleaseGlobalS4Ctx(g_s4Ctx);
    S4Close(g_s4Ctx);

end.
