1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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
);
110 bool wxFSWatcherImplMSW::DoRemove(wxSharedPtr
<wxFSWatchEntryMSW
> 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 int flags
= Watcher2NativeFlags(watch
.GetFlags());
139 int ret
= ReadDirectoryChangesW(watch
.GetHandle(), watch
.GetBuffer(),
140 wxFSWatchEntryMSW::BUFFER_SIZE
, TRUE
,
142 watch
.GetOverlapped(), NULL
);
145 wxLogSysError(_("Unable to set up watch for '%s'"),
152 // TODO we should only specify those flags, which interest us
153 // this needs a bit of thinking, quick impl for now
154 int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags
))
156 static DWORD all_events
= FILE_NOTIFY_CHANGE_FILE_NAME
|
157 FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
|
158 FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
|
159 FILE_NOTIFY_CHANGE_LAST_ACCESS
| FILE_NOTIFY_CHANGE_CREATION
|
160 FILE_NOTIFY_CHANGE_SECURITY
;
166 // ============================================================================
167 // wxFSWatcherImplMSW helper classes implementation
168 // ============================================================================
170 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW
* service
, wxIOCPService
* iocp
) :
171 wxThread(wxTHREAD_JOINABLE
),
172 m_service(service
), m_iocp(iocp
)
176 // finishes this thread
177 bool wxIOCPThread::Finish()
179 wxLogTrace(wxTRACE_FSWATCHER
, "Posting empty status!");
181 // send "empty" packet to i/o completion port, just to wake
182 return m_iocp
->PostEmptyStatus();
185 wxThread::ExitCode
wxIOCPThread::Entry()
187 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Started IOCP thread");
189 // read events in a loop until we get false, which means we should exit
190 while ( ReadEvents() );
192 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] Ended IOCP thread");
196 // wait for events to occur, read them and send to interested parties
197 // returns false it empty status was read, which means we whould exit
199 bool wxIOCPThread::ReadEvents()
201 unsigned long count
= 0;
202 wxFSWatchEntryMSW
* watch
= NULL
;
203 OVERLAPPED
* overlapped
= NULL
;
204 if (!m_iocp
->GetStatus(&count
, &watch
, &overlapped
))
205 return true; // error was logged already, we don't want to exit
207 // this is our exit condition, so we return false
208 if (!count
&& !watch
&& !overlapped
)
211 // in case of spurious wakeup
212 if (!count
|| !watch
)
215 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] Read entry: path='%s'",
218 // extract events from buffer info our vector container
219 wxVector
<wxEventProcessingData
> events
;
220 const char* memory
= static_cast<const char*>(watch
->GetBuffer());
224 const FILE_NOTIFY_INFORMATION
* e
=
225 static_cast<const FILE_NOTIFY_INFORMATION
*>((const void*)memory
);
227 events
.push_back(wxEventProcessingData(e
, watch
));
229 offset
= e
->NextEntryOffset
;
235 ProcessNativeEvents(events
);
237 // reissue the watch. ignore possible errors, we will return true anyway
238 (void) m_service
->SetUpWatch(*watch
);
243 void wxIOCPThread::ProcessNativeEvents(wxVector
<wxEventProcessingData
>& events
)
245 wxVector
<wxEventProcessingData
>::iterator it
= events
.begin();
246 for ( ; it
!= events
.end(); ++it
)
248 const FILE_NOTIFY_INFORMATION
& e
= *(it
->nativeEvent
);
249 const wxFSWatchEntryMSW
* watch
= it
->watch
;
251 wxLogTrace( wxTRACE_FSWATCHER
, "[iocp] %s",
252 FileNotifyInformationToString(e
));
254 int nativeFlags
= e
.Action
;
255 int flags
= Native2WatcherFlags(nativeFlags
);
256 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
258 // TODO think about this...do we ever have any errors to report?
259 wxString errMsg
= "Error occurred";
260 wxFileSystemWatcherEvent
event(flags
, errMsg
);
263 // filter out ignored events and those not asked for.
264 // we never filter out warnings or exceptions
265 else if ((flags
== 0) || !(flags
& watch
->GetFlags()))
270 else if (nativeFlags
== FILE_ACTION_RENAMED_OLD_NAME
)
272 wxFileName oldpath
= GetEventPath(*watch
, e
);
275 // newpath should be in the next entry. what if there isn't?
277 if ( it
!= events
.end() )
279 newpath
= GetEventPath(*(it
->watch
), *(it
->nativeEvent
));
281 wxFileSystemWatcherEvent
event(flags
, oldpath
, newpath
);
287 // CHECK I heard that returned path can be either in short on long
288 // form...need to account for that!
289 wxFileName path
= GetEventPath(*watch
, e
);
290 wxFileSystemWatcherEvent
event(flags
, path
, path
);
296 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent
& evt
)
298 wxLogTrace(wxTRACE_FSWATCHER
, "[iocp] EVT: %s", evt
.ToString());
299 m_service
->SendEvent(evt
);
302 int wxIOCPThread::Native2WatcherFlags(int flags
)
304 static const int flag_mapping
[][2] = {
305 { FILE_ACTION_ADDED
, wxFSW_EVENT_CREATE
},
306 { FILE_ACTION_REMOVED
, wxFSW_EVENT_DELETE
},
308 // TODO take attributes into account to see what happened
309 { FILE_ACTION_MODIFIED
, wxFSW_EVENT_MODIFY
},
311 { FILE_ACTION_RENAMED_OLD_NAME
, wxFSW_EVENT_RENAME
},
313 // ignored as it should always be matched with ***_OLD_NAME
314 { FILE_ACTION_RENAMED_NEW_NAME
, 0 },
317 for (unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
) {
318 if (flags
== flag_mapping
[i
][0])
319 return flag_mapping
[i
][1];
323 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags
));
327 wxString
wxIOCPThread::FileNotifyInformationToString(
328 const FILE_NOTIFY_INFORMATION
& e
)
330 wxString
fname(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
331 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
332 "name(approx)='%s'", e
.NextEntryOffset
, e
.Action
,
333 e
.FileNameLength
, fname
);
336 wxFileName
wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW
& watch
,
337 const FILE_NOTIFY_INFORMATION
& e
)
339 wxFileName path
= watch
.GetPath();
342 wxString
rel(e
.FileName
, e
.FileNameLength
/ sizeof(e
.FileName
[0]));
343 int pathFlags
= wxPATH_GET_VOLUME
| wxPATH_GET_SEPARATOR
;
344 path
= wxFileName(path
.GetPath(pathFlags
) + rel
);
350 // ============================================================================
351 // wxMSWFileSystemWatcher implementation
352 // ============================================================================
354 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
355 wxFileSystemWatcherBase()
360 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName
& path
,
362 wxFileSystemWatcherBase()
370 bool wxMSWFileSystemWatcher::Init()
372 m_service
= new wxFSWatcherImplMSW(this);
373 bool ret
= m_service
->Init();
382 #endif // wxUSE_FSWATCHER