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:
517
src/unix/fswatcher_inotify.cpp
Normal file
517
src/unix/fswatcher_inotify.cpp
Normal file
@@ -0,0 +1,517 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/unix/fswatcher_inotify.cpp
|
||||
// Purpose: inotify-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_INOTIFY
|
||||
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
#include "wx/private/fswatcher.h"
|
||||
|
||||
// ============================================================================
|
||||
// wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
|
||||
// ============================================================================
|
||||
|
||||
// inotify watch descriptor => wxFSWatchEntry* map
|
||||
WX_DECLARE_HASH_MAP(int, wxFSWatchEntry*, wxIntegerHash, wxIntegerEqual,
|
||||
wxFSWatchEntryDescriptors);
|
||||
|
||||
// inotify event cookie => inotify_event* map
|
||||
WX_DECLARE_HASH_MAP(int, inotify_event*, wxIntegerHash, wxIntegerEqual,
|
||||
wxInotifyCookies);
|
||||
|
||||
/**
|
||||
* Helper class encapsulating inotify mechanism
|
||||
*/
|
||||
class wxFSWatcherImplUnix : public wxFSWatcherImpl
|
||||
{
|
||||
public:
|
||||
wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) :
|
||||
wxFSWatcherImpl(watcher),
|
||||
m_loop(NULL),
|
||||
m_source(NULL)
|
||||
{
|
||||
m_handler = new wxFSWSourceHandler(this);
|
||||
}
|
||||
|
||||
~wxFSWatcherImplUnix()
|
||||
{
|
||||
// we close inotify only if initialized before
|
||||
if (IsOk())
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
delete m_handler;
|
||||
}
|
||||
|
||||
bool Init()
|
||||
{
|
||||
wxCHECK_MSG( !IsOk(), false, "Inotify 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" );
|
||||
|
||||
int fd = inotify_init();
|
||||
if (fd == -1)
|
||||
{
|
||||
wxLogSysError( _("Unable to create inotify instance") );
|
||||
return false;
|
||||
}
|
||||
|
||||
int flags = wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION;
|
||||
m_source = static_cast<wxUnixEventLoopSource*>(
|
||||
m_loop->CreateSource(fd, m_handler, flags));
|
||||
return RegisterSource();
|
||||
}
|
||||
|
||||
bool Close()
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
wxCHECK_MSG( m_loop, false,
|
||||
"m_loop shouldn't be null if inotify is initialized" );
|
||||
|
||||
// ignore errors
|
||||
(void) UnregisterSource();
|
||||
|
||||
int ret = close(m_source->GetResource());
|
||||
if (ret == -1)
|
||||
{
|
||||
wxLogSysError( _("Unable to close inotify instance") );
|
||||
}
|
||||
m_source->Invalidate();
|
||||
|
||||
return ret != -1;
|
||||
}
|
||||
|
||||
virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
|
||||
int wd = DoAddInotify(watch.get());
|
||||
if (wd == -1)
|
||||
{
|
||||
wxLogSysError( _("Unable to add inotify watch") );
|
||||
return false;
|
||||
}
|
||||
|
||||
wxFSWatchEntryDescriptors::value_type val(wd, watch.get());
|
||||
if (!m_watchMap.insert(val).second)
|
||||
{
|
||||
wxFAIL_MSG( wxString::Format( "Path %s is already watched",
|
||||
watch->GetPath()) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryUnix> watch)
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
|
||||
int ret = DoRemoveInotify(watch.get());
|
||||
if (ret == -1)
|
||||
{
|
||||
wxLogSysError( _("Unable to remove inotify watch") );
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1)
|
||||
{
|
||||
wxFAIL_MSG( wxString::Format("Path %s is not watched",
|
||||
watch->GetPath()) );
|
||||
}
|
||||
watch->SetWatchDescriptor(-1);
|
||||
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;
|
||||
}
|
||||
|
||||
int ReadEvents()
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), -1,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
|
||||
// read events
|
||||
// TODO differentiate depending on params
|
||||
char buf[128 * sizeof(inotify_event)];
|
||||
int left = ReadEventsToBuf(buf, sizeof(buf));
|
||||
if (left == -1)
|
||||
return -1;
|
||||
|
||||
// left > 0, we have events
|
||||
char* memory = buf;
|
||||
int event_count = 0;
|
||||
while (left > 0) // OPT checking 'memory' would suffice
|
||||
{
|
||||
event_count++;
|
||||
inotify_event* e = (inotify_event*)memory;
|
||||
|
||||
// process one inotify_event
|
||||
ProcessNativeEvent(*e);
|
||||
|
||||
int offset = sizeof(inotify_event) + e->len;
|
||||
left -= offset;
|
||||
memory += offset;
|
||||
}
|
||||
|
||||
// take care of unmatched renames
|
||||
ProcessRenames();
|
||||
|
||||
wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
|
||||
return event_count;
|
||||
}
|
||||
|
||||
bool IsOk()
|
||||
{
|
||||
return m_source && m_source->IsOk();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool RegisterSource()
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
|
||||
bool ret = m_loop->AddSource(m_source);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool UnregisterSource()
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
wxCHECK_MSG( m_loop, false,
|
||||
"m_loop shouldn't be null if inotify is initialized" );
|
||||
|
||||
bool ret = m_loop->RemoveSource(m_source);
|
||||
m_loop = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int DoAddInotify(wxFSWatchEntry* watch)
|
||||
{
|
||||
int flags = Watcher2NativeFlags(watch->GetFlags());
|
||||
int wd = inotify_add_watch(m_source->GetResource(),
|
||||
watch->GetPath().fn_str(),
|
||||
flags);
|
||||
// finally we can set watch descriptor
|
||||
watch->SetWatchDescriptor(wd);
|
||||
return wd;
|
||||
}
|
||||
|
||||
int DoRemoveInotify(wxFSWatchEntry* watch)
|
||||
{
|
||||
return inotify_rm_watch(m_source->GetResource(),
|
||||
watch->GetWatchDescriptor());
|
||||
}
|
||||
|
||||
void ProcessNativeEvent(const inotify_event& inevt)
|
||||
{
|
||||
wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
|
||||
|
||||
// after removing inotify watch we get IN_IGNORED for it, but the watch
|
||||
// will be already removed from our list at that time
|
||||
if (inevt.mask & IN_IGNORED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get watch entry for this event
|
||||
wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd);
|
||||
wxCHECK_RET(it != m_watchMap.end(),
|
||||
"Watch descriptor not present in the watch map!");
|
||||
|
||||
wxFSWatchEntry& watch = *(it->second);
|
||||
int nativeFlags = inevt.mask;
|
||||
int flags = Native2WatcherFlags(nativeFlags);
|
||||
|
||||
// check out for error/warning condition
|
||||
if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
|
||||
{
|
||||
wxString errMsg = GetErrorDescription(Watcher2NativeFlags(flags));
|
||||
wxFileSystemWatcherEvent event(flags, errMsg);
|
||||
SendEvent(event);
|
||||
}
|
||||
// filter out ignored events and those not asked for.
|
||||
// we never filter out warnings or exceptions
|
||||
else if ((flags == 0) || !(flags & watch.GetFlags()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// renames
|
||||
else if (nativeFlags & IN_MOVE)
|
||||
{
|
||||
wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie);
|
||||
if ( it == m_cookies.end() )
|
||||
{
|
||||
int size = sizeof(inevt) + inevt.len;
|
||||
inotify_event* e = (inotify_event*) operator new (size);
|
||||
memcpy(e, &inevt, size);
|
||||
|
||||
wxInotifyCookies::value_type val(e->cookie, e);
|
||||
m_cookies.insert(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
inotify_event& oldinevt = *(it->second);
|
||||
|
||||
wxFileSystemWatcherEvent event(flags);
|
||||
if ( inevt.mask & IN_MOVED_FROM )
|
||||
{
|
||||
event.SetPath(GetEventPath(watch, inevt));
|
||||
event.SetNewPath(GetEventPath(watch, oldinevt));
|
||||
}
|
||||
else
|
||||
{
|
||||
event.SetPath(GetEventPath(watch, oldinevt));
|
||||
event.SetNewPath(GetEventPath(watch, inevt));
|
||||
}
|
||||
SendEvent(event);
|
||||
|
||||
m_cookies.erase(it);
|
||||
delete &oldinevt;
|
||||
}
|
||||
}
|
||||
// every other kind of event
|
||||
else
|
||||
{
|
||||
wxFileName path = GetEventPath(watch, inevt);
|
||||
wxFileSystemWatcherEvent event(flags, path, path);
|
||||
SendEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessRenames()
|
||||
{
|
||||
wxInotifyCookies::iterator it = m_cookies.begin();
|
||||
while ( it != m_cookies.end() )
|
||||
{
|
||||
inotify_event& inevt = *(it->second);
|
||||
|
||||
wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
|
||||
wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
|
||||
|
||||
// get watch entry for this event
|
||||
wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd);
|
||||
wxCHECK_RET(wit != m_watchMap.end(),
|
||||
"Watch descriptor not present in the watch map!");
|
||||
|
||||
wxFSWatchEntry& watch = *(wit->second);
|
||||
int flags = Native2WatcherFlags(inevt.mask);
|
||||
wxFileName path = GetEventPath(watch, inevt);
|
||||
wxFileSystemWatcherEvent event(flags, path, path);
|
||||
SendEvent(event);
|
||||
|
||||
m_cookies.erase(it);
|
||||
delete &inevt;
|
||||
it = m_cookies.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void SendEvent(wxFileSystemWatcherEvent& evt)
|
||||
{
|
||||
wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
|
||||
m_watcher->GetOwner()->ProcessEvent(evt);
|
||||
}
|
||||
|
||||
int ReadEventsToBuf(char* buf, int size)
|
||||
{
|
||||
wxCHECK_MSG( IsOk(), false,
|
||||
"Inotify not initialized or invalid inotify descriptor" );
|
||||
|
||||
memset(buf, 0, size);
|
||||
ssize_t left = read(m_source->GetResource(), buf, size);
|
||||
if (left == -1)
|
||||
{
|
||||
wxLogSysError(_("Unable to read from inotify descriptor"));
|
||||
return -1;
|
||||
}
|
||||
else if (left == 0)
|
||||
{
|
||||
wxLogWarning(_("EOF while reading from inotify descriptor"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
static wxString InotifyEventToString(const inotify_event& inevt)
|
||||
{
|
||||
wxString mask = (inevt.mask & IN_ISDIR) ?
|
||||
wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
|
||||
wxString::Format("%u", inevt.mask);
|
||||
return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
|
||||
"name=%s", inevt.wd, mask, inevt.cookie,
|
||||
inevt.len, inevt.name);
|
||||
}
|
||||
|
||||
static wxFileName GetEventPath(const wxFSWatchEntry& watch,
|
||||
const inotify_event& inevt)
|
||||
{
|
||||
// only when dir is watched, we have non-empty e.name
|
||||
wxFileName path = watch.GetPath();
|
||||
if (path.IsDir())
|
||||
{
|
||||
path = wxFileName(path.GetPath(), inevt.name);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
static int Watcher2NativeFlags(int WXUNUSED(flags))
|
||||
{
|
||||
// TODO: it would be nice to subscribe only to the events we really need
|
||||
return IN_ALL_EVENTS;
|
||||
}
|
||||
|
||||
static int Native2WatcherFlags(int flags)
|
||||
{
|
||||
static const int flag_mapping[][2] = {
|
||||
{ IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
|
||||
{ IN_MODIFY, wxFSW_EVENT_MODIFY },
|
||||
{ IN_ATTRIB, 0 },
|
||||
{ IN_CLOSE_WRITE, 0 },
|
||||
{ IN_CLOSE_NOWRITE, 0 },
|
||||
{ IN_OPEN, 0 },
|
||||
{ IN_MOVED_FROM, wxFSW_EVENT_RENAME },
|
||||
{ IN_MOVED_TO, wxFSW_EVENT_RENAME },
|
||||
{ IN_CREATE, wxFSW_EVENT_CREATE },
|
||||
{ IN_DELETE, wxFSW_EVENT_DELETE },
|
||||
{ IN_DELETE_SELF, wxFSW_EVENT_DELETE },
|
||||
{ IN_MOVE_SELF, wxFSW_EVENT_DELETE },
|
||||
|
||||
{ IN_UNMOUNT, wxFSW_EVENT_ERROR },
|
||||
{ IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
|
||||
|
||||
// ignored, because this is genereted mainly by watcher::Remove()
|
||||
{ IN_IGNORED, 0 }
|
||||
};
|
||||
|
||||
unsigned int i=0;
|
||||
for ( ; i < WXSIZEOF(flag_mapping); ++i) {
|
||||
// in this mapping multiple flags at once don't happen
|
||||
if (flags & flag_mapping[i][0])
|
||||
return flag_mapping[i][1];
|
||||
}
|
||||
|
||||
// never reached
|
||||
wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error description for specified inotify mask
|
||||
*/
|
||||
static const wxString GetErrorDescription(int flag)
|
||||
{
|
||||
switch ( flag )
|
||||
{
|
||||
case IN_UNMOUNT:
|
||||
return _("File system containing watched object was unmounted");
|
||||
case IN_Q_OVERFLOW:
|
||||
return _("Event queue overflowed");
|
||||
}
|
||||
|
||||
// never reached
|
||||
wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxFSWSourceHandler* m_handler; // handler for inotify event source
|
||||
wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
|
||||
wxInotifyCookies m_cookies; // map to track renames
|
||||
wxEventLoopBase* m_loop;
|
||||
wxUnixEventLoopSource* m_source; // our event loop source
|
||||
};
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// wxFSWSourceHandler implementation
|
||||
// ============================================================================
|
||||
|
||||
// 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 inotify descriptor.");
|
||||
}
|
||||
|
||||
void wxFSWSourceHandler::OnExceptionWaiting()
|
||||
{
|
||||
wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// wxInotifyFileSystemWatcher implementation
|
||||
// ============================================================================
|
||||
|
||||
wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
|
||||
: wxFileSystemWatcherBase()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
|
||||
int events)
|
||||
: wxFileSystemWatcherBase()
|
||||
{
|
||||
if (!Init())
|
||||
{
|
||||
if (m_service)
|
||||
delete m_service;
|
||||
return;
|
||||
}
|
||||
|
||||
Add(path, events);
|
||||
}
|
||||
|
||||
wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
|
||||
{
|
||||
}
|
||||
|
||||
bool wxInotifyFileSystemWatcher::Init()
|
||||
{
|
||||
m_service = new wxFSWatcherImplUnix(this);
|
||||
return m_service->Init();
|
||||
}
|
||||
|
||||
#endif // wxHAS_INOTIFY
|
||||
|
||||
#endif // wxUSE_FSWATCHER
|
||||
Reference in New Issue
Block a user