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
to this directory itself or its immediate children will generate the
events. Use AddTree() to monitor the directory recursively.
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
@param path
The name of the path to watch.
@param events
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
- 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.
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.
*/
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);
/**
*/
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);
*/
virtual bool RemoveTree(const wxFileName& path);
void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
void OnWatch(wxCommandEvent& event);
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 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);
void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
void LogEvent(const wxFileSystemWatcherEvent& event);
wxTextCtrl *m_evtConsole; // events console
wxListView *m_filesList; // list of watched paths
wxFileSystemWatcher* m_watcher; // file system watcher
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
};
const static wxString LOG_FORMAT; // how to format events
};
// frame constructor
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title),
// frame constructor
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title),
+ m_watcher(NULL), m_followLinks(false)
{
SetIcon(wxICON(sample));
{
SetIcon(wxICON(sample));
MENU_ID_QUIT = wxID_EXIT,
MENU_ID_CLEAR = wxID_CLEAR,
MENU_ID_WATCH = 101,
MENU_ID_QUIT = wxID_EXIT,
MENU_ID_CLEAR = wxID_CLEAR,
MENU_ID_WATCH = 101,
BTN_ID_ADD = 200,
BTN_ID_ADD_TREE,
BTN_ID_ADD = 200,
BTN_ID_ADD_TREE,
// started by default, because file system watcher is started by default
it->Check(true);
// 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");
// the "About" item should be in the help menu
wxMenu *menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
wxCommandEventHandler(MyFrame::OnAddTree));
Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyFrame::OnRemove));
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)
// and show itself (the frames, unlike simple controls, are not shown when
// created initially)
+void MyFrame::OnFollowLinks(wxCommandEvent& event)
+{
+ m_followLinks = event.IsChecked();
+}
+
void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
{
AddEntry(wxFSWPath_Dir);
void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
{
AddEntry(wxFSWPath_Dir);
wxString prefix;
bool ok = false;
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:
switch ( type )
{
case wxFSWPath_Dir:
- ok = m_watcher->Add(wxFileName::DirName(filename));
+ ok = m_watcher->Add(fn);
prefix = "Dir: ";
break;
case wxFSWPath_Tree:
prefix = "Dir: ";
break;
case wxFSWPath_Tree:
- ok = m_watcher->AddTree(wxFileName::DirName(filename));
+ ok = m_watcher->AddTree(fn);
prefix = "Tree: ";
break;
prefix = "Tree: ";
break;
+ wxString path = m_filesList->GetItemText(idx).Mid(6);
+
+ // This will tell wxFileSystemWatcher whether to dereference symlinks
+ wxFileName fn = wxFileName::DirName(path);
+ if (!m_followLinks)
+ {
+ fn.DontFollowLink();
+ }
+
// TODO we know it is a dir, but it doesn't have to be
// TODO we know it is a dir, but it doesn't have to be
- if (m_filesList->GetItemText(idx).StartsWith("Dir: ", &path))
+ if (m_filesList->GetItemText(idx).StartsWith("Dir: "))
- ret = m_watcher->Remove(wxFileName::DirName(path));
+ ret = m_watcher->Remove(fn);
- else if (m_filesList->GetItemText(idx).StartsWith("Tree: ", &path))
+ else if (m_filesList->GetItemText(idx).StartsWith("Tree: "))
- ret = m_watcher->RemoveTree(wxFileName::DirName(path));
+ ret = m_watcher->RemoveTree(fn);
+void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
+{
+ event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
+}
+
void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
{
// TODO remove when code is rock-solid
void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
{
// TODO remove when code is rock-solid
};
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.
AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
// Add the path itself explicitly as Traverse() doesn't return it.
AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
#endif // __WINDOWS__
wxDir dir(path.GetFullPath());
#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);
RemoveTraverser traverser(this, filespec);
- dir.Traverse(traverser, filespec);
+ dir.Traverse(traverser, filespec, flags);
// As in AddTree() above, handle the path itself explicitly.
Remove(path);
// As in AddTree() above, handle the path itself explicitly.
Remove(path);
public:
TreeTester() : subdirs(5), files(3) {}
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());
{
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
// Create a branch of 5 numbered subdirs, each containing 3
// numbered files
wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
wxFile::write);
}
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__
// Store the initial count; there may already be some watches
const int initial = m_watcher->GetWatchedPathsCount();
// Store the initial count; there may already be some watches
const int initial = m_watcher->GetWatchedPathsCount();
+ GrowTree(dir, false /* no symlinks */);
m_watcher->AddTree(dir);
const int plustree = m_watcher->GetWatchedPathsCount();
m_watcher->AddTree(dir);
const int plustree = m_watcher->GetWatchedPathsCount();
CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
m_watcher->RemoveTree(dir);
CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
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)
}
void WatchTreeWithFilespec(const wxFileName& dir)