Merge SOC2009_FSWATCHER branch into trunk.
Merges everything from the branch with only some minor changes, mostly renamed
wxUSE_FSWATCHER_{INOTIFY,KQUEUE} to wxHAS_{INOTIFY,KQUEUE}.
Add wxFileSystemWatcher and related classes.
Also introduces wxEventLoopSource.
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@62474 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
452
src/unix/fswatcher_kqueue.cpp
Normal file
452
src/unix/fswatcher_kqueue.cpp
Normal file
@@ -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 <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
|
||||
Reference in New Issue
Block a user