Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More complete windows implementation #22

Merged
merged 2 commits into from
Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions source/cppfs/include/cppfs/AbstractFileWatcherBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,19 @@ class CPPFS_API AbstractFileWatcherBackend

/**
* @brief
* Start watching files (blocking)
* Start watching files
*
* @param[in] timeoutMilliSeconds
* timeout value in milliseconds, if less than zero, timeout is infinite
*
* @remarks
* The function is supposed to block until one or more file system
* events have occured. For each event, onFileEvent has to be called.
* After all events have been processed, the function shall return.
* This function shall watch the file system and block until one or more
* events have occured, or if the timeout has been exceeded (timeout >= 0).
* For each event, onFileEvent has to be called with the type of the event and
* a file handle to the file or directory. After all events have been
* processed, the function shall return.
*/
virtual void watch() = 0;
virtual void watch(int timeoutMilliSeconds) = 0;


protected:
Expand Down
16 changes: 10 additions & 6 deletions source/cppfs/include/cppfs/FileWatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,20 @@ class CPPFS_API FileWatcher

/**
* @brief
* Start watching files (blocking)
* Start watching files
*
* @param[in] timeoutMilliSeconds
* timeout value in milliseconds. If less than zero, timeout is infinite,
* i.e. call will be blocking.
*
* @remarks
* This function begins to watch the file system and blocks until
* one or more events have occured. On every event, onFileEvent()
* is called with the type of the event and a file handle to the
* file or directory. Afterwards, the function returns.
* This function begins to watch the file system and blocks until one or more
* events have occured, or the timeout (if set) has been exceeded.
* On every event, onFileEvent() is called with the type of the event and
* a file handle to the file or directory. Afterwards, the function returns.
* To listen to more events, call watch() again.
*/
void watch();
void watch(int timeoutMilliSeconds = -1);


protected:
Expand Down
2 changes: 1 addition & 1 deletion source/cppfs/include/cppfs/null/NullFileWatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend
// Virtual AbstractFileWatcherBackend functions
virtual AbstractFileSystem * fs() const override;
virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override;
virtual void watch() override;
virtual void watch(int timeoutMilliSeconds) override;


protected:
Expand Down
2 changes: 1 addition & 1 deletion source/cppfs/include/cppfs/posix/LocalFileWatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend
// Virtual AbstractFileWatcherBackend functions
virtual AbstractFileSystem * fs() const override;
virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override;
virtual void watch() override;
virtual void watch(int timeoutMilliSeconds) override;


protected:
Expand Down
23 changes: 20 additions & 3 deletions source/cppfs/include/cppfs/windows/LocalFileWatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@


#include <memory>
#include <mutex>
#include <vector>

#include <cppfs/AbstractFileWatcherBackend.h>

#include <cppfs/FileHandle.h>

namespace cppfs
{
Expand Down Expand Up @@ -41,11 +43,26 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend
// Virtual AbstractFileWatcherBackend functions
virtual AbstractFileSystem * fs() const override;
virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override;
virtual void watch() override;
virtual void watch(int timeoutMilliSeconds) override;

protected:
/**
* @brief
* Watcher entry
*/
struct Watcher {
std::shared_ptr<void> handle;
FileHandle fileHandle;
unsigned int events;
RecursiveMode recursive;
std::shared_ptr<void> platform;
};

protected:
std::shared_ptr<LocalFileSystem> m_fs; ///< File system that created this watcher
std::shared_ptr<LocalFileSystem> m_fs; ///< File system that created this watcher
std::shared_ptr<void> m_waitStopEvent; ///< Event to stop watch function
std::shared_ptr<void> m_watchersCS; ///< Watchers critical section
std::vector<Watcher> m_watchers; ///< Watchers
};


Expand Down
4 changes: 2 additions & 2 deletions source/cppfs/source/FileWatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ void FileWatcher::removeHandler(FileEventHandler * eventHandler)
}
}

void FileWatcher::watch()
void FileWatcher::watch(int timeoutMilliSeconds)
{
// Check backend
if (!m_backend) {
return;
}

// Watch files
m_backend->watch();
m_backend->watch(timeoutMilliSeconds);
}

void FileWatcher::onFileEvent(FileHandle & fh, FileEvent event)
Expand Down
2 changes: 1 addition & 1 deletion source/cppfs/source/null/NullFileWatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void NullFileWatcher::add(FileHandle &, unsigned int, RecursiveMode)
{
}

void NullFileWatcher::watch()
void NullFileWatcher::watch(int timeoutMilliSeconds)
{
}

Expand Down
20 changes: 18 additions & 2 deletions source/cppfs/source/posix/LocalFileWatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r
uint32_t flags = 0;
if (events & FileCreated) flags |= IN_CREATE;
if (events & FileRemoved) flags |= IN_DELETE;
if (events & FileModified) flags |= IN_MODIFY;
if (events & FileModified) flags |= (IN_MODIFY | IN_ATTRIB);

// Create watcher
int handle = inotify_add_watch(m_inotify, fh.path().c_str(), flags);
Expand Down Expand Up @@ -78,13 +78,28 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r
m_watchers[handle].recursive = recursive;
}

void LocalFileWatcher::watch()
void LocalFileWatcher::watch(int timeoutMilliSeconds)
{
// Create buffer for receiving events
size_t bufSize = 64 * (sizeof(inotify_event) + NAME_MAX);
std::vector<char> buffer;
buffer.resize(bufSize);

if (timeoutMilliSeconds >= 0) {
fd_set set;
struct timeval timeout;
timeout.tv_sec = timeoutMilliSeconds / 1000;
timeout.tv_usec = (timeoutMilliSeconds - timeout.tv_sec * 1000) * 1000;

FD_ZERO(&set); /* clear the set */
FD_SET(m_inotify, &set); /* add our file descriptor to the set */

int rv = select(m_inotify + 1, &set, NULL, NULL, &timeout);
if (rv <= 0) {
return;
}
}

// Read events
int size = read(m_inotify, buffer.data(), bufSize);
if (size < 0) {
Expand All @@ -102,6 +117,7 @@ void LocalFileWatcher::watch()
if (event->mask & IN_CREATE) eventType = FileCreated;
else if (event->mask & IN_DELETE) eventType = FileRemoved;
else if (event->mask & IN_MODIFY) eventType = FileModified;
else if (event->mask & IN_ATTRIB) eventType = FileModified;

// Get watcher
auto & watcher = m_watchers[event->wd];
Expand Down
167 changes: 162 additions & 5 deletions source/cppfs/source/windows/LocalFileWatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,32 @@

#include <cppfs/windows/LocalFileSystem.h>

#include <Windows.h>

#include <algorithm>

namespace
{
struct LocalWatcher
{
OVERLAPPED ovl;
std::shared_ptr<VOID> event;
DWORD buffer[16384];
};

// NB! Using a std::mutex resulted in spurious deadlocks using VS2017, whilst
// using CRITICAL_SECTION works fine.
struct ScopedCriticalSection
{
LPCRITICAL_SECTION m_pcs;
ScopedCriticalSection(LPCRITICAL_SECTION pcs) : m_pcs(pcs) {
::EnterCriticalSection(m_pcs);
}
~ScopedCriticalSection() {
::LeaveCriticalSection(m_pcs);
}
};
}

namespace cppfs
{
Expand All @@ -11,27 +37,158 @@ namespace cppfs
LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr<LocalFileSystem> fs)
: AbstractFileWatcherBackend(fileWatcher)
, m_fs(fs)
, m_waitStopEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle)
, m_watchersCS(new CRITICAL_SECTION())
{
::InitializeCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get());
}

LocalFileWatcher::~LocalFileWatcher()
{
::DeleteCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get());
m_watchers.clear();
}

AbstractFileSystem * LocalFileWatcher::fs() const
{
return static_cast<AbstractFileSystem *>(m_fs.get());
}

void LocalFileWatcher::add(FileHandle &, unsigned int, RecursiveMode)
void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive)
{
// [TODO] Implement for Windows
HANDLE hDir = ::CreateFileA(fh.path().c_str(), // pointer to the directory name
FILE_LIST_DIRECTORY, // access (read/write) mode
FILE_SHARE_READ // share mode
| FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, // security descriptor
OPEN_EXISTING, // how to create
FILE_FLAG_BACKUP_SEMANTICS // file attributes
| FILE_FLAG_OVERLAPPED,
NULL); // file with attributes to copy
if (hDir == INVALID_HANDLE_VALUE)
throw std::runtime_error("failed to create directory listener");

std::shared_ptr<void> hDirectory(hDir, ::CloseHandle);
std::shared_ptr<void> hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle);
if (!hEvent)
throw std::runtime_error("failed creating wait event");

auto lw = std::make_shared<LocalWatcher>();
lw->event = std::move(hEvent);
lw->ovl.hEvent = lw->event.get();

Watcher w;
w.handle = std::move(hDirectory);
w.fileHandle = fh;
w.events = events;
w.recursive = recursive;
w.platform = std::move(lw);

::SetEvent(m_waitStopEvent.get());
ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get());
m_watchers.push_back(std::move(w));
}

void LocalFileWatcher::watch()
void LocalFileWatcher::watch(int timeoutMilliSeconds)
{
// [TODO] Implement for Windows
}
ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get());
std::vector<HANDLE> waitHandles;
waitHandles.push_back(m_waitStopEvent.get());
for (auto it : m_watchers) {
auto lw = reinterpret_cast<LocalWatcher*>(it.platform.get());
waitHandles.push_back(lw->event.get());
DWORD flags = 0;
if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME;
if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME;
if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE;

DWORD dwBytes = 0;
if (!::GetOverlappedResult(it.handle.get(), &lw->ovl, &dwBytes, FALSE)) {
auto error = GetLastError();
if (error == ERROR_IO_INCOMPLETE)
continue;
}

if (!::ReadDirectoryChangesW(
it.handle.get(),
lw->buffer,
sizeof(lw->buffer),
(BOOL)it.recursive,
flags,
NULL,
&lw->ovl,
NULL)) {
auto error = GetLastError();
if (error != ERROR_IO_PENDING) {
throw std::runtime_error("Error calling ReadDirectoryChangesW: " + std::to_string(error));
}
}
}

auto waitResult = ::WaitForMultipleObjects(
waitHandles.size(),
waitHandles.data(),
FALSE,
timeoutMilliSeconds >= 0 ? timeoutMilliSeconds : INFINITE);
if (waitResult == WAIT_TIMEOUT) {
return;
}
if (waitResult == WAIT_OBJECT_0) {
::ResetEvent(m_waitStopEvent.get());
return;
}

auto index = waitResult - (WAIT_OBJECT_0 + 1);
if (index < 0 || index >= int(m_watchers.size())) {
throw std::runtime_error("Wait returned result: " + std::to_string(waitResult));
}

Watcher& w = m_watchers[index];
auto lw = reinterpret_cast<LocalWatcher*>(w.platform.get());
::ResetEvent(lw->event.get());
DWORD dwBytes = 0;
if (::GetOverlappedResult(w.handle.get(), &lw->ovl, &dwBytes, FALSE) && dwBytes > 0) {
char fileName[32768];
char* p = (char*)lw->buffer;
for (;;) {
FILE_NOTIFY_INFORMATION* fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(p);
int ret = ::WideCharToMultiByte(CP_ACP,
0,
fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName,
sizeof(fileName),
NULL,
NULL);
if (ret != 0) {
std::string fname(fileName, fileName + ret);
std::replace(fname.begin(), fname.end(), '\\', '/');
FileEvent eventType = (FileEvent)0;
switch (fni->Action) {
case FILE_ACTION_ADDED:
eventType = FileCreated;
break;
case FILE_ACTION_REMOVED:
eventType = FileRemoved;
break;
case FILE_ACTION_MODIFIED:
eventType = FileModified;
break;
default:
break;
}
if (w.events & eventType) {
// Get file handle
FileHandle fh = w.fileHandle.open(fname);
// Invoke callback function
onFileEvent(fh, eventType);
}
}
if (fni->NextEntryOffset == 0)
break;
p += fni->NextEntryOffset;
}
}
}

} // namespace cppfs
Loading