forked from Mirrors/openclonk
264 lines
7.7 KiB
C++
264 lines
7.7 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2008-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
// An inotify wrapper
|
|
|
|
#include <C4Include.h>
|
|
#include <C4FileMonitor.h>
|
|
#include <C4Application.h>
|
|
|
|
#include <StdFile.h>
|
|
|
|
#ifdef HAVE_SYS_INOTIFY_H
|
|
#include <sys/inotify.h>
|
|
#include <errno.h>
|
|
|
|
C4FileMonitor::C4FileMonitor(ChangeNotify pCallback): fStarted(false), pCallback(pCallback)
|
|
{
|
|
fd = inotify_init1(IN_CLOEXEC);
|
|
if (fd == -1) fd = inotify_init();
|
|
if (fd == -1) LogF("inotify_init %s", strerror(errno));
|
|
}
|
|
|
|
C4FileMonitor::~C4FileMonitor()
|
|
{
|
|
if (fStarted)
|
|
{
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.RemoveProc(this);
|
|
Thread.ClearCallback(Ev_FileChange, this);
|
|
}
|
|
while (close(fd) == -1 && errno == EINTR) { }
|
|
}
|
|
|
|
void C4FileMonitor::StartMonitoring()
|
|
{
|
|
if (fStarted) return;
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.AddProc(this);
|
|
Thread.SetCallback(Ev_FileChange, this);
|
|
fStarted = true;
|
|
}
|
|
|
|
void C4FileMonitor::AddDirectory(const char * file)
|
|
{
|
|
// Add IN_CLOSE_WRITE?
|
|
int wd = inotify_add_watch(fd, file, IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_MOVE_SELF | IN_ONLYDIR);
|
|
if (wd == -1)
|
|
LogF("inotify_add_watch %s", strerror(errno));
|
|
watch_descriptors[wd] = file;
|
|
}
|
|
|
|
bool C4FileMonitor::Execute(int iTimeout, pollfd * pfd) // some other thread
|
|
{
|
|
if ((pfd->revents & pfd->events) != POLLIN || pfd->fd != fd)
|
|
LogF("C4FileMonitor::Execute unexpectedly called %d %d %hd %hd", fd, pfd->fd, pfd->events, pfd->revents);
|
|
char buf[sizeof(inotify_event) + _MAX_FNAME + 1];
|
|
inotify_event* event = new (buf) inotify_event;
|
|
if (read(fd, buf, sizeof(buf)) > 0)
|
|
{
|
|
const char * file = watch_descriptors[event->wd];
|
|
uint32_t mask = event->mask;
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
if (mask & IN_CREATE)
|
|
Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
|
|
if (mask & IN_MODIFY)
|
|
Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
|
|
if (mask & IN_MOVED_TO)
|
|
Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
|
|
if (mask & IN_MOVE_SELF)
|
|
Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
|
|
// FIXME: (*(inotify_event*)buf).name);
|
|
}
|
|
else
|
|
{
|
|
Log("inotify buffer too small");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread
|
|
{
|
|
if (eEvent != Ev_FileChange) return;
|
|
pCallback((const char *)pEventData, 0);
|
|
}
|
|
|
|
void C4FileMonitor::GetFDs(std::vector<struct pollfd> & fds)
|
|
{
|
|
pollfd pfd = { fd, POLLIN, 0 };
|
|
fds.push_back(pfd);
|
|
}
|
|
|
|
#elif defined(_WIN32)
|
|
#include <C4windowswrapper.h>
|
|
|
|
C4FileMonitor::C4FileMonitor(ChangeNotify pCallback)
|
|
: fStarted(false), pCallback(pCallback), pWatches(NULL)
|
|
{
|
|
hEvent = CreateEvent(NULL, true, false, NULL);
|
|
}
|
|
|
|
C4FileMonitor::~C4FileMonitor()
|
|
{
|
|
if (fStarted)
|
|
{
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.RemoveProc(this);
|
|
Thread.ClearCallback(Ev_FileChange, this);
|
|
}
|
|
while (pWatches)
|
|
{
|
|
CloseHandle(pWatches->hDir);
|
|
TreeWatch *pDelete = pWatches;
|
|
pWatches = pWatches->Next;
|
|
delete pDelete;
|
|
}
|
|
CloseHandle(hEvent);
|
|
}
|
|
|
|
void C4FileMonitor::StartMonitoring()
|
|
{
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.AddProc(this);
|
|
Thread.SetCallback(Ev_FileChange, this);
|
|
fStarted = true;
|
|
}
|
|
|
|
const DWORD C4FileMonitorNotifies = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
|
|
void C4FileMonitor::AddDirectory(const char *szDir)
|
|
{
|
|
// Create file handle
|
|
HANDLE hDir = CreateFileW(GetWideChar(szDir), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
|
|
if (hDir == INVALID_HANDLE_VALUE) return;
|
|
// Create tree watch structure
|
|
TreeWatch *pWatch = new TreeWatch();
|
|
pWatch->hDir = hDir;
|
|
pWatch->DirName = szDir;
|
|
// Build description of async operation
|
|
ZeroMem(&pWatch->ov, sizeof(pWatch->ov));
|
|
pWatch->ov.hEvent = hEvent;
|
|
// Add to list
|
|
pWatch->Next = pWatches;
|
|
pWatches = pWatch;
|
|
// Start async directory change notification
|
|
if (!ReadDirectoryChangesW(hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, NULL, &pWatch->ov, NULL))
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
delete pWatch;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool C4FileMonitor::Execute(int iTimeout, pollfd *)
|
|
{
|
|
// Check event
|
|
if (WaitForSingleObject(hEvent, iTimeout) != WAIT_OBJECT_0)
|
|
return true;
|
|
// Check handles
|
|
for (TreeWatch *pWatch = pWatches; pWatch; pWatch = pWatch->Next)
|
|
{
|
|
DWORD dwBytes = 0;
|
|
// Has a notification?
|
|
if (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false))
|
|
{
|
|
// Read notifications
|
|
const char *pPos = pWatch->Buffer;
|
|
for (;;)
|
|
{
|
|
const _FILE_NOTIFY_INFORMATION *pNotify = reinterpret_cast<const _FILE_NOTIFY_INFORMATION *>(pPos);
|
|
// Handle
|
|
HandleNotify(pWatch->DirName.getData(), pNotify);
|
|
// Get next entry
|
|
if (!pNotify->NextEntryOffset) break;
|
|
pPos += pNotify->NextEntryOffset;
|
|
if (pPos >= pWatch->Buffer + Min<size_t>(sizeof(pWatch->Buffer), dwBytes))
|
|
break;
|
|
break;
|
|
}
|
|
// Restart directory change notification (flush queue)
|
|
ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, NULL, &pWatch->ov, NULL);
|
|
dwBytes = 0;
|
|
while (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false))
|
|
{
|
|
ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, NULL, &pWatch->ov, NULL);
|
|
dwBytes = 0;
|
|
}
|
|
}
|
|
}
|
|
ResetEvent(hEvent);
|
|
return true;
|
|
}
|
|
|
|
void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread
|
|
{
|
|
if (eEvent != Ev_FileChange) return;
|
|
pCallback((const char *)pEventData, 0);
|
|
StdBuf::DeletePointer(pEventData);
|
|
}
|
|
|
|
HANDLE C4FileMonitor::GetEvent()
|
|
{
|
|
return hEvent;
|
|
}
|
|
|
|
void C4FileMonitor::HandleNotify(const char *szDir, const _FILE_NOTIFY_INFORMATION *pNotify)
|
|
{
|
|
// Get filename length
|
|
UINT iCodePage = CP_UTF8;
|
|
int iFileNameBytes = WideCharToMultiByte(iCodePage, 0,
|
|
pNotify->FileName, pNotify->FileNameLength / 2, NULL, 0, NULL, NULL);
|
|
// Set up filename buffer
|
|
StdCopyStrBuf Path(szDir);
|
|
Path.AppendChar(DirectorySeparator);
|
|
Path.Grow(iFileNameBytes);
|
|
char *pFilename = Path.getMPtr(SLen(Path.getData()));
|
|
// Convert filename
|
|
int iWritten = WideCharToMultiByte(iCodePage, 0,
|
|
pNotify->FileName, pNotify->FileNameLength / 2,
|
|
pFilename, iFileNameBytes,
|
|
NULL, NULL);
|
|
if (iWritten != iFileNameBytes)
|
|
Path.Shrink(iFileNameBytes+1);
|
|
// Send notification
|
|
Application.InteractiveThread.PushEvent(Ev_FileChange, Path.GrabPointer());
|
|
}
|
|
|
|
#elif not defined(__APPLE__)
|
|
|
|
// Stubs
|
|
C4FileMonitor::C4FileMonitor(ChangeNotify pCallback)
|
|
{
|
|
#ifdef HAVE_SYS_INOTIFY_H
|
|
C4FileMonitor::pCallback = pCallback;
|
|
#endif
|
|
}
|
|
|
|
C4FileMonitor::~C4FileMonitor() { }
|
|
bool C4FileMonitor::Execute(int iTimeout, pollfd *) { return false; /* blarg... function must return a value */ }
|
|
void C4FileMonitor::StartMonitoring() {}
|
|
void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) {}
|
|
void C4FileMonitor::AddDirectory(const char *szDir) {}
|
|
|
|
// Signal for calling Execute()
|
|
#ifdef STDSCHEDULER_USE_EVENTS
|
|
HANDLE C4FileMonitor::GetEvent() { return 0; }
|
|
#else
|
|
void C4FileMonitor::GetFDs(std::vector<struct pollfd> & FDs) { }
|
|
#endif
|
|
|
|
#endif // HAVE_SYS_INOTIFY_H
|