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