1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/fswatcher_kqueue.cpp
3 // Purpose: kqueue-based wxFileSystemWatcher implementation
4 // Author: Bartosz Bekier
7 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
20 #include "wx/fswatcher.h"
24 #include <sys/types.h>
25 #include <sys/event.h>
26 #include "wx/dynarray.h"
27 #include "wx/private/fswatcher.h"
29 // ============================================================================
30 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
31 // ============================================================================
34 * Helper class encapsulating inotify mechanism
36 class wxFSWatcherImplKqueue
: public wxFSWatcherImpl
39 wxFSWatcherImplKqueue(wxFileSystemWatcherBase
* watcher
) :
40 wxFSWatcherImpl(watcher
),
45 m_handler
= new wxFSWSourceHandler(this);
48 ~wxFSWatcherImplKqueue()
50 // we close kqueue only if initialized before
61 wxCHECK_MSG( !IsOk(), false,
62 "Kqueue appears to be already initialized" );
63 wxCHECK_MSG( m_loop
== NULL
, false, "Event loop != NULL");
65 m_loop
= (wxEventLoopBase::GetActive());
66 wxCHECK_MSG( m_loop
, false, "File system watcher needs an active loop" );
72 wxLogSysError(_("Unable to create kqueue instance"));
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" );
82 return RegisterSource();
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" );
93 (void) UnregisterSource();
95 int ret
= close(m_kfd
);
98 wxLogSysError(_("Unable to close kqueue instance"));
100 m_source
->Invalidate();
105 virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryKq
> watch
)
107 wxCHECK_MSG( IsOk(), false,
108 "Kqueue not initialized or invalid kqueue descriptor" );
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() );
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
);
121 wxLogSysError(_("Unable to add kqueue watch"));
128 virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryKq
> watch
)
130 wxCHECK_MSG( IsOk(), false,
131 "Kqueue not initialized or invalid kqueue descriptor" );
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();
139 wxLogSysError(_("Unable to remove kqueue watch"));
146 virtual bool RemoveAll()
148 wxFSWatchEntries::iterator it
= m_watches
.begin();
149 for ( ; it
!= m_watches
.end(); ++it
)
151 (void) DoRemove(it
->second
);
157 // return true if there was no error, false on error
160 wxCHECK_MSG( IsOk(), false,
161 "Kqueue not initialized or invalid kqueue descriptor" );
167 struct timespec timeout
= {0, 0};
168 int ret
= kevent(m_kfd
, NULL
, 0, &event
, 1, &timeout
);
171 wxLogSysError(_("Unable to get events from kqueue"));
179 // we have event, so process it
180 ProcessNativeEvent(event
);
184 // when ret>0 we still have events, when ret<=0 we return
185 wxFAIL_MSG( "Never reached" );
191 return m_source
&& m_source
->IsOk();
195 wxAbstractEventLoopSource* GetSource() const
201 bool RegisterSource()
203 wxCHECK_MSG( IsOk(), false,
204 "Kqueue not initialized or invalid kqueue descriptor" );
206 return m_loop
->AddSource(m_source
);
209 bool UnregisterSource()
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" );
216 bool ret
= m_loop
->RemoveSource(m_source
);
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
)
227 wxFSWatchEntryKq::wxDirState old
= watch
.GetLastState();
228 watch
.RefreshState();
229 wxFSWatchEntryKq::wxDirState curr
= watch
.GetLastState();
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(); )
241 else if ( *cit
<= *oit
)
243 changedFiles
.push_back(*cit
);
244 changedFlags
.push_back(wxFSW_EVENT_CREATE
);
247 else // ( *cit > *oit )
249 changedFiles
.push_back(*oit
);
250 changedFlags
.push_back(wxFSW_EVENT_DELETE
);
256 if ( oit
== old
.files
.end() )
258 for ( ; cit
!= curr
.files
.end(); ++cit
)
260 changedFiles
.push_back( *cit
);
261 changedFlags
.push_back(wxFSW_EVENT_CREATE
);
264 else if ( cit
== curr
.files
.end() )
266 for ( ; oit
!= old
.files
.end(); ++oit
)
268 changedFiles
.push_back( *oit
);
269 changedFlags
.push_back(wxFSW_EVENT_DELETE
);
273 wxASSERT( changedFiles
.size() == changedFlags
.size() );
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
)
281 wxString action
= (*it2
== wxFSW_EVENT_CREATE
) ?
282 "created" : "deleted";
283 wxLogTrace(wxTRACE_FSWATCHER
, wxString::Format("File: '%s' %s",
289 void ProcessNativeEvent(const struct kevent
& e
)
291 wxASSERT_MSG(e
.udata
, "Null user data associated with kevent!");
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
);
298 wxFSWatchEntryKq
& w
= *(static_cast<wxFSWatchEntry
*>(e
.udata
));
299 int nflags
= e
.fflags
;
301 // clear ignored flags
302 nflags
&= ~NOTE_REVOKE
;
304 // TODO ignore events we didn't ask for + refactor this cascade ifs
308 // when monitoring dir, this means create/delete
309 if ( nflags
& NOTE_WRITE
&& wxDirExists(w
.GetPath()) )
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
);
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
)
325 if ( wxDirExists(*it
) )
327 path
= wxFileName::DirName(w
.GetPath() + *it
);
331 path
= wxFileName::FileName(w
.GetPath() + *it
);
334 wxFileSystemWatcherEvent
event(*changeType
, path
, path
);
338 else if ( nflags
& NOTE_RENAME
)
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());
348 else if ( nflags
& NOTE_WRITE
|| nflags
& NOTE_EXTEND
)
350 nflags
&= ~(NOTE_WRITE
| NOTE_EXTEND
);
351 wxFileSystemWatcherEvent
event(wxFSW_EVENT_MODIFY
,
352 w
.GetPath(), w
.GetPath());
355 else if ( nflags
& NOTE_DELETE
)
357 nflags
&= ~(NOTE_DELETE
);
358 wxFileSystemWatcherEvent
event(wxFSW_EVENT_DELETE
,
359 w
.GetPath(), w
.GetPath());
362 else if ( nflags
& NOTE_ATTRIB
)
364 nflags
&= ~(NOTE_ATTRIB
);
365 wxFileSystemWatcherEvent
event(wxFSW_EVENT_ACCESS
,
366 w
.GetPath(), w
.GetPath());
370 // clear any flags that won't mean anything by themselves
371 nflags
&= ~(NOTE_LINK
);
375 void SendEvent(wxFileSystemWatcherEvent
& evt
)
377 m_watcher
->GetOwner()->ProcessEvent(evt
);
380 static int Watcher2NativeFlags(int WXUNUSED(flags
))
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
|
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
395 // once we get signaled to read, actuall event reading occurs
396 void wxFSWSourceHandler::OnReadWaiting()
398 wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---");
399 m_service
->ReadEvents();
402 void wxFSWSourceHandler::OnWriteWaiting()
404 wxFAIL_MSG("We never write to kqueue descriptor.");
407 void wxFSWSourceHandler::OnExceptionWaiting()
409 wxFAIL_MSG("We never receive exceptions on kqueue descriptor.");
413 // ============================================================================
414 // wxKqueueFileSystemWatcher implementation
415 // ============================================================================
417 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher()
418 : wxFileSystemWatcherBase()
423 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName
& path
,
425 : wxFileSystemWatcherBase()
440 wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher()
444 bool wxKqueueFileSystemWatcher::Init()
446 m_service
= new wxFSWatcherImplKqueue(this);
447 return m_service
->Init();
450 #endif // wxHAS_KQUEUE
452 #endif // wxUSE_FSWATCHER