]> git.saurik.com Git - wxWidgets.git/blobdiff - src/unix/fswatcher_inotify.cpp
Merge SOC2009_FSWATCHER branch into trunk.
[wxWidgets.git] / src / unix / fswatcher_inotify.cpp
diff --git a/src/unix/fswatcher_inotify.cpp b/src/unix/fswatcher_inotify.cpp
new file mode 100644 (file)
index 0000000..229af98
--- /dev/null
@@ -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