////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2023 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWKIT.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// DIM server for network communications to be connected to CRW-DAQ pipe.     //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 2005xxxx - Created by A.K.                                                 //
// 20230608 - Modified for FPC (A.K.)                                         //
////////////////////////////////////////////////////////////////////////////////

program dimsrv; // DIM Server

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$R *.res}

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 {$IFDEF WINDOWS} shellapi, {$ENDIF}
 sysutils, classes, math, forms, interfaces,
 _crw_alloc, _crw_cmdargs, _crw_str, _crw_base64, _crw_fio, _crw_fifo,
 _crw_rtc, _crw_polling, _crw_task, _crw_ascio, _crw_az, _crw_proc,
 _crw_dim, dimlib;

//
// General variables and constants
//
const
 Terminated      : Boolean    = false; // Program should be termunated
 DimList         : TDimList   = nil;   // List if DIM tag/name/service
 DIS_NO_LINK     : LongInt    = -1;    // Mark no link available
 DIS_DNS_VERSION : LongInt    = -1;    // DIS_DNS/VERSION_NUMBER
 DisErrorCounter : Cardinal   = 0;     // DIM Server error counter
 DicErrorCounter : Cardinal   = 0;     // DIM Client error counter
 optPadding      : LongString = '';    // -padding option value
 optTask         : LongString = '';    // -task option value
 optDns          : LongString = '';    // -dns option value
//
// Exception reporter to be called on Exception.
//
procedure DimSrvBugReport(E:Exception; O:TObject; Note:LongString);
var Msg:LongString;
begin
 Msg:=E.ClassName+' - «'+Trim(E.Message)+'»';
 if Assigned(O) then Msg:=Msg+' from '+O.ClassName;
 if (Note<>'') then Msg:=Msg+' at '+Note;
 if StrLeft(Msg,1)<>'.' then Msg:=Msg+'.';
 StdOut.Put:='ERROR: '+Msg;
end;
//
// Callback procedure on dns version received.
// Reply:  @DIS_DNS/VERSION_NUMBER=1402
//
procedure on_rcvd_dns(var tag:TDimLong; data:Pointer; var size:Integer); cdecl;
begin
 if size=SizeOf(LongInt)
 then DIS_DNS_VERSION:=LongInt(data^)
 else DIS_DNS_VERSION:=DIS_NO_LINK;
 StdOut.Put:=Format('@DIS_DNS/VERSION_NUMBER=%d',[DIS_DNS_VERSION]);
end;
//
// Callback procedure on command received.
// Reply:  #tag=mimedata
//
procedure on_rcvd_cmnd(var tag:TDimLong; cmnd:Pointer; var size:Integer); cdecl;
var temp:LongString;
begin
 SetString(temp,PChar(cmnd),size);
 StdOut.Put:=Format('#%d=%s',[tag,base64_encode(temp)]);
 DimList.Data[tag]:=temp;
 temp:='';
end;
//
// Callback procedure on info service data received.
// Reply:  #tag=mimedata
//
procedure on_rcvd_info(var tag:TDimLong; data:Pointer; var size:Integer); cdecl;
var temp:LongString;
begin
 SetString(temp,PChar(data),size);
 StdOut.Put:=Format('#%d=%s',[tag,base64_encode(temp)]);
 DimList.Data[tag]:=temp;
 temp:='';
end;
//
// Callback on command sent.
// Reply:  @tag=0/1
//
procedure on_send_cmnd(var tag:TDimLong; var ret_code:Integer); cdecl;
begin
 StdOut.Put:=Format('@%d=%d',[tag,ret_code]);
end;
//
// Callback procedure on info service update.
// Reply:  @ClientEnter=tag,pid@host on first client connection.
//
procedure on_send_info(var tag:TDimLong; var arg:Pointer; var size:Integer; var first:Integer); cdecl;
var name:TDimNameBuffer;
begin
 size:=Length(DimList.Data[tag]);
 if size>0 then arg:=PChar(DimList.Data[tag]) else arg:=nil;
 if first>0 then begin
  if dis_get_client(name)<>0 then StdOut.Put:=Format('@ClientEnter=%d,%s',[tag,name]);
  dis_set_client_exit_handler(dis_get_conn_id,tag);
 end;
end;
//
// Callback on client request to kill server.
// Reply:  @ClientKill=code,pid@host
//
procedure on_client_kill(var code:Integer); cdecl;
var name:TDimNameBuffer;
begin
 if dis_get_client(name)<>0 then StdOut.Put:=Format('@ClientKill=%d,%s',[code,name]);
 StdIn.Put:='@Exit';
end;
//
// Callback on client exit (disconnection).
// Reply:  @ClientExit=code,pid@host
//
procedure on_client_exit(var code:Integer); cdecl;
var name:TDimNameBuffer;
begin
 if dis_get_client(name)<>0 then StdOut.Put:=Format('@ClientExit=%d,%s',[code,name]);
end;
//
// Callback on Client Error.
// Reply:  @ClientError=msg
//
procedure on_client_error(severity:Integer; error_code:Integer; error_message:PChar); cdecl;
begin
 if (severity>=es_DIM_ERROR) then inc(DicErrorCounter);
 StdOut.Put:=Format('@ClientError=%s (%d) - %s',[dim_severity_name(severity),error_code,error_message]);
end;
//
// Callback on Server Error.
// Reply:  @ServerError=msg
//
procedure on_server_error(severity:Integer; error_code:Integer; error_message:PChar); cdecl;
begin
 if (severity>=es_DIM_ERROR) then inc(DisErrorCounter);
 StdOut.Put:=Format('@ServerError=%s (%d) - %s',[dim_severity_name(severity),error_code,error_message]);
end;
//
// Request: @help
// Reply:   none
// Comment: Print help screen.
//
procedure DoHelp(const cmnd,args:LongString);
begin
 StdOut.Put:='Command line:';
 StdOut.Put:=' dimsrv [-dns d] [-task t] [-padding p]';
 StdOut.Put:='Commands:';
 StdOut.Put:=' @Help        - this help.';
 StdOut.Put:=' @Exit=n      - exit process';
 StdOut.Put:=' @Errors      - print error counter.';
 StdOut.Put:=' @Memory      - print memory usage.';
 StdOut.Put:=' @dns.ex      - run DIM DNS.';
 StdOut.Put:=' @did.exe     - run DID data viewer.';
 StdOut.Put:=' @dimtree.exe - run DimTree data browser.';
 StdOut.Put:=' @Priority=p  - set process priority.';
 StdOut.Put:=' @Padding=p   - set padding mode (0/1).';
 StdOut.Put:=' @DnsNode=n   - set DIM DNS node (n).';
 StdOut.Put:=' @Start=t     - start serving task (t).';
 StdOut.Put:=' @Stop        - stop serving task.';
 StdOut.Put:=' +tag=list    - register service list to tag.';
 StdOut.Put:='      list    - kind,name,frmt,mntr,time,fill:';
 StdOut.Put:='       tag    - tag to identify service as number, 1..64K.';
 StdOut.Put:='       kind   - dis_info,dis_cmnd,dic_info,dic_cmnd.';
 StdOut.Put:='       name   - name to identify command/service in DIM.';
 StdOut.Put:='       frmt   - B,C,L,S,F,D,X=Bin,Char,Long,Short,Float,Double,Xlong.';
 StdOut.Put:='       mntr   - monitored,timed - mode for update.';
 StdOut.Put:='       tout   - timeout,sec for update.';
 StdOut.Put:='       fill   - data fill on error, base64 encode.';
 StdOut.Put:='#tag          - request to update service by tag.';
 StdOut.Put:='#tag=data     - request to update tag with new (base64) data.';
 StdOut.Put:='Reply messages:';
 StdOut.Put:=' @DIS_DNS/VERSION_NUMBER=n  - on DIM DNS version.';
 StdOut.Put:=' #tag=data                  - on tag received (base64) data.';
end;
//
// Request: @exit
// Request: @exit=n
// Reply:   @exit=n
// Comment: Terminate program with exit code n.
//
procedure DoExit(const cmnd,args:LongString);
begin
 Terminated:=true;
 System.ExitCode:=StrToIntDef(args,0);
 StdOut.Put:=Format('%s=%d',[cmnd,System.ExitCode]);
end;
//
// Request: @errors
// Request: @errors=n
// Reply:   @errors=n
// Comment: Return value n of error counter DimErrors.
//
procedure DoErrors(const cmnd,args:LongString);
var
 n : LongInt;
begin
 if Str2Long(args,n) then begin
  n:=LockedExchange(StdIoErrorCount,n);
  inc(n,DisErrorCounter);
  inc(n,DicErrorCounter);
  DisErrorCounter:=0;
  DicErrorCounter:=0;
 end else begin
  n:=LockedGet(StdIoErrorCount);
  inc(n,DisErrorCounter);
  inc(n,DicErrorCounter);
 end;
 StdOut.Put:=Format('%s=%d',[cmnd,n]);
end;
//
// Request: @memory
// Request: @memory=n
// Comment: Return AllocMemSize.
//
procedure DoMemory(const cmnd,args:LongString);
begin
 StdOut.Put:=Format('%s=%d',[cmnd,GetAllocMemSize]);
end;
//
// Request: @dns.exe
// Reply:   @dns.exe=n
// Comment: If DIM_DNS_NODE is local host, start DNS daemon on local host.
//          Return number of running dns.exe instances on local host.
//
procedure DoDnsExe(const cmnd,args:LongString);
begin
 if IsLocalDimDnsNode then
 if LocalDimDnsCount=0 then
 if StartLocalDimDns then Sleep(1000);
 StdOut.Put:=Format('%s=%d',[cmnd,LocalDimDnsCount]);
end;
//
// Request: @did.exe
// Reply:   @did.exe=n
// Comment: Start DID program on local host.
//
procedure DoDidExe(const cmnd,args:LongString);
begin
 StdOut.Put:=Format('%s=%d',[cmnd,ord(StartLocalDimDid)]);
end;
//
// Request: @dimtree.exe
// Reply:   @dimtree.exe=n
// Comment: Start DimTree program on local host.
//
procedure DoDimTreeExe(const cmnd,args:LongString);
begin
 StdOut.Put:=Format('%s=%d',[cmnd,ord(StartLocalDimTree)]);
end;
//
// Request: @priority=class,main,io,timer
// Reply:   @priority=class,main,io,timer
// Comment: Set process priority class (-1/0/1/2=Idle/Normal/High/Realtime), and
//          main,io,timer threads (-3..3=Idle/Lowest/Low/Normal/High/Highest/TimeCritical).
//          Return priorities readback.
//
procedure DoPriority(const cmnd,args:LongString);
var p:Integer; s:LongString;
begin
 // Set priority
 if Str2Long(ExtractWord(1,args,ScanSpaces),p) and (p>=-1) and (p<=2) then dim_set_scheduler_class(p);
 if Str2Long(ExtractWord(2,args,ScanSpaces),p) and (p>=-3) and (p<=3) then dim_set_priority(1,p);
 if Str2Long(ExtractWord(3,args,ScanSpaces),p) and (p>=-3) and (p<=3) then dim_set_priority(2,p);
 if Str2Long(ExtractWord(4,args,ScanSpaces),p) and (p>=-3) and (p<=3) then dim_set_priority(3,p);
 // StdOut priority = Main priority
 if Str2Long(ExtractWord(2,args,ScanSpaces),p) and (p>=-3) and (p<=3) then begin
  StdOut.Priority:=TThreadPriority(p+3);
  StdIn.Priority:=TThreadPriority(p+3);
 end;
 // Get priority readback
 s:='';
 if dim_get_scheduler_class(p)<>0 then s:=s+IntToStr(p) else s:=s+'?'; s:=s+',';
 if dim_get_priority(1,p)<>0      then s:=s+IntToStr(p) else s:=s+'?'; s:=s+',';
 if dim_get_priority(2,p)<>0      then s:=s+IntToStr(p) else s:=s+'?'; s:=s+',';
 if dim_get_priority(3,p)<>0      then s:=s+IntToStr(p) else s:=s+'?';
 // Report
 StdOut.Put:=Format('%s=%s',[cmnd,s]);
end;
//
// Request: @Padding=n
// Reply:   @Padding=n
// Comment: Get/set padding state.
//          DIM padding is 1 by default and may be disabled only.
//
procedure DoPadding(const cmnd,args:LongString);
var
 n : Integer;
const
 p : Integer = 1;
begin
 // Disable padding if one was not disabled
 if Str2Long(args,n) and (n=0) and (p>0) then begin
  dic_disable_padding;
  dis_disable_padding;
  p:=n;
 end;
 // Report
 StdOut.Put:=Format('%s=%d',[cmnd,p]);
end;
//
// Request: @dnsnode
// Request: @dnsnode=n
// Reply:   @dnsnode=n
// Reply:   @DIS_DNS/VERSION_NUMBER=version
// Comment: Get/Set DIM_DNS_NODE=n.
//
procedure DoDnsNode(const cmnd,args:LongString);
var node:TDimNameBuffer;
begin
 if not IsEmptyStr(args) then begin
  if IsSameText(Trim(args),'.')
  then StrPLCopy(node,GetNodeName,SizeOf(node)-1)
  else StrPLCopy(node,Trim(args),SizeOf(node)-1);
  SetEnv('DIM_DNS_NODE',node);
  dim_set_dns_node(node);
 end;
 dic_info_service_stamped('DIS_DNS/VERSION_NUMBER',ONCE_ONLY,5,nil,0,
                          on_rcvd_dns,0,@DIS_NO_LINK,SizeOf(DIS_NO_LINK));
 if dim_get_dns_node(node)=0 then StrPCopy(node,'');
 StdOut.Put:=Format('%s=%s',[cmnd,node]);
end;
//
// Request: @start=task
// Reply:   @start=n
// Comment: Start serving task. Return n=0/1.
//
procedure DoStart(const cmnd,args:LongString);
var task:TDimNameBuffer; ret:Integer; arg:LongString;
begin
 arg:=Trim(args); // arg is task name
 // If empty arg, get it from cmdline
 if IsEmptyStr(arg) then arg:=optTask;
 dis_add_exit_handler(on_client_kill);
 dis_add_client_exit_handler(on_client_exit);
 dic_add_error_handler(on_client_error);
 dis_add_error_handler(on_server_error);
 StrPLCopy(task,Trim(arg),SizeOf(task)-1);
 ret:=dis_start_serving(task);
 StdOut.Put:=Format('%s=%d',[cmnd,ret]);
 StdOut.Put:=ExtractWord(1+Ord(ret<>0),'ERROR: INFO:',ScanSpaces)
            +' Start serving '+task;
end;
//
// Request: @stop
// Reply:   @stop
// Comment: Stop serving task.
//
procedure DoStop(const cmnd,args:LongString);
begin
 dis_stop_serving;
 StdOut.Put:=Format('%s',[cmnd]);
end;
//
// Request: #tag
// Request: #tag=mimedata
// Reply:   @tag=result
// Comment: tag      = Tag to identify service.
//          mimedata = Data in MIME format: base64_encode(data).
//          dis_info : Set new service data, then update service.
//                     result=number of updated clients.
//          dis_cmnd : result=0
//          dic_info : result=0
//          dic_cmnd : Set new service data, then send command.
//                     result=0/1 if command sent successfully.
//
procedure DoTransmit(const cmnd,args:LongString);
var
 tag  : Integer;
 res  : Integer;
 serv : Integer;
 kind : Integer;
begin
 res:=0;
 tag:=StrToIntDef(Copy(cmnd,2,MaxInt),0);
 if tag>0 then begin
  serv:=DimList.Serv[tag];
  kind:=DimList.Kind[tag];
  if serv<>0 then begin
   if kind=dis_info then begin
    if Length(args)>0 then DimList.Data[tag]:=base64_decode(args);
    res:=dis_update_service(serv);
   end;
   if kind=dic_cmnd then begin
    if Length(args)>0 then DimList.Data[tag]:=base64_decode(args);
    if Length(DimList.Name[tag])>0 then
    if Length(DimList.Data[tag])>0 then
    if dic_cmnd_callback(PChar(DimList.Name[tag]),PChar(DimList.Data[tag]),Length(DimList.Data[tag]),
                         on_send_cmnd,tag)<>0 then Exit;
    //res:=dic_cmnd_service(PChar(DimList.Name[tag]),PChar(DimList.Data[tag]),Length(DimList.Data[tag]));
   end;
  end;
 end;
 StdOut.Put:=Format('@%d=%d',[tag,res]);
end;
//
// Request: +tag=kind,name,frmt,mntr,time,fill
// Reply:   +tag=kind,name,frmt,mntr,time,fill,serv
// Comment: Add command/service to server/client list.
//          tag  = Tag to identify command/service.
//          kind = dis_info,dis_cmnd,dic_info,dic_cmnd.
//          name = Name to identify command/service.
//          frmt = B,C,L,S,F,D,X=Bin,Char,Long,Short,Float,Double,eXtended.
//          mntr = monitored,timed update.
//          tout = timeout,sec for update.
//          fill = data fill on error, base64_encode.
//          serv = ServiceID <> 0 or 0 on error.
//
procedure DoAdd(const cmnd,args:LongString);
var
 tag  : Integer;
 serv : Integer;
 kind : Integer;
 mntr : Integer;
 tout : Integer;
 fill : LongString;
 name : TDimNameBuffer;
 frmt : TDimNameBuffer;
begin
 serv:=0;
 tag:=StrToIntDef(Copy(cmnd,2,MaxInt),0);
 kind:=WordIndex(LoCaseStr(ExtractWord(1,args,DimScanSpaces)),DimKindList,DimScanSpaces);
 StrPLCopy(name,ExtractWord(2,args,DimScanSpaces),SizeOf(name)-1);
 StrPLCopy(frmt,ExtractWord(3,args,DimScanSpaces),SizeOf(frmt)-1);
 case WordIndex(LoCaseStr(ExtractWord(4,args,DimScanSpaces)),DimPollList,DimScanSpaces) of
  1:   mntr:=TIMED;
  2:   mntr:=MONITORED;
  else mntr:=MONITORED;
 end;
 tout:=StrToIntDef(ExtractWord(5,args,DimScanSpaces),0);
 fill:=ExtractWord(6,args,DimScanSpaces);
 if Length(fill)>0 then fill:=base64_decode(fill) else fill:=DimDefFill;
 if tag>0 then
 if kind>0 then
 if StrLen(name)>0 then
 if StrLen(frmt)>0 then
 if Length(fill)>0 then
 if DimList.Kind[tag]=0 then
 if DimList.Serv[tag]=0 then
 if DimList.Fill[tag]='' then
 if DimList.Data[tag]='' then
 if DimList.Name[tag]='' then begin
  DimList.Kind[tag]:=kind;
  DimList.Name[tag]:=name;
  DimList.Data[tag]:=DimDefData;
  DimList.Fill[tag]:=fill;
  case kind of
   dis_info : serv:=dis_add_service(name,frmt,nil,0,on_send_info,tag);
   dis_cmnd : serv:=dis_add_cmnd(name,frmt,on_rcvd_cmnd,tag);
   dic_info : serv:=dic_info_service_stamped(name,mntr,tout,nil,0,on_rcvd_info,tag,
                    PChar(DimList.Fill[tag]),Length(DimList.Fill[tag]));
   dic_cmnd : serv:=ord(StrLen(name)>0);
  end;
  DimList.Serv[tag]:=serv;
  if DimList.Serv[tag]=0 then begin
   DimList.Name[tag]:='';
   DimList.Data[tag]:='';
   DimList.Fill[tag]:='';
   DimList.Serv[tag]:=0;
   DimList.Kind[tag]:=0;
  end;
 end;
 StdOut.Put:=Format('%s=%s,%s,%s,%d,%s,%d',[cmnd,ExtractWord(kind,DimKindList,DimScanSpaces),name,
                                             ExtractWord(1+ord(mntr=MONITORED),DimPollList,DimScanSpaces),
                                             tout,base64_encode(fill),serv]);
end;
//
// Application specific commands handling.
//
procedure DoSpecificCommands(const data:LongString);
var
 p    : Integer;
 cmnd : LongString;
 args : LongString;
begin
 if Length(data)>0 then
 try
  case data[1] of
   '#':
    begin
     p:=pos('=',data);
     if p>0 then begin
      cmnd:=Copy(data,1,p-1);
      args:=Copy(data,p+1,Length(data)-p);
     end else begin
      cmnd:=data;
      args:='';
     end;
     DoTransmit(cmnd,args);
    end;
   '+':
    begin
     p:=pos('=',data);
     if p>0 then begin
      cmnd:=Copy(data,1,p-1);
      args:=Copy(data,p+1,Length(data)-p);
     end else begin
      cmnd:=data;
      args:='';
     end;
     DoAdd(cmnd,args);
    end;
  end;
 except
  on E:Exception do begin
   LockedInc(StdIoErrorCount);
   DimBugReport(E,nil,'DoSpecificCommands');
  end;
 end;
end;
//
// Get EXE file version info.
//
function GetVersionInfo(const Name:LongString):LongString;
begin
 Result:=CookieScan(GetFileVersionInfoAsText(ProgName),Name);
end;
function DotEnding(const S:LongString):LongString;
const dot='.';
begin
 Result:=S;
 if (Result<>'') then if (StrFetch(Result,Length(Result))<>dot) then Result:=Result+dot;
end;
procedure PrintVersionInfo(const Fallback:LongString);
begin
 if not IsEmptyStr(GetVersionInfo('ProductName')) then begin
  StdOut.Put:=DotEnding(GetVersionInfo('ProductName')+' version '+GetVersionInfo('ProductVersion'));
  StdOut.Put:=DotEnding(GetVersionInfo('FileDescription'));
  StdOut.Put:=DotEnding(GetVersionInfo('LegalCopyright'));
 end else begin
  StdOut.Put:=Fallback;
 end;
end;
//
// Exception class on DIM library fail.
//
type EDimDllFail=class(EFailException);
//
// Check DIM library is available.
//
procedure CheckDimLibrary;
begin
 if LoadDimLibrary then Exit;
 Raise EDimDllFail.Create('DIM Fatal: '+DimLibraryName+' library fail.');
end;
//
// Initialize DIM.
//
procedure InitializeDIM;
begin
 dim_init;
 //dis_disable_padding;
 //dic_disable_padding;
 dis_add_exit_handler(on_client_kill);
 dis_add_client_exit_handler(on_client_exit);
 dic_add_error_handler(on_client_error);
 dis_add_error_handler(on_server_error);
 // Parse commad line options:
 // -dns node -task TASK -padding mode
 optDns:=''; optTask:=''; optPadding:='';
 CmdArgs.ListOptVal:='-dns;--dns;-task;--task;-padding;--padding';
 // Parse -padding mode
 if CmdArgs.HasOptionValue('-padding')
 then optPadding:=CmdArgs.GetOptionValue('-padding');
 if CmdArgs.HasOptionValue('--padding')
 then optPadding:=CmdArgs.GetOptionValue('--padding');
 optPadding:=Trim(optPadding);
 // Parse -dns node
 if CmdArgs.HasOptionValue('-dns')
 then optDns:=CmdArgs.GetOptionValue('-dns');
 if CmdArgs.HasOptionValue('--dns')
 then optDns:=CmdArgs.GetOptionValue('--dns');
 optDns:=Trim(optDns);
 // Parse -task task
 if CmdArgs.HasOptionValue('-task')
 then optTask:=CmdArgs.GetOptionValue('-task');
 if CmdArgs.HasOptionValue('--task')
 then optTask:=CmdArgs.GetOptionValue('--task');
 optTask:=Trim(optTask);
 // Apply parsed options
 if (optPadding<>'') then DoPadding('@padding',optPadding);
 if (optDns<>'') then DoDnsNode('@dnsnode',optDns);
 // optTask should be applied on @Start
end;
//
// Application specific initialization.
//
procedure SpecificInitialization;
begin
 UseRunCommandEx(True);
 DimList:=TDimList.Create;
 SystemEchoProcedure:=StdOutEcho;
 DimBugReportCallback:=DimSrvBugReport;
 PrintVersionInfo('DIM server for CRW-DAQ.');
 //
 // Register user commands coming from StdIn.
 //
 StdIn.SpecHandler:=DoSpecificCommands;
 StdIn.AddCommand('@Help',        DoHelp);
 StdIn.AddCommand('@Exit',        DoExit);
 StdIn.AddCommand('@Errors',      DoErrors);
 StdIn.AddCommand('@Memory',      DoMemory);
 StdIn.AddCommand('@dns.exe',     DoDnsExe);
 StdIn.AddCommand('@did.exe',     DoDidExe);
 StdIn.AddCommand('@dimtree.exe', DoDimTreeExe);
 StdIn.AddCommand('@Priority',    DoPriority);
 StdIn.AddCommand('@Padding',     DoPadding);
 StdIn.AddCommand('@DnsNode',     DoDnsNode);
 StdIn.AddCommand('@Start',       DoStart);
 StdIn.AddCommand('@Stop',        DoStop);
 // DIM specific initialization.
 CheckDimLibrary;
 InitializeDIM;
end;
//
// Application specific finalization.
//
procedure SpecificFinalization;
begin
 Kill(DimList);
end;
//
// Application specific polling.
//
procedure SpecificPolling;
const LastTicks:QWord=0;
var CurrTicks:QWord;
begin
 CurrTicks:=GetTickCount64;
 if (CurrTicks>LastTicks+1000) then begin
  TTask.PollDetachedPids;
  LastTicks:=CurrTicks;
 end;
 if BecameZombie(FILE_TYPE_PIPE,1000) then StdIn.Put:='@Exit';
end;
//
// Main program
//
begin
 try
  try
   SpecificInitialization;
   while not Terminated do begin
    while StdIn.Count>0 do StdIn.Process(StdIn.Get);
    SpecificPolling;
    Sleep(TPolling.DefPollPeriod);
   end;
  finally
   SpecificFinalization;
  end;
 except
  on E:Exception do DimBugReport(E,nil,'Main');
 end;
 Sleep(200);
 if BecameZombie(FILE_TYPE_PIPE,0) then ExitCode:=1;
end.

//////////////
// END OF FILE
//////////////

