Make wxIOCPService::GetStatus() smarter about its return value, it makes sense to encapsulate the convention used to indicate the thread exit condition inside wxIOCPService class itself instead of sharing it between it wxIOCPThread itself. It will also make it easier to detect more detailed error conditions in this code. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@76185 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
470 lines
14 KiB
C++
470 lines
14 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/msw/fswatcher.cpp
|
|
// Purpose: wxMSWFileSystemWatcher
|
|
// Author: Bartosz Bekier
|
|
// Created: 2009-05-26
|
|
// Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_FSWATCHER
|
|
|
|
#include "wx/fswatcher.h"
|
|
#include "wx/thread.h"
|
|
#include "wx/sharedptr.h"
|
|
#include "wx/msw/fswatcher.h"
|
|
#include "wx/msw/private.h"
|
|
#include "wx/private/fswatcher.h"
|
|
|
|
// ============================================================================
|
|
// wxFSWatcherImplMSW implementation
|
|
// ============================================================================
|
|
|
|
class wxFSWatcherImplMSW : public wxFSWatcherImpl
|
|
{
|
|
public:
|
|
wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher);
|
|
|
|
virtual ~wxFSWatcherImplMSW();
|
|
|
|
bool SetUpWatch(wxFSWatchEntryMSW& watch);
|
|
|
|
void SendEvent(wxFileSystemWatcherEvent& evt);
|
|
|
|
protected:
|
|
bool Init();
|
|
|
|
// adds watch to be monitored for file system changes
|
|
virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch);
|
|
|
|
virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch);
|
|
|
|
private:
|
|
bool DoSetUpWatch(wxFSWatchEntryMSW& watch);
|
|
|
|
static int Watcher2NativeFlags(int flags);
|
|
|
|
wxIOCPService m_iocp;
|
|
wxIOCPThread m_workerThread;
|
|
};
|
|
|
|
wxFSWatcherImplMSW::wxFSWatcherImplMSW(wxFileSystemWatcherBase* watcher) :
|
|
wxFSWatcherImpl(watcher),
|
|
m_workerThread(this, &m_iocp)
|
|
{
|
|
}
|
|
|
|
wxFSWatcherImplMSW::~wxFSWatcherImplMSW()
|
|
{
|
|
// order the worker thread to finish & wait
|
|
m_workerThread.Finish();
|
|
if (m_workerThread.Wait() != 0)
|
|
{
|
|
wxLogError(_("Ungraceful worker thread termination"));
|
|
}
|
|
|
|
// remove all watches
|
|
(void) RemoveAll();
|
|
}
|
|
|
|
bool wxFSWatcherImplMSW::Init()
|
|
{
|
|
wxCHECK_MSG( !m_workerThread.IsAlive(), false,
|
|
"Watcher service is already initialized" );
|
|
|
|
if (m_workerThread.Create() != wxTHREAD_NO_ERROR)
|
|
{
|
|
wxLogError(_("Unable to create IOCP worker thread"));
|
|
return false;
|
|
}
|
|
|
|
// we have valid iocp service and thread
|
|
if (m_workerThread.Run() != wxTHREAD_NO_ERROR)
|
|
{
|
|
wxLogError(_("Unable to start IOCP worker thread"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// adds watch to be monitored for file system changes
|
|
bool wxFSWatcherImplMSW::DoAdd(wxSharedPtr<wxFSWatchEntryMSW> watch)
|
|
{
|
|
// setting up wait for directory changes
|
|
if (!DoSetUpWatch(*watch))
|
|
return false;
|
|
|
|
// associating handle with completion port
|
|
return m_iocp.Add(watch);
|
|
}
|
|
|
|
bool
|
|
wxFSWatcherImplMSW::DoRemove(wxSharedPtr<wxFSWatchEntryMSW> watch)
|
|
{
|
|
return m_iocp.ScheduleForRemoval(watch);
|
|
}
|
|
|
|
// TODO ensuring that we have not already set watch for this handle/dir?
|
|
bool wxFSWatcherImplMSW::SetUpWatch(wxFSWatchEntryMSW& watch)
|
|
{
|
|
wxCHECK_MSG( watch.IsOk(), false, "Invalid watch" );
|
|
if (m_watches.find(watch.GetPath()) == m_watches.end())
|
|
{
|
|
wxLogTrace(wxTRACE_FSWATCHER, "Path '%s' is not watched",
|
|
watch.GetPath());
|
|
return false;
|
|
}
|
|
|
|
wxLogTrace(wxTRACE_FSWATCHER, "Setting up watch for file system changes...");
|
|
return DoSetUpWatch(watch);
|
|
}
|
|
|
|
void wxFSWatcherImplMSW::SendEvent(wxFileSystemWatcherEvent& evt)
|
|
{
|
|
// called from worker thread, so posting event in thread-safe way
|
|
wxQueueEvent(m_watcher->GetOwner(), evt.Clone());
|
|
}
|
|
|
|
bool wxFSWatcherImplMSW::DoSetUpWatch(wxFSWatchEntryMSW& watch)
|
|
{
|
|
BOOL bWatchSubtree = 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 ret = ReadDirectoryChangesW(watch.GetHandle(), watch.GetBuffer(),
|
|
wxFSWatchEntryMSW::BUFFER_SIZE,
|
|
bWatchSubtree,
|
|
flags, NULL,
|
|
watch.GetOverlapped(), NULL);
|
|
if (!ret)
|
|
{
|
|
wxLogSysError(_("Unable to set up watch for '%s'"),
|
|
watch.GetPath());
|
|
}
|
|
|
|
return ret != 0;
|
|
}
|
|
|
|
// TODO we should only specify those flags, which interest us
|
|
// this needs a bit of thinking, quick impl for now
|
|
int wxFSWatcherImplMSW::Watcher2NativeFlags(int WXUNUSED(flags))
|
|
{
|
|
static DWORD all_events = FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
|
|
FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION |
|
|
FILE_NOTIFY_CHANGE_SECURITY;
|
|
|
|
return all_events;
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// wxFSWatcherImplMSW helper classes implementation
|
|
// ============================================================================
|
|
|
|
wxIOCPThread::wxIOCPThread(wxFSWatcherImplMSW* service, wxIOCPService* iocp) :
|
|
wxThread(wxTHREAD_JOINABLE),
|
|
m_service(service), m_iocp(iocp)
|
|
{
|
|
}
|
|
|
|
// finishes this thread
|
|
bool wxIOCPThread::Finish()
|
|
{
|
|
wxLogTrace(wxTRACE_FSWATCHER, "Posting empty status!");
|
|
|
|
// send "empty" packet to i/o completion port, just to wake
|
|
return m_iocp->PostEmptyStatus();
|
|
}
|
|
|
|
wxThread::ExitCode wxIOCPThread::Entry()
|
|
{
|
|
wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Started IOCP thread");
|
|
|
|
// read events in a loop until we get false, which means we should exit
|
|
while ( ReadEvents() );
|
|
|
|
wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Ended IOCP thread");
|
|
return (ExitCode)0;
|
|
}
|
|
|
|
// wait for events to occur, read them and send to interested parties
|
|
// returns false it empty status was read, which means we whould exit
|
|
// true otherwise
|
|
bool wxIOCPThread::ReadEvents()
|
|
{
|
|
unsigned long count = 0;
|
|
wxFSWatchEntryMSW* watch = NULL;
|
|
OVERLAPPED* overlapped = NULL;
|
|
switch ( m_iocp->GetStatus(&count, &watch, &overlapped) )
|
|
{
|
|
case wxIOCPService::Status_OK:
|
|
break; // nothing special to do, continue normally
|
|
|
|
case wxIOCPService::Status_Error:
|
|
return true; // error was logged already, we don't want to exit
|
|
|
|
case wxIOCPService::Status_Exit:
|
|
return false; // stop reading events
|
|
}
|
|
|
|
// if the thread got woken up but we got an empty packet it means that
|
|
// there was an overflow, too many events and not all could fit in
|
|
// the watch buffer. In this case, ReadDirectoryChangesW dumps the
|
|
// buffer.
|
|
if (!count && watch)
|
|
{
|
|
wxLogTrace(wxTRACE_FSWATCHER, "[iocp] Event queue overflowed: path=\"%s\"",
|
|
watch->GetPath());
|
|
|
|
if (watch->GetFlags() & wxFSW_EVENT_WARNING)
|
|
{
|
|
wxFileSystemWatcherEvent
|
|
overflowEvent(wxFSW_EVENT_WARNING, wxFSW_WARNING_OVERFLOW);
|
|
overflowEvent.SetPath(watch->GetPath());
|
|
SendEvent(overflowEvent);
|
|
}
|
|
|
|
// overflow is not a fatal error, we still want to get future events
|
|
// reissue the watch
|
|
(void) m_service->SetUpWatch(*watch);
|
|
return true;
|
|
}
|
|
|
|
// in case of spurious wakeup
|
|
if (!count || !watch)
|
|
return true;
|
|
|
|
wxLogTrace( wxTRACE_FSWATCHER, "[iocp] Read entry: path='%s'",
|
|
watch->GetPath());
|
|
|
|
// First check if we're still interested in this watch, we could have
|
|
// removed it in the meanwhile.
|
|
if ( m_iocp->CompleteRemoval(watch) )
|
|
return true;
|
|
|
|
// extract events from buffer info our vector container
|
|
wxVector<wxEventProcessingData> events;
|
|
const char* memory = static_cast<const char*>(watch->GetBuffer());
|
|
int offset = 0;
|
|
do
|
|
{
|
|
const FILE_NOTIFY_INFORMATION* e =
|
|
static_cast<const FILE_NOTIFY_INFORMATION*>((const void*)memory);
|
|
|
|
events.push_back(wxEventProcessingData(e, watch));
|
|
|
|
offset = e->NextEntryOffset;
|
|
memory += offset;
|
|
}
|
|
while (offset);
|
|
|
|
// process events
|
|
ProcessNativeEvents(events);
|
|
|
|
// reissue the watch. ignore possible errors, we will return true anyway
|
|
(void) m_service->SetUpWatch(*watch);
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxIOCPThread::ProcessNativeEvents(wxVector<wxEventProcessingData>& events)
|
|
{
|
|
wxVector<wxEventProcessingData>::iterator it = events.begin();
|
|
for ( ; it != events.end(); ++it )
|
|
{
|
|
const FILE_NOTIFY_INFORMATION& e = *(it->nativeEvent);
|
|
const wxFSWatchEntryMSW* watch = it->watch;
|
|
|
|
wxLogTrace( wxTRACE_FSWATCHER, "[iocp] %s",
|
|
FileNotifyInformationToString(e));
|
|
|
|
int nativeFlags = e.Action;
|
|
int flags = Native2WatcherFlags(nativeFlags);
|
|
if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
|
|
{
|
|
wxFileSystemWatcherEvent
|
|
event(flags,
|
|
flags & wxFSW_EVENT_ERROR ? wxFSW_WARNING_NONE
|
|
: wxFSW_WARNING_GENERAL);
|
|
SendEvent(event);
|
|
}
|
|
// filter out ignored events and those not asked for.
|
|
// we never filter out warnings or exceptions
|
|
else if ((flags == 0) || !(flags & watch->GetFlags()))
|
|
{
|
|
return;
|
|
}
|
|
// rename case
|
|
else if (nativeFlags == FILE_ACTION_RENAMED_OLD_NAME)
|
|
{
|
|
wxFileName oldpath = GetEventPath(*watch, e);
|
|
wxFileName newpath;
|
|
|
|
// newpath should be in the next entry. what if there isn't?
|
|
++it;
|
|
if ( it != events.end() )
|
|
{
|
|
newpath = GetEventPath(*(it->watch), *(it->nativeEvent));
|
|
}
|
|
wxFileSystemWatcherEvent event(flags, oldpath, newpath);
|
|
SendEvent(event);
|
|
}
|
|
// all other events
|
|
else
|
|
{
|
|
// CHECK I heard that returned path can be either in short on long
|
|
// form...need to account for that!
|
|
wxFileName path = GetEventPath(*watch, e);
|
|
// For files, check that it matches any filespec
|
|
if ( m_service->MatchesFilespec(path, watch->GetFilespec()) )
|
|
{
|
|
wxFileSystemWatcherEvent event(flags, path, path);
|
|
SendEvent(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxIOCPThread::SendEvent(wxFileSystemWatcherEvent& evt)
|
|
{
|
|
wxLogTrace(wxTRACE_FSWATCHER, "[iocp] EVT: %s", evt.ToString());
|
|
m_service->SendEvent(evt);
|
|
}
|
|
|
|
int wxIOCPThread::Native2WatcherFlags(int flags)
|
|
{
|
|
static const int flag_mapping[][2] = {
|
|
{ FILE_ACTION_ADDED, wxFSW_EVENT_CREATE },
|
|
{ FILE_ACTION_REMOVED, wxFSW_EVENT_DELETE },
|
|
|
|
// TODO take attributes into account to see what happened
|
|
{ FILE_ACTION_MODIFIED, wxFSW_EVENT_MODIFY },
|
|
|
|
{ FILE_ACTION_RENAMED_OLD_NAME, wxFSW_EVENT_RENAME },
|
|
|
|
// ignored as it should always be matched with ***_OLD_NAME
|
|
{ FILE_ACTION_RENAMED_NEW_NAME, 0 },
|
|
};
|
|
|
|
for (unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) {
|
|
if (flags == flag_mapping[i][0])
|
|
return flag_mapping[i][1];
|
|
}
|
|
|
|
// never reached
|
|
wxFAIL_MSG(wxString::Format("Unknown file notify change %u", flags));
|
|
return -1;
|
|
}
|
|
|
|
wxString wxIOCPThread::FileNotifyInformationToString(
|
|
const FILE_NOTIFY_INFORMATION& e)
|
|
{
|
|
wxString fname(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
|
|
return wxString::Format("Event: offset=%d, action=%d, len=%d, "
|
|
"name(approx)='%s'", e.NextEntryOffset, e.Action,
|
|
e.FileNameLength, fname);
|
|
}
|
|
|
|
wxFileName wxIOCPThread::GetEventPath(const wxFSWatchEntryMSW& watch,
|
|
const FILE_NOTIFY_INFORMATION& e)
|
|
{
|
|
wxFileName path = watch.GetPath();
|
|
if (path.IsDir())
|
|
{
|
|
wxString rel(e.FileName, e.FileNameLength / sizeof(e.FileName[0]));
|
|
int pathFlags = wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR;
|
|
path = wxFileName(path.GetPath(pathFlags) + rel);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// wxMSWFileSystemWatcher implementation
|
|
// ============================================================================
|
|
|
|
wxMSWFileSystemWatcher::wxMSWFileSystemWatcher() :
|
|
wxFileSystemWatcherBase()
|
|
{
|
|
(void) Init();
|
|
}
|
|
|
|
wxMSWFileSystemWatcher::wxMSWFileSystemWatcher(const wxFileName& path,
|
|
int events) :
|
|
wxFileSystemWatcherBase()
|
|
{
|
|
if (!Init())
|
|
return;
|
|
|
|
Add(path, events);
|
|
}
|
|
|
|
bool wxMSWFileSystemWatcher::Init()
|
|
{
|
|
m_service = new wxFSWatcherImplMSW(this);
|
|
bool ret = m_service->Init();
|
|
if (!ret)
|
|
{
|
|
delete m_service;
|
|
}
|
|
|
|
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 AddAny(path, events, wxFSWPath_Tree);
|
|
}
|
|
|
|
#endif // wxUSE_FSWATCHER
|