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