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