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