Unit MainFormUnit; { TO DO: We should not ignore duplicate requests for data. The second request should send a refresh. The way we currently use this proxy, this probably won't be a problem, but it could be. And it should be done better. } Interface Uses DataNodes, WakeMeSoon, MainConnectionUnit, MessageQueues, StandardHashTables, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, NxCoreThread; Type TConnectionForm = class(TForm) MainSymbolsMemo: TMemo; Label1: TLabel; UserNameEdit: TEdit; Label2: TLabel; PasswordEdit: TEdit; LoginButton: TButton; ResetButton: TButton; Label3: TLabel; ConnectionStatusLabel: TLabel; Label4: TLabel; DestinationEdit: TEdit; CloseConnectionButton: TButton; RegionalsSymbolsMemo: TMemo; Timer1: TTimer; procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); procedure LoginButtonClick(Sender: TObject); procedure ResetButtonClick(Sender: TObject); procedure CloseConnectionButtonClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure Timer1Timer(Sender: TObject); private FL1Links, FRegionalLinks : TStringObjectHashTable; FQueueWatcher : TWakeMeSoon; FOutputStarted : Boolean; FResetAfter : TDateTime; FConnection : TMainConnection; FDone : Boolean; FMessageQueue : TMessageQueue; Procedure CheckQueue; Procedure OnConnectionStatusCallback; Procedure OnConnectionStartedCallback; Procedure OnConnectionBrokenCallback; Procedure OnSubscriptionCallback(Const Data : String); Class Procedure ReleaseAllLinks(Links : TStringObjectHashTable); public Procedure RehashSymbols; Procedure LoadParams(Username, Password, Destination : String); end; Var ConnectionForm: TConnectionForm; Implementation {$R *.dfm} Uses ProxyListenerBaseUnit, NxCoreL1Proxy, NxCoreRegionalProxy, DataNodesDebug, DebugOutput, HashSelector, StringObjectHashtables, StrUtils; //////////////////////////////////////////////////////////////////////// // TProxyContainer // // We store each proxy data node and the link to that proxy together. // That way we can quickly find one to access it or to release it. This // is an object so we can shove it in a hash table. //////////////////////////////////////////////////////////////////////// Type TProxyType = (ptL1, ptRegional); TProxyContainer = Class(TObject) Private FNode : TProxyListenerBase; FLink : TDataNodeLink; Public Constructor Create(ProxyType : TProxyType; Const Symbol : String; MessageQueue : TMessageQueue; Callback : TThreadMethod); Destructor Destroy; Override; Procedure RequestRefresh; End; Constructor TProxyContainer.Create(ProxyType : TProxyType; Const Symbol : String; MessageQueue : TMessageQueue; Callback : TThreadMethod); Begin Case ProxyType Of ptL1 : TNxCoreL1Proxy.Find(Symbol, MessageQueue, Callback, FNode, FLink); ptRegional : TNxCoreRegionalsProxy.Find(Symbol, MessageQueue, Callback, FNode, FLink); End; Assert(Assigned(FNode)); FLink.SetReceiveInput(True) End; Destructor TProxyContainer.Destroy; Begin FLink.Release End; Procedure TProxyContainer.RequestRefresh; Begin Assert(Assigned(Self), 'TProxyContainer.RequestRefresh(Nil)'); Assert(Assigned(FNode), 'FNode = Nil in TProxyContainer.RequestRefresh'); FNode.SendRefresh End; //////////////////////////////////////////////////////////////////////// // TFreeInDataNodeThread // // This will send a request to the data node thead, and in that thread // it will free the given object. This makes sure that we are not // freeing resources before we are really done with them. This is // required because of our non-standard use of the message queue. The // better solution would be to make the message queue into a data node. // // Yuck. //////////////////////////////////////////////////////////////////////// Type TFreeInDataNodeThread = Class(TBroadcastMessage) Private FToDelete : TObject; Protected Procedure BeforeDelivery; Override; Public Constructor Create(ToDelete : TObject); Class Procedure DoIt(ToDelete : TObject); End; Procedure TFreeInDataNodeThread.BeforeDelivery; Begin FToDelete.Free End; Constructor TFreeInDataNodeThread.Create(ToDelete : TObject); Begin FToDelete := ToDelete End; Class Procedure TFreeInDataNodeThread.DoIt(ToDelete : TObject); Begin Create(ToDelete).NullSend End; //////////////////////////////////////////////////////////////////////// // TConnectionForm // // One of these per connection. These allow some modification of the // parameters, and some monitoring of what's going on. // // F1Links contains an entry for every symbol that was requested. The // key is the symbol. If we are tracking the symbol, this will point to // a TProxyContainer object. If we are not (presumaly because of the // hash settings) then this will point to Nil. F1Regionals is similar, // but for regional data. //////////////////////////////////////////////////////////////////////// // We keep track of all requests. The ones that we are actually satisfying // point to a TDataNodeLink. The ones that did not satisfy the hashing // criteria point to Nil. If the hashing criteria changes, someone needs // to call this procedure. Procedure TConnectionForm.RehashSymbols; Var It : TStringObjectHashTableIterator; Symbol : String; ShouldWatch, CurrentlyWatching : Boolean; Begin { TForm1.UpdateSymbols } RefreshNxCoreSubscriptions; It := FL1Links.GetIterator; Try While It.ValidEntry Do Begin Symbol := It.Key; ShouldWatch := HashSelectorForm.FollowSymbol(Symbol); CurrentlyWatching := Assigned(It.Value); If ShouldWatch And Not CurrentlyWatching Then It.Value := TProxyContainer.Create(ptL1, Symbol, FMessageQueue, FQueueWatcher.RequestWakeup) Else If CurrentlyWatching And Not ShouldWatch Then Begin It.Value.Free; It.Value := Nil End; It.Next End Finally It.Free End; It := FRegionalLinks.GetIterator; Try While It.ValidEntry Do Begin Symbol := It.Key; ShouldWatch := HashSelectorForm.FollowSymbol(Symbol); CurrentlyWatching := Assigned(It.Value); If ShouldWatch And Not CurrentlyWatching Then It.Value := TProxyContainer.Create(ptRegional, Symbol, FMessageQueue, FQueueWatcher.RequestWakeup) Else If CurrentlyWatching And Not ShouldWatch Then Begin It.Value.Free; It.Value := Nil End; It.Next End Finally It.Free End; CheckQueue End; { TForm1.UpdateSymbols } // Split string using given delimiter (break string) // This is interesting: If I make these "Out" parameters, then they are // set 0 '' at the beginning of the procedure. If Source and rest are the // same variable, source is set to '' before it is copied into this procedure. Procedure Split(BreakStr, Source : String; Var First, Rest : String); Var BreakPos : Integer; Begin BreakPos := Pos(BreakStr, Source); If BreakPos = 0 Then Begin First := Source; Rest := '' End Else Begin First := LeftBStr(Source, Pred(BreakPos)); Rest := MidBStr(Source, BreakPos + Length(BreakStr), MaxInt) End End; Procedure TConnectionForm.OnSubscriptionCallback(Const Data : String); Var Lines, Line, Command, Symbol, AddedSymbols, AddedRegionalSymbols, AddedMessages : String; Begin //DebugOutputWindow.AddMessage('OnSubscriptionCallback("' + Data + '")'); Lines := Data; While Lines <> '' Do Begin Split(#13#10, Lines, Line, Lines); //DebugOutputWindow.AddMessage('line="' + Line + '", lines="' + Lines + '"'); Split(':', Line, Command, Symbol); If Command = 'Add-L1' Then Begin //DebugOutputWindow.AddMessage('possible command="' + Command + '", Symbol="' + Symbol + '"'); If FL1Links.ContainsKey(Symbol) Then Begin // We are already aware of this symbol If Assigned(FL1Links[Symbol]) Then // And we are tracking this symbol. (FL1Links[Symbol] As TProxyContainer).RequestRefresh End Else Begin If HashSelectorForm.FollowSymbol(Symbol) Then FL1Links[Symbol] := TProxyContainer.Create(ptL1, Symbol, FMessageQueue, FQueueWatcher.RequestWakeup) Else FL1Links[Symbol] := Nil; //MainSymbolsMemo.Lines.Add(Symbol); AddedSymbols := AddedSymbols + Symbol + #13#10; //DebugOutputWindow.AddMessage(DestinationEdit.Text + ' -> ' + Line) AddedMessages := AddedMessages + DestinationEdit.Text + ' -> ' + Line + #13#10; End End Else If Command = 'Add-Regional' Then Begin If FRegionalLinks.ContainsKey(Symbol) Then Begin // We are already aware of this symbol If Assigned(FRegionalLinks[Symbol]) Then // And we are tracking this symbol. (FRegionalLinks[Symbol] As TProxyContainer).RequestRefresh End Else Begin If HashSelectorForm.FollowSymbol(Symbol) Then FRegionalLinks[Symbol] := TProxyContainer.Create(ptRegional, Symbol, FMessageQueue, FQueueWatcher.RequestWakeup) Else FRegionalLinks[Symbol] := Nil; //RegionalsSymbolsMemo.Lines.Add(Symbol); AddedRegionalSymbols := AddedRegionalSymbols + Symbol + #13#10; //DebugOutputWindow.AddMessage(DestinationEdit.Text + ' -> ' + Line) AddedMessages := AddedMessages + DestinationEdit.Text + ' -> ' + Line + #13#10; End End Else If Command = 'Add-All-TOS' Then Begin NxCoreOptions.ForceAllTos := True; DebugOutputWindow.AddMessage('possible command="' + Command + '"'); If FL1Links.ContainsKey('_ALLSYMBOLS') Then Begin // do nothing? refresh on restart not supported End Else Begin FL1Links['_ALLSYMBOLS'] := TProxyContainer.Create(ptL1, '_ALLSYMBOLS', FMessageQueue, FQueueWatcher.RequestWakeup); //MainSymbolsMemo.Lines.Add('_ALLSYMBOLS'); AddedSymbols := AddedSymbols + '_ALLSYMBOLS' + #13#10; //DebugOutputWindow.AddMessage(DestinationEdit.Text + ' -> ' + Line) AddedMessages := AddedMessages + DestinationEdit.Text + ' -> ' + Line + #13#10; End End End; MainSymbolsMemo.Text := MainSymbolsMemo.Text + AddedSymbols; RegionalsSymbolsMemo.Text := RegionalsSymbolsMemo.Text + AddedRegionalSymbols; DebugOutputWindow.AddMessage(AddedMessages) End; Class Procedure TConnectionForm.ReleaseAllLinks(Links : TStringObjectHashTable); Var It : TStringObjectHashTableIterator; Begin It := Links.GetIterator; Try While It.ValidEntry Do Begin If Assigned(It.Value) Then Begin It.Value.Free; It.Value := Nil End; It.Next End Finally It.Free End; End; Procedure TConnectionForm.FormDestroy(Sender: TObject); Begin FConnection.Free; ReleaseAllLinks(FL1Links); ReleaseAllLinks(FRegionalLinks); FQueueWatcher.Free; TFreeInDataNodeThread.DoIt(FMessageQueue); FL1Links.Free; FRegionalLinks.Free End; Procedure RecordDebugOffset; Begin DebugOutputWindow.AddMessage('RecordDebugOffset can be found at $' + IntToHex(Integer(@RecordDebugOffset), 8)) End; Procedure TConnectionForm.FormCreate(Sender: TObject); Begin RecordDebugOffset; FL1Links := TStringObjectHashTable.Create; FRegionalLinks := TStringObjectHashTable.Create; FMessageQueue := TMessageQueue.Create; FQueueWatcher := TWakeMeSoon.Create; FQueueWatcher.OnWakeUp := CheckQueue; FConnection := TMainConnection.Create; FConnection.StatusCallback := OnConnectionStatusCallback; OnConnectionStatusCallback; FConnection.SubscriptionCallback := OnSubscriptionCallback; FConnection.ConnectionStartedCallback := OnConnectionStartedCallback; FConnection.ConnectionBrokenCallback := OnConnectionBrokenCallback End; Procedure TConnectionForm.CheckQueue; Var I : Integer; MessageList : TMessageList; Begin MessageList := FMessageQueue.GetQueue; {LogMemo.Lines.Add('___CheckQueue___'); For I := Low(MessageList) To High(MessageList) Do LogMemo.Lines.Add(' ' + MessageList[I][1].Value + ': ' + MessageList[I][0].Value); LogMemo.Lines.Add(IntToStr(Length(MessageList)))} For I := Low(MessageList) To High(MessageList) Do FConnection.SendMessageToServer(MessageList[I]) End; Procedure TConnectionForm.LoginButtonClick(Sender: TObject); Begin FConnection.UserName := UserNameEdit.Text; FConnection.Password := PasswordEdit.Text; FConnection.Destination := DestinationEdit.Text; Caption := DestinationEdit.Text + ' Proxy' End; Procedure TConnectionForm.ResetButtonClick(Sender: TObject); Begin FConnection.Reset End; Procedure TConnectionForm.OnConnectionStatusCallback; Begin ConnectionStatusLabel.Caption := FConnection.Status End; Procedure TConnectionForm.OnConnectionStartedCallback; Begin DebugOutputWindow.AddMessage(DestinationEdit.Text + ' OnStart'); FResetAfter := 0; FOutputStarted := True; TProxyListenerBase.SetOutputState(True, FMessageQueue) End; Procedure TConnectionForm.OnConnectionBrokenCallback; Const Timeout = 1.0 / 24.0 / 60.0 * 10.0; // 10 minutes. Begin If FOutputStarted Then Begin DebugOutputWindow.AddMessage(DestinationEdit.Text + ' OnStop'); FResetAfter := Now + Timeout; FOutputStarted := False; TProxyListenerBase.SetOutputState(False, FMessageQueue) End End; Procedure TConnectionForm.CloseConnectionButtonClick(Sender: TObject); Begin DebugOutputWindow.AddMessage('Closing connection' + DestinationEdit.Text); Try FDone := True; Close Except On Ex : Exception Do Begin DebugOutputWindow.AddMessage(Ex, 'TConnectionForm.CloseConnectionButtonClick') End End End; Procedure TConnectionForm.FormClose(Sender: TObject; Var Action: TCloseAction); Begin If FDone Then Action := caFree Else Action := caMinimize End; Procedure TConnectionForm.LoadParams(Username, Password, Destination : String); Begin UserNameEdit.Text := Username; PasswordEdit.Text := Password; DestinationEdit.Text := Destination; LoginButton.Click End; Procedure TConnectionForm.Timer1Timer(Sender: TObject); Begin If (FResetAfter > 0) And (Now > FResetAfter) Then Begin FResetAfter := 0; ReleaseAllLinks(FL1Links); ReleaseAllLinks(FRegionalLinks); MainSymbolsMemo.Lines.Clear; RegionalsSymbolsMemo.Lines.Clear; FL1Links.Clear; FRegionalLinks.Clear End End; End.