Files
wxWidgets/src/unix/fswatcher_inotify.cpp
Vadim Zeitlin 5276b0a53c Use wxDELETE() and wxDELETEA() when possible.
Use wxDELETE[A]() functions which automatically NULL out their arguments after
deleting them instead of doing it manually.

Closes #9685.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@64656 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2010-06-20 18:18:23 +00:00

492 lines
15 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// 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_source(NULL),
m_ifd(-1)
{
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" );
wxEventLoopBase *loop = wxEventLoopBase::GetActive();
wxCHECK_MSG( loop, false, "File system watcher needs an event loop" );
m_ifd = inotify_init();
if ( m_ifd == -1 )
{
wxLogSysError( _("Unable to create inotify instance") );
return false;
}
m_source = loop->AddSourceForFD
(
m_ifd,
m_handler,
wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION
);
return m_source != NULL;
}
void Close()
{
wxCHECK_RET( IsOk(),
"Inotify not initialized or invalid inotify descriptor" );
wxDELETE(m_source);
if ( close(m_ifd) != 0 )
{
wxLogSysError( _("Unable to close inotify instance") );
}
}
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() const
{
return m_source != NULL;
}
protected:
int DoAddInotify(wxFSWatchEntry* watch)
{
int flags = Watcher2NativeFlags(watch->GetFlags());
int wd = inotify_add_watch(m_ifd, 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_ifd, 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_ifd, 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
wxEventLoopSource* m_source; // our event loop source
// file descriptor created by inotify_init()
int m_ifd;
};
// ============================================================================
// 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