]> git.saurik.com Git - wxWidgets.git/blob - src/unix/fswatcher_kqueue.cpp
Fixed filename in wxOSX-Cocoa's file dialog using extension of wrong file type.
[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 wxDELETE(m_source);
119 }
120
121 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryKq> watch)
122 {
123 wxCHECK_MSG( IsOk(), false,
124 "Kqueue not initialized or invalid kqueue descriptor" );
125
126 struct kevent event;
127 int action = EV_ADD | EV_ENABLE | EV_CLEAR | EV_ERROR;
128 int flags = Watcher2NativeFlags(watch->GetFlags());
129 EV_SET( &event, watch->GetFileDescriptor(), EVFILT_VNODE, action,
130 flags, 0, watch.get() );
131
132 // TODO more error conditions according to man
133 // TODO best deal with the error here
134 int ret = kevent(m_kfd, &event, 1, NULL, 0, NULL);
135 if (ret == -1)
136 {
137 wxLogSysError(_("Unable to add kqueue watch"));
138 return false;
139 }
140
141 return true;
142 }
143
144 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryKq> watch)
145 {
146 wxCHECK_MSG( IsOk(), false,
147 "Kqueue not initialized or invalid kqueue descriptor" );
148
149 // TODO more error conditions according to man
150 // XXX closing file descriptor removes the watch. The logic resides in
151 // the watch which is not nice, but effective and simple
152 bool ret = watch->Close();
153 if (ret == -1)
154 {
155 wxLogSysError(_("Unable to remove kqueue watch"));
156 return false;
157 }
158
159 return true;
160 }
161
162 virtual bool RemoveAll()
163 {
164 wxFSWatchEntries::iterator it = m_watches.begin();
165 for ( ; it != m_watches.end(); ++it )
166 {
167 (void) DoRemove(it->second);
168 }
169 m_watches.clear();
170 return true;
171 }
172
173 // return true if there was no error, false on error
174 bool ReadEvents()
175 {
176 wxCHECK_MSG( IsOk(), false,
177 "Kqueue not initialized or invalid kqueue descriptor" );
178
179 // read events
180 do
181 {
182 struct kevent event;
183 struct timespec timeout = {0, 0};
184 int ret = kevent(m_kfd, NULL, 0, &event, 1, &timeout);
185 if (ret == -1)
186 {
187 wxLogSysError(_("Unable to get events from kqueue"));
188 return false;
189 }
190 else if (ret == 0)
191 {
192 return true;
193 }
194
195 // we have event, so process it
196 ProcessNativeEvent(event);
197 }
198 while (true);
199
200 // when ret>0 we still have events, when ret<=0 we return
201 wxFAIL_MSG( "Never reached" );
202 return true;
203 }
204
205 bool IsOk() const
206 {
207 return m_source != NULL;
208 }
209
210 protected:
211 // returns all new dirs/files present in the immediate level of the dir
212 // pointed by watch.GetPath(). "new" means created between the last time
213 // the state of watch was computed and now
214 void FindChanges(wxFSWatchEntryKq& watch,
215 wxArrayString& changedFiles,
216 wxArrayInt& changedFlags)
217 {
218 wxFSWatchEntryKq::wxDirState old = watch.GetLastState();
219 watch.RefreshState();
220 wxFSWatchEntryKq::wxDirState curr = watch.GetLastState();
221
222 // iterate over old/curr file lists and compute changes
223 wxArrayString::iterator oit = old.files.begin();
224 wxArrayString::iterator cit = curr.files.begin();
225 for ( ; oit != old.files.end() && cit != curr.files.end(); )
226 {
227 if ( *cit == *oit )
228 {
229 ++cit;
230 ++oit;
231 }
232 else if ( *cit <= *oit )
233 {
234 changedFiles.push_back(*cit);
235 changedFlags.push_back(wxFSW_EVENT_CREATE);
236 ++cit;
237 }
238 else // ( *cit > *oit )
239 {
240 changedFiles.push_back(*oit);
241 changedFlags.push_back(wxFSW_EVENT_DELETE);
242 ++oit;
243 }
244 }
245
246 // border conditions
247 if ( oit == old.files.end() )
248 {
249 for ( ; cit != curr.files.end(); ++cit )
250 {
251 changedFiles.push_back( *cit );
252 changedFlags.push_back(wxFSW_EVENT_CREATE);
253 }
254 }
255 else if ( cit == curr.files.end() )
256 {
257 for ( ; oit != old.files.end(); ++oit )
258 {
259 changedFiles.push_back( *oit );
260 changedFlags.push_back(wxFSW_EVENT_DELETE);
261 }
262 }
263
264 wxASSERT( changedFiles.size() == changedFlags.size() );
265
266 #if 0
267 wxLogTrace(wxTRACE_FSWATCHER, "Changed files:");
268 wxArrayString::iterator it = changedFiles.begin();
269 wxArrayInt::iterator it2 = changedFlags.begin();
270 for ( ; it != changedFiles.end(); ++it, ++it2)
271 {
272 wxString action = (*it2 == wxFSW_EVENT_CREATE) ?
273 "created" : "deleted";
274 wxLogTrace(wxTRACE_FSWATCHER, wxString::Format("File: '%s' %s",
275 *it, action));
276 }
277 #endif
278 }
279
280 void ProcessNativeEvent(const struct kevent& e)
281 {
282 wxASSERT_MSG(e.udata, "Null user data associated with kevent!");
283
284 wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%d, filter=%d, flags=%u, "
285 "fflags=%u, data=%d, user_data=%p",
286 e.ident, e.filter, e.flags, e.fflags, e.data, e.udata);
287
288 // for ease of use
289 wxFSWatchEntryKq& w = *(static_cast<wxFSWatchEntry*>(e.udata));
290 int nflags = e.fflags;
291
292 // clear ignored flags
293 nflags &= ~NOTE_REVOKE;
294
295 // TODO ignore events we didn't ask for + refactor this cascade ifs
296 // check for events
297 while ( nflags )
298 {
299 // when monitoring dir, this means create/delete
300 const wxString basepath = w.GetPath();
301 if ( nflags & NOTE_WRITE && wxDirExists(basepath) )
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
313 wxArrayString::iterator it = changedFiles.begin();
314 wxArrayInt::iterator changeType = changedFlags.begin();
315 for ( ; it != changedFiles.end(); ++it, ++changeType )
316 {
317 wxFileName path;
318 if ( wxDirExists(*it) )
319 {
320 path = wxFileName::DirName(
321 basepath + wxFileName::GetPathSeparator() + *it
322 );
323 }
324 else
325 {
326 path.Assign(basepath, *it);
327 }
328
329 wxFileSystemWatcherEvent event(*changeType, path, path);
330 SendEvent(event);
331 }
332 }
333 else if ( nflags & NOTE_RENAME )
334 {
335 // CHECK it'd be only possible to detect name if we had
336 // parent files listing which we could confront with now and
337 // still we couldn't be sure we have the right name...
338 nflags &= ~NOTE_RENAME;
339 wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME,
340 basepath, wxFileName());
341 SendEvent(event);
342 }
343 else if ( nflags & NOTE_WRITE || nflags & NOTE_EXTEND )
344 {
345 nflags &= ~(NOTE_WRITE | NOTE_EXTEND);
346 wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY,
347 basepath, basepath);
348 SendEvent(event);
349 }
350 else if ( nflags & NOTE_DELETE )
351 {
352 nflags &= ~(NOTE_DELETE);
353 wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE,
354 basepath, basepath);
355 SendEvent(event);
356 }
357 else if ( nflags & NOTE_ATTRIB )
358 {
359 nflags &= ~(NOTE_ATTRIB);
360 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS,
361 basepath, basepath);
362 SendEvent(event);
363 }
364
365 // clear any flags that won't mean anything by themselves
366 nflags &= ~(NOTE_LINK);
367 }
368 }
369
370 void SendEvent(wxFileSystemWatcherEvent& evt)
371 {
372 m_watcher->GetOwner()->ProcessEvent(evt);
373 }
374
375 static int Watcher2NativeFlags(int WXUNUSED(flags))
376 {
377 // TODO: it would be better to only subscribe to what we need
378 return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
379 NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
380 NOTE_REVOKE;
381 }
382
383 wxFSWSourceHandler* m_handler; // handler for kqueue event source
384 wxEventLoopSource* m_source; // our event loop source
385
386 // descriptor created by kqueue()
387 int m_kfd;
388 };
389
390
391 // once we get signaled to read, actuall event reading occurs
392 void wxFSWSourceHandler::OnReadWaiting()
393 {
394 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
395 m_service->ReadEvents();
396 }
397
398 void wxFSWSourceHandler::OnWriteWaiting()
399 {
400 wxFAIL_MSG("We never write to kqueue descriptor.");
401 }
402
403 void wxFSWSourceHandler::OnExceptionWaiting()
404 {
405 wxFAIL_MSG("We never receive exceptions on kqueue descriptor.");
406 }
407
408
409 // ============================================================================
410 // wxKqueueFileSystemWatcher implementation
411 // ============================================================================
412
413 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher()
414 : wxFileSystemWatcherBase()
415 {
416 Init();
417 }
418
419 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName& path,
420 int events)
421 : wxFileSystemWatcherBase()
422 {
423 if (!Init())
424 {
425 wxDELETE(m_service);
426 return;
427 }
428
429 Add(path, events);
430 }
431
432 wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher()
433 {
434 }
435
436 bool wxKqueueFileSystemWatcher::Init()
437 {
438 m_service = new wxFSWatcherImplKqueue(this);
439 return m_service->Init();
440 }
441
442 #endif // wxHAS_KQUEUE
443
444 #endif // wxUSE_FSWATCHER