Implement watching directory correctly in MSW wxFileSystemWatcher.

The directories used to be always monitored recursively, even when this wasn't
requested, in wxMSW implementation. Change this but also implement efficient
support for monitoring the entire hierarchies using the native support for
this.

Also update the sample to allow monitoring directories recursively as well.

See #12847.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@67693 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2011-05-03 23:31:39 +00:00
parent 4610ad4ecb
commit f8d3714816
5 changed files with 155 additions and 28 deletions

View File

@@ -55,6 +55,16 @@ enum
wxFSW_EVENT_WARNING | wxFSW_EVENT_ERROR wxFSW_EVENT_WARNING | wxFSW_EVENT_ERROR
}; };
// Type of the path watched, used only internally for now.
enum wxFSWPathType
{
wxFSWPath_None, // Invalid value for an initialized watch.
wxFSWPath_File, // Plain file.
wxFSWPath_Dir, // Watch a directory and the files in it.
wxFSWPath_Tree // Watch a directory and all its children recursively.
};
/** /**
* Event containing information about file system change. * Event containing information about file system change.
*/ */
@@ -179,19 +189,17 @@ typedef void (wxEvtHandler::*wxFileSystemWatcherEventFunction)
// wxFileSystemWatcherBase: interface for wxFileSystemWatcher // wxFileSystemWatcherBase: interface for wxFileSystemWatcher
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/** // Simple container to store information about one watched path.
* Simple container to store information about one watched file
*/
class wxFSWatchInfo class wxFSWatchInfo
{ {
public: public:
wxFSWatchInfo() : wxFSWatchInfo() :
m_path(wxEmptyString), m_events(-1) m_events(-1), m_type(wxFSWPath_None)
{ {
} }
wxFSWatchInfo(const wxString& path, int events) : wxFSWatchInfo(const wxString& path, int events, wxFSWPathType type) :
m_path(path), m_events(events) m_path(path), m_events(events), m_type(type)
{ {
} }
@@ -205,9 +213,15 @@ public:
return m_events; return m_events;
} }
wxFSWPathType GetType() const
{
return m_type;
}
protected: protected:
wxString m_path; wxString m_path;
int m_events; int m_events;
wxFSWPathType m_type;
}; };
WX_DECLARE_STRING_HASH_MAP(wxFSWatchInfo, wxFSWatchInfoMap); WX_DECLARE_STRING_HASH_MAP(wxFSWatchInfo, wxFSWatchInfoMap);
@@ -304,6 +318,11 @@ protected:
return path_copy.GetFullPath(); return path_copy.GetFullPath();
} }
// Delegates the real work of adding the path to wxFSWatcherImpl::Add() and
// updates m_watches if the new path was successfully added.
bool DoAdd(const wxFileName& path, int events, wxFSWPathType type);
wxFSWatchInfoMap m_watches; // path=>wxFSWatchInfo map wxFSWatchInfoMap m_watches; // path=>wxFSWatchInfo map
wxFSWatcherImpl* m_service; // file system events service wxFSWatcherImpl* m_service; // file system events service
wxEvtHandler* m_owner; // handler for file system events wxEvtHandler* m_owner; // handler for file system events

View File

@@ -23,6 +23,12 @@ public:
wxMSWFileSystemWatcher(const wxFileName& path, wxMSWFileSystemWatcher(const wxFileName& path,
int events = wxFSW_EVENT_ALL); int events = wxFSW_EVENT_ALL);
// Override the base class function to provide a much more efficient
// implementation for it using the platform native support for watching the
// entire directory trees.
virtual bool AddTree(const wxFileName& path, int events = wxFSW_EVENT_ALL,
const wxString& filter = wxEmptyString);
protected: protected:
bool Init(); bool Init();
}; };

View File

@@ -32,7 +32,9 @@ public:
MyFrame(const wxString& title); MyFrame(const wxString& title);
virtual ~MyFrame(); virtual ~MyFrame();
void AddDirectory(const wxString& dir); // Add an entry of the specified type asking the user for the filename if
// the one passed to this function is empty.
void AddEntry(wxFSWPathType type, wxString filename = wxString());
bool CreateWatcherIfNecessary(); bool CreateWatcherIfNecessary();
@@ -47,6 +49,7 @@ private:
void OnAbout(wxCommandEvent& event); void OnAbout(wxCommandEvent& event);
void OnAdd(wxCommandEvent& event); void OnAdd(wxCommandEvent& event);
void OnAddTree(wxCommandEvent& event);
void OnRemove(wxCommandEvent& event); void OnRemove(wxCommandEvent& event);
void OnFileSystemEvent(wxFileSystemWatcherEvent& event); void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
@@ -87,7 +90,7 @@ public:
if ( m_frame->CreateWatcherIfNecessary() ) if ( m_frame->CreateWatcherIfNecessary() )
{ {
if ( !m_dirToWatch.empty() ) if ( !m_dirToWatch.empty() )
m_frame->AddDirectory(m_dirToWatch); m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch);
} }
} }
@@ -144,7 +147,8 @@ MyFrame::MyFrame(const wxString& title)
MENU_ID_WATCH = 101, MENU_ID_WATCH = 101,
BTN_ID_ADD = 200, BTN_ID_ADD = 200,
BTN_ID_REMOVE = 201, BTN_ID_ADD_TREE,
BTN_ID_REMOVE
}; };
// ================================================================ // ================================================================
@@ -194,9 +198,11 @@ MyFrame::MyFrame(const wxString& title)
// buttons // buttons
wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add"); wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add");
wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree");
wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove"); wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove");
wxSizer *btnSizer = new wxGridSizer(2); wxSizer *btnSizer = new wxGridSizer(2);
btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL)); btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL));
btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL));
btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL)); btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL));
// and put it all together // and put it all together
@@ -253,6 +259,8 @@ MyFrame::MyFrame(const wxString& title)
// buttons // buttons
Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED, Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyFrame::OnAdd)); wxCommandEventHandler(MyFrame::OnAdd));
Connect(BTN_ID_ADD_TREE, wxEVT_COMMAND_BUTTON_CLICKED,
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));
@@ -318,29 +326,54 @@ void MyFrame::OnWatch(wxCommandEvent& event)
void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
{ {
AddEntry(wxFSWPath_Dir);
}
void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event))
{
AddEntry(wxFSWPath_Tree);
}
void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
{
if ( filename.empty() )
{
// TODO account for adding the files as well
filename = wxDirSelector("Choose a folder to watch", "",
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if ( filename.empty() )
return;
}
wxCHECK_RET(m_watcher, "Watcher not initialized"); wxCHECK_RET(m_watcher, "Watcher not initialized");
// TODO account for adding the files as well wxLogDebug("Adding %s: '%s'",
const wxString& dir = wxDirSelector("Choose a folder to watch", "", filename,
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); type == wxFSWPath_Dir ? "directory" : "directory tree");
if ( dir.empty() )
bool ok = false;
switch ( type )
{
case wxFSWPath_Dir:
ok = m_watcher->Add(wxFileName::DirName(filename));
break;
case wxFSWPath_Tree:
ok = m_watcher->AddTree(wxFileName::DirName(filename));
break;
case wxFSWPath_File:
case wxFSWPath_None:
wxFAIL_MSG( "Unexpected path type." );
}
if (!ok)
{
wxLogError("Error adding '%s' to watched paths", filename);
return; return;
AddDirectory(dir);
} }
void MyFrame::AddDirectory(const wxString& dir) m_filesList->InsertItem(m_filesList->GetItemCount(), filename);
{
wxLogDebug("Adding directory: '%s'", dir);
if (!m_watcher->Add(wxFileName::DirName(dir), wxFSW_EVENT_ALL))
{
wxLogError("Error adding '%s' to watched paths", dir);
}
else
{
m_filesList->InsertItem(m_filesList->GetItemCount(), dir);
}
} }
void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))

View File

@@ -79,10 +79,30 @@ wxFileSystemWatcherBase::~wxFileSystemWatcherBase()
bool wxFileSystemWatcherBase::Add(const wxFileName& path, int events) bool wxFileSystemWatcherBase::Add(const wxFileName& path, int events)
{ {
// args validation & consistency checks wxFSWPathType type = wxFSWPath_None;
if (!path.FileExists() && !path.DirExists()) if ( path.FileExists() )
{
type = wxFSWPath_File;
}
else if ( path.DirExists() )
{
type = wxFSWPath_Dir;
}
else
{
wxLogError(_("Can't monitor non-existent path \"%s\" for changes."),
path.GetFullPath());
return false; return false;
}
return DoAdd(path, events, type);
}
bool
wxFileSystemWatcherBase::DoAdd(const wxFileName& path,
int events,
wxFSWPathType type)
{
wxString canonical = GetCanonicalPath(path); wxString canonical = GetCanonicalPath(path);
if (canonical.IsEmpty()) if (canonical.IsEmpty())
return false; return false;
@@ -91,7 +111,7 @@ bool wxFileSystemWatcherBase::Add(const wxFileName& path, int events)
wxString::Format("Path '%s' is already watched", canonical)); wxString::Format("Path '%s' is already watched", canonical));
// adding a path in a platform specific way // adding a path in a platform specific way
wxFSWatchInfo watch(canonical, events); wxFSWatchInfo watch(canonical, events, type);
if ( !m_service->Add(watch) ) if ( !m_service->Add(watch) )
return false; return false;

View File

@@ -136,9 +136,32 @@ void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent& evt)
bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch) bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch)
{ {
BOOL bWatchSubtree wxDUMMY_INITIALIZE(FALSE);
switch ( watch.GetType() )
{
case wxFSWPath_File:
wxLogError(_("Monitoring individual files for changes is not "
"supported currently."));
return false;
case wxFSWPath_Dir:
bWatchSubtree = FALSE;
break;
case wxFSWPath_Tree:
bWatchSubtree = TRUE;
break;
case wxFSWPath_None:
wxFAIL_MSG( "Invalid watch type." );
return false;
}
int flags = Watcher2NativeFlags(watch.GetFlags()); int flags = Watcher2NativeFlags(watch.GetFlags());
int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(), int ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
wxFSWatchEntryMSW::BUFFER_SIZE, FALSE, wxFSWatchEntryMSW::BUFFER_SIZE,
bWatchSubtree,
flags, NULL, flags, NULL,
watch.GetOverlapped(), NULL); watch.GetOverlapped(), NULL);
if (!ret) if (!ret)
@@ -385,4 +408,30 @@ bool wxMSWFileSystemWatcher::Init()
return ret; return ret;
} }
bool
wxMSWFileSystemWatcher::AddTree(const wxFileName& path,
int events,
const wxString& filter)
{
if ( !filter.empty() )
{
// Use the inefficient generic version as we can only monitor
// everything under the given directory.
//
// Notice that it would probably be better to still monitor everything
// natively and filter out the changes we're not interested in.
return wxFileSystemWatcherBase::AddTree(path, events, filter);
}
if ( !path.DirExists() )
{
wxLogError(_("Can't monitor non-existent directory \"%s\" for changes."),
path.GetFullPath());
return false;
}
return DoAdd(path, events, wxFSWPath_Tree);
}
#endif // wxUSE_FSWATCHER #endif // wxUSE_FSWATCHER