Unit MonitorAlive; { This file provides a generic mechanism for notifying the database that we are alive. Everything gets sent to the monitor_alive table, assuming that someone is monitoring that. You can monitor multiple things. This unit is only responsible for sending things to the database. The PHP script reading the database is responsible for knowing different rules about different things we are monitoring. This class creates a different thread to talk to the database. This keeps us from slowing down if there is a database problem. } Interface Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; Type TConfigureMonitorAlive = class(TForm) HostText: TEdit; EnablePush: TCheckBox; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; UserText: TEdit; PasswordText: TEdit; DatabaseText: TEdit; PortText: TEdit; procedure EnablePushClick(Sender: TObject); procedure FormCreate(Sender: TObject); private public // This is often used to determine the default name for SendKeepAlive(). Class Function ComputerName : String; // This is a recommended name for the data feed. Class Function DataName(HashFirst, HashCount : Integer) : String; end; // This comes from an external source which verifies that we are connected // to the datafeed. You can send different names, if the database is set up // to monitor different things. This will send the message more or less // immediately. It doesn't make sense to update the database more than once // a minute or so. The caller is responsible for calling this at reasonable // intervals. Procedure SendKeepAlive(KeepAliveName : String); Var ConfigureMonitorAlive: TConfigureMonitorAlive; Implementation {$R *.dfm} Uses MySql, {TimeConversion, Prices, }ConfigFile, DebugOutput, SyncObjs, Math; Procedure Beep; Begin // ??? In the data center now! End; //////////////////////////////////////////////////////////////////////// // TDatabaseThread //////////////////////////////////////////////////////////////////////// Type TDatabaseThread = Class(TThread) Private Host, User, Password, Database, Port : String; Lock : TCriticalSection; Event : TEvent; Names : TStringList; Connection : PMYSQL; Procedure VerifyInit; Procedure GetConfigValues; Procedure EnableInputs; Procedure ReportSqlError; Procedure ReportSqlError1; Function SqlEscape(S : String) : String; Function SendQuery(Query : String) : Boolean; // False if failed. Procedure SendAll; Procedure CloseConnection; Private PastSuccess : Boolean; Procedure ReportSuccess; Procedure ReportFail; Protected Procedure Execute; Override; Public PushValue : Boolean; Constructor Create(CreateSuspended: Boolean); Destructor Destroy; Procedure SendNow; Procedure SendKeepAlive(KeepAliveName : String); End; Procedure TDatabaseThread.ReportSuccess; Begin PastSuccess := True End; Procedure TDatabaseThread.ReportFail; Begin If PastSuccess Then PastSuccess := False Else Beep End; Procedure TDatabaseThread.SendNow; Begin Event.SetEvent End; Function TDatabaseThread.SqlEscape(S : String) : String; Var NewLength : Integer; Begin SetLength(Result, Succ(Length(S) * 2)); NewLength := mysql_escape_string(PChar(Result), PChar(S), Length(S)); SetLength(Result, NewLength) End; Procedure TDatabaseThread.GetConfigValues; Begin If Not Assigned(ConfigureMonitorAlive) Then Exit; Host := ConfigureMonitorAlive.HostText.Text; Password := ConfigureMonitorAlive.PasswordText.Text; User := ConfigureMonitorAlive.UserText.Text; Database := ConfigureMonitorAlive.DataBaseText.Text; Port := ConfigureMonitorAlive.PortText.Text; ConfigureMonitorAlive.HostText.Enabled := False; ConfigureMonitorAlive.PasswordText.Enabled := False; ConfigureMonitorAlive.UserText.Enabled := False; ConfigureMonitorAlive.DataBaseText.Enabled := False; ConfigureMonitorAlive.PortText.Enabled := False End; Procedure TDatabaseThread.EnableInputs; Begin If Not Assigned(ConfigureMonitorAlive) Then Exit; ConfigureMonitorAlive.HostText.Enabled := True; ConfigureMonitorAlive.PasswordText.Enabled := True; ConfigureMonitorAlive.UserText.Enabled := True; ConfigureMonitorAlive.DataBaseText.Enabled := True; ConfigureMonitorAlive.PortText.Enabled := True End; Procedure TDatabaseThread.ReportSqlError1; Begin If Not Assigned(ConfigureMonitorAlive) Then Exit; DebugOutputWindow.AddMessage(Format('SQL Error #%d, "%s", %s.', [mysql_errno(Connection), mysql_error(Connection), DateTimeToStr(Now)])) End; Procedure TDatabaseThread.ReportSqlError; Begin ReportFail; Synchronize(ReportSqlError1) End; Procedure TDatabaseThread.VerifyInit; Var ConnectAttempt : PMYSQL; Flags : LongWord; Begin If Not Assigned(Connection) Then Begin Assert(libmysql_status = LIBMYSQL_READY); Synchronize(GetConfigValues); If {Remote} False Then Flags := CLIENT_LONG_PASSWORD Or CLIENT_COMPRESS Or CLIENT_SSL Else Flags := 0; Connection := mysql_init(Nil); ConnectAttempt := mysql_real_connect(Connection, PChar(Host), PChar(User), PChar(Password), PChar(Database), StrToInt64Def(Port, 0), Nil, Flags); If (Connection <> ConnectAttempt) Then Begin ReportSqlError; CloseConnection; Sleep(5000) End End End; Procedure TDatabaseThread.CloseConnection; Begin Synchronize(EnableInputs); If Assigned(Connection) Then Begin mysql_close(Connection); Connection := Nil End End; Constructor TDatabaseThread.Create(CreateSuspended: Boolean); Begin Lock := TCriticalSection.Create; Event := TEvent.Create(Nil, False, False, ''); Names := TStringList.Create; Names.Sorted := True; Names.Duplicates := dupIgnore; Inherited End; Destructor TDatabaseThread.Destroy; Begin CloseConnection; Lock.Free; Names.Free; Event.Free; Inherited End; Function TDatabaseThread.SendQuery(Query : String) : Boolean; Var QueryResult : Integer; Begin VerifyInit; If Not Assigned(Connection) Then Result := False Else Begin QueryResult := mysql_real_query(Connection, PChar(Query), Length(Query)); If QueryResult = 0 Then Begin Result := True; ReportSuccess End Else Begin ReportSqlError; Result := False; { If mysql_errno(Connection) = 2013 Then Begin // Lost connection. I think mysql does at least one retry // before reporting this. // But we also see , 2006 - server has gone away.} CloseConnection { End}; Beep; Sleep(1000) End End End; Procedure TDatabaseThread.SendAll; Var Name : String; Begin If PushValue Then Repeat Lock.Acquire; Try If Names.Count = 0 Then Name := '' Else Begin Name := Names[Pred(Names.Count)]; Names.Delete(Pred(Names.Count)) End Finally Lock.Release End; If Name = '' Then Break; SendQuery('UPDATE monitor_alive SET last_update=NOW() WHERE name="' + SqlEscape(Name) + '"') Until False Else CloseConnection End; Procedure TDatabaseThread.Execute; Begin Repeat // This is a bit sloppy. The old code made it optional to notify the // thread. Event.WaitFor(10000); SendAll Until False End; Procedure TDatabaseThread.SendKeepAlive(KeepAliveName : String); Begin If KeepAliveName <> '' Then Begin Lock.Acquire; Try Names.Add(KeepAliveName) Finally Lock.Release End End; SendNow End; //////////////////////////////////////////////////////////////////////// // Global //////////////////////////////////////////////////////////////////////// Var DatabaseThread : TDatabaseThread; Procedure SendKeepAlive(KeepAliveName : String); Begin DatabaseThread.SendKeepAlive(KeepAliveName) End; //////////////////////////////////////////////////////////////////////// // TConfigureAlertsPush //////////////////////////////////////////////////////////////////////// Class Function TConfigureMonitorAlive.DataName(HashFirst, HashCount : Integer) : String; Begin Result := 'Proxy ' + TConfigureMonitorAlive.ComputerName; If HashCount > 1 Then Result := Result + ' ' + IntToStr(HashFirst) End; Class Function TConfigureMonitorAlive.ComputerName : String; Var Success : Boolean; NameLength : Cardinal; Begin { TConfigureMonitorAlive.ComputerName } SetLength(Result, Succ(MAX_COMPUTERNAME_LENGTH)); NameLength := Succ(MAX_COMPUTERNAME_LENGTH); Success := GetComputerName(PChar(Result), NameLength); If Success Then SetLength(Result, NameLength) Else Result := 'Unknown' End; { TConfigureMonitorAlive.ComputerName } procedure TConfigureMonitorAlive.EnablePushClick(Sender: TObject); begin DatabaseThread.PushValue := EnablePush.Checked; SendKeepAlive('') end; Procedure TConfigureMonitorAlive.FormCreate(Sender: TObject); Begin DatabaseThread := TDatabaseThread.Create(False); HostText.Text := GetConfigValue(MarketDataProxyProgramSection, 'db_Host', HostText.Text); UserText.Text := GetConfigValue(MarketDataProxyProgramSection, 'db_User', UserText.Text); PasswordText.Text := GetConfigValue(MarketDataProxyProgramSection, 'db_Password', PasswordText.Text); DatabaseText.Text := GetConfigValue(MarketDataProxyProgramSection, 'db_Database', DatabaseText.Text); PortText.Text := GetConfigValue(MarketDataProxyProgramSection, 'db_Port', PortText.Text); EnablePushClick(Nil); WindowState := wsMinimized End; End.