431 lines
11 KiB
ObjectPascal
431 lines
11 KiB
ObjectPascal
unit baseconnection;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, extTypes, Contnrs, cgiDM,reportDMUnit;
|
|
type
|
|
{ TBaseConnection }
|
|
TBaseConnection=class;
|
|
|
|
{ TCommand }
|
|
|
|
TCommand=class
|
|
protected
|
|
fData,
|
|
fResult: TCommandData;
|
|
fCommandID: string;
|
|
fStatus: integer;
|
|
fcurrentStage: string;
|
|
fconnect: TBaseConnection;
|
|
TimeOut: single;
|
|
fisDone,fisFinished: boolean;
|
|
fIsError: boolean;
|
|
fSubClass: string;
|
|
public
|
|
AccessTime: TDateTime;
|
|
|
|
property Arguments: TCommandData read fData;
|
|
property Results: TCommandData read fResult;
|
|
property CommandID: string read fCommandID;
|
|
property Status: integer read fStatus;
|
|
property isDone: boolean read fIsDone;
|
|
property Error: boolean read fIsError;
|
|
property isFinished: boolean read fIsFinished;
|
|
property CurrentStage: string read fCurrentStage;
|
|
property Connect: TBaseConnection read fConnect;
|
|
constructor Create(aConnect: TBaseConnection; ASubClass: string);
|
|
destructor Destroy; override;
|
|
procedure doRun;
|
|
procedure Done;
|
|
function Run: boolean; virtual; abstract;
|
|
class function CommandName: string; virtual; abstract;
|
|
class function CommandSubClass: string; virtual; abstract;
|
|
function CheckArgs(out Errors: TStrings): boolean; virtual; abstract;
|
|
function ParseCommand(ACode: DWORD; iParam: QWORD; ACommand: string; Args: TStrings; intArgs: TParamArray; CmdData: TStream; out Errors: TStrings): boolean;
|
|
function ParseArguments(Args: TStrings; out Errors: TStrings): boolean; virtual; abstract;
|
|
procedure Log(msg: string);
|
|
function ProcessOptionValues(ParamName: string; out Answer: string; out RetValue: QWORD; out OptionValues: TStrings): boolean; virtual; abstract;
|
|
end;
|
|
TCommandClass=class of TCommand;
|
|
|
|
{ TCommandCollection }
|
|
TCommandCollection=Class;
|
|
TCommandCollection=Class(TClassList)
|
|
private
|
|
class var fCollection: TCommandCollection;
|
|
public
|
|
class procedure Register(ACommand: TCommandClass);
|
|
class function Find(Action,SubClass: string): TCommandClass;
|
|
class procedure Init;
|
|
class procedure Done;
|
|
end;
|
|
|
|
TBaseConnection=class(TThread)
|
|
private
|
|
fOwner:TObject;
|
|
fLogger: TLogger;
|
|
fConnectionID: string;
|
|
fTimeout: integer;
|
|
fProcessor: TNIDBDM;
|
|
fReportProcessor: TReportDM;
|
|
Commands: TStrings;
|
|
DoneCommands: TList;
|
|
fCreated,
|
|
fCommandReceived,
|
|
fCommandCompleted: TDateTime;
|
|
nCommandComplete: integer;
|
|
nCommandReceived: integer;
|
|
nCommandReady: integer;
|
|
nErrors: integer;
|
|
fCheckConnect: boolean;
|
|
procedure CleanDone;
|
|
public
|
|
Host: string;
|
|
port: integer;
|
|
DataBase: string;
|
|
User: string;
|
|
UserID: integer;
|
|
LastAccess: TDateTime;
|
|
procedure Log(sender: TObject; msg: string);
|
|
property Created: TDateTime read fCreated;
|
|
property LastReceive: TDateTime read fCommandReceived;
|
|
property LastComplete: TDateTime read fCommandCompleted;
|
|
property CountReceived: integer read nCommandReceived;
|
|
property CountCompleted: integer read nCommandComplete;
|
|
property CountReady: integer read nCommandReady;
|
|
property CountErrors: integer read nErrors;
|
|
property Owner: TObject read fOwner;
|
|
property ConnectionID: string read fConnectionID;
|
|
property Processor: TNIDBDM read fProcessor;
|
|
property ReportProcessor: TReportDM read fReportProcessor;
|
|
procedure Init;
|
|
constructor Create(AOwner: TObject;ATimeOut: integer; aLogger: TLogger);
|
|
destructor Destroy; override;
|
|
// CommandID,Param,ACommand,Fields,iParam.Data
|
|
function AddCommand(ACode: DWORD; iParam: QWORD; ACommandClass,ACommandName: string; Arguments: TStrings; intArgs: TParamArray; CmdData: TStream; out ID: string; out retCode:DWORD; out Errors: TStrings): boolean;
|
|
function RunCommand(ACommand: TCommand): boolean;
|
|
function FindCommand(IDCommand: string): TCommand;
|
|
procedure Idle;
|
|
procedure SetIdle;
|
|
procedure Execute; override;
|
|
function ProcessOptionValues(ReportName,ParamName: string; ParamValues: TStrings; out Answer: string; out RetValue: QWORD; out OptionValues: TStrings): boolean;
|
|
class function newID: string;
|
|
end;
|
|
|
|
implementation
|
|
{ TBaseConnection }
|
|
|
|
procedure TBaseConnection.Init;
|
|
begin
|
|
Processor.connection.RemoteHost:=Host;
|
|
Processor.connection.RemotePort:=Port;
|
|
Processor.connection.Database:=DataBase;
|
|
Processor.OpenConnection;
|
|
end;
|
|
|
|
|
|
constructor TBaseConnection.Create(AOwner: TObject; ATimeOut: integer;
|
|
aLogger: TLogger);
|
|
begin
|
|
inherited Create(true);
|
|
fTimeout:=ATimeOut;
|
|
fOwner := AOwner;
|
|
flogger := ALogger;
|
|
fProcessor:=TNIDBDM.Create(nil);
|
|
fProcessor.logger:=aLogger;
|
|
fReportProcessor:=TReportDM.Create(nil);
|
|
fReportProcessor.NidbData := fProcessor;
|
|
Commands:=TStringList.Create;
|
|
DoneCommands:=TList.Create;
|
|
fCreated := now();
|
|
fCommandReceived := 0;
|
|
fCommandCompleted := 0;
|
|
nCommandComplete:=0;
|
|
nCommandReceived:=0;
|
|
nCommandReady:=0;
|
|
nErrors:=0;
|
|
fConnectionID:=newID;
|
|
|
|
end;
|
|
|
|
destructor TBaseConnection.Destroy;
|
|
begin
|
|
log(self,'Destroy');
|
|
Processor.Free;
|
|
Commands.Free;
|
|
DoneCommands.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TBaseConnection.AddCommand(ACode: DWORD; iParam: QWORD; ACommandClass,
|
|
ACommandName: string; Arguments: TStrings; intArgs: TParamArray;
|
|
CmdData: TStream; out ID: string; out retCode: DWORD; out Errors: TStrings
|
|
): boolean;
|
|
var
|
|
cc: TCommandClass;
|
|
cmd: TCommand;
|
|
begin
|
|
log(self,'AddCommand '+ACommandClass+ ' '+ACommandName);
|
|
fCommandReceived:=Now();
|
|
cc := TCommandCollection.Find(ACommandClass,ACommandName);
|
|
if assigned(cc) then
|
|
begin
|
|
cmd := cc.Create(self,ACommandName);
|
|
cmd.AccessTime:=NOW();
|
|
result := cmd.ParseCommand(ACode,iParam,ACommandName,Arguments,intArgs,CmdData, Errors);
|
|
if result then
|
|
begin
|
|
Commands.AddObject(ACommandName,cmd);
|
|
ID := cmd.CommandID;
|
|
retCode := Commands.Count;
|
|
end
|
|
else
|
|
begin
|
|
ID := 'неверные параметры запроса';
|
|
retCode := ErrorArguments;
|
|
inc(nErrors);
|
|
cmd.fIsError:=true;
|
|
cmd.Done;
|
|
DoneCommands.Add(cmd);
|
|
end;
|
|
inc(nCommandReceived);
|
|
|
|
|
|
|
|
end
|
|
else
|
|
inc(nErrors);
|
|
end;
|
|
|
|
function TBaseConnection.RunCommand(ACommand: TCommand): boolean;
|
|
begin
|
|
ACommand.doRun();
|
|
log(Self,'complete '+ACommand.CommandID);
|
|
fCommandCompleted:=Now();
|
|
inc(nCommandReady);
|
|
end;
|
|
|
|
function TBaseConnection.FindCommand(IDCommand: string): TCommand;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to Commands.Count-1 do
|
|
if (Commands.Objects[i] as TCommand).CommandID=IDCommand then
|
|
begin
|
|
result := Commands.Objects[i] as TCommand;
|
|
result.AccessTime:=now();
|
|
exit;
|
|
end;
|
|
for i := DoneCommands.Count-1 downto 0 do
|
|
if TCommand(DoneCommands[i]).CommandID=IDCommand then
|
|
begin
|
|
result := TCommand(DoneCommands[i]);
|
|
result.AccessTime:=now();
|
|
exit;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
procedure TBaseConnection.Idle;
|
|
var
|
|
d: TDateTime;
|
|
begin
|
|
fCheckConnect:=false;
|
|
d := Created;
|
|
if LastAccess>d then d := LastAccess;
|
|
if (now()-d)*24*60>fTimeout then
|
|
begin
|
|
log(self,'TIMEOUT');
|
|
terminate;
|
|
end
|
|
else
|
|
fProcessor.ExecuteSQL('SELECT 1');
|
|
end;
|
|
|
|
procedure TBaseConnection.SetIdle;
|
|
begin
|
|
fCheckConnect:=true;
|
|
end;
|
|
|
|
procedure TBaseConnection.CleanDone;
|
|
var
|
|
i: integer;
|
|
cmd: TCommand;
|
|
begin
|
|
for i := DoneCommands.Count-1 downto 0 do
|
|
begin
|
|
cmd := TCommand(DoneCommands[i]);
|
|
if cmd.isDone or (now()-cmd.AccessTime > cmd.TimeOut) then
|
|
begin
|
|
cmd.free;
|
|
DoneCommands.Delete(i);
|
|
Dec(nCommandReady);
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseConnection.Log(sender: TObject; msg: string);
|
|
begin
|
|
if assigned(fLogger) then
|
|
flogger(sender,msg);
|
|
end;
|
|
|
|
procedure TBaseConnection.Execute;
|
|
var
|
|
cmd: TCommand;
|
|
begin
|
|
log(self,'started');
|
|
while not terminated do
|
|
begin
|
|
while Commands.Count>0 do
|
|
begin
|
|
cmd := Commands.Objects[0] as TCommand;
|
|
log(self,format('run(%s) %s %s ',[cmd.CommandID,cmd.CommandName, cmd.CommandSubClass]));
|
|
try
|
|
RunCommand(cmd);
|
|
finally
|
|
Commands.Delete(0);
|
|
DoneCommands.Add(cmd);
|
|
end;
|
|
end;
|
|
CleanDone;
|
|
if fCheckConnect then Idle;
|
|
sleep(200);
|
|
end;
|
|
log(self,'finished');
|
|
end;
|
|
|
|
function TBaseConnection.ProcessOptionValues(ReportName, ParamName: string;
|
|
ParamValues: TStrings; out Answer: string; out RetValue: QWORD; out
|
|
OptionValues: TStrings): boolean;
|
|
var
|
|
c: TCommand;
|
|
tmp: TStrings;
|
|
begin
|
|
with TCommandCollection.Find('report',Reportname).Create(self,ReportName) do
|
|
try
|
|
ParseCommand(0,0,ReportName,ParamValues,[],nil,tmp);
|
|
if assigned(tmp) then FreeAndNil(tmp);
|
|
result := ProcessOptionValues(ParamName,answer,RetValue,OptionValues);
|
|
finally
|
|
free
|
|
end;
|
|
end;
|
|
|
|
class function TBaseConnection.newID: string;
|
|
var
|
|
g: TGUID;
|
|
i: integer;
|
|
begin
|
|
createguid(g);
|
|
result := inttohex(g.D1,8)+inttohex(g.D2,4)+inttohex(g.D3,4);
|
|
for i := 0 to 7 do
|
|
result := result + inttohex(g.D4[i],2);
|
|
end;
|
|
|
|
|
|
{ TCommandCollection }
|
|
|
|
class procedure TCommandCollection.Register(ACommand: TCommandClass);
|
|
begin
|
|
fCollection.Add(ACommand);
|
|
end;
|
|
|
|
class function TCommandCollection.Find(Action, SubClass: string): TCommandClass;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to fCollection.Count-1 do
|
|
if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText(SubClass, TCommandClass(fCollection.Items[i]).CommandSubClass) then
|
|
begin
|
|
result := TCommandClass(fCollection.Items[i]) ;
|
|
exit;
|
|
end;
|
|
for i := 0 to fCollection.Count-1 do
|
|
if fCollection.Items[i].InheritsFrom(TCommand) and SameText(Action, TCommandClass(fCollection.Items[i]).CommandName) and SameText('', TCommandClass(fCollection.Items[i]).CommandSubClass) then
|
|
begin
|
|
result := TCommandClass(fCollection.Items[i]) ;
|
|
exit;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
class procedure TCommandCollection.Init;
|
|
begin
|
|
fCollection := TCommandCollection.Create;
|
|
end;
|
|
|
|
class procedure TCommandCollection.Done;
|
|
begin
|
|
fCollection.Free;
|
|
end;
|
|
|
|
{ TCommand }
|
|
|
|
constructor TCommand.Create(aConnect: TBaseConnection; ASubClass: string);
|
|
begin
|
|
fconnect := AConnect;
|
|
fSubClass := ASubClass;
|
|
fStatus:=StatusWaiting;
|
|
fcurrentStage := 'в очереди';
|
|
TimeOut:=1/24/4;
|
|
fCommandID:=TBaseConnection.newID;
|
|
end;
|
|
|
|
destructor TCommand.Destroy;
|
|
begin
|
|
if assigned(fData) then fData.Free;
|
|
if assigned(fResult) then fResult.free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCommand.doRun;
|
|
begin
|
|
fStatus:=StatusProcessing;
|
|
fcurrentStage := 'исполняется';
|
|
try
|
|
if Run then
|
|
begin
|
|
fStatus:=StatusComplete;
|
|
fcurrentStage := 'завершена';
|
|
end
|
|
else
|
|
begin
|
|
fStatus := StatusError;
|
|
fcurrentStage := 'завершена c ошибкой';
|
|
end;
|
|
|
|
except on e: Exception do
|
|
begin
|
|
fStatus:=StatusError;
|
|
fcurrentStage := 'error';
|
|
Results.Name:=e.Message;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCommand.Done;
|
|
begin
|
|
fisDone:=true;
|
|
end;
|
|
|
|
function TCommand.ParseCommand(ACode: DWORD; iParam: QWORD; ACommand: string;
|
|
Args: TStrings; intArgs: TParamArray; CmdData: TStream; out Errors: TStrings
|
|
): boolean;
|
|
begin
|
|
self.fData := TCommandData.Create(ACode,iParam,ACommand,Args,intArgs,cmdData);
|
|
result := ParseArguments(fData.Keys,Errors);
|
|
end;
|
|
|
|
procedure TCommand.Log(msg: string);
|
|
begin
|
|
connect.log(self, self.CommandID+#09+msg)
|
|
end;
|
|
|
|
end.
|
|
|