zeoslib  UNKNOWN
 All Files
ZIBEventAlerter.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 unit ZIBEventAlerter;
54 
55 {$I ZComponent.inc}
56 
57 interface
58 
59 uses
60  SysUtils, Classes,
61 {$IF defined(MSWINDOWS)and not defined(FPC)}
62  Windows,
63 {$IFEND}
64  ZDbcInterbase6, ZConnection, ZDbcIntfs,
65  ZPlainFirebirdDriver, ZPlainFirebirdInterbaseConstants;
66 
67 type
68 
69  TEventAlert = procedure(Sender: TObject; EventName: string; EventCount: longint;
70  var CancelAlerts: boolean) of object;
71  TErrorEvent = procedure(Sender: TObject; ErrorCode: integer) of object;
72 
73  TZIBEventAlerter = class(TComponent)
74  private
75  FEvents: TStrings;
76  FOnEventAlert: TEventAlert;
77  FThreads: TList;
78  FNativeHandle: PISC_DB_HANDLE;
79  ThreadException: boolean;
80  FConnection: TZConnection;
81  FOnError: TErrorEvent;
82  FAutoRegister: boolean;
83  FRegistered: boolean;
84 
85  procedure SetConnection(Value: TZConnection);
86  procedure SetEvents(Value: TStrings);
87  function GetRegistered: boolean;
88  procedure SetRegistered(const Value: boolean);
89  function GetPlainDriver: IZInterbasePlainDriver;
90  protected
91  { Protected declarations }
92  function GetNativeHandle: PISC_DB_HANDLE; virtual;
93  procedure EventChange(Sender: TObject); virtual;
94  procedure ThreadEnded(Sender: TObject); virtual;
95  procedure Notification(AComponent: TComponent; Operation: TOperation); override;
96  public
97  { Public declarations }
98  constructor Create(AOwner: TComponent); override;
99  destructor Destroy; override;
100  procedure RegisterEvents; virtual;
101  procedure UnRegisterEvents; virtual;
102  property NativeHandle: PISC_DB_HANDLE read GetNativeHandle;
103  property PlainDriver: IZInterbasePlainDriver read GetPlainDriver;
104  procedure SetAutoRegister(const Value: boolean);
105  function GetAutoRegister: boolean;
106  published
107  { Published declarations }
108  property AutoRegister: boolean read GetAutoRegister write SetAutoRegister;
109  property Connection: TZConnection read FConnection write SetConnection;
110  property Events: TStrings read FEvents write SetEvents;
111  property Registered: boolean read GetRegistered write SetRegistered;
112  property OnEventAlert: TEventAlert read FOnEventAlert write FOnEventAlert;
113  property OnError: TErrorEvent read FOnError write FOnError;
114  end;
115 
116 implementation
117 
118 uses
119  SyncObjs;
120 
121 const
122  IB_MAX_EVENT_BLOCK = 15; // maximum events handled per block by InterBase
123  IB_MAX_EVENT_LENGTH = 64; // maximum event name length
124 threadvar
125  FStatusVector: TARRAY_ISC_STATUS;
126 
127 type
128 
129  { TIBEventThread }
130  TIBEventThread = class(TThread)
131  private
132  // IB API call parameters
133  WhichEvent: integer;
134  EventID: ISC_LONG;
135  EventBuffer: PAnsiChar;
136  EventBufferLen: Short;
137  ResultBuffer: PAnsiChar;
138  // Local use variables
139  Signal: TSimpleEvent;
140  EventsReceived,
141  FirstTime: boolean;
142  EventGroup,
143  EventCount: integer;
144  Parent: TZIBEventAlerter;
145  FExceptObject: TObject;
146  FExceptAddr: Pointer;
147  FCancelAlerts: boolean;
148  protected
149  procedure Execute; override;
150  procedure SignalEvent; virtual;
151  procedure SignalTerminate; virtual;
152  procedure RegisterEvents; virtual;
153  procedure UnRegisterEvents; virtual;
154  procedure QueueEvents; virtual;
155  procedure SQueEvents;
156  procedure ProcessEvents; virtual;
157  procedure DoEvent;
158  procedure DoHandleException;
159  function HandleException: boolean; virtual;
160  procedure UpdateResultBuffer(Length: UShort; Updated: PAnsiChar);
161  public
162  constructor Create(Owner: TZIBEventAlerter; EventGrp: integer;
163  TermEvent: TNotifyEvent); virtual;
164  destructor Destroy; override;
165  end;
166 
167  Tsib_event_block = function(EventBuffer, ResultBuffer: PPAnsiChar; IDCount: UShort;
168  Event1, Event2, Event3, Event4, Event5, Event6, Event7, Event8, Event9,
169  Event10, Event11, Event12, Event13, Event14, Event15: PAnsiChar): ISC_LONG;
170  cdecl;
171 
172 function TZIBEventAlerter.GetNativeHandle: PISC_DB_HANDLE;
173 begin
174  Result := (FConnection.DbcConnection as IZInterbase6Connection).GetDBHandle;
175 end;
176 
177 function StatusVector: PISC_STATUS;
178 begin
179  Result := @FStatusVector;
180 end;
181 
182 function StatusVectorArray: TARRAY_ISC_STATUS;
183 begin
184  Result := FStatusVector;
185 end;
186 
187 { TZIBEventAlerter }
188 
189 constructor TZIBEventAlerter.Create(AOwner: TComponent);
190 begin
191  inherited Create(AOwner);
192 
193  ThreadException := False;
194  FOnEventAlert := nil;
195  FNativeHandle := nil;
196  FConnection := nil;
197  FAutoRegister := False;
198  FEvents := TStringList.Create;
199  with TStringList(FEvents) do
200  begin
201  Sorted := True; // dupIgnore only works when the TStringList is sorted
202  OnChange := EventChange; // assign the routine which validates the event lenghts
203  Duplicates := dupIgnore; // don't allow duplicate events
204  end;
205  FThreads := TList.Create;
206 end;
207 
208 destructor TZIBEventAlerter.Destroy;
209 begin
210  try
211  if Registered then
212  UnRegisterEvents;
213  except
214  // silence any exceptions which might be raised
215  // by UnRegisterEvents during destruction
216  end;
217 
218 { If Assigned(FConnection) then
219  FConnection.RemoveEventNotifier(Self);
220 }
221 
222  FThreads.Free;
223  FEvents.Free;
224 
225  inherited Destroy;
226 end;
227 
228 procedure TZIBEventAlerter.Notification(AComponent: TComponent;
229  Operation: TOperation);
230 begin
231  inherited Notification(AComponent, Operation);
232  if (Operation = opRemove) and (AComponent = FConnection) then
233  begin
234  if Registered then
235  UnRegisterEvents;
236  FConnection := nil;
237  end;
238 end;
239 
240 
241 // -> ms, 18/08/2004:
242 // Modified so that now the DB connection will be made when events are registered
243 // this is because the method UnregisterEvents of TIBEventThread needs a native
244 // DB handle that can only be retrieved when DB connection is active. If the events
245 // are registered correctly the DB connection must be established. If it is not
246 // established this will be done here. This means that whenever events are registered
247 // (by setting AutoRegister := True or calling RegisterEvents explicitly) and the
248 // DB connection ist not established, this will be done here automatically (including
249 // the retrieval of the native DB handle).
250 Procedure TZIBEventAlerter.RegisterEvents;
251 Var i: Integer;
252 Begin
253  If (not (csDesigning in ComponentState)) and (Assigned(FConnection)) Then Begin
254  Try
255  If (FThreads.Count = 0) Then Begin
256  If (FEvents.Count > 0) Then Begin
257  For i := 0 To ((FEvents.Count - 1) div IB_MAX_EVENT_BLOCK) Do
258  FThreads.Add(TIBEventThread.Create(Self, i, ThreadEnded));
259  End;
260  End;
261  Finally
262  FRegistered := FThreads.Count <> 0;
263  If FRegistered Then Begin
264  If not FConnection.Connected Then
265  FConnection.Connect;
266  FNativeHandle := GetNativeHandle;
267  End;
268  End;
269  End;
270 End; // RegisterEvents
271 
272 
273 // -> ms, 18/08/2004:
274 // Modified so that the native DB handle will now be retrieved by
275 // method RegisterEvents. Retrieving it here caused an Exception
276 // even if DB was connected.
277 Procedure TZIBEventAlerter.SetConnection(Value: TZConnection);
278 Var
279  WasRegistered: boolean;
280 Begin
281  If (Value <> FConnection) Then Begin
282  If (csDesigning in ComponentState) Then
283  FConnection := Value
284  Else Begin
285  WasRegistered := Registered;
286  If WasRegistered Then
287  UnRegisterEvents;
288  FConnection := Value;
289  If WasRegistered Then
290  RegisterEvents;
291  End;
292  End;
293 End; // SetConnection
294 
295 
296 procedure TZIBEventAlerter.SetEvents(Value: TStrings);
297 begin
298  FEvents.Assign(Value);
299 end;
300 
301 procedure TZIBEventAlerter.SetRegistered(const Value: boolean);
302 begin
303  FRegistered := Value;
304  if csDesigning in ComponentState then
305  exit;
306  if Value then
307  RegisterEvents
308  else
309  UnRegisterEvents;
310 end;
311 
312 procedure TZIBEventAlerter.UnregisterEvents;
313 var
314  i: integer;
315  Temp: TIBEventThread;
316 begin
317  if csDesigning in ComponentState then
318  exit;
319  if (FThreads.Count > 0) then
320  begin
321  for i := (FThreads.Count - 1) downto 0 do
322  begin
323  Temp := TIBEventThread(FThreads[i]);
324  FThreads.Delete(i);
325 
326  Temp.SignalTerminate;
327  Temp.WaitFor;
328  Temp.Free;
329  end;
330  end;
331  FRegistered := FThreads.Count <> 0;
332 end;
333 
334 function TZIBEventAlerter.GetPlainDriver: IZInterbasePlainDriver;
335 begin
336  Result := (FConnection.DbcConnection as IZInterbase6Connection).GetPlainDriver;
337 end;
338 
339 { TIBEventThread }
340 
341 procedure EventCallback(P: Pointer; Length: Short; Updated: PAnsiChar); cdecl;
342 begin
343  if (Assigned(P) and Assigned(Updated)) then
344  begin
345  TIBEventThread(P).UpdateResultBuffer(Length, Updated);
346  TIBEventThread(P).SignalEvent;
347  end;
348 end;
349 
350 procedure TIBEventThread.DoEvent;
351 begin
352  Parent.FOnEventAlert(Parent, Parent.FEvents[((EventGroup * IB_MAX_EVENT_BLOCK) + WhichEvent)],
353  StatusVectorArray[WhichEvent], FCancelAlerts)
354 end;
355 
356 procedure TIBEventThread.UpdateResultBuffer(Length: UShort; Updated: PAnsiChar);
357 begin
358  Move(Updated[0], ResultBuffer[0], Length);
359 end;
360 
361 procedure TIBEventThread.QueueEvents;
362 begin
363  EventsReceived := False;
364  Signal.ResetEvent;
365  Synchronize(SQueEvents);
366 end;
367 
368 procedure TIBEventThread.ProcessEvents;
369 var
370  i: integer;
371 begin
372  Parent.PlainDriver.isc_event_counts(StatusVector, EventBufferLen,
373  EventBuffer, ResultBuffer);
374  if (Assigned(Parent.FOnEventAlert) and (not FirstTime)) then
375  begin
376  FCancelAlerts := False;
377  for i := 0 to (EventCount - 1) do
378  begin
379  if (StatusVectorArray[i] <> 0) then
380  begin
381  WhichEvent := i;
382  Synchronize(DoEvent)
383  end;
384  end;
385  end;
386  FirstTime := False;
387 end;
388 
389 procedure TIBEventThread.UnRegisterEvents;
390 begin
391  Parent.PlainDriver.isc_cancel_events(StatusVector, Parent.FNativeHandle, @EventID);
392  Parent.PlainDriver.isc_free(EventBuffer);
393  EventBuffer := nil;
394  Parent.PlainDriver.isc_free(ResultBuffer);
395  ResultBuffer := nil;
396 end;
397 
398 procedure TIBEventThread.RegisterEvents;
399 var
400  sib_event_block: Tsib_event_block;
401 
402  function EBP(Index: integer): PAnsiChar;
403  begin
404  Inc(Index, (EventGroup * IB_MAX_EVENT_BLOCK));
405  if (Index > Parent.FEvents.Count) then
406  Result := nil
407  else
408  {$IFDEF UNICODE}
409  Result := PAnsiChar(AnsiString(Parent.FEvents[Index - 1]));
410  {$ELSE}
411  Result := PAnsiChar(Parent.FEvents[Index - 1]);
412  {$ENDIF}
413  end;
414 begin
415  EventBuffer := nil;
416  ResultBuffer := nil;
417  EventBufferLen := 0;
418  FirstTime := True;
419  EventCount := (Parent.FEvents.Count - (EventGroup * IB_MAX_EVENT_BLOCK));
420  if (EventCount > IB_MAX_EVENT_BLOCK) then
421  EventCount := IB_MAX_EVENT_BLOCK;
422 
423 {
424  if Parent.Connection.Protocol='interbase-6' then
425  sib_event_block := Tsib_event_block(ZPlainInterbase6.isc_event_block)
426  else if Parent.Connection.Protocol='firebird-1.0' then
427  sib_event_block := Tsib_event_block(ZPlainFirebird10.isc_event_block)
428  else if Parent.Connection.Protocol='firebird-1.5' then
429  sib_event_block := Tsib_event_block(ZPlainFirebird15.isc_event_block)
430  else if Parent.Connection.Protocol='firebirdd-1.5' then
431  sib_event_block := Tsib_event_block(ZPlainFirebird15.isc_event_block)
432  else if Parent.Connection.Protocol='firebird-2.0' then
433  sib_event_block := Tsib_event_block(ZPlainFirebird20.isc_event_block)
434  else if Parent.Connection.Protocol='firebirdd-2.0' then
435  sib_event_block := Tsib_event_block(ZPlainFirebird20.isc_event_block)
436  else if Parent.Connection.Protocol='firebird-2.1' then
437  sib_event_block := Tsib_event_block(ZPlainFirebird21.isc_event_block)
438  else if Parent.Connection.Protocol='firebirdd-2.1' then
439  sib_event_block := Tsib_event_block(ZPlainFirebird21.isc_event_block)
440 
441  else
442  sib_event_block := Tsib_event_block(ZPlainInterbase6.isc_event_block);
443  }
444  sib_event_block := Tsib_event_block(Parent.GetPlainDriver.GetFirebirdAPI.isc_event_block);
445  EventBufferLen := sib_event_block(@EventBuffer,
446  @ResultBuffer, EventCount,
447  EBP(1), EBP(2), EBP(3), EBP(4), EBP(5), EBP(6), EBP(7), EBP(8),
448  EBP(9), EBP(10), EBP(11), EBP(12), EBP(13), EBP(14), EBP(15));
449 
450 
451 end;
452 
453 procedure TIBEventThread.SignalEvent;
454 begin
455  EventsReceived := True;
456  Signal.SetEvent;
457 end;
458 
459 procedure TIBEventThread.SignalTerminate;
460 begin
461  if not Terminated then
462  begin
463  Terminate;
464  Signal.SetEvent;
465  end;
466 end;
467 
468 procedure TIBEventThread.DoHandleException;
469 begin
470  SysUtils.ShowException(FExceptObject, FExceptAddr);
471 end;
472 
473 function TIBEventThread.HandleException: boolean;
474 begin
475  if not Parent.ThreadException then
476  begin
477  Result := True;
478  Parent.ThreadException := True;
479  FExceptObject := ExceptObject;
480  FExceptAddr := ExceptAddr;
481  try
482  if not (FExceptObject is EAbort) then
483  Synchronize(DoHandleException);
484  finally
485  FExceptObject := nil;
486  FExceptAddr := nil;
487  end;
488  end
489  else
490  Result := False;
491 end;
492 
493 procedure TIBEventThread.Execute;
494 begin
495  RegisterEvents;
496  QueueEvents;
497  try
498  repeat
499  Signal.WaitFor(INFINITE);
500  if EventsReceived then
501  begin
502  ProcessEvents;
503  QueueEvents;
504  end;
505  until Terminated;
506  ReturnValue := 0;
507  except
508  if HandleException then
509  ReturnValue := 1
510  else
511  ReturnValue := 0;
512  end;
513 end;
514 
515 {$WARNINGS OFF}
516 constructor TIBEventThread.Create(Owner: TZIBEventAlerter;
517  EventGrp: integer; TermEvent: TNotifyEvent);
518 begin
519  inherited Create(True);
520  FCancelAlerts := False;
521  Signal := TSimpleEvent.Create;
522  Parent := Owner;
523  EventGroup := EventGrp;
524  OnTerminate := TermEvent;
525  Resume;
526 end;
527 {$WARNINGS ON}
528 
529 destructor TIBEventThread.Destroy;
530 begin
531  try
532  UnRegisterEvents;
533  except
534  if HandleException then
535  ReturnValue := 1
536  else
537  ReturnValue := 0;
538  end;
539  Signal.Free;
540  inherited Destroy;
541 end;
542 
543 procedure TZIBEventAlerter.EventChange(Sender: TObject);
544 var
545  i: integer;
546  WasRegistered: boolean;
547 begin
548  WasRegistered := Registered;
549  try
550  if WasRegistered then
551  UnRegisterEvents;
552  TStringList(FEvents).OnChange := nil;
553  try
554  for i := (FEvents.Count - 1) downto 0 do
555  begin
556  if (FEvents[i] = EmptyStr) then
557  begin
558  FEvents.Delete(i);
559  end
560  else if (Length(FEvents[i]) > (IB_MAX_EVENT_LENGTH - 1)) then
561  begin
562  FEvents[i] := Copy(FEvents[i], 1, (IB_MAX_EVENT_LENGTH - 1));
563  end;
564  end;
565  finally
566  TStringList(FEvents).OnChange := EventChange;
567  end;
568  finally
569  if WasRegistered then
570  RegisterEvents;
571  end;
572 end;
573 
574 function TZIBEventAlerter.GetRegistered: boolean;
575 begin
576  Result := FRegistered;
577 end;
578 
579 procedure TZIBEventAlerter.ThreadEnded(Sender: TObject);
580 var
581  ThreadIdx: integer;
582 begin
583  if (Sender is TIBEventThread) then
584  begin
585  ThreadIdx := FThreads.IndexOf(Sender);
586  if (ThreadIdx > -1) then
587  FThreads.Delete(ThreadIdx);
588  if (TIBEventThread(Sender).ReturnValue = 1) then
589  begin
590  if Registered then
591  UnRegisterEvents;
592  ThreadException := False;
593  end
594  end;
595 end;
596 
597 procedure TZIBEventAlerter.SetAutoRegister(const Value: boolean);
598 begin
599  if FAutoRegister <> Value then
600  begin
601  FAutoRegister := Value;
602  if FAutoRegister and (not Registered) and
603  Assigned(FConnection) and FConnection.Connected then
604  RegisterEvents;
605  end;
606 end;
607 
608 function TZIBEventAlerter.GetAutoRegister: boolean;
609 begin
610  Result := FAutoRegister;
611 end;
612 
613 procedure TIBEventThread.SQueEvents;
614 var
615  Status: ISC_STATUS;
616 begin
617  Status := -999999;
618  try
619  Status := Parent.PlainDriver.isc_que_events(StatusVector,
620  Parent.FNativeHandle, @EventID, EventBufferLen,
621  EventBuffer, TISC_CALLBACK(@EventCallback), PVoid(Self));
622  except
623  on E: Exception do
624  if Status <> -999999 then
625  if Assigned(Parent.OnError) then
626  if E is EZSQLException then
627  Parent.OnError(Parent, EZSQLException(E).ErrorCode)
628  else
629  Parent.OnError(Parent, 0);
630  end;
631 end;
632 
633 end.
634