]> git.saurik.com Git - wxWidgets.git/blob - src/msw/fswatcher.cpp
Fix wxFileSystemWatcher::Remove() in wxMSW.
[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 // 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
31 class wxFSWatcherImplMSW : public wxFSWatcherImpl
32 {
33 public:
34 wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher);
35
36 virtual ~wxFSWatcherImplMSW();
37
38 bool SetUpWatch(wxFSWatchEntryMSW& watch);
39
40 void SendEvent(wxFileSystemWatcherEvent& evt);
41
42 protected:
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
50 private:
51 bool DoSetUpWatch(wxFSWatchEntryMSW& watch);
52
53 static int Watcher2NativeFlags(int flags);
54
55 wxIOCPService m_iocp;
56 wxIOCPThread m_workerThread;
57 };
58
59 wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher) :
60 wxFSWatcherImpl(watcher),
61 m_workerThread(this, &m_iocp)
62 {
63 }
64
65 wxFSWatcherImplMSW::~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
78 bool 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
100 bool 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
110 bool
111 wxFSWatcherImplMSW::DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)
112 {
113 return m_iocp.ScheduleForRemoval(watch);
114 }
115
116 // TODO ensuring that we have not already set watch for this handle/dir?
117 bool 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
131 void 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
137 bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch)
138 {
139 int flags = Watcher2NativeFlags(watch.GetFlags());
140 int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
141 wxFSWatchEntryMSW::BUFFER_SIZE, TRUE,
142 flags, NULL,
143 watch.GetOverlapped(), NULL);
144 if (!ret)
145 {
146 wxLogSysError(_("Unable to set up watch for '%s'"),
147 watch.GetPath());
148 }
149
150 return ret != 0;
151 }
152
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))
156 {
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;
162
163 return all_events;
164 }
165
166
167 // ============================================================================
168 // wxFSWatcherImplMSW helper classes implementation
169 // ============================================================================
170
171 wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
172 wxThread(wxTHREAD_JOINABLE),
173 m_service(service), m_iocp(iocp)
174 {
175 }
176
177 // finishes this thread
178 bool wxIOCPThread::Finish()
179 {
180 wxLogTrace(wxTRACE_FSWATCHER, "Posting empty status!");
181
182 // send "empty" packet to i/o completion port, just to wake
183 return m_iocp->PostEmptyStatus();
184 }
185
186 wxThread::ExitCode wxIOCPThread::Entry()
187 {
188 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Started IOCP thread");
189
190 // read events in a loop until we get false, which means we should exit
191 while ( ReadEvents() );
192
193 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Ended IOCP thread");
194 return (ExitCode)0;
195 }
196
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
199 // true otherwise
200 bool wxIOCPThread::ReadEvents()
201 {
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
207
208 // this is our exit condition, so we return false
209 if (!count && !watch && !overlapped)
210 return false;
211
212 // in case of spurious wakeup
213 if (!count || !watch)
214 return true;
215
216 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] Read entry: path='%s'",
217 watch->GetPath());
218
219 // First check if we're still interested in this watch, we could have
220 // removed it in the meanwhile.
221 if ( m_iocp->CompleteRemoval(watch) )
222 return true;
223
224 // extract events from buffer info our vector container
225 wxVector<wxEventProcessingData> events;
226 const char* memory = static_cast<const char*>(watch->GetBuffer());
227 int offset = 0;
228 do
229 {
230 const FILE_NOTIFY_INFORMATION* e =
231 static_cast<const FILE_NOTIFY_INFORMATION*>((const void*)memory);
232
233 events.push_back(wxEventProcessingData(e, watch));
234
235 offset = e->NextEntryOffset;
236 memory += offset;
237 }
238 while (offset);
239
240 // process events
241 ProcessNativeEvents(events);
242
243 // reissue the watch. ignore possible errors, we will return true anyway
244 (void) m_service->SetUpWatch(*watch);
245
246 return true;
247 }
248
249 void wxIOCPThread::ProcessNativeEvents(wxVector<wxEventProcessingData>& events)
250 {
251 wxVector<wxEventProcessingData>::iterator it = events.begin();
252 for ( ; it != events.end(); ++it )
253 {
254 const FILE_NOTIFY_INFORMATION& e = *(it->nativeEvent);
255 const wxFSWatchEntryMSW* watch = it->watch;
256
257 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] %s",
258 FileNotifyInformationToString(e));
259
260 int nativeFlags = e.Action;
261 int flags = Native2WatcherFlags(nativeFlags);
262 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
263 {
264 // TODO think about this...do we ever have any errors to report?
265 wxString errMsg = "Error occurred";
266 wxFileSystemWatcherEvent event(flags, errMsg);
267 SendEvent(event);
268 }
269 // filter out ignored events and those not asked for.
270 // we never filter out warnings or exceptions
271 else if ((flags == 0) || !(flags & watch->GetFlags()))
272 {
273 return;
274 }
275 // rename case
276 else if (nativeFlags == FILE_ACTION_RENAMED_OLD_NAME)
277 {
278 wxFileName oldpath = GetEventPath(*watch, e);
279 wxFileName newpath;
280
281 // newpath should be in the next entry. what if there isn't?
282 ++it;
283 if ( it != events.end() )
284 {
285 newpath = GetEventPath(*(it->watch), *(it->nativeEvent));
286 }
287 wxFileSystemWatcherEvent event(flags, oldpath, newpath);
288 SendEvent(event);
289 }
290 // all other events
291 else
292 {
293 // CHECK I heard that returned path can be either in short on long
294 // form...need to account for that!
295 wxFileName path = GetEventPath(*watch, e);
296 wxFileSystemWatcherEvent event(flags, path, path);
297 SendEvent(event);
298 }
299 }
300 }
301
302 void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
303 {
304 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
305 m_service->SendEvent(evt);
306 }
307
308 int wxIOCPThread::Native2WatcherFlags(int flags)
309 {
310 static const int flag_mapping[][2] = {
311 { FILE_ACTION_ADDED, wxFSW_EVENT_CREATE },
312 { FILE_ACTION_REMOVED, wxFSW_EVENT_DELETE },
313
314 // TODO take attributes into account to see what happened
315 { FILE_ACTION_MODIFIED, wxFSW_EVENT_MODIFY },
316
317 { FILE_ACTION_RENAMED_OLD_NAME, wxFSW_EVENT_RENAME },
318
319 // ignored as it should always be matched with ***_OLD_NAME
320 { FILE_ACTION_RENAMED_NEW_NAME, 0 },
321 };
322
323 for (unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) {
324 if (flags == flag_mapping[i][0])
325 return flag_mapping[i][1];
326 }
327
328 // never reached
329 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags));
330 return -1;
331 }
332
333 wxString wxIOCPThread::FileNotifyInformationToString(
334 const FILE_NOTIFY_INFORMATION& e)
335 {
336 wxString fname(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
337 return wxString::Format("Event: offset=%d, action=%d, len=%d, "
338 "name(approx)='%s'", e.NextEntryOffset, e.Action,
339 e.FileNameLength, fname);
340 }
341
342 wxFileName wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW& watch,
343 const FILE_NOTIFY_INFORMATION& e)
344 {
345 wxFileName path = watch.GetPath();
346 if (path.IsDir())
347 {
348 wxString rel(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
349 int pathFlags = wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR;
350 path = wxFileName(path.GetPath(pathFlags) + rel);
351 }
352 return path;
353 }
354
355
356 // ============================================================================
357 // wxMSWFileSystemWatcher implementation
358 // ============================================================================
359
360 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
361 wxFileSystemWatcherBase()
362 {
363 (void) Init();
364 }
365
366 wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
367 int events) :
368 wxFileSystemWatcherBase()
369 {
370 if (!Init())
371 return;
372
373 Add(path, events);
374 }
375
376 bool wxMSWFileSystemWatcher::Init()
377 {
378 m_service = new wxFSWatcherImplMSW(this);
379 bool ret = m_service->Init();
380 if (!ret)
381 {
382 delete m_service;
383 }
384
385 return ret;
386 }
387
388 #endif // wxUSE_FSWATCHER