1 {*********************************************************}
3 { Zeos Database Objects }
4 { Interbase Database Connectivity Classes }
6 { Copyright (c) 1999-2003 Zeos Development Group }
7 { Written by Sergey Merkuriev }
9 {*********************************************************}
11 {@********************************************************}
12 { Copyright (c) 1999-2012 Zeos Development Group }
14 { License Agreement: }
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. }
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. }
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) }
47 { http://www.sourceforge.net/projects/zeoslib. }
50 { Zeos Development Group. }
51 {********************************************************@}
53 {*********************************************************}
55 { TZPgEventAlerter, Asynchronous notifying. }
56 { By Ivan Rog - 2010 }
59 { Silvio Clecio - http://silvioprog.com.br }
61 {*********************************************************}
68 SysUtils, Classes, ExtCtrls,
69 ZDbcPostgreSql, ZPlainPostgreSqlDriver, ZConnection, ZAbstractRODataset;
72 TZPgNotifyEvent = procedure(Sender: TObject; Event: string;
73 ProcessID: Integer; Payload: string) of object;
77 TZPgEventAlerter = class (TComponent)
83 FConnection: TZConnection;
84 FNotifyFired : TZPgNotifyEvent;
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
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;
99 procedure CloseNotify;
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
108 constructor Create (AOwner: TComponent); override;
109 destructor Destroy; override;
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
122 {$IFDEF WITH_UNITANSISTRINGS}
128 constructor TZPgEventAlerter.Create(AOwner: TComponent);
132 inherited Create(AOwner);
133 FEvents := TStringList.Create;
134 FChildAlerters := TList.Create;
135 FChildEvents := TStringList.Create;
136 with TStringList(FEvents) do
138 Duplicates := dupIgnore;
141 with TStringList(FChildEvents) do
143 Duplicates := dupIgnore;
146 FTimer := TTimer.Create(Self);
147 FTimer.Enabled := False;
149 FTimer.OnTimer := TimerTick;
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
155 FConnection := AOwner.Components[I] as TZConnection;
160 destructor TZPgEventAlerter.Destroy;
162 if FProcessor = nil then
171 procedure TZPgEventAlerter.SetInterval(Value: Cardinal);
173 FTimer.Interval := Value;
176 function TZPgEventAlerter.GetInterval: Cardinal;
178 Result := FTimer.Interval;
181 procedure TZPgEventAlerter.SetEvents(Value: TStrings);
185 FEvents.Assign(Value);
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
192 procedure TZPgEventAlerter.SetActive(Value: Boolean);
194 if FActive <> Value then
196 if FProcessor = nil then
208 else //we have processor attached - we dont need to open or close notifications
211 FProcessor.RefreshEvents;
216 procedure TZPgEventAlerter.SetConnection(Value: TZConnection);
218 if FConnection <> Value then
220 if FProcessor = nil then //we are closing notifiers only whern there is no processor attached
222 FConnection := Value;
226 procedure TZPgEventAlerter.TimerTick(Sender: TObject);
229 FTimer.Enabled := False
232 if FProcessor <> nil then
233 FTimer.Enabled := False
239 procedure TZPgEventAlerter.OpenNotify;
242 Tmp : array [0..255] of AnsiChar;
243 Handle : PZPostgreSQLConnect;
244 ICon : IZPostgreSQLConnection;
245 PlainDRV : IZPostgreSQLPlainDriver;
248 if not Boolean(Pos('postgresql', FConnection.Protocol)) then
249 raise EZDatabaseError.Create('Ivalid connection protocol. Need <postgres>, get ' +
250 FConnection.Protocol + '.');
253 if not Assigned(FConnection) then
255 if ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
257 if not FConnection.Connected then
259 ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
260 Handle := ICon.GetConnectionHandle;
261 PlainDRV := ICon.GetPlainDriver;
264 for I := 0 to FChildEvents.Count-1 do
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
277 FTimer.Enabled := True;
280 procedure TZPgEventAlerter.CloseNotify;
283 tmp : array [0..255] of AnsiChar;
284 Handle : PZPostgreSQLConnect;
285 ICon : IZPostgreSQLConnection;
286 PlainDRV : IZPostgreSQLPlainDriver;
292 FTimer.Enabled := False;
293 ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
294 Handle := ICon.GetConnectionHandle;
295 PlainDRV := ICon.GetPlainDriver;
298 for I := 0 to FChildEvents.Count-1 do
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
312 procedure TZPgEventAlerter.CheckEvents;
314 Notify: PZPostgreSQLNotify;
315 Handle : PZPostgreSQLConnect;
316 ICon : IZPostgreSQLConnection;
317 PlainDRV : IZPostgreSQLPlainDriver;
319 ICon := (FConnection.DbcConnection as IZPostgreSQLConnection);
320 Handle := ICon.GetConnectionHandle;
323 FTimer.Enabled := False;
327 if not FConnection.Connected then
332 PlainDRV := ICon.GetPlainDriver;
334 if PlainDRV.ConsumeInput(Handle)=1 then
338 Notify := PlainDRV.Notifies(Handle);
341 HandleNotify(Notify);
342 PlainDRV.FreeNotify(Notify);
347 procedure TZPgEventAlerter.HandleNotify(Notify: PZPostgreSQLNotify);
350 CurrentChild: TZPgEventAlerter;
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));
355 for I := 0 to FChildAlerters.Count-1 do //propagating event to child listeners
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);
363 procedure TZPgEventAlerter.SetProcessor(Value: TZPgEventAlerter);
365 if FProcessor <> Value then
367 if FProcessor <> nil then //remove assignment from old processor
369 FProcessor.RemoveChildAlerter(Self);
372 if FProcessor <> nil then //add assignment to new processor
374 if FProcessor.Connection <> FConnection then
376 raise Exception.Create('Cannot set processor with different connection');
379 FProcessor.AddChildAlerter(Self);
385 procedure TZPgEventAlerter.RefreshEvents;
388 CurrentChild: TZPgEventAlerter;
391 for I := 0 to FChildAlerters.Count-1 do
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]);
402 for i := 0 to Events.Count-1 do
403 if FChildEvents.IndexOf(Events.Strings[i]) = -1 then
404 FChildEvents.Add(Events.Strings[i]);
406 if FProcessor <> nil then //refreshing eventrs in our processor
407 FProcessor.RefreshEvents
410 if Active then //refreshing listeners after change of events - to make sure we will listen for everything
418 procedure TZPgEventAlerter.AddChildAlerter(Child: TZPgEventAlerter);
420 FChildAlerters.Add(Child);
424 procedure TZPgEventAlerter.RemoveChildAlerter(Child: TZPgEventAlerter);
428 i := FChildAlerters.IndexOf(Child);
429 FChildAlerters.Delete(i);
433 procedure TZPgEventAlerter.SetChildEvents(Value: TStrings);