]> git.saurik.com Git - wxWidgets.git/blobdiff - src/unix/fswatcher_inotify.cpp
Fix crash in wxDC::GetMultiLineTextExtent() after last commit.
[wxWidgets.git] / src / unix / fswatcher_inotify.cpp
index 229af98eee66685b107d8d8ed9f2b407ce8350c2..5bcc475698ef63bc3fb3f6c5329a4796619c2fe3 100644 (file)
@@ -45,8 +45,8 @@ class wxFSWatcherImplUnix : public wxFSWatcherImpl
 public:
     wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) :
         wxFSWatcherImpl(watcher),
-        m_loop(NULL),
-        m_source(NULL)
+        m_source(NULL),
+        m_ifd(-1)
     {
         m_handler = new wxFSWSourceHandler(this);
     }
@@ -65,42 +65,38 @@ public:
     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" );
+        wxEventLoopBase *loop = wxEventLoopBase::GetActive();
+        wxCHECK_MSG( loop, false, "File system watcher needs an event loop" );
 
-        int fd = inotify_init();
-        if (fd == -1)
+        m_ifd = inotify_init();
+        if ( m_ifd == -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();
+        m_source = loop->AddSourceForFD
+                         (
+                          m_ifd,
+                          m_handler,
+                          wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION
+                         );
+
+        return m_source != NULL;
     }
 
-    bool Close()
+    void Close()
     {
-        wxCHECK_MSG( IsOk(), false,
+        wxCHECK_RET( IsOk(),
                     "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();
+        wxDELETE(m_source);
 
-        int ret = close(m_source->GetResource());
-        if (ret == -1)
+        if ( close(m_ifd) != 0 )
         {
             wxLogSysError( _("Unable to close inotify instance") );
         }
-        m_source->Invalidate();
-
-        return ret != -1;
     }
 
     virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
@@ -143,6 +139,9 @@ public:
             wxFAIL_MSG( wxString::Format("Path %s is not watched",
                                           watch->GetPath()) );
         }
+        // Cache the wd in case any events arrive late
+        m_staleDescriptors.Add(watch->GetWatchDescriptor());
+
         watch->SetWatchDescriptor(-1);
         return true;
     }
@@ -193,39 +192,16 @@ public:
         return event_count;
     }
 
-    bool IsOk()
+    bool IsOk() const
     {
-        return m_source && m_source->IsOk();
+        return m_source != NULL;
     }
 
 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);
+        int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags);
         // finally we can set watch descriptor
         watch->SetWatchDescriptor(wd);
         return wd;
@@ -233,8 +209,7 @@ protected:
 
     int DoRemoveInotify(wxFSWatchEntry* watch)
     {
-        return inotify_rm_watch(m_source->GetResource(),
-                                watch->GetWatchDescriptor());
+        return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor());
     }
 
     void ProcessNativeEvent(const inotify_event& inevt)
@@ -245,13 +220,39 @@ protected:
         // will be already removed from our list at that time
         if (inevt.mask & IN_IGNORED)
         {
+            // It is now safe to remove it from the stale descriptors too, we
+            // won't get any more events for it.
+            // However if we're here because a dir that we're still watching
+            // has just been deleted, its wd won't be on this list
+            const int pos = m_staleDescriptors.Index(inevt.wd);
+            if ( pos != wxNOT_FOUND )
+            {
+                m_staleDescriptors.RemoveAt(static_cast<size_t>(pos));
+                wxLogTrace(wxTRACE_FSWATCHER,
+                       "Removed wd %i from the stale-wd cache", inevt.wd);
+            }
             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!");
+        if (it == m_watchMap.end())
+        {
+            // It's not in the map; check if was recently removed from it.
+            if (m_staleDescriptors.Index(inevt.wd) != wxNOT_FOUND)
+            {
+                wxLogTrace(wxTRACE_FSWATCHER,
+                           "Got an event for stale wd %i", inevt.wd);
+            }
+            else
+            {
+                wxFAIL_MSG("Event for unknown watch descriptor.");
+            }
+
+            // In any case, don't process this event: it's either for an
+            // already removed entry, or for a completely unknown one.
+            return;
+        }
 
         wxFSWatchEntry& watch = *(it->second);
         int nativeFlags = inevt.mask;
@@ -270,11 +271,81 @@ protected:
         {
             return;
         }
+
+        // Creation
+        // We need do something here only if the original watch was recursive;
+        // we don't watch a child dir itself inside a non-tree watch.
+        // We watch only dirs explicitly, so we don't want file IN_CREATEs.
+        // Distinguish by whether nativeFlags contain IN_ISDIR
+        else if ((nativeFlags & IN_CREATE) &&
+                 (watch.GetType() == wxFSWPath_Tree) && (inevt.mask & IN_ISDIR))
+        {
+            wxFileName fn = GetEventPath(watch, inevt);
+            // Though it's a dir, fn treats it as a file. So:
+            fn.AssignDir(fn.GetFullPath());
+
+            if (m_watcher->AddAny(fn, wxFSW_EVENT_ALL,
+                                   wxFSWPath_Tree, watch.GetFilespec()))
+            {
+                // Tell the owner, in case it's interested
+                // If there's a filespec, assume he's not
+                if (watch.GetFilespec().empty())
+                {
+                    wxFileSystemWatcherEvent event(flags, fn, fn);
+                    SendEvent(event);
+                }
+            }
+        }
+
+        // Deletion
+        // We watch only dirs explicitly, so we don't want file IN_DELETEs.
+        // We obviously can't check using DirExists() as the object has been
+        // deleted; and nativeFlags here doesn't contain IN_ISDIR, even for
+        // a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need
+        // to do something here only inside a tree watch, or if it's the parent
+        // dir that's deleted. Otherwise let the parent dir cope
+        else if ((nativeFlags & IN_DELETE_SELF) &&
+                    ((watch.GetType() == wxFSWPath_Dir) ||
+                     (watch.GetType() == wxFSWPath_Tree)))
+        {
+            // We must remove the deleted directory from the map, so that
+            // DoRemoveInotify() isn't called on it in the future. Don't assert
+            // if the wd isn't found: repeated IN_DELETE_SELFs can occur
+            wxFileName fn = GetEventPath(watch, inevt);
+            wxString path(fn.GetPathWithSep());
+
+            if (m_watchMap.erase(inevt.wd) == 1)
+            {
+                // Delete from wxFileSystemWatcher
+                wxDynamicCast(m_watcher, wxInotifyFileSystemWatcher)->
+                                            OnDirDeleted(path);
+
+                // Now remove from our local list of watched items
+                wxFSWatchEntries::iterator wit =
+                                        m_watches.find(path);
+                if (wit != m_watches.end())
+                {
+                    m_watches.erase(wit);
+                }
+
+                // Cache the wd in case any events arrive late
+                m_staleDescriptors.Add(inevt.wd);
+            }
+
+            // Tell the owner, in case it's interested
+            // If there's a filespec, assume he's not
+            if (watch.GetFilespec().empty())
+            {
+                wxFileSystemWatcherEvent event(flags, fn, fn);
+                SendEvent(event);
+            }
+        }
+
         // renames
         else if (nativeFlags & IN_MOVE)
         {
-            wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie);
-            if ( it == m_cookies.end() )
+            wxInotifyCookies::iterator it2 = m_cookies.find(inevt.cookie);
+            if ( it2 == m_cookies.end() )
             {
                 int size = sizeof(inevt) + inevt.len;
                 inotify_event* e = (inotify_event*) operator new (size);
@@ -285,22 +356,27 @@ protected:
             }
             else
             {
-                inotify_event& oldinevt = *(it->second);
+                inotify_event& oldinevt = *(it2->second);
 
-                wxFileSystemWatcherEvent event(flags);
-                if ( inevt.mask & IN_MOVED_FROM )
-                {
-                    event.SetPath(GetEventPath(watch, inevt));
-                    event.SetNewPath(GetEventPath(watch, oldinevt));
-                }
-                else
+                // Tell the owner, in case it's interested
+                // If there's a filespec, assume he's not
+                if ( watch.GetFilespec().empty() )
                 {
-                    event.SetPath(GetEventPath(watch, oldinevt));
-                    event.SetNewPath(GetEventPath(watch, inevt));
+                    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);
                 }
-                SendEvent(event);
 
-                m_cookies.erase(it);
+                m_cookies.erase(it2);
                 delete &oldinevt;
             }
         }
@@ -308,8 +384,12 @@ protected:
         else
         {
             wxFileName path = GetEventPath(watch, inevt);
-            wxFileSystemWatcherEvent event(flags, path, path);
-            SendEvent(event);
+            // For files, check that it matches any filespec
+            if ( MatchesFilespec(path, watch.GetFilespec()) )
+            {
+                wxFileSystemWatcherEvent event(flags, path, path);
+                SendEvent(event);
+            }
         }
     }
 
@@ -328,11 +408,18 @@ protected:
             wxCHECK_RET(wit != m_watchMap.end(),
                              "Watch descriptor not present in the watch map!");
 
+            // Tell the owner, in case it's interested
+            // If there's a filespec, assume he's not
             wxFSWatchEntry& watch = *(wit->second);
-            int flags = Native2WatcherFlags(inevt.mask);
-            wxFileName path = GetEventPath(watch, inevt);
-            wxFileSystemWatcherEvent event(flags, path, path);
-            SendEvent(event);
+            if ( watch.GetFilespec().empty() )
+            {
+                int flags = Native2WatcherFlags(inevt.mask);
+                wxFileName path = GetEventPath(watch, inevt);
+                {
+                    wxFileSystemWatcherEvent event(flags, path, path);
+                    SendEvent(event);
+                }
+            }
 
             m_cookies.erase(it);
             delete &inevt;
@@ -352,7 +439,7 @@ protected:
                     "Inotify not initialized or invalid inotify descriptor" );
 
         memset(buf, 0, size);
-        ssize_t left = read(m_source->GetResource(), buf, size);
+        ssize_t left = read(m_ifd, buf, size);
         if (left == -1)
         {
             wxLogSysError(_("Unable to read from inotify descriptor"));
@@ -372,9 +459,12 @@ protected:
         wxString mask = (inevt.mask & IN_ISDIR) ?
                         wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
                         wxString::Format("%u", inevt.mask);
+        const char* name = "";
+        if (inevt.len)
+            name = inevt.name;
         return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
                                 "name=%s", inevt.wd, mask, inevt.cookie,
-                                inevt.len, inevt.name);
+                                inevt.len, name);
     }
 
     static wxFileName GetEventPath(const wxFSWatchEntry& watch,
@@ -382,7 +472,7 @@ protected:
     {
         // only when dir is watched, we have non-empty e.name
         wxFileName path = watch.GetPath();
-        if (path.IsDir())
+        if (path.IsDir() && inevt.len)
         {
             path = wxFileName(path.GetPath(), inevt.name);
         }
@@ -450,9 +540,12 @@ protected:
 
     wxFSWSourceHandler* m_handler;        // handler for inotify event source
     wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
+    wxArrayInt m_staleDescriptors;        // stores recently-removed watches
     wxInotifyCookies m_cookies;           // map to track renames
-    wxEventLoopBase* m_loop;
-    wxUnixEventLoopSource* m_source;      // our event loop source
+    wxEventLoopSource* m_source;          // our event loop source
+
+    // file descriptor created by inotify_init()
+    int m_ifd;
 };
 
 
@@ -512,6 +605,19 @@ bool wxInotifyFileSystemWatcher::Init()
     return m_service->Init();
 }
 
+void wxInotifyFileSystemWatcher::OnDirDeleted(const wxString& path)
+{
+    if (!path.empty())
+    {
+        wxFSWatchInfoMap::iterator it = m_watches.find(path);
+        wxCHECK_RET(it != m_watches.end(),
+                    wxString::Format("Path '%s' is not watched", path));
+
+        // path has been deleted, so we must forget it whatever its refcount
+        m_watches.erase(it);
+    }
+}
+
 #endif // wxHAS_INOTIFY
 
 #endif // wxUSE_FSWATCHER