]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/msw/fswatcher.cpp
Make public headers compatible with Objective-C++ with ARC.
[wxWidgets.git] / src / msw / fswatcher.cpp
... / ...
CommitLineData
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
30class wxFSWatcherImplMSW : public wxFSWatcherImpl
31{
32public:
33 wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher);
34
35 virtual ~wxFSWatcherImplMSW();
36
37 bool SetUpWatch(wxFSWatchEntryMSW& watch);
38
39 void SendEvent(wxFileSystemWatcherEvent& evt);
40
41protected:
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
49private:
50 bool DoSetUpWatch(wxFSWatchEntryMSW& watch);
51
52 static int Watcher2NativeFlags(int flags);
53
54 wxIOCPService m_iocp;
55 wxIOCPThread m_workerThread;
56};
57
58wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher) :
59 wxFSWatcherImpl(watcher),
60 m_workerThread(this, &m_iocp)
61{
62}
63
64wxFSWatcherImplMSW::~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
77bool 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
99bool 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
109bool
110wxFSWatcherImplMSW::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?
116bool 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
130void 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
136bool 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
177int 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
193wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
194 wxThread(wxTHREAD_JOINABLE),
195 m_service(service), m_iocp(iocp)
196{
197}
198
199// finishes this thread
200bool 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
208wxThread::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
222bool 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
271void 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
328void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
329{
330 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
331 m_service->SendEvent(evt);
332}
333
334int 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
359wxString 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
368wxFileName 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
386wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
387 wxFileSystemWatcherBase()
388{
389 (void) Init();
390}
391
392wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
393 int events) :
394 wxFileSystemWatcherBase()
395{
396 if (!Init())
397 return;
398
399 Add(path, events);
400}
401
402bool 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
414bool
415wxMSWFileSystemWatcher::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