The rounded corners look really dumb at this size.
[wxWidgets.git] / src / msw / fswatcher.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/fswatcher.cpp
3 // Purpose: wxMSWFileSystemWatcher
4 // Author: Bartosz Bekier
5 // Created: 2009-05-26
6 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #ifdef __BORLANDC__
14 #pragma hdrstop
15 #endif
16
17 #if wxUSE_FSWATCHER
18
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"
25
26 // ============================================================================
27 // wxFSWatcherImplMSW implementation
28 // ============================================================================
29
30 class wxFSWatcherImplMSW : public wxFSWatcherImpl
31 {
32 public:
33 wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher);
34
35 virtual ~wxFSWatcherImplMSW();
36
37 bool SetUpWatch(wxFSWatchEntryMSW& watch);
38
39 void SendEvent(wxFileSystemWatcherEvent& evt);
40
41 protected:
42 bool Init();
43
44 // adds watch to be monitored for file system changes
45 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch);
46
47 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch);
48
49 private:
50 bool DoSetUpWatch(wxFSWatchEntryMSW& watch);
51
52 static int Watcher2NativeFlags(int flags);
53
54 wxIOCPService m_iocp;
55 wxIOCPThread m_workerThread;
56 };
57
58 wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher) :
59 wxFSWatcherImpl(watcher),
60 m_workerThread(this, &m_iocp)
61 {
62 }
63
64 wxFSWatcherImplMSW::~wxFSWatcherImplMSW()
65 {
66 // order the worker thread to finish & wait
67 m_workerThread.Finish();
68 if (m_workerThread.Wait() != 0)
69 {
70 wxLogError(_("Ungraceful worker thread termination"));
71 }
72
73 // remove all watches
74 (void) RemoveAll();
75 }
76
77 bool wxFSWatcherImplMSW::Init()
78 {
79 wxCHECK_MSG( !m_workerThread.IsAlive(), false,
80 "Watcher service is already initialized" );
81
82 if (m_workerThread.Create() != wxTHREAD_NO_ERROR)
83 {
84 wxLogError(_("Unable to create IOCP worker thread"));
85 return false;
86 }
87
88 // we have valid iocp service and thread
89 if (m_workerThread.Run() != wxTHREAD_NO_ERROR)
90 {
91 wxLogError(_("Unable to start IOCP worker thread"));
92 return false;
93 }
94
95 return true;
96 }
97
98 // adds watch to be monitored for file system changes
99 bool wxFSWatcherImplMSW::DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch)
100 {
101 // setting up wait for directory changes
102 if (!DoSetUpWatch(*watch))
103 return false;
104
105 // associating handle with completion port
106 return m_iocp.Add(watch);
107 }
108
109 bool
110 wxFSWatcherImplMSW::DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)
111 {
112 return m_iocp.ScheduleForRemoval(watch);
113 }
114
115 // TODO ensuring that we have not already set watch for this handle/dir?
116 bool wxFSWatcherImplMSW::SetUpWatch(wxFSWatchEntryMSW& watch)
117 {
118 wxCHECK_MSG( watch.IsOk(), false, "Invalid watch" );
119 if (m_watches.find(watch.GetPath()) == m_watches.end())
120 {
121 wxLogTrace(wxTRACE_FSWATCHER, "Path '%s' is not watched",
122 watch.GetPath());
123 return false;
124 }
125
126 wxLogTrace(wxTRACE_FSWATCHER, "Setting up watch for file system changes...");
127 return DoSetUpWatch(watch);
128 }
129
130 void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent& evt)
131 {
132 // called from worker thread, so posting event in thread-safe way
133 wxQueueEvent(m_watcher->GetOwner(), evt.Clone());
134 }
135
136 bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch)
137 {
138 BOOL bWatchSubtree = FALSE;
139
140 switch ( watch.GetType() )
141 {
142 case wxFSWPath_File:
143 wxLogError(_("Monitoring individual files for changes is not "
144 "supported currently."));
145 return false;
146
147 case wxFSWPath_Dir:
148 bWatchSubtree = FALSE;
149 break;
150
151 case wxFSWPath_Tree:
152 bWatchSubtree = TRUE;
153 break;
154
155 case wxFSWPath_None:
156 wxFAIL_MSG( "Invalid watch type." );
157 return false;
158 }
159
160 int flags = Watcher2NativeFlags(watch.GetFlags());
161 int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
162 wxFSWatchEntryMSW::BUFFER_SIZE,
163 bWatchSubtree,
164 flags, NULL,
165 watch.GetOverlapped(), NULL);
166 if (!ret)
167 {
168 wxLogSysError(_("Unable to set up watch for '%s'"),
169 watch.GetPath());
170 }
171
172 return ret != 0;
173 }
174
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))
178 {
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;
184
185 return all_events;
186 }
187
188
189 // ============================================================================
190 // wxFSWatcherImplMSW helper classes implementation
191 // ============================================================================
192
193 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
194 wxThread(wxTHREAD_JOINABLE),
195 m_service(service), m_iocp(iocp)
196 {
197 }
198
199 // finishes this thread
200 bool wxIOCPThread::Finish()
201 {
202 wxLogTrace(wxTRACE_FSWATCHER, "Posting empty status!");
203
204 // send "empty" packet to i/o completion port, just to wake
205 return m_iocp->PostEmptyStatus();
206 }
207
208 wxThread::ExitCode wxIOCPThread::Entry()
209 {
210 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Started IOCP thread");
211
212 // read events in a loop until we get false, which means we should exit
213 while ( ReadEvents() );
214
215 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Ended IOCP thread");
216 return (ExitCode)0;
217 }
218
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
221 // true otherwise
222 bool wxIOCPThread::ReadEvents()
223 {
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
229
230 // this is our exit condition, so we return false
231 if (!count && !watch && !overlapped)
232 return false;
233
234 // in case of spurious wakeup
235 if (!count || !watch)
236 return true;
237
238 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] Read entry: path='%s'",
239 watch->GetPath());
240
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) )
244 return true;
245
246 // extract events from buffer info our vector container
247 wxVector<wxEventProcessingData> events;
248 const char* memory = static_cast<const char*>(watch->GetBuffer());
249 int offset = 0;
250 do
251 {
252 const FILE_NOTIFY_INFORMATION* e =
253 static_cast<const FILE_NOTIFY_INFORMATION*>((const void*)memory);
254
255 events.push_back(wxEventProcessingData(e, watch));
256
257 offset = e->NextEntryOffset;
258 memory += offset;
259 }
260 while (offset);
261
262 // process events
263 ProcessNativeEvents(events);
264
265 // reissue the watch. ignore possible errors, we will return true anyway
266 (void) m_service->SetUpWatch(*watch);
267
268 return true;
269 }
270
271 void wxIOCPThread::ProcessNativeEvents(wxVector<wxEventProcessingData>& events)
272 {
273 wxVector<wxEventProcessingData>::iterator it = events.begin();
274 for ( ; it != events.end(); ++it )
275 {
276 const FILE_NOTIFY_INFORMATION& e = *(it->nativeEvent);
277 const wxFSWatchEntryMSW* watch = it->watch;
278
279 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] %s",
280 FileNotifyInformationToString(e));
281
282 int nativeFlags = e.Action;
283 int flags = Native2WatcherFlags(nativeFlags);
284 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
285 {
286 // TODO think about this...do we ever have any errors to report?
287 wxString errMsg = "Error occurred";
288 wxFileSystemWatcherEvent event(flags, errMsg);
289 SendEvent(event);
290 }
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()))
294 {
295 return;
296 }
297 // rename case
298 else if (nativeFlags == FILE_ACTION_RENAMED_OLD_NAME)
299 {
300 wxFileName oldpath = GetEventPath(*watch, e);
301 wxFileName newpath;
302
303 // newpath should be in the next entry. what if there isn't?
304 ++it;
305 if ( it != events.end() )
306 {
307 newpath = GetEventPath(*(it->watch), *(it->nativeEvent));
308 }
309 wxFileSystemWatcherEvent event(flags, oldpath, newpath);
310 SendEvent(event);
311 }
312 // all other events
313 else
314 {
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()) )
320 {
321 wxFileSystemWatcherEvent event(flags, path, path);
322 SendEvent(event);
323 }
324 }
325 }
326 }
327
328 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
329 {
330 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
331 m_service->SendEvent(evt);
332 }
333
334 int wxIOCPThread::Native2WatcherFlags(int flags)
335 {
336 static const int flag_mapping[][2] = {
337 { FILE_ACTION_ADDED, wxFSW_EVENT_CREATE },
338 { FILE_ACTION_REMOVED, wxFSW_EVENT_DELETE },
339
340 // TODO take attributes into account to see what happened
341 { FILE_ACTION_MODIFIED, wxFSW_EVENT_MODIFY },
342
343 { FILE_ACTION_RENAMED_OLD_NAME, wxFSW_EVENT_RENAME },
344
345 // ignored as it should always be matched with ***_OLD_NAME
346 { FILE_ACTION_RENAMED_NEW_NAME, 0 },
347 };
348
349 for (unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) {
350 if (flags == flag_mapping[i][0])
351 return flag_mapping[i][1];
352 }
353
354 // never reached
355 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags));
356 return -1;
357 }
358
359 wxString wxIOCPThread::FileNotifyInformationToString(
360 const FILE_NOTIFY_INFORMATION& e)
361 {
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);
366 }
367
368 wxFileName wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW& watch,
369 const FILE_NOTIFY_INFORMATION& e)
370 {
371 wxFileName path = watch.GetPath();
372 if (path.IsDir())
373 {
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);
377 }
378 return path;
379 }
380
381
382 // ============================================================================
383 // wxMSWFileSystemWatcher implementation
384 // ============================================================================
385
386 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
387 wxFileSystemWatcherBase()
388 {
389 (void) Init();
390 }
391
392 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
393 int events) :
394 wxFileSystemWatcherBase()
395 {
396 if (!Init())
397 return;
398
399 Add(path, events);
400 }
401
402 bool wxMSWFileSystemWatcher::Init()
403 {
404 m_service = new wxFSWatcherImplMSW(this);
405 bool ret = m_service->Init();
406 if (!ret)
407 {
408 delete m_service;
409 }
410
411 return ret;
412 }
413
414 bool
415 wxMSWFileSystemWatcher::AddTree(const wxFileName& path,
416 int events,
417 const wxString& filter)
418 {
419 if ( !filter.empty() )
420 {
421 // Use the inefficient generic version as we can only monitor
422 // everything under the given directory.
423 //
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);
427 }
428
429
430 if ( !path.DirExists() )
431 {
432 wxLogError(_("Can't monitor non-existent directory \"%s\" for changes."),
433 path.GetFullPath());
434 return false;
435 }
436
437 return AddAny(path, events, wxFSWPath_Tree);
438 }
439
440 #endif // wxUSE_FSWATCHER