1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/fswatcher.cpp
3 // Purpose: wxMSWFileSystemWatcher
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 #include "wx/thread.h"
21 #include "wx/sharedptr.h"
22 #include "wx/msw/fswatcher.h"
23 #include "wx/msw/private.h"
24 #include "wx/private/fswatcher.h"
25
26 // ============================================================================
27 // wxFSWatcherImplMSW implementation
28 // ============================================================================
29
30 class wxFSWatcherImplMSW : public wxFSWatcherImpl
31 {
32 public:
33 wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher);
34
35 virtual ~wxFSWatcherImplMSW();
36
37 bool SetUpWatch(wxFSWatchEntryMSW& watch);
38
39 void SendEvent(wxFileSystemWatcherEvent& evt);
40
41 protected:
42 bool Init();
43
44 // adds watch to be monitored for file system changes
45 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch);
46
47 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch);
48
49 private:
50 bool DoSetUpWatch(wxFSWatchEntryMSW& watch);
51
52 static int Watcher2NativeFlags(int flags);
53
54 wxIOCPService m_iocp;
55 wxIOCPThread m_workerThread;
56 };
57
wxFSWatcherImplMSW(wxFileSystemWatcherBase * watcher)58 wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher) :
59 wxFSWatcherImpl(watcher),
60 m_workerThread(this, &m_iocp)
61 {
62 }
63
~wxFSWatcherImplMSW()64 wxFSWatcherImplMSW::~wxFSWatcherImplMSW()
65 {
66 // order the worker thread to finish & wait
67 m_workerThread.Finish();
68 if (m_workerThread.Wait() != 0)
69 {
70 wxLogError(_("Ungraceful worker thread termination"));
71 }
72
73 // remove all watches
74 (void) RemoveAll();
75 }
76
Init()77 bool wxFSWatcherImplMSW::Init()
78 {
79 wxCHECK_MSG( !m_workerThread.IsAlive(), false,
80 "Watcher service is already initialized" );
81
82 if (m_workerThread.Create() != wxTHREAD_NO_ERROR)
83 {
84 wxLogError(_("Unable to create IOCP worker thread"));
85 return false;
86 }
87
88 // we have valid iocp service and thread
89 if (m_workerThread.Run() != wxTHREAD_NO_ERROR)
90 {
91 wxLogError(_("Unable to start IOCP worker thread"));
92 return false;
93 }
94
95 return true;
96 }
97
98 // adds watch to be monitored for file system changes
DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch)99 bool wxFSWatcherImplMSW::DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch)
100 {
101 // setting up wait for directory changes
102 if (!DoSetUpWatch(*watch))
103 return false;
104
105 // associating handle with completion port
106 return m_iocp.Add(watch);
107 }
108
109 bool
DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)110 wxFSWatcherImplMSW::DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)
111 {
112 return m_iocp.ScheduleForRemoval(watch);
113 }
114
115 // TODO ensuring that we have not already set watch for this handle/dir?
SetUpWatch(wxFSWatchEntryMSW & watch)116 bool wxFSWatcherImplMSW::SetUpWatch(wxFSWatchEntryMSW& watch)
117 {
118 wxCHECK_MSG( watch.IsOk(), false, "Invalid watch" );
119 if (m_watches.find(watch.GetPath()) == m_watches.end())
120 {
121 wxLogTrace(wxTRACE_FSWATCHER, "Path '%s' is not watched",
122 watch.GetPath());
123 return false;
124 }
125
126 wxLogTrace(wxTRACE_FSWATCHER, "Setting up watch for file system changes...");
127 return DoSetUpWatch(watch);
128 }
129
SendEvent(wxFileSystemWatcherEvent & evt)130 void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent& evt)
131 {
132 // called from worker thread, so posting event in thread-safe way
133 wxQueueEvent(m_watcher->GetOwner(), evt.Clone());
134 }
135
DoSetUpWatch(wxFSWatchEntryMSW & watch)136 bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch)
137 {
138 BOOL bWatchSubtree = FALSE;
139
140 switch ( watch.GetType() )
141 {
142 case wxFSWPath_File:
143 wxLogError(_("Monitoring individual files for changes is not "
144 "supported currently."));
145 return false;
146
147 case wxFSWPath_Dir:
148 bWatchSubtree = FALSE;
149 break;
150
151 case wxFSWPath_Tree:
152 bWatchSubtree = TRUE;
153 break;
154
155 case wxFSWPath_None:
156 wxFAIL_MSG( "Invalid watch type." );
157 return false;
158 }
159
160 int flags = Watcher2NativeFlags(watch.GetFlags());
161 int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
162 wxFSWatchEntryMSW::BUFFER_SIZE,
163 bWatchSubtree,
164 flags, NULL,
165 watch.GetOverlapped(), NULL);
166 if (!ret)
167 {
168 wxLogSysError(_("Unable to set up watch for '%s'"),
169 watch.GetPath());
170 }
171
172 return ret != 0;
173 }
174
175 // TODO we should only specify those flags, which interest us
176 // this needs a bit of thinking, quick impl for now
Watcher2NativeFlags(int WXUNUSED (flags))177 int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags))
178 {
179 static DWORD all_events = FILE_NOTIFY_CHANGE_FILE_NAME |
180 FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES |
181 FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
182 FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION |
183 FILE_NOTIFY_CHANGE_SECURITY;
184
185 return all_events;
186 }
187
188
189 // ============================================================================
190 // wxFSWatcherImplMSW helper classes implementation
191 // ============================================================================
192
wxIOCPThread(wxFSWatcherImplMSW * service,wxIOCPService * iocp)193 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
194 wxThread(wxTHREAD_JOINABLE),
195 m_service(service), m_iocp(iocp)
196 {
197 }
198
199 // finishes this thread
Finish()200 bool wxIOCPThread::Finish()
201 {
202 wxLogTrace(wxTRACE_FSWATCHER, "Posting empty status!");
203
204 // send "empty" packet to i/o completion port, just to wake
205 return m_iocp->PostEmptyStatus();
206 }
207
Entry()208 wxThread::ExitCode wxIOCPThread::Entry()
209 {
210 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Started IOCP thread");
211
212 // read events in a loop until we get false, which means we should exit
213 while ( ReadEvents() );
214
215 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Ended IOCP thread");
216 return (ExitCode)0;
217 }
218
219 // wait for events to occur, read them and send to interested parties
220 // returns false it empty status was read, which means we would exit
221 // true otherwise
ReadEvents()222 bool wxIOCPThread::ReadEvents()
223 {
224 DWORD count = 0;
225 wxFSWatchEntryMSW* watch = NULL;
226 OVERLAPPED* overlapped = NULL;
227 switch ( m_iocp->GetStatus(&count, &watch, &overlapped) )
228 {
229 case wxIOCPService::Status_OK:
230 break; // nothing special to do, continue normally
231
232 case wxIOCPService::Status_Error:
233 return true; // error was logged already, we don't want to exit
234
235 case wxIOCPService::Status_Deleted:
236 {
237 wxFileSystemWatcherEvent
238 removeEvent(wxFSW_EVENT_DELETE,
239 watch->GetPath(),
240 wxFileName());
241 SendEvent(removeEvent);
242 }
243
244 // It isn't useful to continue watching this directory as it
245 // doesn't exist any more -- and even recreating a directory with
246 // the same name still wouldn't resume generating events for the
247 // existing wxIOCPService, so it's useless to continue.
248 return false;
249
250 case wxIOCPService::Status_Exit:
251 return false; // stop reading events
252 }
253
254 // if the thread got woken up but we got an empty packet it means that
255 // there was an overflow, too many events and not all could fit in
256 // the watch buffer. In this case, ReadDirectoryChangesW dumps the
257 // buffer.
258 if (!count && watch)
259 {
260 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Event queue overflowed: path=\"%s\"",
261 watch->GetPath());
262
263 if (watch->GetFlags() & wxFSW_EVENT_WARNING)
264 {
265 wxFileSystemWatcherEvent
266 overflowEvent(wxFSW_EVENT_WARNING, wxFSW_WARNING_OVERFLOW);
267 overflowEvent.SetPath(watch->GetPath());
268 SendEvent(overflowEvent);
269 }
270
271 // overflow is not a fatal error, we still want to get future events
272 // reissue the watch
273 (void) m_service->SetUpWatch(*watch);
274 return true;
275 }
276
277 // in case of spurious wakeup
278 if (!count || !watch)
279 return true;
280
281 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] Read entry: path='%s'",
282 watch->GetPath());
283
284 // First check if we're still interested in this watch, we could have
285 // removed it in the meanwhile.
286 if ( m_iocp->CompleteRemoval(watch) )
287 return true;
288
289 // extract events from buffer info our vector container
290 wxVector<wxEventProcessingData> events;
291 const char* memory = static_cast<const char*>(watch->GetBuffer());
292 int offset = 0;
293 do
294 {
295 const FILE_NOTIFY_INFORMATION* e =
296 static_cast<const FILE_NOTIFY_INFORMATION*>((const void*)memory);
297
298 events.push_back(wxEventProcessingData(e, watch));
299
300 offset = e->NextEntryOffset;
301 memory += offset;
302 }
303 while (offset);
304
305 // process events
306 ProcessNativeEvents(events);
307
308 // reissue the watch. ignore possible errors, we will return true anyway
309 (void) m_service->SetUpWatch(*watch);
310
311 return true;
312 }
313
ProcessNativeEvents(wxVector<wxEventProcessingData> & events)314 void wxIOCPThread::ProcessNativeEvents(wxVector<wxEventProcessingData>& events)
315 {
316 wxVector<wxEventProcessingData>::iterator it = events.begin();
317 for ( ; it != events.end(); ++it )
318 {
319 const FILE_NOTIFY_INFORMATION& e = *(it->nativeEvent);
320 const wxFSWatchEntryMSW* watch = it->watch;
321
322 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] %s",
323 FileNotifyInformationToString(e));
324
325 int nativeFlags = e.Action;
326 int flags = Native2WatcherFlags(nativeFlags);
327 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
328 {
329 wxFileSystemWatcherEvent
330 event(flags,
331 flags & wxFSW_EVENT_ERROR ? wxFSW_WARNING_NONE
332 : wxFSW_WARNING_GENERAL);
333 SendEvent(event);
334 }
335 // filter out ignored events and those not asked for.
336 // we never filter out warnings or exceptions
337 else if ((flags == 0) || !(flags & watch->GetFlags()))
338 {
339 return;
340 }
341 // rename case
342 else if (nativeFlags == FILE_ACTION_RENAMED_OLD_NAME)
343 {
344 wxFileName oldpath = GetEventPath(*watch, e);
345 wxFileName newpath;
346
347 // newpath should be in the next entry. what if there isn't?
348 ++it;
349 if ( it != events.end() )
350 {
351 newpath = GetEventPath(*(it->watch), *(it->nativeEvent));
352 }
353 wxFileSystemWatcherEvent event(flags, oldpath, newpath);
354 SendEvent(event);
355 }
356 // all other events
357 else
358 {
359 // CHECK I heard that returned path can be either in short on long
360 // form...need to account for that!
361 wxFileName path = GetEventPath(*watch, e);
362 // For files, check that it matches any filespec
363 if ( m_service->MatchesFilespec(path, watch->GetFilespec()) )
364 {
365 wxFileSystemWatcherEvent event(flags, path, path);
366 SendEvent(event);
367 }
368 }
369 }
370 }
371
SendEvent(wxFileSystemWatcherEvent & evt)372 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
373 {
374 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
375 m_service->SendEvent(evt);
376 }
377
Native2WatcherFlags(int flags)378 int wxIOCPThread::Native2WatcherFlags(int flags)
379 {
380 static const int flag_mapping[][2] = {
381 { FILE_ACTION_ADDED, wxFSW_EVENT_CREATE },
382 { FILE_ACTION_REMOVED, wxFSW_EVENT_DELETE },
383
384 // TODO take attributes into account to see what happened
385 { FILE_ACTION_MODIFIED, wxFSW_EVENT_MODIFY },
386
387 { FILE_ACTION_RENAMED_OLD_NAME, wxFSW_EVENT_RENAME },
388
389 // ignored as it should always be matched with ***_OLD_NAME
390 { FILE_ACTION_RENAMED_NEW_NAME, 0 },
391 };
392
393 for (unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) {
394 if (flags == flag_mapping[i][0])
395 return flag_mapping[i][1];
396 }
397
398 // never reached
399 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags));
400 return -1;
401 }
402
FileNotifyInformationToString(const FILE_NOTIFY_INFORMATION & e)403 wxString wxIOCPThread::FileNotifyInformationToString(
404 const FILE_NOTIFY_INFORMATION& e)
405 {
406 wxString fname(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
407 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
408 "name(approx)='%s'", e.NextEntryOffset, e.Action,
409 e.FileNameLength, fname);
410 }
411
GetEventPath(const wxFSWatchEntryMSW & watch,const FILE_NOTIFY_INFORMATION & e)412 wxFileName wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW& watch,
413 const FILE_NOTIFY_INFORMATION& e)
414 {
415 wxFileName path = watch.GetPath();
416 if (path.IsDir())
417 {
418 wxString rel(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
419 int pathFlags = wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR;
420 path = wxFileName(path.GetPath(pathFlags) + rel);
421 }
422 return path;
423 }
424
425
426 // ============================================================================
427 // wxMSWFileSystemWatcher implementation
428 // ============================================================================
429
wxMSWFileSystemWatcher()430 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
431 wxFileSystemWatcherBase()
432 {
433 (void) Init();
434 }
435
wxMSWFileSystemWatcher(const wxFileName & path,int events)436 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
437 int events) :
438 wxFileSystemWatcherBase()
439 {
440 if (!Init())
441 return;
442
443 Add(path, events);
444 }
445
Init()446 bool wxMSWFileSystemWatcher::Init()
447 {
448 m_service = new wxFSWatcherImplMSW(this);
449 bool ret = m_service->Init();
450 if (!ret)
451 {
452 delete m_service;
453 }
454
455 return ret;
456 }
457
458 bool
AddTree(const wxFileName & path,int events,const wxString & filter)459 wxMSWFileSystemWatcher::AddTree(const wxFileName& path,
460 int events,
461 const wxString& filter)
462 {
463 if ( !filter.empty() )
464 {
465 // Use the inefficient generic version as we can only monitor
466 // everything under the given directory.
467 //
468 // Notice that it would probably be better to still monitor everything
469 // natively and filter out the changes we're not interested in.
470 return wxFileSystemWatcherBase::AddTree(path, events, filter);
471 }
472
473
474 if ( !path.DirExists() )
475 {
476 wxLogError(_("Can't monitor non-existent directory \"%s\" for changes."),
477 path.GetFullPath());
478 return false;
479 }
480
481 return AddAny(path, events, wxFSWPath_Tree);
482 }
483
484 #endif // wxUSE_FSWATCHER
485