Respect wxFileName::DontFollowLink() in wxFileSystemWatcher.

Watch the link itself and not its target if DontFollowLink() had been called.

Closes #14543.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72751 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2012-10-24 18:21:31 +00:00
parent d2a02746c6
commit 0fccda2ced
4 changed files with 123 additions and 22 deletions

View File

@@ -60,6 +60,10 @@ public:
to this directory itself or its immediate children will generate the to this directory itself or its immediate children will generate the
events. Use AddTree() to monitor the directory recursively. events. Use AddTree() to monitor the directory recursively.
Note that on platforms that use symbolic links, you should consider the
possibility that @a path is a symlink. To watch the symlink itself and
not its target you may call wxFileName::DontFollowLink() on @a path.
@param path @param path
The name of the path to watch. The name of the path to watch.
@param events @param events
@@ -68,29 +72,38 @@ public:
virtual bool Add(const wxFileName& path, int events = wxFSW_EVENT_ALL); virtual bool Add(const wxFileName& path, int events = wxFSW_EVENT_ALL);
/** /**
This is the same as Add(), but recursively adds every file/directory in This is the same as Add(), but also recursively adds every
the tree rooted at @a path. file/directory in the tree rooted at @a path.
Additionally a file mask can be specified to include only files Additionally a file mask can be specified to include only files
matching that particular mask. matching that particular mask.
This method is implemented efficiently under MSW but shouldn't be used This method is implemented efficiently on MSW, but should be used with
for the directories with a lot of children (such as e.g. the root care on other platforms for directories with lots of children (e.g. the
directory) under the other platforms as it calls Add() there for each root directory) as it calls Add() for each subdirectory, potentially
subdirectory potentially creating a lot of watches and taking a long creating a lot of watches and taking a long time to execute.
time to execute.
Note that on platforms that use symbolic links, you will probably want
to have called wxFileName::DontFollowLink on @a path. This is especially
important if the symlink targets may themselves be watched.
*/ */
virtual bool AddTree(const wxFileName& path, int events = wxFSW_EVENT_ALL, virtual bool AddTree(const wxFileName& path, int events = wxFSW_EVENT_ALL,
const wxString& filter = wxEmptyString); const wxString& filter = wxEmptyString);
/** /**
Removes @a path from the list of watched paths. Removes @a path from the list of watched paths.
See the comment in Add() about symbolic links. @a path should treat
symbolic links in the same way as in the original Add() call.
*/ */
virtual bool Remove(const wxFileName& path); virtual bool Remove(const wxFileName& path);
/** /**
Same as Remove(), but also removes every file/directory belonging to This is the same as Remove(), but also removes every file/directory
the tree rooted at @a path. belonging to the tree rooted at @a path.
See the comment in AddTree() about symbolic links. @a path should treat
symbolic links in the same way as in the original AddTree() call.
*/ */
virtual bool RemoveTree(const wxFileName& path); virtual bool RemoveTree(const wxFileName& path);

View File

@@ -46,11 +46,13 @@ private:
void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); } void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); } void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
void OnWatch(wxCommandEvent& event); void OnWatch(wxCommandEvent& event);
void OnFollowLinks(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event); void OnAbout(wxCommandEvent& event);
void OnAdd(wxCommandEvent& event); void OnAdd(wxCommandEvent& event);
void OnAddTree(wxCommandEvent& event); void OnAddTree(wxCommandEvent& event);
void OnRemove(wxCommandEvent& event); void OnRemove(wxCommandEvent& event);
void OnRemoveUpdateUI(wxUpdateUIEvent& event);
void OnFileSystemEvent(wxFileSystemWatcherEvent& event); void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
void LogEvent(const wxFileSystemWatcherEvent& event); void LogEvent(const wxFileSystemWatcherEvent& event);
@@ -58,6 +60,7 @@ private:
wxTextCtrl *m_evtConsole; // events console wxTextCtrl *m_evtConsole; // events console
wxListView *m_filesList; // list of watched paths wxListView *m_filesList; // list of watched paths
wxFileSystemWatcher* m_watcher; // file system watcher wxFileSystemWatcher* m_watcher; // file system watcher
bool m_followLinks; // should symlinks be dereferenced
const static wxString LOG_FORMAT; // how to format events const static wxString LOG_FORMAT; // how to format events
}; };
@@ -135,7 +138,7 @@ IMPLEMENT_APP(MyApp)
// frame constructor // frame constructor
MyFrame::MyFrame(const wxString& title) MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title), : wxFrame(NULL, wxID_ANY, title),
m_watcher(NULL) m_watcher(NULL), m_followLinks(false)
{ {
SetIcon(wxICON(sample)); SetIcon(wxICON(sample));
@@ -145,6 +148,7 @@ MyFrame::MyFrame(const wxString& title)
MENU_ID_QUIT = wxID_EXIT, MENU_ID_QUIT = wxID_EXIT,
MENU_ID_CLEAR = wxID_CLEAR, MENU_ID_CLEAR = wxID_CLEAR,
MENU_ID_WATCH = 101, MENU_ID_WATCH = 101,
MENU_ID_DEREFERENCE,
BTN_ID_ADD = 200, BTN_ID_ADD = 200,
BTN_ID_ADD_TREE, BTN_ID_ADD_TREE,
@@ -166,6 +170,18 @@ MyFrame::MyFrame(const wxString& title)
// started by default, because file system watcher is started by default // started by default, because file system watcher is started by default
it->Check(true); it->Check(true);
#if defined(__UNIX__)
// Let the user decide whether to dereference symlinks. If he makes the
// wrong choice, asserts will occur if the symlink target is also watched
it = menuMon->AppendCheckItem(MENU_ID_DEREFERENCE,
"&Follow symlinks\tCtrl-F",
_("If checked, dereference symlinks")
);
it->Check(false);
Connect(MENU_ID_DEREFERENCE, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(MyFrame::OnFollowLinks));
#endif // __UNIX__
// the "About" item should be in the help menu // the "About" item should be in the help menu
wxMenu *menuHelp = new wxMenu; wxMenu *menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog"); menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
@@ -263,6 +279,8 @@ MyFrame::MyFrame(const wxString& title)
wxCommandEventHandler(MyFrame::OnAddTree)); wxCommandEventHandler(MyFrame::OnAddTree));
Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED, Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyFrame::OnRemove)); wxCommandEventHandler(MyFrame::OnRemove));
Connect(BTN_ID_REMOVE, wxEVT_UPDATE_UI,
wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI));
// and show itself (the frames, unlike simple controls, are not shown when // and show itself (the frames, unlike simple controls, are not shown when
// created initially) // created initially)
@@ -324,6 +342,11 @@ void MyFrame::OnWatch(wxCommandEvent& event)
} }
} }
void MyFrame::OnFollowLinks(wxCommandEvent& event)
{
m_followLinks = event.IsChecked();
}
void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
{ {
AddEntry(wxFSWPath_Dir); AddEntry(wxFSWPath_Dir);
@@ -353,15 +376,23 @@ void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
wxString prefix; wxString prefix;
bool ok = false; bool ok = false;
// This will tell wxFileSystemWatcher whether to dereference symlinks
wxFileName fn = wxFileName::DirName(filename);
if (!m_followLinks)
{
fn.DontFollowLink();
}
switch ( type ) switch ( type )
{ {
case wxFSWPath_Dir: case wxFSWPath_Dir:
ok = m_watcher->Add(wxFileName::DirName(filename)); ok = m_watcher->Add(fn);
prefix = "Dir: "; prefix = "Dir: ";
break; break;
case wxFSWPath_Tree: case wxFSWPath_Tree:
ok = m_watcher->AddTree(wxFileName::DirName(filename)); ok = m_watcher->AddTree(fn);
prefix = "Tree: "; prefix = "Tree: ";
break; break;
@@ -390,15 +421,23 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
return; return;
bool ret; bool ret;
wxString path; wxString path = m_filesList->GetItemText(idx).Mid(6);
// TODO we know it is a dir, but it doesn't have to be
if (m_filesList->GetItemText(idx).StartsWith("Dir: ", &path)) // This will tell wxFileSystemWatcher whether to dereference symlinks
wxFileName fn = wxFileName::DirName(path);
if (!m_followLinks)
{ {
ret = m_watcher->Remove(wxFileName::DirName(path)); fn.DontFollowLink();
} }
else if (m_filesList->GetItemText(idx).StartsWith("Tree: ", &path))
// TODO we know it is a dir, but it doesn't have to be
if (m_filesList->GetItemText(idx).StartsWith("Dir: "))
{ {
ret = m_watcher->RemoveTree(wxFileName::DirName(path)); ret = m_watcher->Remove(fn);
}
else if (m_filesList->GetItemText(idx).StartsWith("Tree: "))
{
ret = m_watcher->RemoveTree(fn);
} }
else else
{ {
@@ -415,6 +454,11 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
} }
} }
void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
{
event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
}
void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event) void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
{ {
// TODO remove when code is rock-solid // TODO remove when code is rock-solid

View File

@@ -198,8 +198,14 @@ bool wxFileSystemWatcherBase::AddTree(const wxFileName& path, int events,
}; };
wxDir dir(path.GetFullPath()); wxDir dir(path.GetFullPath());
// Prevent asserts or infinite loops in trees containing symlinks
int flags = wxDIR_DEFAULT; // TODO: we ignore files, so why use wxDIR_FILES?
if ( !path.ShouldFollowLink() )
{
flags |= wxDIR_NO_FOLLOW;
}
AddTraverser traverser(this, events, filespec); AddTraverser traverser(this, events, filespec);
dir.Traverse(traverser, filespec); dir.Traverse(traverser, filespec, flags);
// Add the path itself explicitly as Traverse() doesn't return it. // Add the path itself explicitly as Traverse() doesn't return it.
AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec); AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
@@ -260,8 +266,17 @@ bool wxFileSystemWatcherBase::RemoveTree(const wxFileName& path)
#endif // __WINDOWS__ #endif // __WINDOWS__
wxDir dir(path.GetFullPath()); wxDir dir(path.GetFullPath());
// AddTree() might have used the wxDIR_NO_FOLLOW to prevent asserts or
// infinite loops in trees containing symlinks. We need to do the same
// or we'll try to remove unwatched items. Let's hope the caller used
// the same ShouldFollowLink() setting as in AddTree()...
int flags = wxDIR_DEFAULT; // See the TODO in AddTree()
if ( !path.ShouldFollowLink() )
{
flags |= wxDIR_NO_FOLLOW;
}
RemoveTraverser traverser(this, filespec); RemoveTraverser traverser(this, filespec);
dir.Traverse(traverser, filespec); dir.Traverse(traverser, filespec, flags);
// As in AddTree() above, handle the path itself explicitly. // As in AddTree() above, handle the path itself explicitly.
Remove(path); Remove(path);

View File

@@ -650,12 +650,13 @@ void FileSystemWatcherTestCase::TestTrees()
public: public:
TreeTester() : subdirs(5), files(3) {} TreeTester() : subdirs(5), files(3) {}
void GrowTree(wxFileName dir) void GrowTree(wxFileName dir, bool withSymlinks)
{ {
CPPUNIT_ASSERT(dir.Mkdir()); CPPUNIT_ASSERT(dir.Mkdir());
// Now add a subdir with an easy name to remember in WatchTree() // Now add a subdir with an easy name to remember in WatchTree()
dir.AppendDir("child"); dir.AppendDir("child");
CPPUNIT_ASSERT(dir.Mkdir()); CPPUNIT_ASSERT(dir.Mkdir());
wxFileName child(dir); // Create a copy to which to symlink
// Create a branch of 5 numbered subdirs, each containing 3 // Create a branch of 5 numbered subdirs, each containing 3
// numbered files // numbered files
@@ -672,6 +673,18 @@ void FileSystemWatcherTestCase::TestTrees()
wxFile(prefix + wxString::Format("file%u", f+1) + ext[f], wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
wxFile::write); wxFile::write);
} }
#if defined(__UNIX__)
if ( withSymlinks )
{
// Create a symlink to a files, and another to 'child'
CPPUNIT_ASSERT_EQUAL(0,
symlink(wxString(prefix + "file1").c_str(),
wxString(prefix + "file.lnk").c_str()));
CPPUNIT_ASSERT_EQUAL(0,
symlink(child.GetFullPath().c_str(),
wxString(prefix + "dir.lnk").c_str()));
}
#endif // __UNIX__
} }
} }
@@ -720,7 +733,7 @@ void FileSystemWatcherTestCase::TestTrees()
// Store the initial count; there may already be some watches // Store the initial count; there may already be some watches
const int initial = m_watcher->GetWatchedPathsCount(); const int initial = m_watcher->GetWatchedPathsCount();
GrowTree(dir); GrowTree(dir, false /* no symlinks */);
m_watcher->AddTree(dir); m_watcher->AddTree(dir);
const int plustree = m_watcher->GetWatchedPathsCount(); const int plustree = m_watcher->GetWatchedPathsCount();
@@ -750,6 +763,22 @@ void FileSystemWatcherTestCase::TestTrees()
CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount()); CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
m_watcher->RemoveTree(dir); m_watcher->RemoveTree(dir);
CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount()); CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
#if defined(__UNIX__)
// Finally, test a tree containing internal symlinks
RmDir(dir);
GrowTree(dir, true /* test symlinks */);
// Without the DontFollowLink() call AddTree() would now assert
// (and without the assert, it would infinitely loop)
wxFileName fn = dir;
fn.DontFollowLink();
CPPUNIT_ASSERT(m_watcher->AddTree(fn));
CPPUNIT_ASSERT(m_watcher->RemoveTree(fn));
// Regrow the tree without symlinks, ready for the next test
RmDir(dir);
GrowTree(dir, false);
#endif // __UNIX__
} }
void WatchTreeWithFilespec(const wxFileName& dir) void WatchTreeWithFilespec(const wxFileName& dir)