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