--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// Name: src/unix/fswatcher_kqueue.cpp
+// Purpose: kqueue-based wxFileSystemWatcher implementation
+// Author: Bartosz Bekier
+// Created: 2009-05-26
+// RCS-ID: $Id$
+// Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+#if wxUSE_FSWATCHER
+
+#include "wx/fswatcher.h"
+
+#ifdef wxHAS_KQUEUE
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include "wx/dynarray.h"
+#include "wx/private/fswatcher.h"
+
+// ============================================================================
+// wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
+// ============================================================================
+
+/**
+ * Helper class encapsulating inotify mechanism
+ */
+class wxFSWatcherImplKqueue : public wxFSWatcherImpl
+{
+public:
+ wxFSWatcherImplKqueue(wxFileSystemWatcherBase* watcher) :
+ wxFSWatcherImpl(watcher),
+ m_loop(NULL),
+ m_source(NULL),
+ m_kfd(-1)
+ {
+ m_handler = new wxFSWSourceHandler(this);
+ }
+
+ ~wxFSWatcherImplKqueue()
+ {
+ // we close kqueue only if initialized before
+ if (IsOk())
+ {
+ Close();
+ }
+
+ delete m_handler;
+ }
+
+ bool Init()
+ {
+ wxCHECK_MSG( !IsOk(), false,
+ "Kqueue appears to be already initialized" );
+ wxCHECK_MSG( m_loop == NULL, false, "Event loop != NULL");
+
+ m_loop = (wxEventLoopBase::GetActive());
+ wxCHECK_MSG( m_loop, false, "File system watcher needs an active loop" );
+
+ // create kqueue
+ m_kfd = kqueue();
+ if (m_kfd == -1)
+ {
+ wxLogSysError(_("Unable to create kqueue instance"));
+ return false;
+ }
+
+ // create source
+ int flags = wxEVENT_SOURCE_INPUT;
+ m_source = m_loop->CreateSource(m_kfd, m_handler, flags);
+ wxCHECK_MSG( m_source, false,
+ "Active loop has no support for fd-based sources" );
+
+ return RegisterSource();
+ }
+
+ bool Close()
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+ wxCHECK_MSG( m_loop, false,
+ "m_loop shouldn't be null if kqueue is initialized" );
+
+ // ignore errors
+ (void) UnregisterSource();
+
+ int ret = close(m_kfd);
+ if (ret == -1)
+ {
+ wxLogSysError(_("Unable to close kqueue instance"));
+ }
+ m_source->Invalidate();
+
+ return ret != -1;
+ }
+
+ virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryKq> watch)
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+
+ struct kevent event;
+ int action = EV_ADD | EV_ENABLE | EV_CLEAR | EV_ERROR;
+ int flags = Watcher2NativeFlags(watch->GetFlags());
+ EV_SET( &event, watch->GetFileDescriptor(), EVFILT_VNODE, action,
+ flags, 0, watch.get() );
+
+ // TODO more error conditions according to man
+ // TODO best deal with the error here
+ int ret = kevent(m_kfd, &event, 1, NULL, 0, NULL);
+ if (ret == -1)
+ {
+ wxLogSysError(_("Unable to add kqueue watch"));
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryKq> watch)
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+
+ // TODO more error conditions according to man
+ // XXX closing file descriptor removes the watch. The logic resides in
+ // the watch which is not nice, but effective and simple
+ bool ret = watch->Close();
+ if (ret == -1)
+ {
+ wxLogSysError(_("Unable to remove kqueue watch"));
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual bool RemoveAll()
+ {
+ wxFSWatchEntries::iterator it = m_watches.begin();
+ for ( ; it != m_watches.end(); ++it )
+ {
+ (void) DoRemove(it->second);
+ }
+ m_watches.clear();
+ return true;
+ }
+
+ // return true if there was no error, false on error
+ bool ReadEvents()
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+
+ // read events
+ do
+ {
+ struct kevent event;
+ struct timespec timeout = {0, 0};
+ int ret = kevent(m_kfd, NULL, 0, &event, 1, &timeout);
+ if (ret == -1)
+ {
+ wxLogSysError(_("Unable to get events from kqueue"));
+ return false;
+ }
+ else if (ret == 0)
+ {
+ return true;
+ }
+
+ // we have event, so process it
+ ProcessNativeEvent(event);
+ }
+ while (true);
+
+ // when ret>0 we still have events, when ret<=0 we return
+ wxFAIL_MSG( "Never reached" );
+ return true;
+ }
+
+ bool IsOk()
+ {
+ return m_source && m_source->IsOk();
+ }
+
+/*
+ wxAbstractEventLoopSource* GetSource() const
+ {
+ return m_source;
+ }*/
+
+protected:
+ bool RegisterSource()
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+
+ return m_loop->AddSource(m_source);
+ }
+
+ bool UnregisterSource()
+ {
+ wxCHECK_MSG( IsOk(), false,
+ "Kqueue not initialized or invalid kqueue descriptor" );
+ wxCHECK_MSG( m_loop, false,
+ "m_loop shouldn't be null if kqueue is initialized" );
+
+ bool ret = m_loop->RemoveSource(m_source);
+ m_loop = NULL;
+ return ret;
+ }
+
+ // returns all new dirs/files present in the immediate level of the dir
+ // pointed by watch.GetPath(). "new" means created between the last time
+ // the state of watch was computed and now
+ void FindChanges(wxFSWatchEntryKq& watch, wxArrayString& changedFiles,
+ wxArrayInt& changedFlags)
+ {
+ wxFSWatchEntryKq::wxDirState old = watch.GetLastState();
+ watch.RefreshState();
+ wxFSWatchEntryKq::wxDirState curr = watch.GetLastState();
+
+ // iterate over old/curr file lists and compute changes
+ wxArrayString::iterator oit = old.files.begin();
+ wxArrayString::iterator cit = curr.files.begin();
+ for ( ; oit != old.files.end() && cit != curr.files.end(); )
+ {
+ if ( *cit == *oit )
+ {
+ ++cit;
+ ++oit;
+ }
+ else if ( *cit <= *oit )
+ {
+ changedFiles.push_back(*cit);
+ changedFlags.push_back(wxFSW_EVENT_CREATE);
+ ++cit;
+ }
+ else // ( *cit > *oit )
+ {
+ changedFiles.push_back(*oit);
+ changedFlags.push_back(wxFSW_EVENT_DELETE);
+ ++oit;
+ }
+ }
+
+ // border conditions
+ if ( oit == old.files.end() )
+ {
+ for ( ; cit != curr.files.end(); ++cit )
+ {
+ changedFiles.push_back( *cit );
+ changedFlags.push_back(wxFSW_EVENT_CREATE);
+ }
+ }
+ else if ( cit == curr.files.end() )
+ {
+ for ( ; oit != old.files.end(); ++oit )
+ {
+ changedFiles.push_back( *oit );
+ changedFlags.push_back(wxFSW_EVENT_DELETE);
+ }
+ }
+
+ wxASSERT( changedFiles.size() == changedFlags.size() );
+
+#if 0
+ wxLogTrace(wxTRACE_FSWATCHER, "Changed files:");
+ wxArrayString::iterator it = changedFiles.begin();
+ wxArrayInt::iterator it2 = changedFlags.begin();
+ for ( ; it != changedFiles.end(); ++it, ++it2)
+ {
+ wxString action = (*it2 == wxFSW_EVENT_CREATE) ?
+ "created" : "deleted";
+ wxLogTrace(wxTRACE_FSWATCHER, wxString::Format("File: '%s' %s",
+ *it, action));
+ }
+#endif
+ }
+
+ void ProcessNativeEvent(const struct kevent& e)
+ {
+ wxASSERT_MSG(e.udata, "Null user data associated with kevent!");
+
+ wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%d, filter=%d, flags=%u, "
+ "fflags=%u, data=%d, user_data=%p",
+ e.ident, e.filter, e.flags, e.fflags, e.data, e.udata);
+
+ // for ease of use
+ wxFSWatchEntryKq& w = *(static_cast<wxFSWatchEntry*>(e.udata));
+ int nflags = e.fflags;
+
+ // clear ignored flags
+ nflags &= ~NOTE_REVOKE;
+
+ // TODO ignore events we didn't ask for + refactor this cascade ifs
+ // check for events
+ while ( nflags )
+ {
+ // when monitoring dir, this means create/delete
+ if ( nflags & NOTE_WRITE && wxDirExists(w.GetPath()) )
+ {
+ // NOTE_LINK is set when the dir was created, but we
+ // don't care - we look for new names in directory
+ // regardless of type. Also, clear all this, because
+ // it cannot mean more by itself
+ nflags &= ~(NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK);
+
+ wxArrayString changedFiles;
+ wxArrayInt changedFlags;
+ FindChanges(w, changedFiles, changedFlags);
+ wxArrayString::iterator it = changedFiles.begin();
+ wxArrayInt::iterator changeType = changedFlags.begin();
+ for ( ; it != changedFiles.end(); ++it, ++changeType )
+ {
+ wxFileName path;
+ if ( wxDirExists(*it) )
+ {
+ path = wxFileName::DirName(w.GetPath() + *it);
+ }
+ else
+ {
+ path = wxFileName::FileName(w.GetPath() + *it);
+ }
+
+ wxFileSystemWatcherEvent event(*changeType, path, path);
+ SendEvent(event);
+ }
+ }
+ else if ( nflags & NOTE_RENAME )
+ {
+ // CHECK it'd be only possible to detect name if we had
+ // parent files listing which we could confront with now and
+ // still we couldn't be sure we have the right name...
+ nflags &= ~NOTE_RENAME;
+ wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME,
+ w.GetPath(), wxFileName());
+ SendEvent(event);
+ }
+ else if ( nflags & NOTE_WRITE || nflags & NOTE_EXTEND )
+ {
+ nflags &= ~(NOTE_WRITE | NOTE_EXTEND);
+ wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY,
+ w.GetPath(), w.GetPath());
+ SendEvent(event);
+ }
+ else if ( nflags & NOTE_DELETE )
+ {
+ nflags &= ~(NOTE_DELETE);
+ wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE,
+ w.GetPath(), w.GetPath());
+ SendEvent(event);
+ }
+ else if ( nflags & NOTE_ATTRIB )
+ {
+ nflags &= ~(NOTE_ATTRIB);
+ wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS,
+ w.GetPath(), w.GetPath());
+ SendEvent(event);
+ }
+
+ // clear any flags that won't mean anything by themselves
+ nflags &= ~(NOTE_LINK);
+ }
+ }
+
+ void SendEvent(wxFileSystemWatcherEvent& evt)
+ {
+ m_watcher->GetOwner()->ProcessEvent(evt);
+ }
+
+ static int Watcher2NativeFlags(int WXUNUSED(flags))
+ {
+ // TODO: it would be better to only subscribe to what we need
+ return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
+ NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
+ NOTE_REVOKE;
+ }
+
+ wxFSWSourceHandler* m_handler; // handler for kqueue event source
+ wxEventLoopBase* m_loop; // event loop we have registered with
+ wxAbstractEventLoopSource* m_source; // our event loop source
+ int m_kfd;
+};
+
+
+// once we get signaled to read, actuall event reading occurs
+void wxFSWSourceHandler::OnReadWaiting()
+{
+ wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
+ m_service->ReadEvents();
+}
+
+void wxFSWSourceHandler::OnWriteWaiting()
+{
+ wxFAIL_MSG("We never write to kqueue descriptor.");
+}
+
+void wxFSWSourceHandler::OnExceptionWaiting()
+{
+ wxFAIL_MSG("We never receive exceptions on kqueue descriptor.");
+}
+
+
+// ============================================================================
+// wxKqueueFileSystemWatcher implementation
+// ============================================================================
+
+wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher()
+ : wxFileSystemWatcherBase()
+{
+ Init();
+}
+
+wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName& path,
+ int events)
+ : wxFileSystemWatcherBase()
+{
+ if (!Init())
+ {
+ if (m_service)
+ {
+ delete m_service;
+ m_service = NULL;
+ }
+ return;
+ }
+
+ Add(path, events);
+}
+
+wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher()
+{
+}
+
+bool wxKqueueFileSystemWatcher::Init()
+{
+ m_service = new wxFSWatcherImplKqueue(this);
+ return m_service->Init();
+}
+
+#endif // wxHAS_KQUEUE
+
+#endif // wxUSE_FSWATCHER