]> git.saurik.com Git - wxWidgets.git/blame - src/msw/fswatcher.cpp
Forward port 2.8 fix.
[wxWidgets.git] / src / msw / fswatcher.cpp
CommitLineData
6b8ef0b3
VZ
1/////////////////////////////////////////////////////////////////////////////
2// Name: 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
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
110bool wxFSWatcherImplMSW::DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)
111{
112 return true;
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 int flags = Watcher2NativeFlags(watch.GetFlags());
139 int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
140 wxFSWatchEntryMSW::BUFFER_SIZE, TRUE,
141 flags, NULL,
142 watch.GetOverlapped(), NULL);
143 if (!ret)
144 {
145 wxLogSysError(_("Unable to set up watch for '%s'"),
146 watch.GetPath());
147 }
148
149 return ret != 0;
150}
151
152// TODO we should only specify those flags, which interest us
153// this needs a bit of thinking, quick impl for now
154int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags))
155{
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;
161
162 return all_events;
163}
164
165
166// ============================================================================
167// wxFSWatcherImplMSW helper classes implementation
168// ============================================================================
169
170wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
171 wxThread(wxTHREAD_JOINABLE),
172 m_service(service), m_iocp(iocp)
173{
174}
175
176// finishes this thread
177bool wxIOCPThread::Finish()
178{
179 wxLogTrace(wxTRACE_FSWATCHER, "Posting empty status!");
180
181 // send "empty" packet to i/o completion port, just to wake
182 return m_iocp->PostEmptyStatus();
183}
184
185wxThread::ExitCode wxIOCPThread::Entry()
186{
187 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Started IOCP thread");
188
189 // read events in a loop until we get false, which means we should exit
190 while ( ReadEvents() );
191
192 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Ended IOCP thread");
193 return (ExitCode)0;
194}
195
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
198// true otherwise
199bool wxIOCPThread::ReadEvents()
200{
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
206
207 // this is our exit condition, so we return false
208 if (!count && !watch && !overlapped)
209 return false;
210
211 // in case of spurious wakeup
212 if (!count || !watch)
213 return true;
214
215 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] Read entry: path='%s'",
216 watch->GetPath());
217
218 // extract events from buffer info our vector container
219 wxVector<wxEventProcessingData> events;
220 const char* memory = static_cast<const char*>(watch->GetBuffer());
221 int offset = 0;
222 do
223 {
224 const FILE_NOTIFY_INFORMATION* e =
225 static_cast<const FILE_NOTIFY_INFORMATION*>((const void*)memory);
226
227 events.push_back(wxEventProcessingData(e, watch));
228
229 offset = e->NextEntryOffset;
230 memory += offset;
231 }
232 while (offset);
233
234 // process events
235 ProcessNativeEvents(events);
236
237 // reissue the watch. ignore possible errors, we will return true anyway
238 (void) m_service->SetUpWatch(*watch);
239
240 return true;
241}
242
243void wxIOCPThread::ProcessNativeEvents(wxVector<wxEventProcessingData>& events)
244{
245 wxVector<wxEventProcessingData>::iterator it = events.begin();
246 for ( ; it != events.end(); ++it )
247 {
248 const FILE_NOTIFY_INFORMATION& e = *(it->nativeEvent);
249 const wxFSWatchEntryMSW* watch = it->watch;
250
251 wxLogTrace( wxTRACE_FSWATCHER, "[iocp] %s",
252 FileNotifyInformationToString(e));
253
254 int nativeFlags = e.Action;
255 int flags = Native2WatcherFlags(nativeFlags);
256 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
257 {
258 // TODO think about this...do we ever have any errors to report?
259 wxString errMsg = "Error occurred";
260 wxFileSystemWatcherEvent event(flags, errMsg);
261 SendEvent(event);
262 }
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()))
266 {
267 return;
268 }
269 // rename case
270 else if (nativeFlags == FILE_ACTION_RENAMED_OLD_NAME)
271 {
272 wxFileName oldpath = GetEventPath(*watch, e);
273 wxFileName newpath;
274
275 // newpath should be in the next entry. what if there isn't?
276 ++it;
277 if ( it != events.end() )
278 {
279 newpath = GetEventPath(*(it->watch), *(it->nativeEvent));
280 }
281 wxFileSystemWatcherEvent event(flags, oldpath, newpath);
282 SendEvent(event);
283 }
284 // all other events
285 else
286 {
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);
291 SendEvent(event);
292 }
293 }
294}
295
296void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
297{
298 wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
299 m_service->SendEvent(evt);
300}
301
302int wxIOCPThread::Native2WatcherFlags(int flags)
303{
304 static const int flag_mapping[][2] = {
305 { FILE_ACTION_ADDED, wxFSW_EVENT_CREATE },
306 { FILE_ACTION_REMOVED, wxFSW_EVENT_DELETE },
307
308 // TODO take attributes into account to see what happened
309 { FILE_ACTION_MODIFIED, wxFSW_EVENT_MODIFY },
310
311 { FILE_ACTION_RENAMED_OLD_NAME, wxFSW_EVENT_RENAME },
312
313 // ignored as it should always be matched with ***_OLD_NAME
314 { FILE_ACTION_RENAMED_NEW_NAME, 0 },
315 };
316
317 for (unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) {
318 if (flags == flag_mapping[i][0])
319 return flag_mapping[i][1];
320 }
321
322 // never reached
323 wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags));
324 return -1;
325}
326
327wxString wxIOCPThread::FileNotifyInformationToString(
328 const FILE_NOTIFY_INFORMATION& e)
329{
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);
334}
335
336wxFileName wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW& watch,
337 const FILE_NOTIFY_INFORMATION& e)
338{
339 wxFileName path = watch.GetPath();
340 if (path.IsDir())
341 {
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);
345 }
346 return path;
347}
348
349
350// ============================================================================
351// wxMSWFileSystemWatcher implementation
352// ============================================================================
353
354wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
355 wxFileSystemWatcherBase()
356{
357 (void) Init();
358}
359
360wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
361 int events) :
362 wxFileSystemWatcherBase()
363{
364 if (!Init())
365 return;
366
367 Add(path, events);
368}
369
370bool wxMSWFileSystemWatcher::Init()
371{
372 m_service = new wxFSWatcherImplMSW(this);
373 bool ret = m_service->Init();
374 if (!ret)
375 {
376 delete m_service;
377 }
378
379 return ret;
380}
381
382#endif // wxUSE_FSWATCHER