dolphin/Externals/wxWidgets3/src/msw/fswatcher.cpp

465 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;
if (!m_iocp->GetStatus(&count, &watch, &overlapped))
return true; // error was logged already, we don't want to exit
// this is our exit condition, so we return false
if (!count && !watch && !overlapped)
return false;
// 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