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