1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/fswatcher.cpp
3 // Purpose: wxMSWFileSystemWatcher
4 // Author: Bartosz Bekier
6 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
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"
26 // ============================================================================
27 // wxFSWatcherImplMSW implementation
28 // ============================================================================
30 class wxFSWatcherImplMSW
: public wxFSWatcherImpl
33 wxFSWatcherImplMSW(wxFileSystemWatcherBase
* watcher
);
35 virtual ~wxFSWatcherImplMSW();
37 bool SetUpWatch(wxFSWatchEntryMSW
& watch
);
39 void SendEvent(wxFileSystemWatcherEvent
& evt
);
44 // adds watch to be monitored for file system changes
45 virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryMSW
> watch
);
47 virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryMSW
> watch
);
50 bool DoSetUpWatch(wxFSWatchEntryMSW
& watch
);
52 static int Watcher2NativeFlags(int flags
);
55 wxIOCPThread m_workerThread
;
58 wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase
* watcher
) :
59 wxFSWatcherImpl(watcher
),
60 m_workerThread(this, &m_iocp
)
64 wxFSWatcherImplMSW::~wxFSWatcherImplMSW()
66 // order the worker thread to finish & wait
67 m_workerThread
.Finish();
68 if (m_workerThread
.Wait() != 0)
70 wxLogError(_("Ungraceful worker thread termination"));
77 bool wxFSWatcherImplMSW::Init()
79 wxCHECK_MSG( !m_workerThread
.IsAlive(), false,
80 "Watcher service is already initialized" );
82 if (m_workerThread
.Create() != wxTHREAD_NO_ERROR
)
84 wxLogError(_("Unable to create IOCP worker thread"));
88 // we have valid iocp service and thread
89 if (m_workerThread
.Run() != wxTHREAD_NO_ERROR
)
91 wxLogError(_("Unable to start IOCP worker thread"));
98 // adds watch to be monitored for file system changes
99 bool wxFSWatcherImplMSW::DoAdd(wxSharedPtr
<wxFSWatchEntryMSW
> watch
)
101 // setting up wait for directory changes
102 if (!DoSetUpWatch(*watch
))
105 // associating handle with completion port
106 return m_iocp
.Add(watch
);
110 wxFSWatcherImplMSW::DoRemove(wxSharedPtr
<wxFSWatchEntryMSW
> watch
)
112 return m_iocp
.ScheduleForRemoval(watch
);
115 // TODO ensuring that we have not already set watch for this handle/dir?
116 bool wxFSWatcherImplMSW::SetUpWatch(wxFSWatchEntryMSW
& watch
)
118 wxCHECK_MSG( watch
.IsOk(), false, "Invalid watch" );
119 if (m_watches
.find(watch
.GetPath()) == m_watches
.end())
121 wxLogTrace(wxTRACE_FSWATCHER
, "Path '%s' is not watched",
126 wxLogTrace(wxTRACE_FSWATCHER
, "Setting up watch for file system changes...");
127 return DoSetUpWatch(watch
);
130 void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent
& evt
)
132 // called from worker thread, so posting event in thread-safe way
133 wxQueueEvent(m_watcher
->GetOwner(), evt
.Clone());
136 bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW
& watch
)
138 BOOL bWatchSubtree
= FALSE
;
140 switch ( watch
.GetType() )
143 wxLogError(_("Monitoring individual files for changes is not "
144 "supported currently."));
148 bWatchSubtree
= FALSE
;
152 bWatchSubtree
= TRUE
;
156 wxFAIL_MSG( "Invalid watch type." );
160 int flags
= Watcher2NativeFlags(watch
.GetFlags());
161 int ret
= ReadDirectoryChangesW(watch
.GetHandle(), watch
.GetBuffer(),
162 wxFSWatchEntryMSW::BUFFER_SIZE
,
165 watch
.GetOverlapped(), NULL
);
168 wxLogSysError(_("Unable to set up watch for '%s'"),
175 // TODO we should only specify those flags, which interest us
176 // this needs a bit of thinking, quick impl for now
177 int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags
))
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
;
189 // ============================================================================
190 // wxFSWatcherImplMSW helper classes implementation
191 // ============================================================================
193 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW
* service
, wxIOCPService
* iocp
) :
194 wxThread(wxTHREAD_JOINABLE
),
195 m_service(service
), m_iocp(iocp
)
199 // finishes this thread
200 bool wxIOCPThread::Finish()
202 wxLogTrace(wxTRACE_FSWATCHER
, "Posting empty status!");
204 // send "empty" packet to i/o completion port, just to wake
205 return m_iocp
->PostEmptyStatus();
208 wxThread::ExitCode
wxIOCPThread::Entry()
210 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Started IOCP thread");
212 // read events in a loop until we get false, which means we should exit
213 while ( ReadEvents() );
215 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Ended IOCP thread");
219 // wait for events to occur, read them and send to interested parties
220 // returns false it empty status was read, which means we whould exit
222 bool wxIOCPThread::ReadEvents()
224 unsigned long count
= 0;
225 wxFSWatchEntryMSW
* watch
= NULL
;
226 OVERLAPPED
* overlapped
= NULL
;
227 if (!m_iocp
->GetStatus(&count
, &watch
, &overlapped
))
228 return true; // error was logged already, we don't want to exit
230 // this is our exit condition, so we return false
231 if (!count
&& !watch
&& !overlapped
)
234 // in case of spurious wakeup
235 if (!count
|| !watch
)
238 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] Read entry: path='%s'",
241 // First check if we're still interested in this watch, we could have
242 // removed it in the meanwhile.
243 if ( m_iocp
->CompleteRemoval(watch
) )
246 // extract events from buffer info our vector container
247 wxVector
<wxEventProcessingData
> events
;
248 const char* memory
= static_cast<const char*>(watch
->GetBuffer());
252 const FILE_NOTIFY_INFORMATION
* e
=
253 static_cast<const FILE_NOTIFY_INFORMATION
*>((const void*)memory
);
255 events
.push_back(wxEventProcessingData(e
, watch
));
257 offset
= e
->NextEntryOffset
;
263 ProcessNativeEvents(events
);
265 // reissue the watch. ignore possible errors, we will return true anyway
266 (void) m_service
->SetUpWatch(*watch
);
271 void wxIOCPThread::ProcessNativeEvents(wxVector
<wxEventProcessingData
>& events
)
273 wxVector
<wxEventProcessingData
>::iterator it
= events
.begin();
274 for ( ; it
!= events
.end(); ++it
)
276 const FILE_NOTIFY_INFORMATION
& e
= *(it
->nativeEvent
);
277 const wxFSWatchEntryMSW
* watch
= it
->watch
;
279 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] %s",
280 FileNotifyInformationToString(e
));
282 int nativeFlags
= e
.Action
;
283 int flags
= Native2WatcherFlags(nativeFlags
);
284 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
286 // TODO think about this...do we ever have any errors to report?
287 wxString errMsg
= "Error occurred";
288 wxFileSystemWatcherEvent
event(flags
, errMsg
);
291 // filter out ignored events and those not asked for.
292 // we never filter out warnings or exceptions
293 else if ((flags
== 0) || !(flags
& watch
->GetFlags()))
298 else if (nativeFlags
== FILE_ACTION_RENAMED_OLD_NAME
)
300 wxFileName oldpath
= GetEventPath(*watch
, e
);
303 // newpath should be in the next entry. what if there isn't?
305 if ( it
!= events
.end() )
307 newpath
= GetEventPath(*(it
->watch
), *(it
->nativeEvent
));
309 wxFileSystemWatcherEvent
event(flags
, oldpath
, newpath
);
315 // CHECK I heard that returned path can be either in short on long
316 // form...need to account for that!
317 wxFileName path
= GetEventPath(*watch
, e
);
318 // For files, check that it matches any filespec
319 if ( m_service
->MatchesFilespec(path
, watch
->GetFilespec()) )
321 wxFileSystemWatcherEvent
event(flags
, path
, path
);
328 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent
& evt
)
330 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] EVT: %s", evt
.ToString());
331 m_service
->SendEvent(evt
);
334 int wxIOCPThread::Native2WatcherFlags(int flags
)
336 static const int flag_mapping
[][2] = {
337 { FILE_ACTION_ADDED
, wxFSW_EVENT_CREATE
},
338 { FILE_ACTION_REMOVED
, wxFSW_EVENT_DELETE
},
340 // TODO take attributes into account to see what happened
341 { FILE_ACTION_MODIFIED
, wxFSW_EVENT_MODIFY
},
343 { FILE_ACTION_RENAMED_OLD_NAME
, wxFSW_EVENT_RENAME
},
345 // ignored as it should always be matched with ***_OLD_NAME
346 { FILE_ACTION_RENAMED_NEW_NAME
, 0 },
349 for (unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
) {
350 if (flags
== flag_mapping
[i
][0])
351 return flag_mapping
[i
][1];
355 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags
));
359 wxString
wxIOCPThread::FileNotifyInformationToString(
360 const FILE_NOTIFY_INFORMATION
& e
)
362 wxString
fname(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
363 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
364 "name(approx)='%s'", e
.NextEntryOffset
, e
.Action
,
365 e
.FileNameLength
, fname
);
368 wxFileName
wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW
& watch
,
369 const FILE_NOTIFY_INFORMATION
& e
)
371 wxFileName path
= watch
.GetPath();
374 wxString
rel(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
375 int pathFlags
= wxPATH_GET_VOLUME
| wxPATH_GET_SEPARATOR
;
376 path
= wxFileName(path
.GetPath(pathFlags
) + rel
);
382 // ============================================================================
383 // wxMSWFileSystemWatcher implementation
384 // ============================================================================
386 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
387 wxFileSystemWatcherBase()
392 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName
& path
,
394 wxFileSystemWatcherBase()
402 bool wxMSWFileSystemWatcher::Init()
404 m_service
= new wxFSWatcherImplMSW(this);
405 bool ret
= m_service
->Init();
415 wxMSWFileSystemWatcher::AddTree(const wxFileName
& path
,
417 const wxString
& filter
)
419 if ( !filter
.empty() )
421 // Use the inefficient generic version as we can only monitor
422 // everything under the given directory.
424 // Notice that it would probably be better to still monitor everything
425 // natively and filter out the changes we're not interested in.
426 return wxFileSystemWatcherBase::AddTree(path
, events
, filter
);
430 if ( !path
.DirExists() )
432 wxLogError(_("Can't monitor non-existent directory \"%s\" for changes."),
437 return AddAny(path
, events
, wxFSWPath_Tree
);
440 #endif // wxUSE_FSWATCHER