1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/fswatchercmn.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/private/fswatcher.h"
21
22 // ============================================================================
23 // helpers
24 // ============================================================================
25
26 wxDEFINE_EVENT(wxEVT_FSWATCHER, wxFileSystemWatcherEvent);
27
GetFSWEventChangeTypeName(int type)28 static wxString GetFSWEventChangeTypeName(int type)
29 {
30 switch (type)
31 {
32 case wxFSW_EVENT_CREATE:
33 return "CREATE";
34 case wxFSW_EVENT_DELETE:
35 return "DELETE";
36 case wxFSW_EVENT_RENAME:
37 return "RENAME";
38 case wxFSW_EVENT_MODIFY:
39 return "MODIFY";
40 case wxFSW_EVENT_ACCESS:
41 return "ACCESS";
42 case wxFSW_EVENT_ATTRIB: // Currently this is wxGTK-only
43 return "ATTRIBUTE";
44 #ifdef wxHAS_INOTIFY
45 case wxFSW_EVENT_UNMOUNT: // Currently this is wxGTK-only
46 return "UNMOUNT";
47 #endif
48 case wxFSW_EVENT_WARNING:
49 return "WARNING";
50 case wxFSW_EVENT_ERROR:
51 return "ERROR";
52 }
53
54 // should never be reached!
55 wxFAIL_MSG("Unknown change type");
56 return "INVALID_TYPE";
57 }
58
59
60 // ============================================================================
61 // wxFileSystemWatcherEvent implementation
62 // ============================================================================
63
64 IMPLEMENT_DYNAMIC_CLASS(wxFileSystemWatcherEvent, wxEvent);
65
ToString() const66 wxString wxFileSystemWatcherEvent::ToString() const
67 {
68 if (IsError())
69 {
70 return wxString::Format("FSW_EVT type=%d (%s) message='%s'", m_changeType,
71 GetFSWEventChangeTypeName(m_changeType), GetErrorDescription());
72 }
73 return wxString::Format("FSW_EVT type=%d (%s) path='%s'", m_changeType,
74 GetFSWEventChangeTypeName(m_changeType), GetPath().GetFullPath());
75 }
76
77
78 // ============================================================================
79 // wxFileSystemWatcherEvent implementation
80 // ============================================================================
81
wxFileSystemWatcherBase()82 wxFileSystemWatcherBase::wxFileSystemWatcherBase() :
83 m_service(0), m_owner(this)
84 {
85 }
86
~wxFileSystemWatcherBase()87 wxFileSystemWatcherBase::~wxFileSystemWatcherBase()
88 {
89 RemoveAll();
90 if (m_service)
91 {
92 delete m_service;
93 }
94 }
95
Add(const wxFileName & path,int events)96 bool wxFileSystemWatcherBase::Add(const wxFileName& path, int events)
97 {
98 wxFSWPathType type = wxFSWPath_None;
99 if ( path.FileExists() )
100 {
101 type = wxFSWPath_File;
102 }
103 else if ( path.DirExists() )
104 {
105 type = wxFSWPath_Dir;
106 }
107 else
108 {
109 // Don't overreact to being passed a non-existent item. It may have
110 // only just been deleted, in which case doing nothing is correct
111 wxLogTrace(wxTRACE_FSWATCHER,
112 "Can't monitor non-existent path \"%s\" for changes.",
113 path.GetFullPath());
114 return false;
115 }
116
117 return AddAny(path, events, type);
118 }
119
120 bool
AddAny(const wxFileName & path,int events,wxFSWPathType type,const wxString & filespec)121 wxFileSystemWatcherBase::AddAny(const wxFileName& path,
122 int events,
123 wxFSWPathType type,
124 const wxString& filespec)
125 {
126 wxString canonical = GetCanonicalPath(path);
127 if (canonical.IsEmpty())
128 return false;
129
130 // adding a path in a platform specific way
131 wxFSWatchInfo watch(canonical, events, type, filespec);
132 if ( !m_service->Add(watch) )
133 return false;
134
135 // on success, either add path to our 'watch-list'
136 // or, if already watched, inc the refcount. This may happen if
137 // a dir is Add()ed, then later AddTree() is called on a parent dir
138 wxFSWatchInfoMap::iterator it = m_watches.find(canonical);
139 if ( it == m_watches.end() )
140 {
141 wxFSWatchInfoMap::value_type val(canonical, watch);
142 m_watches.insert(val);
143 }
144 else
145 {
146 wxFSWatchInfo& watch2 = it->second;
147 const int count = watch2.IncRef();
148 wxLogTrace(wxTRACE_FSWATCHER,
149 "'%s' is now watched %d times", canonical, count);
150
151 wxUnusedVar(count); // could be unused if debug tracing is disabled
152 }
153 return true;
154 }
155
Remove(const wxFileName & path)156 bool wxFileSystemWatcherBase::Remove(const wxFileName& path)
157 {
158 // args validation & consistency checks
159 wxString canonical = GetCanonicalPath(path);
160 if (canonical.IsEmpty())
161 return false;
162
163 wxFSWatchInfoMap::iterator it = m_watches.find(canonical);
164 wxCHECK_MSG(it != m_watches.end(), false,
165 wxString::Format("Path '%s' is not watched", canonical));
166
167 // Decrement the watch's refcount and remove from watch-list if 0
168 bool ret = true;
169 wxFSWatchInfo& watch = it->second;
170 if ( !watch.DecRef() )
171 {
172 // remove in a platform specific way
173 ret = m_service->Remove(watch);
174
175 m_watches.erase(it);
176 }
177 return ret;
178 }
179
AddTree(const wxFileName & path,int events,const wxString & filespec)180 bool wxFileSystemWatcherBase::AddTree(const wxFileName& path, int events,
181 const wxString& filespec)
182 {
183 if (!path.DirExists())
184 return false;
185
186 // OPT could be optimised if we stored information about relationships
187 // between paths
188 class AddTraverser : public wxDirTraverser
189 {
190 public:
191 AddTraverser(wxFileSystemWatcherBase* watcher, int events,
192 const wxString& filespec) :
193 m_watcher(watcher), m_events(events), m_filespec(filespec)
194 {
195 }
196
197 virtual wxDirTraverseResult OnFile(const wxString& WXUNUSED(filename))
198 {
199 // There is no need to watch individual files as we watch the
200 // parent directory which will notify us about any changes in them.
201 return wxDIR_CONTINUE;
202 }
203
204 virtual wxDirTraverseResult OnDir(const wxString& dirname)
205 {
206 if ( m_watcher->AddAny(wxFileName::DirName(dirname),
207 m_events, wxFSWPath_Tree, m_filespec) )
208 {
209 wxLogTrace(wxTRACE_FSWATCHER,
210 "--- AddTree adding directory '%s' ---", dirname);
211 }
212 return wxDIR_CONTINUE;
213 }
214
215 private:
216 wxFileSystemWatcherBase* m_watcher;
217 int m_events;
218 wxString m_filespec;
219 };
220
221 wxDir dir(path.GetFullPath());
222 // Prevent asserts or infinite loops in trees containing symlinks
223 int flags = wxDIR_DIRS;
224 if ( !path.ShouldFollowLink() )
225 {
226 flags |= wxDIR_NO_FOLLOW;
227 }
228 AddTraverser traverser(this, events, filespec);
229 dir.Traverse(traverser, filespec, flags);
230
231 // Add the path itself explicitly as Traverse() doesn't return it.
232 AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
233
234 return true;
235 }
236
RemoveTree(const wxFileName & path)237 bool wxFileSystemWatcherBase::RemoveTree(const wxFileName& path)
238 {
239 if (!path.DirExists())
240 return false;
241
242 // OPT could be optimised if we stored information about relationships
243 // between paths
244 class RemoveTraverser : public wxDirTraverser
245 {
246 public:
247 RemoveTraverser(wxFileSystemWatcherBase* watcher,
248 const wxString& filespec) :
249 m_watcher(watcher), m_filespec(filespec)
250 {
251 }
252
253 virtual wxDirTraverseResult OnFile(const wxString& WXUNUSED(filename))
254 {
255 // We never watch the individual files when watching the tree, so
256 // nothing to do here.
257 return wxDIR_CONTINUE;
258 }
259
260 virtual wxDirTraverseResult OnDir(const wxString& dirname)
261 {
262 m_watcher->Remove(wxFileName::DirName(dirname));
263 return wxDIR_CONTINUE;
264 }
265
266 private:
267 wxFileSystemWatcherBase* m_watcher;
268 wxString m_filespec;
269 };
270
271 // If AddTree() used a filespec, we must use the same one
272 wxString canonical = GetCanonicalPath(path);
273 wxFSWatchInfoMap::iterator it = m_watches.find(canonical);
274 wxCHECK_MSG( it != m_watches.end(), false,
275 wxString::Format("Path '%s' is not watched", canonical) );
276 wxFSWatchInfo watch = it->second;
277 const wxString filespec = watch.GetFilespec();
278
279 #if defined(__WINDOWS__)
280 // When there's no filespec, the wxMSW AddTree() would have set a watch
281 // on only the passed 'path'. We must therefore remove only this
282 if (filespec.empty())
283 {
284 return Remove(path);
285 }
286 // Otherwise fall through to the generic implementation
287 #endif // __WINDOWS__
288
289 wxDir dir(path.GetFullPath());
290 // AddTree() might have used the wxDIR_NO_FOLLOW to prevent asserts or
291 // infinite loops in trees containing symlinks. We need to do the same
292 // or we'll try to remove unwatched items. Let's hope the caller used
293 // the same ShouldFollowLink() setting as in AddTree()...
294 int flags = wxDIR_DIRS;
295 if ( !path.ShouldFollowLink() )
296 {
297 flags |= wxDIR_NO_FOLLOW;
298 }
299 RemoveTraverser traverser(this, filespec);
300 dir.Traverse(traverser, filespec, flags);
301
302 // As in AddTree() above, handle the path itself explicitly.
303 Remove(path);
304
305 return true;
306 }
307
RemoveAll()308 bool wxFileSystemWatcherBase::RemoveAll()
309 {
310 const bool ret = m_service->RemoveAll();
311 m_watches.clear();
312 return ret;
313 }
314
GetWatchedPathsCount() const315 int wxFileSystemWatcherBase::GetWatchedPathsCount() const
316 {
317 return m_watches.size();
318 }
319
GetWatchedPaths(wxArrayString * paths) const320 int wxFileSystemWatcherBase::GetWatchedPaths(wxArrayString* paths) const
321 {
322 wxCHECK_MSG( paths != NULL, -1, "Null array passed to retrieve paths");
323
324 wxFSWatchInfoMap::const_iterator it = m_watches.begin();
325 for ( ; it != m_watches.end(); ++it)
326 {
327 paths->push_back(it->first);
328 }
329
330 return m_watches.size();
331 }
332
333 #endif // wxUSE_FSWATCHER
334