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 int flags
= Watcher2NativeFlags(watch
.GetFlags());
140 int ret
= ReadDirectoryChangesW(watch
.GetHandle(), watch
.GetBuffer(),
141 wxFSWatchEntryMSW::BUFFER_SIZE
, FALSE
,
143 watch
.GetOverlapped(), NULL
);
146 wxLogSysError(_("Unable to set up watch for '%s'"),
153 // TODO we should only specify those flags, which interest us
154 // this needs a bit of thinking, quick impl for now
155 int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags
))
157 static DWORD all_events
= FILE_NOTIFY_CHANGE_FILE_NAME
|
158 FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
|
159 FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
|
160 FILE_NOTIFY_CHANGE_LAST_ACCESS
| FILE_NOTIFY_CHANGE_CREATION
|
161 FILE_NOTIFY_CHANGE_SECURITY
;
167 // ============================================================================
168 // wxFSWatcherImplMSW helper classes implementation
169 // ============================================================================
171 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW
* service
, wxIOCPService
* iocp
) :
172 wxThread(wxTHREAD_JOINABLE
),
173 m_service(service
), m_iocp(iocp
)
177 // finishes this thread
178 bool wxIOCPThread::Finish()
180 wxLogTrace(wxTRACE_FSWATCHER
, "Posting empty status!");
182 // send "empty" packet to i/o completion port, just to wake
183 return m_iocp
->PostEmptyStatus();
186 wxThread::ExitCode
wxIOCPThread::Entry()
188 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Started IOCP thread");
190 // read events in a loop until we get false, which means we should exit
191 while ( ReadEvents() );
193 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Ended IOCP thread");
197 // wait for events to occur, read them and send to interested parties
198 // returns false it empty status was read, which means we whould exit
200 bool wxIOCPThread::ReadEvents()
202 unsigned long count
= 0;
203 wxFSWatchEntryMSW
* watch
= NULL
;
204 OVERLAPPED
* overlapped
= NULL
;
205 if (!m_iocp
->GetStatus(&count
, &watch
, &overlapped
))
206 return true; // error was logged already, we don't want to exit
208 // this is our exit condition, so we return false
209 if (!count
&& !watch
&& !overlapped
)
212 // in case of spurious wakeup
213 if (!count
|| !watch
)
216 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] Read entry: path='%s'",
219 // First check if we're still interested in this watch, we could have
220 // removed it in the meanwhile.
221 if ( m_iocp
->CompleteRemoval(watch
) )
224 // extract events from buffer info our vector container
225 wxVector
<wxEventProcessingData
> events
;
226 const char* memory
= static_cast<const char*>(watch
->GetBuffer());
230 const FILE_NOTIFY_INFORMATION
* e
=
231 static_cast<const FILE_NOTIFY_INFORMATION
*>((const void*)memory
);
233 events
.push_back(wxEventProcessingData(e
, watch
));
235 offset
= e
->NextEntryOffset
;
241 ProcessNativeEvents(events
);
243 // reissue the watch. ignore possible errors, we will return true anyway
244 (void) m_service
->SetUpWatch(*watch
);
249 void wxIOCPThread::ProcessNativeEvents(wxVector
<wxEventProcessingData
>& events
)
251 wxVector
<wxEventProcessingData
>::iterator it
= events
.begin();
252 for ( ; it
!= events
.end(); ++it
)
254 const FILE_NOTIFY_INFORMATION
& e
= *(it
->nativeEvent
);
255 const wxFSWatchEntryMSW
* watch
= it
->watch
;
257 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] %s",
258 FileNotifyInformationToString(e
));
260 int nativeFlags
= e
.Action
;
261 int flags
= Native2WatcherFlags(nativeFlags
);
262 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
264 // TODO think about this...do we ever have any errors to report?
265 wxString errMsg
= "Error occurred";
266 wxFileSystemWatcherEvent
event(flags
, errMsg
);
269 // filter out ignored events and those not asked for.
270 // we never filter out warnings or exceptions
271 else if ((flags
== 0) || !(flags
& watch
->GetFlags()))
276 else if (nativeFlags
== FILE_ACTION_RENAMED_OLD_NAME
)
278 wxFileName oldpath
= GetEventPath(*watch
, e
);
281 // newpath should be in the next entry. what if there isn't?
283 if ( it
!= events
.end() )
285 newpath
= GetEventPath(*(it
->watch
), *(it
->nativeEvent
));
287 wxFileSystemWatcherEvent
event(flags
, oldpath
, newpath
);
293 // CHECK I heard that returned path can be either in short on long
294 // form...need to account for that!
295 wxFileName path
= GetEventPath(*watch
, e
);
296 wxFileSystemWatcherEvent
event(flags
, path
, path
);
302 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent
& evt
)
304 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] EVT: %s", evt
.ToString());
305 m_service
->SendEvent(evt
);
308 int wxIOCPThread::Native2WatcherFlags(int flags
)
310 static const int flag_mapping
[][2] = {
311 { FILE_ACTION_ADDED
, wxFSW_EVENT_CREATE
},
312 { FILE_ACTION_REMOVED
, wxFSW_EVENT_DELETE
},
314 // TODO take attributes into account to see what happened
315 { FILE_ACTION_MODIFIED
, wxFSW_EVENT_MODIFY
},
317 { FILE_ACTION_RENAMED_OLD_NAME
, wxFSW_EVENT_RENAME
},
319 // ignored as it should always be matched with ***_OLD_NAME
320 { FILE_ACTION_RENAMED_NEW_NAME
, 0 },
323 for (unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
) {
324 if (flags
== flag_mapping
[i
][0])
325 return flag_mapping
[i
][1];
329 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags
));
333 wxString
wxIOCPThread::FileNotifyInformationToString(
334 const FILE_NOTIFY_INFORMATION
& e
)
336 wxString
fname(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
337 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
338 "name(approx)='%s'", e
.NextEntryOffset
, e
.Action
,
339 e
.FileNameLength
, fname
);
342 wxFileName
wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW
& watch
,
343 const FILE_NOTIFY_INFORMATION
& e
)
345 wxFileName path
= watch
.GetPath();
348 wxString
rel(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
349 int pathFlags
= wxPATH_GET_VOLUME
| wxPATH_GET_SEPARATOR
;
350 path
= wxFileName(path
.GetPath(pathFlags
) + rel
);
356 // ============================================================================
357 // wxMSWFileSystemWatcher implementation
358 // ============================================================================
360 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
361 wxFileSystemWatcherBase()
366 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName
& path
,
368 wxFileSystemWatcherBase()
376 bool wxMSWFileSystemWatcher::Init()
378 m_service
= new wxFSWatcherImplMSW(this);
379 bool ret
= m_service
->Init();
388 #endif // wxUSE_FSWATCHER