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

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

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Unit for SQLook routines.                                                  //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20250729 - Created by A.K.                                                 //
////////////////////////////////////////////////////////////////////////////////

unit unit_sqlook; //  Unit for SQLook routines.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math, lcltype, lclintf,
 {$IFDEF WINDOWS} shellapi, {$ENDIF}
 Forms, Interfaces, StdCtrls, ExtCtrls, zcomponent,
 _crw_alloc, _crw_str, _crw_rtc, _crw_fio, _crw_sesman,
 _crw_wmctrl, _crw_proc, _crw_sect,
 _crw_apputils, _crw_apptools,
 _crw_dbcon, _crw_dbapi,
 form_databasebrowser;

function SQLookWndClass:LongString;
function SQLookWndTitle:LongString;
function FindAppPid(WinClass,AppTitle:LongString):TPid;
function FindAppActivate(WinClass,AppTitle:LongString; Activate:Boolean=true):Boolean;

 {
 Start session:
 if StartSession('1')   then ... - run single instance - session 1.
 if StartSession('$ 1') then ... - use option --session N or run session 1.
 if StartSession('? 9') then ... - run first found free session in range [1..9].
 }
function StartSession(arg:LongString):Boolean;

type
 TFormSQLook = class(TFormDataBaseBrowser)
 public
  TimerTick55:TTimer;
  TimerTick1s:TTimer;
 public
  procedure FormClose(Sender: TObject; var Action: TCloseAction);
  procedure TimerTick55Timer(Sender:TObject);
  procedure TimerTick1sTimer(Sender:TObject);
  procedure ActionOpenManual(Sender:TObject);
 public
  procedure PrepareForRun;
 end;

const
 FormSQLook:TFormSQLook=nil;

implementation

function SQLookWndClass:LongString;
begin
 Result:='';
 if IsWindows then Result:='Window';
 if IsUnix then Result:='sqlook.Sqlook';
end;

function SQLookWndTitle:LongString;
begin
 Result:='SQLook - Database Browser';
end;

function FindAppPid(WinClass,AppTitle:LongString):TPid;
var wnd:HWND;
begin
 Result:=0;
 wnd:=wmctrl.FindWindow(0,WinClass,AppTitle);
 if wmctrl.IsWindow(wnd) then Result:=wmctrl.WindowPid(wnd);
end;

function FindAppActivate(WinClass,AppTitle:LongString; Activate:Boolean=true):Boolean;
var wnd:HWND; ad:Integer;
begin
 wnd:=wmctrl.FindWindow(0,WinClass,AppTitle);
 Result:=wmctrl.IsWindow(wnd);
 if Activate and Result then begin
  if IsUnix then begin
   ad:=wmctrl.ActiveDesktop;
   wmctrl.SetWindowDesktop(wnd,ad);
  end;
  wmctrl.ActivateWindow(wnd);
 end;
end;

procedure CheckApplicationTerminate;
begin
 if Assigned(FormSQLook) then Exit;
 if not Application.Terminated then Application.Terminate;
end;

procedure SQLookSystemEcho(const Msg:LongString);
begin
 if (Msg<>'') then StdOutputFifo.PutText(Msg);
end;

procedure SQLookBlasterLogger(const Msg:LongString);
begin
 DebugOutText(stdfSound,Msg);
end;

procedure SQLookPollSession;
var line,cmd:LongString;
begin
 CheckApplicationTerminate;
 if SessionManager.HasIpcServer then
 try
  while SessionManager.ReadIpcMessage(line) do begin
   Echo(line);
   if IsLexeme(line,lex_AtCmnd) then begin
    cmd:=ExtractWord(1,line,ScanSpaces);
    if SameText(cmd,'@Activate') then begin
     Application.Restore;
     Application.BringToFront;
    end;
   end;
  end;
 except
  on E:Exception do BugReport(E,nil,'CrwDaqPollSession');
 end;
end;

function IterStdOut(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
begin
 Result:=True;
 TStrings(Custom).Add(Line);
end;

procedure SQLookPollStdOut;
var Buff:LongString; Memo:TMemo;
begin
 try
  CheckApplicationTerminate;
  Buff:=StdOutputFifo.GetText;
  if (Buff='') or (FormSQLook=nil) then Exit;
  Memo:=FormSQLook.MemoDbcLog;
  Memo.Lines.BeginUpdate;
  try
   ForEachStringLine(Buff,IterStdOut,Memo.Lines);
  finally
   Memo.Lines.EndUpdate;
  end;
  Memo.SelStart:=MaxInt; // or Memo.CaretPos:=Point(0,Memo.Lines.Count-1);
 except
  on E:Exception do BugReport(E,nil,'SQLookPollStdOut');
 end;
end;

 {
 Start session:
 if StartSession('1')   then ... - run single instance - session 1.
 if StartSession('$ 1') then ... - use option --session N or run session 1.
 if StartSession('? 9') then ... - run first found free session in range [1..9].
 }
function StartSession(arg:LongString):Boolean;
begin
 Result:=SessionManager.Start(arg);
 if Result then begin
  DefaultFormatSettings.DecimalSeparator:='.';
  SessionManager.RedirectStdIo;
  SessionManager.OpenLeakageLog;
  SessionManager.OpenReadIniLog;
  SessionManager.OpenDebugLog;
  SessionManager.OpenErrorLog;
  SessionManager.OpenSoundLog;
  SessionManager.SetGuardIniPath;
  SessionManager.SetSystemEcho(SQLookSystemEcho);
  SessionManager.SetBlasterLogger(SQLookBlasterLogger);
  SetEnv('SQLOOK_SYS_TMP_DIR',SessionManager.SystemTmpDir);
  SetEnv('SQLOOK_VAR_TMP_DIR',SessionManager.VarTmpDir);
  //SessionManager.SetSystemSendToMainConsole(SystemCalculatorFifoPutText);
  //SetEnvironPrinter(0); SetEnvironLpPageIndents;
  //DefaultCanShowModalLimit:=3;
 end;
 Tick55Actions.Add(SQLookPollStdOut);
 if SessionManager.HasIpcServer then Tick55Actions.Add(SQLookPollSession);
end;

procedure Initialize_SybSystems;
begin
 if InitSubSystems.Count>0 then begin
  InitSubSystems.Execute;
  InitSubSystems.Clear;
 end;
end;

procedure Finalize_SybSystems;
begin
 if DoneSubSystems.Count>0 then begin
  DoneSubSystems.Execute;
  DoneSubSystems.Clear;
 end;
end;

/////////////////////////////
// TFormSQLook implementation
/////////////////////////////

procedure TFormSQLook.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 inherited FormClose(Sender,Action);
 TimerTick1s.Enabled:=false;
 TimerTick55.Enabled:=false;
 Finalize_SybSystems;
end;

procedure TFormSQLook.TimerTick55Timer(Sender:TObject);
begin
 if Assigned(Self) then
 try
  Tick55Actions.Execute;
 except
  on E:Exception do BugReport(E,Self,'TimerTick55OnTimer');
 end;
end;

procedure TFormSQLook.TimerTick1sTimer(Sender:TObject);
begin
 if Assigned(Self) then
 try
  SecondActions.Execute;
 except
  on E:Exception do BugReport(E,Self,'TimerTick1sOnTimer');
 end;
end;

procedure TFormSQLook.ActionOpenManual(Sender:TObject);
const man='/opt/crwdaq/resource/manual/databasebrowsermanual.htm';
var htm,cmd,ans:LongString;
begin
 htm:=''; cmd:=''; ans:='';
 if IsWindows then htm:=GetEnv('SystemDrive');
 htm:=UnifyFileAlias(AdaptFileName(DropPathDelim(htm)+man));
 if not FileIsReadable(htm) then Exit;
 if IsNonEmptyStr(file_which(AdaptFileName('unix.exe'))) then begin
  if IsUnix then cmd:='unix getapppath firefox chromium -t text/html -a .html --run '+htm;
  if IsWindows then cmd:='unix getapppath firefox.exe chrome.exe -t htmlfile -a .html --run '+htm;
 end else begin
  if IsUnix then cmd:='xdg-open '+htm;
  if IsWindows then cmd:=GetShellCmd('start "Open" '+htm);
 end;
 if (cmd='') then Exit;
 if RunCommand(cmd,ans)
 then writeln('Succeed RunCommand: '+cmd)
 else writeln('Failed RunCommand: '+cmd);
end;

procedure TFormSQLook.PrepareForRun;
var s:LongString; d:LongInt; b:Boolean;
var fsz:packed record fs,gf,gl:LongInt; end;
begin
 s:=''; d:=0; b:=False;
 if Assigned(Self) then begin
  Master:=@FormSQLook;
  LocateToCenterOfScreen;
  Caption:=SQLookWndTitle;
  RadioGroupDbcClose.ItemIndex:=1;
  DatabaseBrowserFreeOnClose:=True;
  ComboBoxDbcEngine.ItemIndex:=db_engine_sqldb;
  TimerTick55:=TTimer.Create(Self);
  TimerTick55.OnTimer:=TimerTick55Timer;
  TimerTick55.Interval:=55;
  TimerTick55.Enabled:=True;
  TimerTick1s:=TTimer.Create(Self);
  TimerTick1s.OnTimer:=TimerTick1sTimer;
  TimerTick1s.Interval:=1000;
  TimerTick1s.Enabled:=True;
  OnClose:=FormClose;
  SecondActions.Add(Initialize_SybSystems);
  Self.ActionDatabaseBrowserManual.OnExecute:=ActionOpenManual;
  { UseReadIniLog }
  if ReadIniFileBoolean(SysIniFile,SectSystem,'UseReadIniLog%b',b) and b then begin
   fsz.fs:=0; fsz.gf:=0; fsz.gl:=0;
   if ReadIniFileRecord(SysIniFile,SectSystem,'ReadIniLogFifo%d;%d;%d',fsz) then begin
    if (fsz.fs>0) then ReadIniLogFifoSize:=Max(1024,Min(1024*1024*8,1024*fsz.fs));
    if (fsz.gf>0) then ReadIniLogFifoGrowFactor:=Max(1,Min(4,fsz.gf));
    if (fsz.gl>0) then ReadIniLogFifoGrowLimit:=Max(1024*1024,Min(1024*1024*128,1024*fsz.gl));
   end;
   OpenIniLogFile(SessionManager.VarTmpFile('readini.log'));
   DebugOutSetFifo(stdfReadIniLog,ReadIniLogFifoSize,ReadIniLogFifoGrowFactor,ReadIniLogFifoGrowLimit);
  end;
  { TempDir }
  if ReadIniFilePath(SysIniFile,SectSystem,'TempDir',HomeDir,s)
  then SetTempDir(s);
  { DebugFile }
  if ReadIniFilePath(SysIniFile,SectSystem,'DebugFile',HomeDir,s) then begin
   fsz.fs:=0; fsz.gf:=0; fsz.gl:=0;
   if ReadIniFileRecord(SysIniFile,SectSystem,'DebugFifo%d;%d;%d',fsz) then begin
    if fsz.fs>0 then DebugOutFifoSize:=Max(1024,Min(1024*1024*8,1024*fsz.fs));
    if fsz.gf>0 then DebugOutFifoGrowFactor:=Max(1,Min(4,fsz.gf));
    if fsz.gl>0 then DebugOutFifoGrowLimit:=Max(1024*1024,Min(1024*1024*128,1024*fsz.gl));
   end;
   DebugOutOpenFile(stdfDebug,s,DebugOutFifoSize,55,true,true);
   DebugOutSetFifo(stdfDebug,DebugOutFifoSize,DebugOutFifoGrowFactor,DebugOutFifoGrowLimit);
  end;
  if ReadIniFileBoolean(SysIniFile,SectSystem,'UseKernelGetTickCount64%b',b)
  then UseKernelGetTickCount64:=b;
  { MaxTimerRecursionLevel }
  if ReadIniFileLongInt(SysIniFile,SectSystem,'MaxTimerRecursionLevel%d',d) then begin
   Tick55Actions.MaxLevel:=d;
   SecondActions.MaxLevel:=d;
   OnIdleActions.MaxLevel:=d;
   InitSubSystems.MaxLevel:=d;
   DoneSubSystems.MaxLevel:=d;
  end;
  { MaxAppProcMessagesLevel }
  if ReadIniFileLongInt(SysIniFile,'[System]','MaxAppProcMessagesLevel%d',d) then begin
   if (d>=0) then MaxApplicationProcessMessagesLevel:=d;
  end;
  { UsesFixFormatG }
  if ReadIniFileBoolean(SysIniFile,SectSystem,'UsesFixFormatG%b',b)
  then UsesFixFormatG:=b;
  { Apply StartupDirectory }
  if ReadIniFilePath(SysIniFile,SectSystem,'StartupDirectory',HomeDir,s) then begin
   s:=GetRealFilePathName(DropBackSlash(s));
   if DirExists(s) then SetCurrDir(s);
   DebugOut(stdfDebug,'');
   DebugOut(stdfDebug,'SetCurrentDirectory: '+GetCurrDir);
   DebugOut(stdfDebug,'');
  end;
 end;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_unit_sqlook;
begin
 UseRunCommandEx(True);
end;

procedure Free_unit_sqlook;
begin
end;

initialization

 Init_unit_sqlook;

finalization

 Free_unit_sqlook;

end.

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

