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
> WXUNUSED(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
, TRUE
,
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 // extract events from buffer info our vector container
220 wxVector
<wxEventProcessingData
> events
;
221 const char* memory
= static_cast<const char*>(watch
->GetBuffer());
225 const FILE_NOTIFY_INFORMATION
* e
=
226 static_cast<const FILE_NOTIFY_INFORMATION
*>((const void*)memory
);
228 events
.push_back(wxEventProcessingData(e
, watch
));
230 offset
= e
->NextEntryOffset
;
236 ProcessNativeEvents(events
);
238 // reissue the watch. ignore possible errors, we will return true anyway
239 (void) m_service
->SetUpWatch(*watch
);
244 void wxIOCPThread::ProcessNativeEvents(wxVector
<wxEventProcessingData
>& events
)
246 wxVector
<wxEventProcessingData
>::iterator it
= events
.begin();
247 for ( ; it
!= events
.end(); ++it
)
249 const FILE_NOTIFY_INFORMATION
& e
= *(it
->nativeEvent
);
250 const wxFSWatchEntryMSW
* watch
= it
->watch
;
252 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] %s",
253 FileNotifyInformationToString(e
));
255 int nativeFlags
= e
.Action
;
256 int flags
= Native2WatcherFlags(nativeFlags
);
257 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
259 // TODO think about this...do we ever have any errors to report?
260 wxString errMsg
= "Error occurred";
261 wxFileSystemWatcherEvent
event(flags
, errMsg
);
264 // filter out ignored events and those not asked for.
265 // we never filter out warnings or exceptions
266 else if ((flags
== 0) || !(flags
& watch
->GetFlags()))
271 else if (nativeFlags
== FILE_ACTION_RENAMED_OLD_NAME
)
273 wxFileName oldpath
= GetEventPath(*watch
, e
);
276 // newpath should be in the next entry. what if there isn't?
278 if ( it
!= events
.end() )
280 newpath
= GetEventPath(*(it
->watch
), *(it
->nativeEvent
));
282 wxFileSystemWatcherEvent
event(flags
, oldpath
, newpath
);
288 // CHECK I heard that returned path can be either in short on long
289 // form...need to account for that!
290 wxFileName path
= GetEventPath(*watch
, e
);
291 wxFileSystemWatcherEvent
event(flags
, path
, path
);
297 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent
& evt
)
299 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] EVT: %s", evt
.ToString());
300 m_service
->SendEvent(evt
);
303 int wxIOCPThread::Native2WatcherFlags(int flags
)
305 static const int flag_mapping
[][2] = {
306 { FILE_ACTION_ADDED
, wxFSW_EVENT_CREATE
},
307 { FILE_ACTION_REMOVED
, wxFSW_EVENT_DELETE
},
309 // TODO take attributes into account to see what happened
310 { FILE_ACTION_MODIFIED
, wxFSW_EVENT_MODIFY
},
312 { FILE_ACTION_RENAMED_OLD_NAME
, wxFSW_EVENT_RENAME
},
314 // ignored as it should always be matched with ***_OLD_NAME
315 { FILE_ACTION_RENAMED_NEW_NAME
, 0 },
318 for (unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
) {
319 if (flags
== flag_mapping
[i
][0])
320 return flag_mapping
[i
][1];
324 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags
));
328 wxString
wxIOCPThread::FileNotifyInformationToString(
329 const FILE_NOTIFY_INFORMATION
& e
)
331 wxString
fname(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
332 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
333 "name(approx)='%s'", e
.NextEntryOffset
, e
.Action
,
334 e
.FileNameLength
, fname
);
337 wxFileName
wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW
& watch
,
338 const FILE_NOTIFY_INFORMATION
& e
)
340 wxFileName path
= watch
.GetPath();
343 wxString
rel(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
344 int pathFlags
= wxPATH_GET_VOLUME
| wxPATH_GET_SEPARATOR
;
345 path
= wxFileName(path
.GetPath(pathFlags
) + rel
);
351 // ============================================================================
352 // wxMSWFileSystemWatcher implementation
353 // ============================================================================
355 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
356 wxFileSystemWatcherBase()
361 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName
& path
,
363 wxFileSystemWatcherBase()
371 bool wxMSWFileSystemWatcher::Init()
373 m_service
= new wxFSWatcherImplMSW(this);
374 bool ret
= m_service
->Init();
383 #endif // wxUSE_FSWATCHER