]> git.saurik.com Git - wxWidgets.git/blob - src/unix/fswatcher_inotify.cpp
Refactor children traversal in wxWindow::TransferData{To,From}Window().
[wxWidgets.git] / src / unix / fswatcher_inotify.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/fswatcher_inotify.cpp
3 // Purpose: inotify-based wxFileSystemWatcher implementation
4 // Author: Bartosz Bekier
5 // Created: 2009-05-26
6 // RCS-ID: $Id$
7 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_FSWATCHER
19
20 #include "wx/fswatcher.h"
21
22 #ifdef wxHAS_INOTIFY
23
24 #include <sys/inotify.h>
25 #include <unistd.h>
26 #include "wx/private/fswatcher.h"
27
28 // ============================================================================
29 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
30 // ============================================================================
31
32 // inotify watch descriptor => wxFSWatchEntry* map
33 WX_DECLARE_HASH_MAP(int, wxFSWatchEntry*, wxIntegerHash, wxIntegerEqual,
34 wxFSWatchEntryDescriptors);
35
36 // inotify event cookie => inotify_event* map
37 WX_DECLARE_HASH_MAP(int, inotify_event*, wxIntegerHash, wxIntegerEqual,
38 wxInotifyCookies);
39
40 /**
41 * Helper class encapsulating inotify mechanism
42 */
43 class wxFSWatcherImplUnix : public wxFSWatcherImpl
44 {
45 public:
46 wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) :
47 wxFSWatcherImpl(watcher),
48 m_source(NULL),
49 m_ifd(-1)
50 {
51 m_handler = new wxFSWSourceHandler(this);
52 }
53
54 ~wxFSWatcherImplUnix()
55 {
56 // we close inotify only if initialized before
57 if (IsOk())
58 {
59 Close();
60 }
61
62 delete m_handler;
63 }
64
65 bool Init()
66 {
67 wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" );
68
69 wxEventLoopBase *loop = wxEventLoopBase::GetActive();
70 wxCHECK_MSG( loop, false, "File system watcher needs an event loop" );
71
72 m_ifd = inotify_init();
73 if ( m_ifd == -1 )
74 {
75 wxLogSysError( _("Unable to create inotify instance") );
76 return false;
77 }
78
79 m_source = loop->AddSourceForFD
80 (
81 m_ifd,
82 m_handler,
83 wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION
84 );
85
86 return m_source != NULL;
87 }
88
89 void Close()
90 {
91 wxCHECK_RET( IsOk(),
92 "Inotify not initialized or invalid inotify descriptor" );
93
94 wxDELETE(m_source);
95
96 if ( close(m_ifd) != 0 )
97 {
98 wxLogSysError( _("Unable to close inotify instance") );
99 }
100 }
101
102 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
103 {
104 wxCHECK_MSG( IsOk(), false,
105 "Inotify not initialized or invalid inotify descriptor" );
106
107 int wd = DoAddInotify(watch.get());
108 if (wd == -1)
109 {
110 wxLogSysError( _("Unable to add inotify watch") );
111 return false;
112 }
113
114 wxFSWatchEntryDescriptors::value_type val(wd, watch.get());
115 if (!m_watchMap.insert(val).second)
116 {
117 wxFAIL_MSG( wxString::Format( "Path %s is already watched",
118 watch->GetPath()) );
119 return false;
120 }
121
122 return true;
123 }
124
125 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryUnix> watch)
126 {
127 wxCHECK_MSG( IsOk(), false,
128 "Inotify not initialized or invalid inotify descriptor" );
129
130 int ret = DoRemoveInotify(watch.get());
131 if (ret == -1)
132 {
133 wxLogSysError( _("Unable to remove inotify watch") );
134 return false;
135 }
136
137 if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1)
138 {
139 wxFAIL_MSG( wxString::Format("Path %s is not watched",
140 watch->GetPath()) );
141 }
142 // Cache the wd in case any events arrive late
143 m_staleDescriptors.Add(watch->GetWatchDescriptor());
144
145 watch->SetWatchDescriptor(-1);
146 return true;
147 }
148
149 virtual bool RemoveAll()
150 {
151 wxFSWatchEntries::iterator it = m_watches.begin();
152 for ( ; it != m_watches.end(); ++it )
153 {
154 (void) DoRemove(it->second);
155 }
156 m_watches.clear();
157 return true;
158 }
159
160 int ReadEvents()
161 {
162 wxCHECK_MSG( IsOk(), -1,
163 "Inotify not initialized or invalid inotify descriptor" );
164
165 // read events
166 // TODO differentiate depending on params
167 char buf[128 * sizeof(inotify_event)];
168 int left = ReadEventsToBuf(buf, sizeof(buf));
169 if (left == -1)
170 return -1;
171
172 // left > 0, we have events
173 char* memory = buf;
174 int event_count = 0;
175 while (left > 0) // OPT checking 'memory' would suffice
176 {
177 event_count++;
178 inotify_event* e = (inotify_event*)memory;
179
180 // process one inotify_event
181 ProcessNativeEvent(*e);
182
183 int offset = sizeof(inotify_event) + e->len;
184 left -= offset;
185 memory += offset;
186 }
187
188 // take care of unmatched renames
189 ProcessRenames();
190
191 wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
192 return event_count;
193 }
194
195 bool IsOk() const
196 {
197 return m_source != NULL;
198 }
199
200 protected:
201 int DoAddInotify(wxFSWatchEntry* watch)
202 {
203 int flags = Watcher2NativeFlags(watch->GetFlags());
204 int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags);
205 // finally we can set watch descriptor
206 watch->SetWatchDescriptor(wd);
207 return wd;
208 }
209
210 int DoRemoveInotify(wxFSWatchEntry* watch)
211 {
212 return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor());
213 }
214
215 void ProcessNativeEvent(const inotify_event& inevt)
216 {
217 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
218
219 // after removing inotify watch we get IN_IGNORED for it, but the watch
220 // will be already removed from our list at that time
221 if (inevt.mask & IN_IGNORED)
222 {
223 // It is now safe to remove it from the stale descriptors too, we
224 // won't get any more events for it.
225 // However if we're here because a dir that we're still watching
226 // has just been deleted, its wd won't be on this list
227 const int pos = m_staleDescriptors.Index(inevt.wd);
228 if ( pos != wxNOT_FOUND )
229 {
230 m_staleDescriptors.RemoveAt(static_cast<size_t>(pos));
231 wxLogTrace(wxTRACE_FSWATCHER,
232 "Removed wd %i from the stale-wd cache", inevt.wd);
233 }
234 return;
235 }
236
237 // get watch entry for this event
238 wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd);
239
240 // wd will be -1 for IN_Q_OVERFLOW, which would trigger the wxFAIL_MSG
241 if (inevt.wd != -1)
242 {
243 if (it == m_watchMap.end())
244 {
245 // It's not in the map; check if was recently removed from it.
246 if (m_staleDescriptors.Index(inevt.wd) != wxNOT_FOUND)
247 {
248 wxLogTrace(wxTRACE_FSWATCHER,
249 "Got an event for stale wd %i", inevt.wd);
250 }
251 else
252 {
253 // In theory we shouldn't reach here. In practice, some
254 // events, e.g. IN_MODIFY, arrive just after the IN_IGNORED
255 // so their wd has already been discarded. Warn about them.
256 wxFileSystemWatcherEvent
257 event
258 (
259 wxFSW_EVENT_WARNING,
260 wxString::Format
261 (
262 _("Unexpected event for \"%s\": no "
263 "matching watch descriptor."),
264 inevt.len ? inevt.name : ""
265 )
266 );
267 SendEvent(event);
268
269 }
270
271 // In any case, don't process this event: it's either for an
272 // already removed entry, or for an unknown one.
273 return;
274 }
275 }
276
277 int nativeFlags = inevt.mask;
278 int flags = Native2WatcherFlags(nativeFlags);
279
280 // check out for error/warning condition
281 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
282 {
283 wxString errMsg = GetErrorDescription(nativeFlags);
284 wxFileSystemWatcherEvent event(flags, errMsg);
285 SendEvent(event);
286 return;
287 }
288
289 if (inevt.wd == -1)
290 {
291 // Although this is not supposed to happen, we seem to be getting
292 // occasional IN_ACCESS/IN_MODIFY events without valid watch value.
293 wxFileSystemWatcherEvent
294 event
295 (
296 wxFSW_EVENT_WARNING,
297 wxString::Format
298 (
299 _("Invalid inotify event for \"%s\""),
300 inevt.len ? inevt.name : ""
301 )
302 );
303 SendEvent(event);
304 return;
305 }
306
307 wxFSWatchEntry& watch = *(it->second);
308
309 // Now IN_UNMOUNT. We must do so here, as it's not in the watch flags
310 if (nativeFlags & IN_UNMOUNT)
311 {
312 wxFileName path = GetEventPath(watch, inevt);
313 wxFileSystemWatcherEvent event(wxFSW_EVENT_UNMOUNT, path, path);
314 SendEvent(event);
315 }
316 // filter out ignored events and those not asked for.
317 // we never filter out warnings or exceptions
318 else if ((flags == 0) || !(flags & watch.GetFlags()))
319 {
320 return;
321 }
322
323 // Creation
324 // We need do something here only if the original watch was recursive;
325 // we don't watch a child dir itself inside a non-tree watch.
326 // We watch only dirs explicitly, so we don't want file IN_CREATEs.
327 // Distinguish by whether nativeFlags contain IN_ISDIR
328 else if ((nativeFlags & IN_CREATE) &&
329 (watch.GetType() == wxFSWPath_Tree) && (inevt.mask & IN_ISDIR))
330 {
331 wxFileName fn = GetEventPath(watch, inevt);
332 // Though it's a dir, fn treats it as a file. So:
333 fn.AssignDir(fn.GetFullPath());
334
335 if (m_watcher->AddAny(fn, wxFSW_EVENT_ALL,
336 wxFSWPath_Tree, watch.GetFilespec()))
337 {
338 // Tell the owner, in case it's interested
339 // If there's a filespec, assume he's not
340 if (watch.GetFilespec().empty())
341 {
342 wxFileSystemWatcherEvent event(flags, fn, fn);
343 SendEvent(event);
344 }
345 }
346 }
347
348 // Deletion
349 // We watch only dirs explicitly, so we don't want file IN_DELETEs.
350 // We obviously can't check using DirExists() as the object has been
351 // deleted; and nativeFlags here doesn't contain IN_ISDIR, even for
352 // a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need
353 // to do something here only inside a tree watch, or if it's the parent
354 // dir that's deleted. Otherwise let the parent dir cope
355 else if ((nativeFlags & IN_DELETE_SELF) &&
356 ((watch.GetType() == wxFSWPath_Dir) ||
357 (watch.GetType() == wxFSWPath_Tree)))
358 {
359 // We must remove the deleted directory from the map, so that
360 // DoRemoveInotify() isn't called on it in the future. Don't assert
361 // if the wd isn't found: repeated IN_DELETE_SELFs can occur
362 wxFileName fn = GetEventPath(watch, inevt);
363 wxString path(fn.GetPathWithSep());
364
365 if (m_watchMap.erase(inevt.wd) == 1)
366 {
367 // Delete from wxFileSystemWatcher
368 wxDynamicCast(m_watcher, wxInotifyFileSystemWatcher)->
369 OnDirDeleted(path);
370
371 // Now remove from our local list of watched items
372 wxFSWatchEntries::iterator wit =
373 m_watches.find(path);
374 if (wit != m_watches.end())
375 {
376 m_watches.erase(wit);
377 }
378
379 // Cache the wd in case any events arrive late
380 m_staleDescriptors.Add(inevt.wd);
381 }
382
383 // Tell the owner, in case it's interested
384 // If there's a filespec, assume he's not
385 if (watch.GetFilespec().empty())
386 {
387 wxFileSystemWatcherEvent event(flags, fn, fn);
388 SendEvent(event);
389 }
390 }
391
392 // renames
393 else if (nativeFlags & IN_MOVE)
394 {
395 // IN_MOVE events are produced in the following circumstances:
396 // * A move within a dir (what the user sees as a rename) gives an
397 // IN_MOVED_FROM and IN_MOVED_TO pair, each with the same path.
398 // * A move within watched dir foo/ e.g. from foo/bar1/ to foo/bar2/
399 // will also produce such a pair, but with different paths.
400 // * A move to baz/ will give only an IN_MOVED_FROM for foo/bar1;
401 // if baz/ is inside a different watch, that gets the IN_MOVED_TO.
402
403 // The first event to arrive, usually the IN_MOVED_FROM, is
404 // cached to await a matching IN_MOVED_TO; should none arrive, the
405 // unpaired event will be processed later in ProcessRenames().
406 wxInotifyCookies::iterator it2 = m_cookies.find(inevt.cookie);
407 if ( it2 == m_cookies.end() )
408 {
409 int size = sizeof(inevt) + inevt.len;
410 inotify_event* e = (inotify_event*) operator new (size);
411 memcpy(e, &inevt, size);
412
413 wxInotifyCookies::value_type val(e->cookie, e);
414 m_cookies.insert(val);
415 }
416 else
417 {
418 inotify_event& oldinevt = *(it2->second);
419
420 // Tell the owner, in case it's interested
421 // If there's a filespec, assume he's not
422 if ( watch.GetFilespec().empty() )
423 {
424 // The the only way to know the path for the first event,
425 // normally the IN_MOVED_FROM, is to retrieve the watch
426 // corresponding to oldinevt. This is needed for a move
427 // within a watch.
428 wxFSWatchEntry* oldwatch;
429 wxFSWatchEntryDescriptors::iterator oldwatch_it =
430 m_watchMap.find(oldinevt.wd);
431 if (oldwatch_it != m_watchMap.end())
432 {
433 oldwatch = oldwatch_it->second;
434 }
435 else
436 {
437 wxLogTrace(wxTRACE_FSWATCHER,
438 "oldinevt's watch descriptor not in the watch map");
439 // For want of a better alternative, use 'watch'. That
440 // will work fine for renames, though not for moves
441 oldwatch = &watch;
442 }
443
444 wxFileSystemWatcherEvent event(flags);
445 if ( inevt.mask & IN_MOVED_FROM )
446 {
447 event.SetPath(GetEventPath(watch, inevt));
448 event.SetNewPath(GetEventPath(*oldwatch, oldinevt));
449 }
450 else
451 {
452 event.SetPath(GetEventPath(*oldwatch, oldinevt));
453 event.SetNewPath(GetEventPath(watch, inevt));
454 }
455 SendEvent(event);
456 }
457
458 m_cookies.erase(it2);
459 delete &oldinevt;
460 }
461 }
462 // every other kind of event
463 else
464 {
465 wxFileName path = GetEventPath(watch, inevt);
466 // For files, check that it matches any filespec
467 if ( MatchesFilespec(path, watch.GetFilespec()) )
468 {
469 wxFileSystemWatcherEvent event(flags, path, path);
470 SendEvent(event);
471 }
472 }
473 }
474
475 void ProcessRenames()
476 {
477 // After all of a batch of events has been processed, this deals with
478 // any still-unpaired IN_MOVED_FROM or IN_MOVED_TO events.
479 wxInotifyCookies::iterator it = m_cookies.begin();
480 while ( it != m_cookies.end() )
481 {
482 inotify_event& inevt = *(it->second);
483
484 wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
485 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
486
487 // get watch entry for this event
488 wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd);
489 if (wit == m_watchMap.end())
490 {
491 wxLogTrace(wxTRACE_FSWATCHER,
492 "Watch descriptor not present in the watch map!");
493 }
494 else
495 {
496 // Tell the owner, in case it's interested
497 // If there's a filespec, assume he's not
498 wxFSWatchEntry& watch = *(wit->second);
499 if ( watch.GetFilespec().empty() )
500 {
501 int flags = Native2WatcherFlags(inevt.mask);
502 wxFileName path = GetEventPath(watch, inevt);
503 {
504 wxFileSystemWatcherEvent event(flags, path, path);
505 SendEvent(event);
506 }
507 }
508 }
509
510 m_cookies.erase(it);
511 delete &inevt;
512 it = m_cookies.begin();
513 }
514 }
515
516 void SendEvent(wxFileSystemWatcherEvent& evt)
517 {
518 wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
519 m_watcher->GetOwner()->ProcessEvent(evt);
520 }
521
522 int ReadEventsToBuf(char* buf, int size)
523 {
524 wxCHECK_MSG( IsOk(), false,
525 "Inotify not initialized or invalid inotify descriptor" );
526
527 memset(buf, 0, size);
528 ssize_t left = read(m_ifd, buf, size);
529 if (left == -1)
530 {
531 wxLogSysError(_("Unable to read from inotify descriptor"));
532 return -1;
533 }
534 else if (left == 0)
535 {
536 wxLogWarning(_("EOF while reading from inotify descriptor"));
537 return -1;
538 }
539
540 return left;
541 }
542
543 static wxString InotifyEventToString(const inotify_event& inevt)
544 {
545 wxString mask = (inevt.mask & IN_ISDIR) ?
546 wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
547 wxString::Format("%u", inevt.mask);
548 const char* name = "";
549 if (inevt.len)
550 name = inevt.name;
551 return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
552 "name=%s", inevt.wd, mask, inevt.cookie,
553 inevt.len, name);
554 }
555
556 static wxFileName GetEventPath(const wxFSWatchEntry& watch,
557 const inotify_event& inevt)
558 {
559 // only when dir is watched, we have non-empty e.name
560 wxFileName path = watch.GetPath();
561 if (path.IsDir() && inevt.len)
562 {
563 path = wxFileName(path.GetPath(), inevt.name);
564 }
565 return path;
566 }
567
568 static int Watcher2NativeFlags(int flags)
569 {
570 // Start with the standard case of wanting all events
571 if (flags == wxFSW_EVENT_ALL)
572 {
573 return IN_ALL_EVENTS;
574 }
575
576 static const int flag_mapping[][2] = {
577 { wxFSW_EVENT_ACCESS, IN_ACCESS },
578 { wxFSW_EVENT_MODIFY, IN_MODIFY },
579 { wxFSW_EVENT_ATTRIB, IN_ATTRIB },
580 { wxFSW_EVENT_RENAME, IN_MOVE },
581 { wxFSW_EVENT_CREATE, IN_CREATE },
582 { wxFSW_EVENT_DELETE, IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF },
583 { wxFSW_EVENT_UNMOUNT, IN_UNMOUNT }
584 // wxFSW_EVENT_ERROR/WARNING make no sense here
585 };
586
587 int native_flags = 0;
588 for ( unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i)
589 {
590 if (flags & flag_mapping[i][0])
591 native_flags |= flag_mapping[i][1];
592 }
593
594 return native_flags;
595 }
596
597 static int Native2WatcherFlags(int flags)
598 {
599 static const int flag_mapping[][2] = {
600 { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
601 { IN_MODIFY, wxFSW_EVENT_MODIFY },
602 { IN_ATTRIB, wxFSW_EVENT_ATTRIB },
603 { IN_CLOSE_WRITE, 0 },
604 { IN_CLOSE_NOWRITE, 0 },
605 { IN_OPEN, 0 },
606 { IN_MOVED_FROM, wxFSW_EVENT_RENAME },
607 { IN_MOVED_TO, wxFSW_EVENT_RENAME },
608 { IN_CREATE, wxFSW_EVENT_CREATE },
609 { IN_DELETE, wxFSW_EVENT_DELETE },
610 { IN_DELETE_SELF, wxFSW_EVENT_DELETE },
611 { IN_MOVE_SELF, wxFSW_EVENT_DELETE },
612
613 { IN_UNMOUNT, wxFSW_EVENT_UNMOUNT},
614 { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
615
616 // ignored, because this is generated mainly by watcher::Remove()
617 { IN_IGNORED, 0 }
618 };
619
620 unsigned int i=0;
621 for ( ; i < WXSIZEOF(flag_mapping); ++i) {
622 // in this mapping multiple flags at once don't happen
623 if (flags & flag_mapping[i][0])
624 return flag_mapping[i][1];
625 }
626
627 // never reached
628 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
629 return -1;
630 }
631
632 /**
633 * Returns error description for specified inotify mask
634 */
635 static const wxString GetErrorDescription(int flag)
636 {
637 switch ( flag )
638 {
639 case IN_Q_OVERFLOW:
640 return _("Event queue overflowed");
641 }
642
643 // never reached
644 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
645 return wxEmptyString;
646 }
647
648 wxFSWSourceHandler* m_handler; // handler for inotify event source
649 wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
650 wxArrayInt m_staleDescriptors; // stores recently-removed watches
651 wxInotifyCookies m_cookies; // map to track renames
652 wxEventLoopSource* m_source; // our event loop source
653
654 // file descriptor created by inotify_init()
655 int m_ifd;
656 };
657
658
659 // ============================================================================
660 // wxFSWSourceHandler implementation
661 // ============================================================================
662
663 // once we get signaled to read, actuall event reading occurs
664 void wxFSWSourceHandler::OnReadWaiting()
665 {
666 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
667 m_service->ReadEvents();
668 }
669
670 void wxFSWSourceHandler::OnWriteWaiting()
671 {
672 wxFAIL_MSG("We never write to inotify descriptor.");
673 }
674
675 void wxFSWSourceHandler::OnExceptionWaiting()
676 {
677 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
678 }
679
680
681 // ============================================================================
682 // wxInotifyFileSystemWatcher implementation
683 // ============================================================================
684
685 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
686 : wxFileSystemWatcherBase()
687 {
688 Init();
689 }
690
691 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
692 int events)
693 : wxFileSystemWatcherBase()
694 {
695 if (!Init())
696 {
697 if (m_service)
698 delete m_service;
699 return;
700 }
701
702 Add(path, events);
703 }
704
705 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
706 {
707 }
708
709 bool wxInotifyFileSystemWatcher::Init()
710 {
711 m_service = new wxFSWatcherImplUnix(this);
712 return m_service->Init();
713 }
714
715 void wxInotifyFileSystemWatcher::OnDirDeleted(const wxString& path)
716 {
717 if (!path.empty())
718 {
719 wxFSWatchInfoMap::iterator it = m_watches.find(path);
720 wxCHECK_RET(it != m_watches.end(),
721 wxString::Format("Path '%s' is not watched", path));
722
723 // path has been deleted, so we must forget it whatever its refcount
724 m_watches.erase(it);
725 }
726 }
727
728 #endif // wxHAS_INOTIFY
729
730 #endif // wxUSE_FSWATCHER