1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/fswatcher.cpp
3 // Purpose: wxMSWFileSystemWatcher
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"
21 #include "wx/thread.h"
22 #include "wx/sharedptr.h"
23 #include "wx/msw/fswatcher.h"
24 #include "wx/msw/private.h"
25 #include "wx/private/fswatcher.h"
27 // ============================================================================
28 // wxFSWatcherImplMSW implementation
29 // ============================================================================
31 class wxFSWatcherImplMSW
: public wxFSWatcherImpl
34 wxFSWatcherImplMSW(wxFileSystemWatcherBase
* watcher
);
36 virtual ~wxFSWatcherImplMSW();
38 bool SetUpWatch(wxFSWatchEntryMSW
& watch
);
40 void SendEvent(wxFileSystemWatcherEvent
& evt
);
45 // adds watch to be monitored for file system changes
46 virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryMSW
> watch
);
48 virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryMSW
> watch
);
51 bool DoSetUpWatch(wxFSWatchEntryMSW
& watch
);
53 static int Watcher2NativeFlags(int flags
);
56 wxIOCPThread m_workerThread
;
59 wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase
* watcher
) :
60 wxFSWatcherImpl(watcher
),
61 m_workerThread(this, &m_iocp
)
65 wxFSWatcherImplMSW::~wxFSWatcherImplMSW()
67 // order the worker thread to finish & wait
68 m_workerThread
.Finish();
69 if (m_workerThread
.Wait() != 0)
71 wxLogError(_("Ungraceful worker thread termination"));
78 bool wxFSWatcherImplMSW::Init()
80 wxCHECK_MSG( !m_workerThread
.IsAlive(), false,
81 "Watcher service is already initialized" );
83 if (m_workerThread
.Create() != wxTHREAD_NO_ERROR
)
85 wxLogError(_("Unable to create IOCP worker thread"));
89 // we have valid iocp service and thread
90 if (m_workerThread
.Run() != wxTHREAD_NO_ERROR
)
92 wxLogError(_("Unable to start IOCP worker thread"));
99 // adds watch to be monitored for file system changes
100 bool wxFSWatcherImplMSW::DoAdd(wxSharedPtr
<wxFSWatchEntryMSW
> watch
)
102 // setting up wait for directory changes
103 if (!DoSetUpWatch(*watch
))
106 // associating handle with completion port
107 return m_iocp
.Add(watch
);
111 wxFSWatcherImplMSW::DoRemove(wxSharedPtr
<wxFSWatchEntryMSW
> watch
)
113 return m_iocp
.ScheduleForRemoval(watch
);
116 // TODO ensuring that we have not already set watch for this handle/dir?
117 bool wxFSWatcherImplMSW::SetUpWatch(wxFSWatchEntryMSW
& watch
)
119 wxCHECK_MSG( watch
.IsOk(), false, "Invalid watch" );
120 if (m_watches
.find(watch
.GetPath()) == m_watches
.end())
122 wxLogTrace(wxTRACE_FSWATCHER
, "Path '%s' is not watched",
127 wxLogTrace(wxTRACE_FSWATCHER
, "Setting up watch for file system changes...");
128 return DoSetUpWatch(watch
);
131 void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent
& evt
)
133 // called from worker thread, so posting event in thread-safe way
134 wxQueueEvent(m_watcher
->GetOwner(), evt
.Clone());
137 bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW
& watch
)
139 BOOL bWatchSubtree
wxDUMMY_INITIALIZE(FALSE
);
141 switch ( watch
.GetType() )
144 wxLogError(_("Monitoring individual files for changes is not "
145 "supported currently."));
149 bWatchSubtree
= FALSE
;
153 bWatchSubtree
= TRUE
;
157 wxFAIL_MSG( "Invalid watch type." );
161 int flags
= Watcher2NativeFlags(watch
.GetFlags());
162 int ret
= ReadDirectoryChangesW(watch
.GetHandle(), watch
.GetBuffer(),
163 wxFSWatchEntryMSW::BUFFER_SIZE
,
166 watch
.GetOverlapped(), NULL
);
169 wxLogSysError(_("Unable to set up watch for '%s'"),
176 // TODO we should only specify those flags, which interest us
177 // this needs a bit of thinking, quick impl for now
178 int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags
))
180 static DWORD all_events
= FILE_NOTIFY_CHANGE_FILE_NAME
|
181 FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
|
182 FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
|
183 FILE_NOTIFY_CHANGE_LAST_ACCESS
| FILE_NOTIFY_CHANGE_CREATION
|
184 FILE_NOTIFY_CHANGE_SECURITY
;
190 // ============================================================================
191 // wxFSWatcherImplMSW helper classes implementation
192 // ============================================================================
194 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW
* service
, wxIOCPService
* iocp
) :
195 wxThread(wxTHREAD_JOINABLE
),
196 m_service(service
), m_iocp(iocp
)
200 // finishes this thread
201 bool wxIOCPThread::Finish()
203 wxLogTrace(wxTRACE_FSWATCHER
, "Posting empty status!");
205 // send "empty" packet to i/o completion port, just to wake
206 return m_iocp
->PostEmptyStatus();
209 wxThread::ExitCode
wxIOCPThread::Entry()
211 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Started IOCP thread");
213 // read events in a loop until we get false, which means we should exit
214 while ( ReadEvents() );
216 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Ended IOCP thread");
220 // wait for events to occur, read them and send to interested parties
221 // returns false it empty status was read, which means we whould exit
223 bool wxIOCPThread::ReadEvents()
225 unsigned long count
= 0;
226 wxFSWatchEntryMSW
* watch
= NULL
;
227 OVERLAPPED
* overlapped
= NULL
;
228 if (!m_iocp
->GetStatus(&count
, &watch
, &overlapped
))
229 return true; // error was logged already, we don't want to exit
231 // this is our exit condition, so we return false
232 if (!count
&& !watch
&& !overlapped
)
235 // in case of spurious wakeup
236 if (!count
|| !watch
)
239 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] Read entry: path='%s'",
242 // First check if we're still interested in this watch, we could have
243 // removed it in the meanwhile.
244 if ( m_iocp
->CompleteRemoval(watch
) )
247 // extract events from buffer info our vector container
248 wxVector
<wxEventProcessingData
> events
;
249 const char* memory
= static_cast<const char*>(watch
->GetBuffer());
253 const FILE_NOTIFY_INFORMATION
* e
=
254 static_cast<const FILE_NOTIFY_INFORMATION
*>((const void*)memory
);
256 events
.push_back(wxEventProcessingData(e
, watch
));
258 offset
= e
->NextEntryOffset
;
264 ProcessNativeEvents(events
);
266 // reissue the watch. ignore possible errors, we will return true anyway
267 (void) m_service
->SetUpWatch(*watch
);
272 void wxIOCPThread::ProcessNativeEvents(wxVector
<wxEventProcessingData
>& events
)
274 wxVector
<wxEventProcessingData
>::iterator it
= events
.begin();
275 for ( ; it
!= events
.end(); ++it
)
277 const FILE_NOTIFY_INFORMATION
& e
= *(it
->nativeEvent
);
278 const wxFSWatchEntryMSW
* watch
= it
->watch
;
280 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] %s",
281 FileNotifyInformationToString(e
));
283 int nativeFlags
= e
.Action
;
284 int flags
= Native2WatcherFlags(nativeFlags
);
285 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
287 // TODO think about this...do we ever have any errors to report?
288 wxString errMsg
= "Error occurred";
289 wxFileSystemWatcherEvent
event(flags
, errMsg
);
292 // filter out ignored events and those not asked for.
293 // we never filter out warnings or exceptions
294 else if ((flags
== 0) || !(flags
& watch
->GetFlags()))
299 else if (nativeFlags
== FILE_ACTION_RENAMED_OLD_NAME
)
301 wxFileName oldpath
= GetEventPath(*watch
, e
);
304 // newpath should be in the next entry. what if there isn't?
306 if ( it
!= events
.end() )
308 newpath
= GetEventPath(*(it
->watch
), *(it
->nativeEvent
));
310 wxFileSystemWatcherEvent
event(flags
, oldpath
, newpath
);
316 // CHECK I heard that returned path can be either in short on long
317 // form...need to account for that!
318 wxFileName path
= GetEventPath(*watch
, e
);
319 wxFileSystemWatcherEvent
event(flags
, path
, path
);
325 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent
& evt
)
327 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] EVT: %s", evt
.ToString());
328 m_service
->SendEvent(evt
);
331 int wxIOCPThread::Native2WatcherFlags(int flags
)
333 static const int flag_mapping
[][2] = {
334 { FILE_ACTION_ADDED
, wxFSW_EVENT_CREATE
},
335 { FILE_ACTION_REMOVED
, wxFSW_EVENT_DELETE
},
337 // TODO take attributes into account to see what happened
338 { FILE_ACTION_MODIFIED
, wxFSW_EVENT_MODIFY
},
340 { FILE_ACTION_RENAMED_OLD_NAME
, wxFSW_EVENT_RENAME
},
342 // ignored as it should always be matched with ***_OLD_NAME
343 { FILE_ACTION_RENAMED_NEW_NAME
, 0 },
346 for (unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
) {
347 if (flags
== flag_mapping
[i
][0])
348 return flag_mapping
[i
][1];
352 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags
));
356 wxString
wxIOCPThread::FileNotifyInformationToString(
357 const FILE_NOTIFY_INFORMATION
& e
)
359 wxString
fname(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
360 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
361 "name(approx)='%s'", e
.NextEntryOffset
, e
.Action
,
362 e
.FileNameLength
, fname
);
365 wxFileName
wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW
& watch
,
366 const FILE_NOTIFY_INFORMATION
& e
)
368 wxFileName path
= watch
.GetPath();
371 wxString
rel(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
372 int pathFlags
= wxPATH_GET_VOLUME
| wxPATH_GET_SEPARATOR
;
373 path
= wxFileName(path
.GetPath(pathFlags
) + rel
);
379 // ============================================================================
380 // wxMSWFileSystemWatcher implementation
381 // ============================================================================
383 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
384 wxFileSystemWatcherBase()
389 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName
& path
,
391 wxFileSystemWatcherBase()
399 bool wxMSWFileSystemWatcher::Init()
401 m_service
= new wxFSWatcherImplMSW(this);
402 bool ret
= m_service
->Init();
412 wxMSWFileSystemWatcher::AddTree(const wxFileName
& path
,
414 const wxString
& filter
)
416 if ( !filter
.empty() )
418 // Use the inefficient generic version as we can only monitor
419 // everything under the given directory.
421 // Notice that it would probably be better to still monitor everything
422 // natively and filter out the changes we're not interested in.
423 return wxFileSystemWatcherBase::AddTree(path
, events
, filter
);
427 if ( !path
.DirExists() )
429 wxLogError(_("Can't monitor non-existent directory \"%s\" for changes."),
434 return DoAdd(path
, events
, wxFSWPath_Tree
);
437 #endif // wxUSE_FSWATCHER