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
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
The name of the path to watch.
@param events
@@ -68,29 +72,38 @@ public:
virtual bool Add(const wxFileName& path, int events = wxFSW_EVENT_ALL);
/**
This is the same as Add(), but recursively adds every file/directory in
the tree rooted at @a path.
This is the same as Add(), but also recursively adds every
file/directory in the tree rooted at @a path.
Additionally a file mask can be specified to include only files
matching that particular mask.
This method is implemented efficiently under MSW but shouldn't be used
for the directories with a lot of children (such as e.g. the root
directory) under the other platforms as it calls Add() there for each
subdirectory potentially creating a lot of watches and taking a long
time to execute.
This method is implemented efficiently on MSW, but should be used with
care on other platforms for directories with lots of children (e.g. the
root directory) as it calls Add() for each subdirectory, potentially
creating a lot of watches and taking a long 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,
const wxString& filter = wxEmptyString);
/**
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);
/**
Same as Remove(), but also removes every file/directory belonging to
the tree rooted at @a path.
This is the same as Remove(), but also removes every file/directory
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);

View File

@@ -46,11 +46,13 @@ private:
void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
void OnWatch(wxCommandEvent& event);
void OnFollowLinks(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnAdd(wxCommandEvent& event);
void OnAddTree(wxCommandEvent& event);
void OnRemove(wxCommandEvent& event);
void OnRemoveUpdateUI(wxUpdateUIEvent& event);
void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
void LogEvent(const wxFileSystemWatcherEvent& event);
@@ -58,6 +60,7 @@ private:
wxTextCtrl *m_evtConsole; // events console
wxListView *m_filesList; // list of watched paths
wxFileSystemWatcher* m_watcher; // file system watcher
bool m_followLinks; // should symlinks be dereferenced
const static wxString LOG_FORMAT; // how to format events
};
@@ -135,7 +138,7 @@ IMPLEMENT_APP(MyApp)
// frame constructor
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title),
m_watcher(NULL)
m_watcher(NULL), m_followLinks(false)
{
SetIcon(wxICON(sample));
@@ -145,6 +148,7 @@ MyFrame::MyFrame(const wxString& title)
MENU_ID_QUIT = wxID_EXIT,
MENU_ID_CLEAR = wxID_CLEAR,
MENU_ID_WATCH = 101,
MENU_ID_DEREFERENCE,
BTN_ID_ADD = 200,
BTN_ID_ADD_TREE,
@@ -166,6 +170,18 @@ MyFrame::MyFrame(const wxString& title)
// started by default, because file system watcher is started by default
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
wxMenu *menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
@@ -263,6 +279,8 @@ MyFrame::MyFrame(const wxString& title)
wxCommandEventHandler(MyFrame::OnAddTree));
Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
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
// 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))
{
AddEntry(wxFSWPath_Dir);
@@ -353,15 +376,23 @@ void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
wxString prefix;
bool ok = false;
// This will tell wxFileSystemWatcher whether to dereference symlinks
wxFileName fn = wxFileName::DirName(filename);
if (!m_followLinks)
{
fn.DontFollowLink();
}
switch ( type )
{
case wxFSWPath_Dir:
ok = m_watcher->Add(wxFileName::DirName(filename));
ok = m_watcher->Add(fn);
prefix = "Dir: ";
break;
case wxFSWPath_Tree:
ok = m_watcher->AddTree(wxFileName::DirName(filename));
ok = m_watcher->AddTree(fn);
prefix = "Tree: ";
break;
@@ -390,15 +421,23 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
return;
bool ret;
wxString path;
// TODO we know it is a dir, but it doesn't have to be
if (m_filesList->GetItemText(idx).StartsWith("Dir: ", &path))
wxString path = m_filesList->GetItemText(idx).Mid(6);
// 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
{
@@ -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)
{
// 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());
// 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);
dir.Traverse(traverser, filespec);
dir.Traverse(traverser, filespec, flags);
// Add the path itself explicitly as Traverse() doesn't return it.
AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
@@ -260,8 +266,17 @@ bool wxFileSystemWatcherBase::RemoveTree(const wxFileName& path)
#endif // __WINDOWS__
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);
dir.Traverse(traverser, filespec);
dir.Traverse(traverser, filespec, flags);
// As in AddTree() above, handle the path itself explicitly.
Remove(path);

View File

@@ -650,12 +650,13 @@ void FileSystemWatcherTestCase::TestTrees()
public:
TreeTester() : subdirs(5), files(3) {}
void GrowTree(wxFileName dir)
void GrowTree(wxFileName dir, bool withSymlinks)
{
CPPUNIT_ASSERT(dir.Mkdir());
// Now add a subdir with an easy name to remember in WatchTree()
dir.AppendDir("child");
CPPUNIT_ASSERT(dir.Mkdir());
wxFileName child(dir); // Create a copy to which to symlink
// Create a branch of 5 numbered subdirs, each containing 3
// numbered files
@@ -672,6 +673,18 @@ void FileSystemWatcherTestCase::TestTrees()
wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
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
const int initial = m_watcher->GetWatchedPathsCount();
GrowTree(dir);
GrowTree(dir, false /* no symlinks */);
m_watcher->AddTree(dir);
const int plustree = m_watcher->GetWatchedPathsCount();
@@ -750,6 +763,22 @@ void FileSystemWatcherTestCase::TestTrees()
CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
m_watcher->RemoveTree(dir);
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)