zeoslib  UNKNOWN
 All Files
ZPgEventAlerter.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { Interbase Database Connectivity Classes }
5 { }
6 { Copyright (c) 1999-2003 Zeos Development Group }
7 { Written by Sergey Merkuriev }
8 { }
9 {*********************************************************}
10 
11 {@********************************************************}
12 { Copyright (c) 1999-2012 Zeos Development Group }
13 { }
14 { License Agreement: }
15 { }
16 { This library is distributed in the hope that it will be }
17 { useful, but WITHOUT ANY WARRANTY; without even the }
18 { implied warranty of MERCHANTABILITY or FITNESS FOR }
19 { A PARTICULAR PURPOSE. See the GNU Lesser General }
20 { Public License for more details. }
21 { }
22 { The source code of the ZEOS Libraries and packages are }
23 { distributed under the Library GNU General Public }
24 { License (see the file COPYING / COPYING.ZEOS) }
25 { with the following modification: }
26 { As a special exception, the copyright holders of this }
27 { library give you permission to link this library with }
28 { independent modules to produce an executable, }
29 { regardless of the license terms of these independent }
30 { modules, and to copy and distribute the resulting }
31 { executable under terms of your choice, provided that }
32 { you also meet, for each linked independent module, }
33 { the terms and conditions of the license of that module. }
34 { An independent module is a module which is not derived }
35 { from or based on this library. If you modify this }
36 { library, you may extend this exception to your version }
37 { of the library, but you are not obligated to do so. }
38 { If you do not wish to do so, delete this exception }
39 { statement from your version. }
40 { }
41 { }
42 { The project web site is located on: }
43 { http://zeos.firmos.at (FORUM) }
44 { http://sourceforge.net/p/zeoslib/tickets/ (BUGTRACKER)}
45 { svn://svn.code.sf.net/p/zeoslib/code-0/trunk (SVN) }
46 { }
47 { http://www.sourceforge.net/projects/zeoslib. }
48 { }
49 { }
50 { Zeos Development Group. }
51 {********************************************************@}
52 
53 {*********************************************************}
54 { }
55 { TZPgEventAlerter, Asynchronous notifying. }
56 { By Ivan Rog - 2010 }
57 { }
58 { Contributors: }
59 { Silvio Clecio - http://silvioprog.com.br }
60 { }
61 {*********************************************************}
62 
63 unit ZPgEventAlerter;
64 
65 interface
66 {$I ZComponent.inc}
67 uses
68  SysUtils, Classes, ExtCtrls,
69  ZDbcPostgreSql, ZPlainPostgreSqlDriver, ZConnection, ZAbstractRODataset;
70 
71 type
72  TZPgNotifyEvent = procedure(Sender: TObject; Event: string;
73  ProcessID: Integer; Payload: string) of object;
74 
75  { TZPgEventAlerter }
76 
77  TZPgEventAlerter = class (TComponent)
78  private
79  FActive : Boolean;
80  FEvents : TStrings;
81 
82  FTimer : TTimer;
83  FConnection: TZConnection;
84  FNotifyFired : TZPgNotifyEvent;
85 
86  FProcessor : TZPgEventAlerter; //processor component - it will actually handle notifications received from DB
87  //if processor is not assignet - component is handling notifications by itself
88  FChildAlerters :TList; //list of TZPgEventAlerter that have our component attached as processor
89  FChildEvents : TStrings; //list of actual events to be handled - gathered from events of all childe
90  protected
91  procedure SetActive (Value: Boolean);
92  function GetInterval : Cardinal;
93  procedure SetInterval (Value: Cardinal);
94  procedure SetEvents (Value: TStrings);
95  procedure SetConnection (Value: TZConnection);
96  procedure TimerTick (Sender: TObject);
97  procedure CheckEvents;
98  procedure OpenNotify;
99  procedure CloseNotify;
100 
101  procedure SetProcessor(Value: TZPgEventAlerter);
102  procedure AddChildAlerter(Child: TZPgEventAlerter);
103  procedure RemoveChildAlerter(Child: TZPgEventAlerter);
104  procedure HandleNotify(Notify: PZPostgreSQLNotify); //launching OnNotify event fo Self and all child components (if event name is matched)
105  procedure SetChildEvents (Value: TStrings);
106  procedure RefreshEvents; //gathering all events from all child components (no duplicates), also propagating these events "down" to our processor
107  public
108  constructor Create (AOwner: TComponent); override;
109  destructor Destroy; override;
110  published
111  property Connection: TZConnection read FConnection write SetConnection;
112  property Active: Boolean read FActive write SetActive;
113  property Events: TStrings read FEvents write SetEvents;
114  property Interval: Cardinal read GetInterval write SetInterval default 250;
115  property OnNotify: TZPgNotifyEvent read FNotifyFired write FNotifyFired;
116  property Processor: TZPgEventAlerter read FProcessor write SetProcessor; //property to assign processor handling notifications
117  property ChildEvents: TStrings read FChildEvents write SetChildEvents; //read onlu property to keep all events in one place
118  end;
119 
120 implementation
121 
122 {$IFDEF WITH_UNITANSISTRINGS}
123 uses AnsiStrings;
124 {$ENDIF}
125 
126 { TZPgEventAlerter }
127 
128 constructor TZPgEventAlerter.Create(AOwner: TComponent);
129 var
130  I: Integer;
131 begin
132  inherited Create(AOwner);
133  FEvents := TStringList.Create;
134  FChildAlerters := TList.Create;
135  FChildEvents := TStringList.Create;
136  with TStringList(FEvents) do
137  begin
138  Duplicates := dupIgnore;
139  end;
140 
141  with TStringList(FChildEvents) do
142  begin
143  Duplicates := dupIgnore;
144  end;
145 
146  FTimer := TTimer.Create(Self);
147  FTimer.Enabled := False;
148  SetInterval(250);
149  FTimer.OnTimer := TimerTick;
150  FActive := False;
151  if (csDesigning in ComponentState) and Assigned(AOwner) then
152  for I := AOwner.ComponentCount - 1 downto 0 do
153  if AOwner.Components[I] is TZConnection then
154  begin
155  FConnection := AOwner.Components[I] as TZConnection;
156  Break;
157  end;
158 end;
159 
160 destructor TZPgEventAlerter.Destroy;
161 begin
162  if FProcessor = nil then
163  CloseNotify;
164  FEvents.Free;
165  FTimer.Free;
166  FChildAlerters.Free;
167  FChildEvents.Free;
168  inherited Destroy;
169 end;
170 
171 procedure TZPgEventAlerter.SetInterval(Value: Cardinal);
172 begin
173  FTimer.Interval := Value;
174 end;
175 
176 function TZPgEventAlerter.GetInterval: Cardinal;
177 begin
178  Result := FTimer.Interval;
179 end;
180 
181 procedure TZPgEventAlerter.SetEvents(Value: TStrings);
182 var
183  I: Integer;
184 begin
185  FEvents.Assign(Value);
186 
187  for I := 0 to FEvents.Count -1 do
188  FEvents[I] := Trim(FEvents[I]);
189  RefreshEvents; //we must propagate events down to our processor
190 end;
191 
192 procedure TZPgEventAlerter.SetActive(Value: Boolean);
193 begin
194  if FActive <> Value then
195  begin
196  if FProcessor = nil then
197  begin
198  if Value then
199  begin
200  RefreshEvents;
201  OpenNotify;
202  end
203  else
204  begin
205  CloseNotify;
206  end
207  end
208  else //we have processor attached - we dont need to open or close notifications
209  begin
210  FActive := Value;
211  FProcessor.RefreshEvents;
212  end;
213  end;
214 end;
215 
216 procedure TZPgEventAlerter.SetConnection(Value: TZConnection);
217 begin
218  if FConnection <> Value then
219  begin
220  if FProcessor = nil then //we are closing notifiers only whern there is no processor attached
221  CloseNotify;
222  FConnection := Value;
223  end;
224 end;
225 
226 procedure TZPgEventAlerter.TimerTick(Sender: TObject);
227 begin
228  if not FActive then
229  FTimer.Enabled := False
230  else
231  begin
232  if FProcessor <> nil then
233  FTimer.Enabled := False
234  else
235  CheckEvents;
236  end;
237 end;
238 
239 procedure TZPgEventAlerter.OpenNotify;
240 var
241  I : Integer;
242  Tmp : array [0..255] of AnsiChar;
243  Handle : PZPostgreSQLConnect;
244  ICon : IZPostgreSQLConnection;
245  PlainDRV : IZPostgreSQLPlainDriver;
246  Res: PGresult;
247 begin
248  if not Boolean(Pos('postgresql', FConnection.Protocol)) then
249  raise EZDatabaseError.Create('Ivalid connection protocol. Need <postgres>, get ' +
250  FConnection.Protocol + '.');
251  if FActive then
252  Exit;
253  if not Assigned(FConnection) then
254  Exit;
255  if ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
256  Exit;
257  if not FConnection.Connected then
258  Exit;
259  ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
260  Handle := ICon.GetConnectionHandle;
261  PlainDRV := ICon.GetPlainDriver;
262  if Handle = nil then
263  Exit;
264  for I := 0 to FChildEvents.Count-1 do
265  begin
266  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrPCopy(Tmp, 'listen ' + AnsiString(FChildEvents.Strings[I]));
267  Res := PlainDRV.ExecuteQuery(Handle, Tmp);
268  if (PlainDRV.GetResultStatus(Res) <> TZPostgreSQLExecStatusType(
269  PGRES_COMMAND_OK)) then
270  begin
271  PlainDRV.Clear(Res);
272  Exit;
273  end;
274  PlainDRV.Clear(Res);
275  end;
276  FActive := True;
277  FTimer.Enabled := True;
278 end;
279 
280 procedure TZPgEventAlerter.CloseNotify;
281 var
282  I : Integer;
283  tmp : array [0..255] of AnsiChar;
284  Handle : PZPostgreSQLConnect;
285  ICon : IZPostgreSQLConnection;
286  PlainDRV : IZPostgreSQLPlainDriver;
287  Res: PGresult;
288 begin
289  if not FActive then
290  Exit;
291  FActive := False;
292  FTimer.Enabled := False;
293  ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
294  Handle := ICon.GetConnectionHandle;
295  PlainDRV := ICon.GetPlainDriver;
296  if Handle = nil then
297  Exit;
298  for I := 0 to FChildEvents.Count-1 do
299  begin
300  {$IFDEF WITH_STRPCOPY_DEPRECATED}AnsiStrings.{$ENDIF}StrPCopy(Tmp, 'unlisten ' + AnsiString(FChildEvents.Strings[i]));
301  Res := PlainDRV.ExecuteQuery(Handle, Tmp);
302  if (PlainDRV.GetResultStatus(Res) <> TZPostgreSQLExecStatusType(
303  PGRES_COMMAND_OK)) then
304  begin
305  PlainDRV.Clear(Res);
306  Exit;
307  end;
308  PlainDRV.Clear(Res);
309  end;
310 end;
311 
312 procedure TZPgEventAlerter.CheckEvents;
313 var
314  Notify: PZPostgreSQLNotify;
315  Handle : PZPostgreSQLConnect;
316  ICon : IZPostgreSQLConnection;
317  PlainDRV : IZPostgreSQLPlainDriver;
318 begin
319  ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
320  Handle := ICon.GetConnectionHandle;
321  if Handle=nil then
322  begin
323  FTimer.Enabled := False;
324  FActive := False;
325  Exit;
326  end;
327  if not FConnection.Connected then
328  begin
329  CloseNotify;
330  Exit;
331  end;
332  PlainDRV := ICon.GetPlainDriver;
333 
334  if PlainDRV.ConsumeInput(Handle)=1 then
335  begin
336  while True do
337  begin
338  Notify := PlainDRV.Notifies(Handle);
339  if Notify = nil then
340  Break;
341  HandleNotify(Notify);
342  PlainDRV.FreeNotify(Notify);
343  end;
344  end;
345 end;
346 
347 procedure TZPgEventAlerter.HandleNotify(Notify: PZPostgreSQLNotify);
348 var
349  i: Integer;
350  CurrentChild: TZPgEventAlerter;
351 begin
352  if Assigned(FNotifyFired) and (FEvents.IndexOf(String(Notify{$IFDEF OLDFPC}^{$ENDIF}.relname)) <> -1) then
353  FNotifyFired(Self, String(Notify{$IFDEF OLDFPC}^{$ENDIF}.relname), Notify{$IFDEF OLDFPC}^{$ENDIF}.be_pid,String(Notify{$IFDEF OLDFPC}^{$ENDIF}.payload));
354 
355  for I := 0 to FChildAlerters.Count-1 do //propagating event to child listeners
356  begin
357  CurrentChild :=TZPgEventAlerter(FChildAlerters[i]);
358  if CurrentChild.Active and (CurrentChild.ChildEvents.IndexOf(String(Notify{$IFDEF OLDFPC}^{$ENDIF}.relname)) <> -1) then //but only active ones
359  CurrentChild.HandleNotify(Notify);
360  end;
361 end;
362 
363 procedure TZPgEventAlerter.SetProcessor(Value: TZPgEventAlerter);
364 begin
365  if FProcessor <> Value then
366  begin
367  if FProcessor <> nil then //remove assignment from old processor
368  begin
369  FProcessor.RemoveChildAlerter(Self);
370  end;
371  FProcessor := Value;
372  if FProcessor <> nil then //add assignment to new processor
373  begin
374  if FProcessor.Connection <> FConnection then
375  begin
376  raise Exception.Create('Cannot set processor with different connection');
377  Exit;
378  end;
379  FProcessor.AddChildAlerter(Self);
380  end;
381 
382  end;
383 end;
384 
385 procedure TZPgEventAlerter.RefreshEvents;
386 var
387  i,j: integer;
388  CurrentChild: TZPgEventAlerter;
389 begin
390  FChildEvents.Clear;
391  for I := 0 to FChildAlerters.Count-1 do
392  begin
393  CurrentChild := TZPgEventAlerter(FChildAlerters[i]);
394  if CurrentChild.Active or ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
395  begin //gathering vent namse from all childs
396  for j := 0 to CurrentChild.ChildEvents.Count-1 do
397  if FChildEvents.IndexOf(CurrentChild.ChildEvents.Strings[j]) = -1 then
398  FChildEvents.Add(CurrentChild.ChildEvents.Strings[j]);
399  end;
400  end;
401 
402  for i := 0 to Events.Count-1 do
403  if FChildEvents.IndexOf(Events.Strings[i]) = -1 then
404  FChildEvents.Add(Events.Strings[i]);
405 
406  if FProcessor <> nil then //refreshing eventrs in our processor
407  FProcessor.RefreshEvents
408  else
409  begin
410  if Active then //refreshing listeners after change of events - to make sure we will listen for everything
411  begin
412  Active := False;
413  Active := True;
414  end;
415  end;
416 end;
417 
418 procedure TZPgEventAlerter.AddChildAlerter(Child: TZPgEventAlerter);
419 begin
420  FChildAlerters.Add(Child);
421  RefreshEvents;
422 end;
423 
424 procedure TZPgEventAlerter.RemoveChildAlerter(Child: TZPgEventAlerter);
425 var
426  i: integer;
427 begin
428  i := FChildAlerters.IndexOf(Child);
429  FChildAlerters.Delete(i);
430  RefreshEvents;
431 end;
432 
433 procedure TZPgEventAlerter.SetChildEvents(Value: TStrings);
434 begin
435  Exit;
436 end;
437 
438 end.
439