Refactor wxEventLoopSource-related code.
[wxWidgets.git] / src / unix / fswatcher_kqueue.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/fswatcher_kqueue.cpp
3 // Purpose: kqueue-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_KQUEUE
23
24 #include <sys/types.h>
25 #include <sys/event.h>
26
27 #include "wx/dynarray.h"
28 #include "wx/evtloop.h"
29 #include "wx/evtloopsrc.h"
30
31 #include "wx/private/fswatcher.h"
32
33 // ============================================================================
34 // wxFSWSourceHandler helper class
35 // ============================================================================
36
37 class wxFSWatcherImplKqueue;
38
39 /**
40 * Handler for handling i/o from inotify descriptor
41 */
42 class wxFSWSourceHandler : public wxEventLoopSourceHandler
43 {
44 public:
45 wxFSWSourceHandler(wxFSWatcherImplKqueue* service) :
46 m_service(service)
47 { }
48
49 virtual void OnReadWaiting();
50 virtual void OnWriteWaiting();
51 virtual void OnExceptionWaiting();
52
53 protected:
54 wxFSWatcherImplKqueue* m_service;
55 };
56
57 // ============================================================================
58 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
59 // ============================================================================
60
61 /**
62 * Helper class encapsulating inotify mechanism
63 */
64 class wxFSWatcherImplKqueue : public wxFSWatcherImpl
65 {
66 public:
67 wxFSWatcherImplKqueue(wxFileSystemWatcherBase* watcher) :
68 wxFSWatcherImpl(watcher),
69 m_source(NULL),
70 m_kfd(-1)
71 {
72 m_handler = new wxFSWSourceHandler(this);
73 }
74
75 virtual ~wxFSWatcherImplKqueue()
76 {
77 // we close kqueue only if initialized before
78 if (IsOk())
79 {
80 Close();
81 }
82
83 delete m_handler;
84 }
85
86 bool Init()
87 {
88 wxCHECK_MSG( !IsOk(), false,
89 "Kqueue appears to be already initialized" );
90
91 wxEventLoopBase *loop = wxEventLoopBase::GetActive();
92 wxCHECK_MSG( loop, false, "File system watcher needs an active loop" );
93
94 // create kqueue
95 m_kfd = kqueue();
96 if (m_kfd == -1)
97 {
98 wxLogSysError(_("Unable to create kqueue instance"));
99 return false;
100 }
101
102 // create source
103 m_source = loop->AddSourceForFD(m_kfd, m_handler, wxEVENT_SOURCE_INPUT);
104
105 return m_source != NULL;
106 }
107
108 void Close()
109 {
110 wxCHECK_RET( IsOk(),
111 "Kqueue not initialized or invalid kqueue descriptor" );
112
113 if ( close(m_kfd) != 0 )
114 {
115 wxLogSysError(_("Error closing kqueue instance"));
116 }
117
118 delete m_source;
119 m_source = NULL;
120 }
121
122 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryKq> watch)
123 {
124 wxCHECK_MSG( IsOk(), false,
125 "Kqueue not initialized or invalid kqueue descriptor" );
126
127 struct kevent event;
128 int action = EV_ADD | EV_ENABLE | EV_CLEAR | EV_ERROR;
129 int flags = Watcher2NativeFlags(watch->GetFlags());
130 EV_SET( &event, watch->GetFileDescriptor(), EVFILT_VNODE, action,
131 flags, 0, watch.get() );
132
133 // TODO more error conditions according to man
134 // TODO best deal with the error here
135 int ret = kevent(m_kfd, &event, 1, NULL, 0, NULL);
136 if (ret == -1)
137 {
138 wxLogSysError(_("Unable to add kqueue watch"));
139 return false;
140 }
141
142 return true;
143 }
144
145 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryKq> watch)
146 {
147 wxCHECK_MSG( IsOk(), false,
148 "Kqueue not initialized or invalid kqueue descriptor" );
149
150 // TODO more error conditions according to man
151 // XXX closing file descriptor removes the watch. The logic resides in
152 // the watch which is not nice, but effective and simple
153 bool ret = watch->Close();
154 if (ret == -1)
155 {
156 wxLogSysError(_("Unable to remove kqueue watch"));
157 return false;
158 }
159
160 return true;
161 }
162
163 virtual bool RemoveAll()
164 {
165 wxFSWatchEntries::iterator it = m_watches.begin();
166 for ( ; it != m_watches.end(); ++it )
167 {
168 (void) DoRemove(it->second);
169 }
170 m_watches.clear();
171 return true;
172 }
173
174 // return true if there was no error, false on error
175 bool ReadEvents()
176 {
177 wxCHECK_MSG( IsOk(), false,
178 "Kqueue not initialized or invalid kqueue descriptor" );
179
180 // read events
181 do
182 {
183 struct kevent event;
184 struct timespec timeout = {0, 0};
185 int ret = kevent(m_kfd, NULL, 0, &event, 1, &timeout);
186 if (ret == -1)
187 {
188 wxLogSysError(_("Unable to get events from kqueue"));
189 return false;
190 }
191 else if (ret == 0)
192 {
193 return true;
194 }
195
196 // we have event, so process it
197 ProcessNativeEvent(event);
198 }
199 while (true);
200
201 // when ret>0 we still have events, when ret<=0 we return
202 wxFAIL_MSG( "Never reached" );
203 return true;
204 }
205
206 bool IsOk() const
207 {
208 return m_source != NULL;
209 }
210
211 protected:
212 // returns all new dirs/files present in the immediate level of the dir
213 // pointed by watch.GetPath(). "new" means created between the last time
214 // the state of watch was computed and now
215 void FindChanges(wxFSWatchEntryKq& watch,
216 wxArrayString& changedFiles,
217 wxArrayInt& changedFlags)
218 {
219 wxFSWatchEntryKq::wxDirState old = watch.GetLastState();
220 watch.RefreshState();
221 wxFSWatchEntryKq::wxDirState curr = watch.GetLastState();
222
223 // iterate over old/curr file lists and compute changes
224 wxArrayString::iterator oit = old.files.begin();
225 wxArrayString::iterator cit = curr.files.begin();
226 for ( ; oit != old.files.end() && cit != curr.files.end(); )
227 {
228 if ( *cit == *oit )
229 {
230 ++cit;
231 ++oit;
232 }
233 else if ( *cit <= *oit )
234 {
235 changedFiles.push_back(*cit);
236 changedFlags.push_back(wxFSW_EVENT_CREATE);
237 ++cit;
238 }
239 else // ( *cit > *oit )
240 {
241 changedFiles.push_back(*oit);
242 changedFlags.push_back(wxFSW_EVENT_DELETE);
243 ++oit;
244 }
245 }
246
247 // border conditions
248 if ( oit == old.files.end() )
249 {
250 for ( ; cit != curr.files.end(); ++cit )
251 {
252 changedFiles.push_back( *cit );
253 changedFlags.push_back(wxFSW_EVENT_CREATE);
254 }
255 }
256 else if ( cit == curr.files.end() )
257 {
258 for ( ; oit != old.files.end(); ++oit )
259 {
260 changedFiles.push_back( *oit );
261 changedFlags.push_back(wxFSW_EVENT_DELETE);
262 }
263 }
264
265 wxASSERT( changedFiles.size() == changedFlags.size() );
266
267 #if 0
268 wxLogTrace(wxTRACE_FSWATCHER, "Changed files:");
269 wxArrayString::iterator it = changedFiles.begin();
270 wxArrayInt::iterator it2 = changedFlags.begin();
271 for ( ; it != changedFiles.end(); ++it, ++it2)
272 {
273 wxString action = (*it2 == wxFSW_EVENT_CREATE) ?
274 "created" : "deleted";
275 wxLogTrace(wxTRACE_FSWATCHER, wxString::Format("File: '%s' %s",
276 *it, action));
277 }
278 #endif
279 }
280
281 void ProcessNativeEvent(const struct kevent& e)
282 {
283 wxASSERT_MSG(e.udata, "Null user data associated with kevent!");
284
285 wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%d, filter=%d, flags=%u, "
286 "fflags=%u, data=%d, user_data=%p",
287 e.ident, e.filter, e.flags, e.fflags, e.data, e.udata);
288
289 // for ease of use
290 wxFSWatchEntryKq& w = *(static_cast<wxFSWatchEntry*>(e.udata));
291 int nflags = e.fflags;
292
293 // clear ignored flags
294 nflags &= ~NOTE_REVOKE;
295
296 // TODO ignore events we didn't ask for + refactor this cascade ifs
297 // check for events
298 while ( nflags )
299 {
300 // when monitoring dir, this means create/delete
301 if ( nflags & NOTE_WRITE && wxDirExists(w.GetPath()) )
302 {
303 // NOTE_LINK is set when the dir was created, but we
304 // don't care - we look for new names in directory
305 // regardless of type. Also, clear all this, because
306 // it cannot mean more by itself
307 nflags &= ~(NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK);
308
309 wxArrayString changedFiles;
310 wxArrayInt changedFlags;
311 FindChanges(w, changedFiles, changedFlags);
312 wxArrayString::iterator it = changedFiles.begin();
313 wxArrayInt::iterator changeType = changedFlags.begin();
314 for ( ; it != changedFiles.end(); ++it, ++changeType )
315 {
316 wxFileName path;
317 if ( wxDirExists(*it) )
318 {
319 path = wxFileName::DirName(w.GetPath() + *it);
320 }
321 else
322 {
323 path = wxFileName::FileName(w.GetPath() + *it);
324 }
325
326 wxFileSystemWatcherEvent event(*changeType, path, path);
327 SendEvent(event);
328 }
329 }
330 else if ( nflags & NOTE_RENAME )
331 {
332 // CHECK it'd be only possible to detect name if we had
333 // parent files listing which we could confront with now and
334 // still we couldn't be sure we have the right name...
335 nflags &= ~NOTE_RENAME;
336 wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME,
337 w.GetPath(), wxFileName());
338 SendEvent(event);
339 }
340 else if ( nflags & NOTE_WRITE || nflags & NOTE_EXTEND )
341 {
342 nflags &= ~(NOTE_WRITE | NOTE_EXTEND);
343 wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY,
344 w.GetPath(), w.GetPath());
345 SendEvent(event);
346 }
347 else if ( nflags & NOTE_DELETE )
348 {
349 nflags &= ~(NOTE_DELETE);
350 wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE,
351 w.GetPath(), w.GetPath());
352 SendEvent(event);
353 }
354 else if ( nflags & NOTE_ATTRIB )
355 {
356 nflags &= ~(NOTE_ATTRIB);
357 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS,
358 w.GetPath(), w.GetPath());
359 SendEvent(event);
360 }
361
362 // clear any flags that won't mean anything by themselves
363 nflags &= ~(NOTE_LINK);
364 }
365 }
366
367 void SendEvent(wxFileSystemWatcherEvent& evt)
368 {
369 m_watcher->GetOwner()->ProcessEvent(evt);
370 }
371
372 static int Watcher2NativeFlags(int WXUNUSED(flags))
373 {
374 // TODO: it would be better to only subscribe to what we need
375 return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
376 NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
377 NOTE_REVOKE;
378 }
379
380 wxFSWSourceHandler* m_handler; // handler for kqueue event source
381 wxEventLoopSource* m_source; // our event loop source
382
383 // descriptor created by kqueue()
384 int m_kfd;
385 };
386
387
388 // once we get signaled to read, actuall event reading occurs
389 void wxFSWSourceHandler::OnReadWaiting()
390 {
391 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
392 m_service->ReadEvents();
393 }
394
395 void wxFSWSourceHandler::OnWriteWaiting()
396 {
397 wxFAIL_MSG("We never write to kqueue descriptor.");
398 }
399
400 void wxFSWSourceHandler::OnExceptionWaiting()
401 {
402 wxFAIL_MSG("We never receive exceptions on kqueue descriptor.");
403 }
404
405
406 // ============================================================================
407 // wxKqueueFileSystemWatcher implementation
408 // ============================================================================
409
410 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher()
411 : wxFileSystemWatcherBase()
412 {
413 Init();
414 }
415
416 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName& path,
417 int events)
418 : wxFileSystemWatcherBase()
419 {
420 if (!Init())
421 {
422 if (m_service)
423 {
424 delete m_service;
425 m_service = NULL;
426 }
427 return;
428 }
429
430 Add(path, events);
431 }
432
433 wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher()
434 {
435 }
436
437 bool wxKqueueFileSystemWatcher::Init()
438 {
439 m_service = new wxFSWatcherImplKqueue(this);
440 return m_service->Init();
441 }
442
443 #endif // wxHAS_KQUEUE
444
445 #endif // wxUSE_FSWATCHER