X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/40152925d51548122ffee555fce9648016b96a1e..6b8ef0b35d674bc262eb2005ac1321762c831d31:/src/unix/fswatcher_kqueue.cpp diff --git a/src/unix/fswatcher_kqueue.cpp b/src/unix/fswatcher_kqueue.cpp new file mode 100644 index 0000000000..ccadcb6090 --- /dev/null +++ b/src/unix/fswatcher_kqueue.cpp @@ -0,0 +1,452 @@ +///////////////////////////////////////////////////////////////////////////// +// 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 +// 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 +#include +#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 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 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(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