module firebird_events; private import ibase; private import firebirdCommon; private import firebird_database; private import firebird_status; private import std.math; /* private import firebird_date; private import firebird_transaction; private import std.c.string; */ class Events { const size_t MAXEVENTNAMELEN = 127; alias Events[] ObjRefs; ObjRefs mObjectReferences; alias ubyte[] Buffer; Buffer mEventBuffer; Buffer mResultsBuffer; int mRefCount; // Reference counter Database* mDatabase; ISC_LONG mId; // Firebird internal Id of these events bool mQueued; // Has isc_que_events() been called? bool mTrapped; // EventHandled() was called since last que_events() void FireActions() { /+ if(mTrapped) { typedef EventBufferIterator EventIterator; EventIterator eit(mEventBuffer.begin()+1); EventIterator rit(mResultsBuffer.begin()+1); for(ObjRefs::iterator oit = mObjectReferences.begin(); oit != mObjectReferences.end(); ++oit, ++eit, ++rit) { if(eit == EventIterator(mEventBuffer.end()) || rit == EventIterator(mResultsBuffer.end())) throw new Exception("EventsImpl::FireActions Internal buffer size error"); uint vnew = rit.get_count(); uint vold = eit.get_count(); if(vnew > vold) { // Fire the action try { (*oit).ibppEventHandler(this, eit.get_name(), cast(int(vnew - vold)); } catch(Exception e) { std::copy(rit.begin(), rit.end(), eit.begin()); throw; } std::copy(rit.begin(), rit.end(), eit.begin()); } // This handles initialization too, where vold ==(uint(-1) // Thanks to M. Hieke for this idea and related initialization to(-1) if(vnew != vold) std::copy(rit.begin(), rit.end(), eit.begin()); } } +/ } void Queue() { if(! mQueued) { if(mDatabase.GetDbHandle() == null) throw new Exception("EventsImpl::Queue Database is not connected"); STATUS vector = new STATUS; mTrapped = false; mQueued = true; isc_que_events(vector.StatusHandle, mDatabase.GetDbHandle(), &mId, cast(short) mEventBuffer.length, &mEventBuffer[0], cast(/*isc_callback*/ISC_EVENT_CALLBACK) &EventHandler, cast(void*) this); if(vector.StatusErrors()) { mId = 0; // Should be, but better be safe mQueued = false; throw new Exception("EventsImpl::Queue vector isc_que_events failed"); } } } void Cancel() { if(mQueued) { if(mDatabase.GetDbHandle() == null) throw new Exception("EventsImpl::Cancel Database is not connected"); STATUS vector = new STATUS; // A call to cancel_events will call *once* the handler routine, even // though no events had fired. This is why we first set mEventsQueued // to false, so that we can be sure to dismiss those unwanted callbacks // subsequent to the execution of isc_cancel_events(). mTrapped = false; mQueued = false; isc_cancel_events(vector.StatusHandle, mDatabase.GetDbHandle(), &mId); if(vector.StatusErrors()) { mQueued = true; // Need to restore this as cancel failed throw new Exception("EventsImpl::Cancel vector isc_cancel_events failed"); } mId = 0; // Should be, but better be safe } } public: void AttachDatabase(Database* database) { if(database == null) throw new Exception("EventsImpl::AttachDatabase Can't attach a null Database object."); mDatabase = database; } void DetachDatabaseImpl() { if(mDatabase == null) return; mDatabase = null; } this(Database* database) { mRefCount = 0; mDatabase = null; mId = 0; mQueued = mTrapped = false; AttachDatabase(database); } ~this() { try { Clear(); } catch(Exception e) { } } // This function must keep this prototype to stay compatible with // what isc_que_events() expects static void EventHandler(char* object, short size, char* tmpbuffer) { // >>>>> This method is a STATIC member !! <<<<< // Consider this method as a kind of "interrupt handler". It should do as // few work as possible as quickly as possible and then return. // Never forget: this is called by the Firebird client code, on *some* // thread which might not be(and won't probably be) any of your application // thread. This function is to be considered as an "interrupt-handler" of a // hardware driver. // There can be spurious calls to EventHandler from FB internal. We must // dismiss those calls. if(object == null || size == 0 || tmpbuffer == null) return; Events* evi = cast(Events*)object; // Ugly, but wanted, c-style cast if(evi.mQueued) { try { char* rb = cast(char*) &evi.mResultsBuffer[0]; if(evi.mEventBuffer.length < cast(uint)size) size = cast(short)evi.mEventBuffer.length; for(int i = 0; i < size; i++) rb[i] = tmpbuffer[i]; evi.mTrapped = true; evi.mQueued = false; } catch(Exception e) { } } } void Add(string *eventname, Events *objref) { if(eventname.length == 0) throw new Exception("Events::Add Zero length event names not permitted"); if(eventname.length > MAXEVENTNAMELEN) throw new Exception("Events::Add Event name is too long"); if((mEventBuffer.length + eventname.length + 5) > 32766) // max signed 16 bits integer minus one throw new Exception("Events::Add Can't add this event, the events list would overflow IB/FB limitation"); Cancel(); // 1) Alloc or grow the buffers size_t prev_buffer_size = mEventBuffer.length; size_t needed =((prev_buffer_size==0) ? 1 : 0) + eventname.length + 5; // Initial alloc will require one more byte, we need 4 more bytes for // the count itself, and one byte for the string length prefix mEventBuffer.length = mEventBuffer.length + needed; mResultsBuffer.length = mResultsBuffer.length + needed; if(prev_buffer_size == 0) mEventBuffer[0] = mResultsBuffer[0] = 1; // First byte is a 'one'. Documentation ?? // 2) Update the buffers(append) /+ { Buffer::iterator it = mEventBuffer.begin() + ((prev_buffer_size==0) ? 1 : prev_buffer_size); // Byte after current content *(it++) = static_cast(eventname.length()); it = std::copy(eventname.begin(), eventname.end(), it); // We initialize the counts to(uint(-1) to initialize properly, see FireActions() *(it++) = -1; *(it++) = -1; *(it++) = -1; *it = -1; } +/ // copying new event to the results buffer to keep event_buffer_ and results_buffer_ consistant, // otherwise we might get a problem in `FireActions` //copy(mEventBuffer.begin() + prev_buffer_size, mEventBuffer.end(), mResultsBuffer.begin() + prev_buffer_size); mEventBuffer ~= mResultsBuffer; // 3) Alloc or grow the objref array and update the objref array(append) //mObjectReferences.push_back(objref); mObjectReferences ~= *objref; Queue(); } void Drop(string *eventname) { if(eventname.length == 0) throw new Exception("EventsImpl::Drop Zero length event names not permitted"); if(eventname.length > MAXEVENTNAMELEN) throw new Exception("EventsImpl::Drop Event name is too long"); if(mEventBuffer.length <= 1) return; // Nothing to do, but not an error Cancel(); // 1) Find the event in the buffers /+ typedef EventBufferIterator EventIterator; EventIterator eit(mEventBuffer.begin()+1); EventIterator rit(mResultsBuffer.begin()+1); for(ObjRefs::iterator oit = mObjectReferences.begin(); oit != mObjectReferences.end(); ++oit, ++eit, ++rit) { if(eventname != eit.get_name()) continue; // 2) Event found, remove it mEventBuffer.erase(eit.begin(), eit.end()); mResultsBuffer.erase(rit.begin(), rit.end()); mObjectReferences.erase(oit); break; } +/ Queue(); } void List(string[] *events) { events.length = 0; if(mEventBuffer.length <= 1) return; // Nothing to do, but not an error /+ typedef EventBufferIterator EventIterator; EventIterator eit(mEventBuffer.begin()+1); for(ObjRefs::iterator oit = mObjectReferences.begin(); oit != mObjectReferences.end(); ++oit, ++eit) { events.push_back(eit.get_name()); } +/ } void Clear() { Cancel(); mObjectReferences.length = 0; mEventBuffer.length = 0; mResultsBuffer.length = 0; } void Dispatch() { // If no events registered, nothing to do of course. if(mEventBuffer.length == 0) return; // Let's fire the events actions for all the events which triggered, if any, and requeue. FireActions(); Queue(); } Database *DbHandle() { if(mDatabase == null) throw new Exception("Events::DatabasePtr No Database is attached."); return mDatabase; } }