]> git.stg.codes - stg.git/blob - libs/ibpp/events.cpp
More subscriptions, less notifiers.
[stg.git] / libs / ibpp / events.cpp
1 ///////////////////////////////////////////////////////////////////////////////\r
2 //\r
3 //      File    : $Id: events.cpp,v 1.1 2007/05/05 17:00:42 faust Exp $\r
4 //      Subject : IBPP, internal EventsImpl class implementation\r
5 //\r
6 ///////////////////////////////////////////////////////////////////////////////\r
7 //\r
8 //      (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org)\r
9 //\r
10 //      The contents of this file are subject to the IBPP License (the "License");\r
11 //      you may not use this file except in compliance with the License.  You may\r
12 //      obtain a copy of the License at http://www.ibpp.org or in the 'license.txt'\r
13 //      file which must have been distributed along with this file.\r
14 //\r
15 //      This software, distributed under the License, is distributed on an "AS IS"\r
16 //      basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the\r
17 //      License for the specific language governing rights and limitations\r
18 //      under the License.\r
19 //\r
20 ///////////////////////////////////////////////////////////////////////////////\r
21 //\r
22 //      COMMENTS\r
23 //      * Tabulations should be set every four characters when editing this file.\r
24 //\r
25 //      SPECIAL WARNING COMMENT (by Olivier Mascia, 2000 Nov 12)\r
26 //      The way this source file handles events is not publicly documented, in\r
27 //      the ibase.h header file or in the IB 6.0 documentation. This documentation\r
28 //      suggests to use the API isc_event_block to construct vectors of events.\r
29 //      Unfortunately, this API takes a variable number of parameters to specify\r
30 //      the list of event names. In addition, the documentation warn on not using\r
31 //      more than 15 names. This is a sad limitation, partly because the maximum\r
32 //      number of parameters safely processed in such an API is very compiler\r
33 //      dependant and also because isc_event_counts() is designed to return counts\r
34 //      through the IB status vector which is a vector of 20 32-bits integers !\r
35 //      From reverse engineering of the isc_event_block() API in\r
36 //      source file jrd/alt.c (as available on fourceforge.net/project/InterBase as\r
37 //      of 2000 Nov 12), it looks like the internal format of those EPB is simple.\r
38 //      An EPB starts by a byte with value 1. A version identifier of some sort.\r
39 //      Then a small cluster is used for each event name. The cluster starts with\r
40 //      a byte for the length of the event name (no final '\0'). Followed by the N\r
41 //      characters of the name itself (no final '\0'). The cluster ends with 4 bytes\r
42 //      preset to 0.\r
43 //\r
44 //      SPECIAL CREDIT (July 2004) : this is a complete re-implementation of this\r
45 //      class, directly based on work by Val Samko.\r
46 //      The whole event handling has then be completely redesigned, based on the old\r
47 //      EPB class to bring up the current IBPP::Events implementation.\r
48 //\r
49 ///////////////////////////////////////////////////////////////////////////////\r
50 \r
51 #ifdef _MSC_VER\r
52 #pragma warning(disable: 4786 4996)\r
53 #ifndef _DEBUG\r
54 #pragma warning(disable: 4702)\r
55 #endif\r
56 #endif\r
57 \r
58 #include "_ibpp.h"\r
59 \r
60 #ifdef HAS_HDRSTOP\r
61 #pragma hdrstop\r
62 #endif\r
63 \r
64 using namespace ibpp_internals;\r
65 \r
66 const size_t EventsImpl::MAXEVENTNAMELEN = 127;\r
67 \r
68 //      (((((((( OBJECT INTERFACE IMPLEMENTATION ))))))))\r
69 \r
70 void EventsImpl::Add(const std::string& eventname, IBPP::EventInterface* objref)\r
71 {\r
72         if (eventname.size() == 0)\r
73                 throw LogicExceptionImpl("Events::Add", _("Zero length event names not permitted"));\r
74         if (eventname.size() > MAXEVENTNAMELEN)\r
75                 throw LogicExceptionImpl("Events::Add", _("Event name is too long"));\r
76         if ((mEventBuffer.size() + eventname.length() + 5) > 32766)     // max signed 16 bits integer minus one\r
77                 throw LogicExceptionImpl("Events::Add",\r
78                         _("Can't add this event, the events list would overflow IB/FB limitation"));\r
79 \r
80         Cancel();\r
81 \r
82         // 1) Alloc or grow the buffers\r
83         size_t prev_buffer_size = mEventBuffer.size();\r
84         size_t needed = ((prev_buffer_size==0) ? 1 : 0) + eventname.length() + 5;\r
85         // Initial alloc will require one more byte, we need 4 more bytes for\r
86         // the count itself, and one byte for the string length prefix\r
87 \r
88         mEventBuffer.resize(mEventBuffer.size() + needed);\r
89         mResultsBuffer.resize(mResultsBuffer.size() + needed);\r
90         if (prev_buffer_size == 0)\r
91                 mEventBuffer[0] = mResultsBuffer[0] = 1; // First byte is a 'one'. Documentation ??\r
92 \r
93         // 2) Update the buffers (append)\r
94         {\r
95                 Buffer::iterator it = mEventBuffer.begin() +\r
96                                 ((prev_buffer_size==0) ? 1 : prev_buffer_size); // Byte after current content\r
97                 *(it++) = static_cast<char>(eventname.length());\r
98                 it = std::copy(eventname.begin(), eventname.end(), it);\r
99                 // We initialize the counts to (uint32_t)(-1) to initialize properly, see FireActions()\r
100                 *(it++) = -1; *(it++) = -1; *(it++) = -1; *it = -1;\r
101         }\r
102 \r
103         // copying new event to the results buffer to keep event_buffer_ and results_buffer_ consistant,\r
104         // otherwise we might get a problem in `FireActions`\r
105         // Val Samko, val@digiways.com\r
106         std::copy(mEventBuffer.begin() + prev_buffer_size,\r
107                 mEventBuffer.end(), mResultsBuffer.begin() + prev_buffer_size);\r
108 \r
109         // 3) Alloc or grow the objref array and update the objref array (append)\r
110         mObjectReferences.push_back(objref);\r
111 \r
112         Queue();\r
113 }\r
114 \r
115 void EventsImpl::Drop(const std::string& eventname)\r
116 {\r
117         if (eventname.size() == 0)\r
118                 throw LogicExceptionImpl("EventsImpl::Drop", _("Zero length event names not permitted"));\r
119         if (eventname.size() > MAXEVENTNAMELEN)\r
120                 throw LogicExceptionImpl("EventsImpl::Drop", _("Event name is too long"));\r
121 \r
122         if (mEventBuffer.size() <= 1) return;   // Nothing to do, but not an error\r
123 \r
124         Cancel();\r
125 \r
126         // 1) Find the event in the buffers\r
127         typedef EventBufferIterator<Buffer::iterator> EventIterator;\r
128         EventIterator eit(mEventBuffer.begin()+1);\r
129         EventIterator rit(mResultsBuffer.begin()+1);\r
130 \r
131         for (ObjRefs::iterator oit = mObjectReferences.begin();\r
132                         oit != mObjectReferences.end();\r
133                                 ++oit, ++eit, ++rit)\r
134         {\r
135                 if (eventname != eit.get_name()) continue;\r
136                 \r
137                 // 2) Event found, remove it\r
138                 mEventBuffer.erase(eit.begin(), eit.end());\r
139                 mResultsBuffer.erase(rit.begin(), rit.end());\r
140                 mObjectReferences.erase(oit);\r
141                 break;\r
142         }\r
143 \r
144         Queue();\r
145 }\r
146 \r
147 void EventsImpl::List(std::vector<std::string>& events)\r
148 {\r
149         events.clear();\r
150         \r
151         if (mEventBuffer.size() <= 1) return;   // Nothing to do, but not an error\r
152 \r
153         typedef EventBufferIterator<Buffer::iterator> EventIterator;\r
154         EventIterator eit(mEventBuffer.begin()+1);\r
155 \r
156         for (ObjRefs::iterator oit = mObjectReferences.begin();\r
157                         oit != mObjectReferences.end();\r
158                                 ++oit, ++eit)\r
159         {\r
160                 events.push_back(eit.get_name());\r
161         }\r
162 }\r
163 \r
164 void EventsImpl::Clear()\r
165 {\r
166         Cancel();\r
167         \r
168         mObjectReferences.clear();\r
169         mEventBuffer.clear();\r
170         mResultsBuffer.clear();\r
171 }\r
172 \r
173 void EventsImpl::Dispatch()\r
174 {\r
175         // If no events registered, nothing to do of course.\r
176         if (mEventBuffer.size() == 0) return;\r
177 \r
178         // Let's fire the events actions for all the events which triggered, if any, and requeue.\r
179         FireActions();\r
180         Queue();\r
181 }\r
182 \r
183 IBPP::Database EventsImpl::DatabasePtr() const\r
184 {\r
185         if (mDatabase == 0) throw LogicExceptionImpl("Events::DatabasePtr",\r
186                         _("No Database is attached."));\r
187         return mDatabase;\r
188 }\r
189 \r
190 IBPP::IEvents* EventsImpl::AddRef()\r
191 {\r
192         ASSERTION(mRefCount >= 0);\r
193         ++mRefCount;\r
194         return this;\r
195 }\r
196 \r
197 void EventsImpl::Release()\r
198 {\r
199         // Release cannot throw, except in DEBUG builds on assertion\r
200         ASSERTION(mRefCount >= 0);\r
201         --mRefCount;\r
202         try { if (mRefCount <= 0) delete this; }\r
203                 catch (...) { }\r
204 }\r
205 \r
206 //      (((((((( OBJECT INTERNAL METHODS ))))))))\r
207 \r
208 void EventsImpl::Queue()\r
209 {\r
210         if (! mQueued)\r
211         {\r
212                 if (mDatabase->GetHandle() == 0)\r
213                         throw LogicExceptionImpl("EventsImpl::Queue",\r
214                                   _("Database is not connected"));\r
215 \r
216                 IBS vector;\r
217                 mTrapped = false;\r
218                 mQueued = true;\r
219                 (*gds.Call()->m_que_events)(vector.Self(), mDatabase->GetHandlePtr(), &mId,\r
220                         short(mEventBuffer.size()), &mEventBuffer[0],\r
221                                 (isc_callback)EventHandler, (char*)this);\r
222 \r
223                 if (vector.Errors())\r
224                 {\r
225                         mId = 0;        // Should be, but better be safe\r
226                         mQueued = false;\r
227                         throw SQLExceptionImpl(vector, "EventsImpl::Queue",\r
228                                 _("isc_que_events failed"));\r
229                 }\r
230         }\r
231 }\r
232 \r
233 void EventsImpl::Cancel()\r
234 {\r
235         if (mQueued)\r
236         {\r
237                 if (mDatabase->GetHandle() == 0) throw LogicExceptionImpl("EventsImpl::Cancel",\r
238                         _("Database is not connected"));\r
239 \r
240                 IBS vector;\r
241 \r
242                 // A call to cancel_events will call *once* the handler routine, even\r
243                 // though no events had fired. This is why we first set mEventsQueued\r
244                 // to false, so that we can be sure to dismiss those unwanted callbacks\r
245                 // subsequent to the execution of isc_cancel_events().\r
246                 mTrapped = false;\r
247                 mQueued = false;\r
248                 (*gds.Call()->m_cancel_events)(vector.Self(), mDatabase->GetHandlePtr(), &mId);\r
249 \r
250             if (vector.Errors())\r
251                 {\r
252                         mQueued = true; // Need to restore this as cancel failed\r
253                 throw SQLExceptionImpl(vector, "EventsImpl::Cancel",\r
254                         _("isc_cancel_events failed"));\r
255                 }\r
256 \r
257                 mId = 0;        // Should be, but better be safe\r
258         }\r
259 }\r
260 \r
261 void EventsImpl::FireActions()\r
262 {\r
263         if (mTrapped)\r
264         {\r
265                 typedef EventBufferIterator<Buffer::iterator> EventIterator;\r
266                 EventIterator eit(mEventBuffer.begin()+1);\r
267                 EventIterator rit(mResultsBuffer.begin()+1);\r
268 \r
269                 for (ObjRefs::iterator oit = mObjectReferences.begin();\r
270                          oit != mObjectReferences.end();\r
271                                  ++oit, ++eit, ++rit)\r
272                 {\r
273                         if (eit == EventIterator(mEventBuffer.end())\r
274                                   || rit == EventIterator(mResultsBuffer.end()))\r
275                                 throw LogicExceptionImpl("EventsImpl::FireActions", _("Internal buffer size error"));\r
276                         uint32_t vnew = rit.get_count();\r
277                         uint32_t vold = eit.get_count();\r
278                         if (vnew > vold)\r
279                         {\r
280                                 // Fire the action\r
281                                 try\r
282                                 {\r
283                                         (*oit)->ibppEventHandler(this, eit.get_name(), (int)(vnew - vold));\r
284                                 }\r
285                                 catch (...)\r
286                                 {\r
287                                         std::copy(rit.begin(), rit.end(), eit.begin());\r
288                                         throw;\r
289                                 }\r
290                                 std::copy(rit.begin(), rit.end(), eit.begin());\r
291                         }\r
292                         // This handles initialization too, where vold == (uint32_t)(-1)\r
293                         // Thanks to M. Hieke for this idea and related initialization to (-1)\r
294                         if (vnew != vold)\r
295                                 std::copy(rit.begin(), rit.end(), eit.begin());\r
296                 }\r
297         }\r
298 }\r
299 \r
300 // This function must keep this prototype to stay compatible with\r
301 // what isc_que_events() expects\r
302 \r
303 void EventsImpl::EventHandler(const char* object, short size, const char* tmpbuffer)\r
304 {\r
305         // >>>>> This method is a STATIC member !! <<<<<\r
306         // Consider this method as a kind of "interrupt handler". It should do as\r
307         // few work as possible as quickly as possible and then return.\r
308         // Never forget: this is called by the Firebird client code, on *some*\r
309         // thread which might not be (and won't probably be) any of your application\r
310         // thread. This function is to be considered as an "interrupt-handler" of a\r
311         // hardware driver.\r
312 \r
313         // There can be spurious calls to EventHandler from FB internal. We must\r
314         // dismiss those calls.\r
315         if (object == 0 || size == 0 || tmpbuffer == 0) return;\r
316                 \r
317         EventsImpl* evi = (EventsImpl*)object;  // Ugly, but wanted, c-style cast\r
318 \r
319         if (evi->mQueued)\r
320         {\r
321                 try\r
322                 {\r
323                         char* rb = &evi->mResultsBuffer[0];\r
324                         if (evi->mEventBuffer.size() < (unsigned)size) size = (short)evi->mEventBuffer.size();\r
325                         for (int i = 0; i < size; i++)\r
326                                 rb[i] = tmpbuffer[i];\r
327                         evi->mTrapped = true;\r
328                         evi->mQueued = false;\r
329                 }\r
330                 catch (...) { }\r
331         }\r
332 }\r
333 \r
334 void EventsImpl::AttachDatabaseImpl(DatabaseImpl* database)\r
335 {\r
336         if (database == 0) throw LogicExceptionImpl("EventsImpl::AttachDatabase",\r
337                         _("Can't attach a null Database object."));\r
338 \r
339         if (mDatabase != 0) mDatabase->DetachEventsImpl(this);\r
340         mDatabase = database;\r
341         mDatabase->AttachEventsImpl(this);\r
342 }\r
343 \r
344 void EventsImpl::DetachDatabaseImpl()\r
345 {\r
346         if (mDatabase == 0) return;\r
347 \r
348         mDatabase->DetachEventsImpl(this);\r
349         mDatabase = 0;\r
350 }\r
351 \r
352 EventsImpl::EventsImpl(DatabaseImpl* database)\r
353         : mRefCount(0)\r
354 {\r
355         mDatabase = 0;\r
356         mId = 0;\r
357         mQueued = mTrapped = false;\r
358         AttachDatabaseImpl(database);\r
359 }\r
360 \r
361 EventsImpl::~EventsImpl()\r
362 {\r
363         try { Clear(); }\r
364                 catch (...) { }\r
365         \r
366         try { if (mDatabase != 0) mDatabase->DetachEventsImpl(this); }\r
367                 catch (...) { }\r
368 }\r
369 \r
370 //\r
371 //      EOF\r
372 //\r