]> git.saurik.com Git - wxWidgets.git/blob - src/unix/fswatcher_inotify.cpp
No longer use semi-static IDs for wxPropertyGrid embedded controls and tool bar tools.
[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 watch->SetWatchDescriptor(-1);
143 return true;
144 }
145
146 virtual bool RemoveAll()
147 {
148 wxFSWatchEntries::iterator it = m_watches.begin();
149 for ( ; it != m_watches.end(); ++it )
150 {
151 (void) DoRemove(it->second);
152 }
153 m_watches.clear();
154 return true;
155 }
156
157 int ReadEvents()
158 {
159 wxCHECK_MSG( IsOk(), -1,
160 "Inotify not initialized or invalid inotify descriptor" );
161
162 // read events
163 // TODO differentiate depending on params
164 char buf[128 * sizeof(inotify_event)];
165 int left = ReadEventsToBuf(buf, sizeof(buf));
166 if (left == -1)
167 return -1;
168
169 // left > 0, we have events
170 char* memory = buf;
171 int event_count = 0;
172 while (left > 0) // OPT checking 'memory' would suffice
173 {
174 event_count++;
175 inotify_event* e = (inotify_event*)memory;
176
177 // process one inotify_event
178 ProcessNativeEvent(*e);
179
180 int offset = sizeof(inotify_event) + e->len;
181 left -= offset;
182 memory += offset;
183 }
184
185 // take care of unmatched renames
186 ProcessRenames();
187
188 wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
189 return event_count;
190 }
191
192 bool IsOk() const
193 {
194 return m_source != NULL;
195 }
196
197 protected:
198 int DoAddInotify(wxFSWatchEntry* watch)
199 {
200 int flags = Watcher2NativeFlags(watch->GetFlags());
201 int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags);
202 // finally we can set watch descriptor
203 watch->SetWatchDescriptor(wd);
204 return wd;
205 }
206
207 int DoRemoveInotify(wxFSWatchEntry* watch)
208 {
209 return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor());
210 }
211
212 void ProcessNativeEvent(const inotify_event& inevt)
213 {
214 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
215
216 // after removing inotify watch we get IN_IGNORED for it, but the watch
217 // will be already removed from our list at that time
218 if (inevt.mask & IN_IGNORED)
219 {
220 return;
221 }
222
223 // get watch entry for this event
224 wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd);
225 wxCHECK_RET(it != m_watchMap.end(),
226 "Watch descriptor not present in the watch map!");
227
228 wxFSWatchEntry& watch = *(it->second);
229 int nativeFlags = inevt.mask;
230 int flags = Native2WatcherFlags(nativeFlags);
231
232 // check out for error/warning condition
233 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
234 {
235 wxString errMsg = GetErrorDescription(Watcher2NativeFlags(flags));
236 wxFileSystemWatcherEvent event(flags, errMsg);
237 SendEvent(event);
238 }
239 // filter out ignored events and those not asked for.
240 // we never filter out warnings or exceptions
241 else if ((flags == 0) || !(flags & watch.GetFlags()))
242 {
243 return;
244 }
245 // renames
246 else if (nativeFlags & IN_MOVE)
247 {
248 wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie);
249 if ( it == m_cookies.end() )
250 {
251 int size = sizeof(inevt) + inevt.len;
252 inotify_event* e = (inotify_event*) operator new (size);
253 memcpy(e, &inevt, size);
254
255 wxInotifyCookies::value_type val(e->cookie, e);
256 m_cookies.insert(val);
257 }
258 else
259 {
260 inotify_event& oldinevt = *(it->second);
261
262 wxFileSystemWatcherEvent event(flags);
263 if ( inevt.mask & IN_MOVED_FROM )
264 {
265 event.SetPath(GetEventPath(watch, inevt));
266 event.SetNewPath(GetEventPath(watch, oldinevt));
267 }
268 else
269 {
270 event.SetPath(GetEventPath(watch, oldinevt));
271 event.SetNewPath(GetEventPath(watch, inevt));
272 }
273 SendEvent(event);
274
275 m_cookies.erase(it);
276 delete &oldinevt;
277 }
278 }
279 // every other kind of event
280 else
281 {
282 wxFileName path = GetEventPath(watch, inevt);
283 wxFileSystemWatcherEvent event(flags, path, path);
284 SendEvent(event);
285 }
286 }
287
288 void ProcessRenames()
289 {
290 wxInotifyCookies::iterator it = m_cookies.begin();
291 while ( it != m_cookies.end() )
292 {
293 inotify_event& inevt = *(it->second);
294
295 wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
296 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
297
298 // get watch entry for this event
299 wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd);
300 wxCHECK_RET(wit != m_watchMap.end(),
301 "Watch descriptor not present in the watch map!");
302
303 wxFSWatchEntry& watch = *(wit->second);
304 int flags = Native2WatcherFlags(inevt.mask);
305 wxFileName path = GetEventPath(watch, inevt);
306 wxFileSystemWatcherEvent event(flags, path, path);
307 SendEvent(event);
308
309 m_cookies.erase(it);
310 delete &inevt;
311 it = m_cookies.begin();
312 }
313 }
314
315 void SendEvent(wxFileSystemWatcherEvent& evt)
316 {
317 wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
318 m_watcher->GetOwner()->ProcessEvent(evt);
319 }
320
321 int ReadEventsToBuf(char* buf, int size)
322 {
323 wxCHECK_MSG( IsOk(), false,
324 "Inotify not initialized or invalid inotify descriptor" );
325
326 memset(buf, 0, size);
327 ssize_t left = read(m_ifd, buf, size);
328 if (left == -1)
329 {
330 wxLogSysError(_("Unable to read from inotify descriptor"));
331 return -1;
332 }
333 else if (left == 0)
334 {
335 wxLogWarning(_("EOF while reading from inotify descriptor"));
336 return -1;
337 }
338
339 return left;
340 }
341
342 static wxString InotifyEventToString(const inotify_event& inevt)
343 {
344 wxString mask = (inevt.mask & IN_ISDIR) ?
345 wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
346 wxString::Format("%u", inevt.mask);
347 return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
348 "name=%s", inevt.wd, mask, inevt.cookie,
349 inevt.len, inevt.name);
350 }
351
352 static wxFileName GetEventPath(const wxFSWatchEntry& watch,
353 const inotify_event& inevt)
354 {
355 // only when dir is watched, we have non-empty e.name
356 wxFileName path = watch.GetPath();
357 if (path.IsDir())
358 {
359 path = wxFileName(path.GetPath(), inevt.name);
360 }
361 return path;
362 }
363
364 static int Watcher2NativeFlags(int WXUNUSED(flags))
365 {
366 // TODO: it would be nice to subscribe only to the events we really need
367 return IN_ALL_EVENTS;
368 }
369
370 static int Native2WatcherFlags(int flags)
371 {
372 static const int flag_mapping[][2] = {
373 { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
374 { IN_MODIFY, wxFSW_EVENT_MODIFY },
375 { IN_ATTRIB, 0 },
376 { IN_CLOSE_WRITE, 0 },
377 { IN_CLOSE_NOWRITE, 0 },
378 { IN_OPEN, 0 },
379 { IN_MOVED_FROM, wxFSW_EVENT_RENAME },
380 { IN_MOVED_TO, wxFSW_EVENT_RENAME },
381 { IN_CREATE, wxFSW_EVENT_CREATE },
382 { IN_DELETE, wxFSW_EVENT_DELETE },
383 { IN_DELETE_SELF, wxFSW_EVENT_DELETE },
384 { IN_MOVE_SELF, wxFSW_EVENT_DELETE },
385
386 { IN_UNMOUNT, wxFSW_EVENT_ERROR },
387 { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
388
389 // ignored, because this is genereted mainly by watcher::Remove()
390 { IN_IGNORED, 0 }
391 };
392
393 unsigned int i=0;
394 for ( ; i < WXSIZEOF(flag_mapping); ++i) {
395 // in this mapping multiple flags at once don't happen
396 if (flags & flag_mapping[i][0])
397 return flag_mapping[i][1];
398 }
399
400 // never reached
401 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
402 return -1;
403 }
404
405 /**
406 * Returns error description for specified inotify mask
407 */
408 static const wxString GetErrorDescription(int flag)
409 {
410 switch ( flag )
411 {
412 case IN_UNMOUNT:
413 return _("File system containing watched object was unmounted");
414 case IN_Q_OVERFLOW:
415 return _("Event queue overflowed");
416 }
417
418 // never reached
419 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
420 return wxEmptyString;
421 }
422
423 wxFSWSourceHandler* m_handler; // handler for inotify event source
424 wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
425 wxInotifyCookies m_cookies; // map to track renames
426 wxEventLoopSource* m_source; // our event loop source
427
428 // file descriptor created by inotify_init()
429 int m_ifd;
430 };
431
432
433 // ============================================================================
434 // wxFSWSourceHandler implementation
435 // ============================================================================
436
437 // once we get signaled to read, actuall event reading occurs
438 void wxFSWSourceHandler::OnReadWaiting()
439 {
440 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
441 m_service->ReadEvents();
442 }
443
444 void wxFSWSourceHandler::OnWriteWaiting()
445 {
446 wxFAIL_MSG("We never write to inotify descriptor.");
447 }
448
449 void wxFSWSourceHandler::OnExceptionWaiting()
450 {
451 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
452 }
453
454
455 // ============================================================================
456 // wxInotifyFileSystemWatcher implementation
457 // ============================================================================
458
459 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
460 : wxFileSystemWatcherBase()
461 {
462 Init();
463 }
464
465 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
466 int events)
467 : wxFileSystemWatcherBase()
468 {
469 if (!Init())
470 {
471 if (m_service)
472 delete m_service;
473 return;
474 }
475
476 Add(path, events);
477 }
478
479 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
480 {
481 }
482
483 bool wxInotifyFileSystemWatcher::Init()
484 {
485 m_service = new wxFSWatcherImplUnix(this);
486 return m_service->Init();
487 }
488
489 #endif // wxHAS_INOTIFY
490
491 #endif // wxUSE_FSWATCHER