1
0
forked from 0ad/0ad

no longer emulate FAM, due to a stupid design flaw. instead, implement sysdep/dir_watch interface

This was SVN commit r862.
This commit is contained in:
janwas 2004-07-31 15:50:30 +00:00
parent 1ffe320d23
commit 1739f66428
3 changed files with 341 additions and 568 deletions

View File

@ -0,0 +1,341 @@
// directory watch implementation for Win32
// Copyright (c) 2004 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "precompiled.h"
#include "lib.h"
#include "win_internal.h"
#include <assert.h>
#include <string>
#include <map>
#include <list>
#pragma data_seg(".LIB$WTX")
WIN_REGISTER_FUNC(wdir_watch_shutdown);
#pragma data_seg()
// rationale for polling:
// much simpler than pure asynchronous notification: no need for a
// worker thread, mutex, and in/out queues. polling isn't inefficient:
// we do not examine each file; we only need to check if Windows
// has sent a change notification via ReadDirectoryChangesW.
//
// the main reason, however, is that user code will want to poll anyway,
// instead of select() from a worker thread: handling asynchronous file
// changes is much more work, requiring everything to be thread-safe.
// we currently poll once a frame, so that file changes will happen
// at a defined time.
// rationale for using I/O completion ports for notification:
// alternatives:
// - multiple threads with blocking I/O. a good many mount points
// and therefore directory watches are possible, so this is out.
// - normal overlapped I/O: build a contiguous array of the hEvents
// in all OVERLAPPED structures, and WaitForMultipleObjects.
// having to (re)build the array after every watch add/remove sucks.
// - callback notification: notification function is called when the thread
// that initiated the I/O (ReadDirectoryChangesW) enters an alertable
// wait state (e.g. with SleepEx). we need to poll for notifications
// from the mainline (see above). unfortunately, cannot come up with
// a robust yet quick way of working off all pending APCs -
// SleepEx(1) is a hack. even worse, it was noted in a previous project
// that APCs are sometimes delivered from within Windows APIs, without
// having used SleepEx (it seems threads enter an "AWS" sometimes when
// calling the kernel).
//
// IOCPs work well and are elegant; have not yet noticed any drawbacks.
// the completion key is used to associate Watch with the directory handle.
// list of all active watches. required, since we have to be able to
// cancel watches; also makes detecting duplicates possible.
//
// only store pointer in container - they're not copy-equivalent
// (dtor would close hDir).
//
// key is intptr_t "reqnum"; they aren't reused to avoid problems
// with stale reqnums after cancelling;
// hence, map instead of vector and freelist.
struct Watch;
typedef std::map<intptr_t, Watch*> Watches;
typedef Watches::iterator WatchIt;
// list of all active watches to detect duplicates and
// for easier cleanup. only store pointer in container -
// they're not copy-equivalent.
static Watches watches;
// don't worry about size; heap-allocated.
struct Watch
{
intptr_t reqnum;
std::string dir_name;
HANDLE hDir;
DWORD dummy_nbytes;
// storage for RDC lpBytesReturned, to avoid BoundsChecker warning
// (dox are unclear on whether the pointer must be valid).
OVERLAPPED ovl;
// fields aren't used.
// overlapped I/O completation notification is via IOCP.
char change_buf[15000];
// better be big enough - if too small,
// we miss changes made to a directory.
// issue code uses sizeof(change_buf) to determine size.
Watch(intptr_t _reqnum, const std::string& _dir_name, HANDLE _hDir)
: reqnum(_reqnum), dir_name(_dir_name), hDir(_hDir)
{
memset(&ovl, 0, sizeof(ovl));
// change_buf[] doesn't need init
watches[reqnum] = this;
}
~Watch()
{
CloseHandle(hDir);
hDir = INVALID_HANDLE_VALUE;
watches.erase(reqnum);
}
};
// global state
static HANDLE hIOCP = 0;
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
static intptr_t last_reqnum = 1000;
// start value provides a little protection against passing in bogus reqnums
// (we don't bother using a tag for safety though - it isn't important)
typedef std::list<std::string> Events;
static Events pending_events;
// rationale:
// we need a queue, instead of just taking events from the change_buf,
// because we need to re-issue the watch immediately after it returns
// data. of course we can't have the app read from the buffer while
// waiting for RDC to write to the buffer - race condition.
// an alternative to a queue would be to allocate another buffer,
// but that's more complicated, and this way is cleaner anyway.
static int wdir_watch_shutdown()
{
CloseHandle(hIOCP);
hIOCP = INVALID_HANDLE_VALUE;
// free all (dynamically allocated) Watch-es
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
delete it->second;
return 0;
}
// HACK - see call site
static int get_packet();
// path: portable and relative, must add current directory and convert to native
// better to use a cached string from rel_chdir - secure
int dir_add_watch(const char* const dir, intptr_t* const _reqnum)
{
int err = -1;
WIN_SAVE_LAST_ERROR; // Create*
intptr_t reqnum;
*_reqnum = 0;
{
const std::string dir_s(dir);
// make sure dir is not already being watched
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
if(dir == it->second->dir_name)
goto fail;
// open handle to directory
const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
const DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
const HANDLE hDir = CreateFile(dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
if(hDir == INVALID_HANDLE_VALUE)
goto fail;
// assign a new (unique) request number. don't do this earlier - prevents
// DOS via wasting reqnums due to invalid directory parameters.
// need it before binding dir to IOCP because it is our "key".
if(last_reqnum == INT_MAX)
{
debug_warn("dir_add_watch: request numbers are no longer unique");
CloseHandle(hDir);
goto fail;
}
reqnum = ++last_reqnum;
// associate Watch* with the directory handle. when we receive a packet
// from the IOCP, we will need to re-issue the watch.
const ULONG_PTR key = (ULONG_PTR)reqnum;
// create IOCP (if not already done) and attach hDir to it
hIOCP = CreateIoCompletionPort(hDir, hIOCP, key, 0);
if(hIOCP == 0 || hIOCP == INVALID_HANDLE_VALUE)
{
CloseHandle(hDir);
goto fail;
}
// allocate watch, add to list, associate with reqnum
try
{
Watch* w = new Watch(reqnum, dir_s, hDir);
// add trailing \ if not already there
if(dir_s[dir_s.length()-1] != '\\')
w->dir_name += '\\';
}
catch(std::bad_alloc)
{
goto fail;
}
// post a dummy kickoff packet; the IOCP polling code will "re"issue
// the corresponding watch. this keeps the ReadDirectoryChangesW call
// and directory <--> Watch association code in one place.
//
// we call get_packet so that it's issued immediately,
// instead of only at the next call to dir_get_changed_file.
PostQueuedCompletionStatus(hIOCP, 0, key, 0);
get_packet();
}
err = 0;
*_reqnum = reqnum;
fail:
WIN_RESTORE_LAST_ERROR;
return err;
}
int dir_cancel_watch(const intptr_t reqnum)
{
Watch* w = watches[reqnum];
// contrary to dox, the RDC IOs do not issue a completion notification.
// no packet was received on the IOCP while or after cancelling in a test.
//
// if cancel somehow fails though, no matter - the Watch is freed, and
// its reqnum isn't reused; if we receive a packet, it's ignored.
BOOL ret = CancelIo(w->hDir);
delete w;
return ret? 0 : -1;
}
static int extract_events(Watch* w)
{
// points to current FILE_NOTIFY_INFORMATION;
// char* simplifies advancing to the next (variable length) FNI.
char* pos = w->change_buf;
// for every packet in buffer: (there's at least one)
for(;;)
{
const FILE_NOTIFY_INFORMATION* const fni = (const FILE_NOTIFY_INFORMATION*)pos;
// convert filename from Windows BSTR
// (can't use wcstombs - FileName isn't 0-terminated)
std::string fn;
for(int i = 0; i < (int)fni->FileNameLength/2; i++)
fn += (char)fni->FileName[i];
pending_events.push_back(fn);
// advance to next entry in buffer (variable length)
const DWORD ofs = fni->NextEntryOffset;
// .. this one was the last - done.
if(!ofs)
break;
pos += ofs;
}
return 0;
}
// if a packet is pending, extract its events and re-issue its watch.
static int get_packet()
{
// poll for change notifications from all pending watches
DWORD bytes_transferred;
// used to determine if packet is valid or a kickoff
ULONG_PTR key;
OVERLAPPED* povl;
BOOL got_packet = GetQueuedCompletionStatus(hIOCP, &bytes_transferred, &key, &povl, 0);
if(!got_packet) // no new packet - done
return 1;
Watch* w = watches[(intptr_t)key];
// this is an actual packet, not just a kickoff for issuing the watch.
// extract the events and push them onto AppState's queue.
if(bytes_transferred != 0)
extract_events(w);
// (re-)issue change notification request.
// it's safe to reuse Watch.change_buf, because we copied out all events.
const DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION;
const DWORD buf_size = sizeof(w->change_buf);
memset(&w->ovl, 0, sizeof(w->ovl));
BOOL ret = ReadDirectoryChangesW(w->hDir, w->change_buf, buf_size, FALSE, filter, &w->dummy_nbytes, &w->ovl, 0);
if(!ret)
debug_warn("ReadDirectoryChangesW failed");
return 0;
}
int dir_get_changed_file(char* fn)
{
// slight optimization: only call get_packet if queue is empty
if(pending_events.empty())
get_packet();
if(pending_events.empty())
return -1;
const std::string& fn_s = pending_events.front();
strcpy(fn, fn_s.c_str());
pending_events.pop_front();
return 0;
}

View File

@ -1,495 +0,0 @@
// SGI File Alteration Monitor for Win32
// Copyright (c) 2004 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#include "precompiled.h"
#include "wfam.h"
#include "lib.h"
#include "win_internal.h"
#include <assert.h>
#include <string>
#include <map>
#include <list>
// no module init/shutdown necessary: all global data is allocated
// as part of a FAMConnection, which must be FAMClose-d by caller.
//
// that means every routine has a FAMConnection* parameter, but it's safer.
// rationale for polling:
// much simpler than pure asynchronous notification: no need for a
// worker thread, mutex, and in/out queues. polling isn't inefficient:
// we do not examine each file; we only need to check if Windows
// has sent a change notification via ReadDirectoryChangesW.
//
// the main reason, however, is that user code will want to poll anyway,
// instead of select() from a worker thread: handling asynchronous file
// changes is much more work, requiring everything to be thread-safe.
// we currently poll once a frame, so that file changes will happen
// at a defined time.
// rationale for using I/O completion ports for notification:
// alternatives:
// - multiple threads with blocking I/O. a good many mount points
// and therefore directory watches are possible, so this is out.
// - normal overlapped I/O: build a contiguous array of the hEvents
// in all OVERLAPPED structures, and WaitForMultipleObjects.
// having to (re)build the array after every watch add/remove sucks.
// - callback notification: notification function is called when the thread
// that initiated the I/O (ReadDirectoryChangesW) enters an alertable
// wait state (e.g. with SleepEx). we need to poll for notifications
// from the mainline (see above). unfortunately, cannot come up with
// a robust yet quick way of working off all pending APCs -
// SleepEx(1) is a hack. even worse, it was noted in a previous project
// that APCs are sometimes delivered from within Windows APIs, without
// having used SleepEx (it seems threads enter an "AWS" sometimes when
// calling the kernel).
//
// IOCPs work well and are elegant; have not yet noticed any drawbacks.
// the completion key is used to associate Watch with the directory handle.
// don't worry about size: the user only passes around a pointer
// to this struct, due to the pImpl idiom. this is heap-allocated.
struct Watch
{
std::string dir_name;
HANDLE hDir;
// user pointer from from FAMMonitorDirectory; passed to FAMEvent
void* user_data;
DWORD dummy_nbytes;
// storage for RDC lpBytesReturned, to avoid BoundsChecker warning
// (dox are unclear on whether the pointer must be valid).
OVERLAPPED ovl;
// fields aren't used.
// overlapped I/O completation notification is via IOCP.
char change_buf[15000];
// better be big enough - if too small,
// we miss changes made to a directory.
// issue code uses sizeof(change_buf) to determine size.
Watch()
{
hDir = INVALID_HANDLE_VALUE;
// change_buf[] doesn't need init
}
~Watch()
{
CloseHandle(hDir);
hDir = INVALID_HANDLE_VALUE;
}
};
// list of all active watches. required, since we have to be able to
// cancel watches; also makes detecting duplicates possible.
//
// only store pointer in container - they're not copy-equivalent
// (dtor would close hDir).
//
// key is int reqnum - that's what FAMCancelMonitor is passed.
// reqnums aren't reused to avoid problems with stale reqnums after
// cancelling; hence, map instead of vector and freelist.
typedef std::map<int, Watch*> Watches;
typedef Watches::iterator WatchIt;
typedef std::list<FAMEvent> Events;
// don't worry about size: the user only passes around a pointer
// to this struct, due to the pImpl idiom. this is heap-allocated.
struct AppState
{
std::string app_name;
HANDLE hIOCP;
Events pending_events;
// rationale:
// we need a queue, instead of just taking events from the change_buf,
// because we need to re-issue the watch immediately after it returns
// data. of course we can't have the app read from the buffer while
// waiting for RDC to write to the buffer - race condition.
// an alternative to a queue would be to allocate another buffer,
// but that's more complicated, and this way is cleaner anyway.
//
// FAMEvents are somewhat large (~300 bytes), and FIFO,
// so make it a list.
// list of all active watches to detect duplicates and
// for easier cleanup. only store pointer in container -
// they're not copy-equivalent.
Watches watches;
int last_reqnum;
AppState()
{
hIOCP = 0;
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
// provide a little protection against random reqnums passed in
last_reqnum = 1000;
}
~AppState()
{
CloseHandle(hIOCP);
hIOCP = INVALID_HANDLE_VALUE;
// free all (dynamically allocated) Watch-es
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
delete it->second;
}
};
// macros to return pointers to the above from the FAM* structs (pImpl)
// (macro instead of function so we can bail out of the "calling" function)
//
// WARNING: expands to multiple statements. can't use STMT, because
// the macros defined variables (*_ptr_name) that must be visible.
#define GET_APP_STATE(fc, state_ptr_var)\
AppState* state_ptr_var = (AppState*)fc->internal;\
if(!state_ptr_var)\
{\
debug_warn("no FAM connection");\
return -1;\
}
///////////////////////////////////////////////////////////////////////////////
static int alloc_watch(FAMConnection* const fc, const FAMRequest* const fr, Watch*& _w)
{
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
SAFE_NEW(Watch, w);
if(!w)
return ERR_NO_MEM;
watches[fr->reqnum] = w;
_w = w;
return 0;
}
static int find_watch(FAMConnection* const fc, const FAMRequest* const fr, Watch*& w)
{
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
WatchIt it = watches.find(fr->reqnum);
if(it == watches.end())
return -1;
w = it->second;
return 0;
}
#define GET_WATCH(fc, fr, watch_ptr_var)\
Watch* watch_ptr_var;\
CHECK_ERR(find_watch(fc, fr, watch_ptr_var))
static int free_watch(FAMConnection* const fc, const FAMRequest* const fr, Watch*& w)
{
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
WatchIt it = watches.find(fr->reqnum);
if(it == watches.end())
return -1;
delete it->second;
watches.erase(it);
w = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
int FAMOpen2(FAMConnection* const fc, const char* const app_name)
{
SAFE_NEW(AppState, state);
if(!state)
return ERR_NO_MEM;
state->app_name = app_name;
fc->internal = state;
return 0;
}
int FAMClose(FAMConnection* const fc)
{
GET_APP_STATE(fc, state);
delete state;
fc->internal = 0;
return 0;
}
// HACK - see call site
static int get_packet(FAMConnection* fc);
int FAMMonitorDirectory(FAMConnection* const fc, const char* const _dir, FAMRequest* const fr, void* const user_data)
{
int err = -1;
WIN_SAVE_LAST_ERROR; // Create*
{
const std::string dir(_dir);
GET_APP_STATE(fc, state);
Watches& watches = state->watches;
HANDLE& hIOCP = state->hIOCP;
int& last_reqnum = state->last_reqnum;
// make sure dir is not already being watched
for(WatchIt it = watches.begin(); it != watches.end(); ++it)
if(dir == it->second->dir_name)
goto fail;
// open handle to directory
const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
const DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
const HANDLE hDir = CreateFile(_dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
if(hDir == INVALID_HANDLE_VALUE)
goto fail;
// assign a new (unique) request number. don't do this earlier - prevents
// DOS via wasting reqnums due to invalid directory parameters.
// need it before binding dir to IOCP because it is our "key".
if(last_reqnum == INT_MAX)
{
debug_warn("FAMMonitorDirectory: request numbers are no longer unique");
CloseHandle(hDir);
goto fail;
}
const int reqnum = ++last_reqnum;
fr->reqnum = reqnum;
// associate Watch* with the directory handle. when we receive a packet
// from the IOCP, we will need to re-issue the watch.
const ULONG_PTR key = (ULONG_PTR)reqnum;
// create IOCP (if not already done) and attach hDir to it
hIOCP = CreateIoCompletionPort(hDir, hIOCP, key, 0);
if(hIOCP == 0 || hIOCP == INVALID_HANDLE_VALUE)
{
CloseHandle(hDir);
goto fail;
}
// create Watch and associate with FAM structs
Watch* w;
if(alloc_watch(fc, fr, w) < 0)
goto fail;
w->dir_name = dir;
w->hDir = hDir;
w->user_data = user_data;
if(dir[dir.length()-1] != '\\')
w->dir_name += '\\';
// post a dummy kickoff packet; the IOCP polling code will "re"issue
// the corresponding watch. this keeps the ReadDirectoryChangesW call
// and directory <--> Watch association code in one place.
//
// we call get_packet so that it's issued immediately,
// instead of only at the next call to FAMPending.
PostQueuedCompletionStatus(hIOCP, 0, key, 0);
get_packet(fc);
}
err = 0;
fail:
WIN_RESTORE_LAST_ERROR;
return err;
}
int FAMCancelMonitor(FAMConnection* const fc, FAMRequest* const fr)
{
GET_WATCH(fc, fr, w);
// contrary to dox, the RDC IOs do not issue a completion notification.
// no packet was received on the IOCP while or after cancelling in a test.
//
// if cancel somehow fails though, no matter - the Watch is freed, and
// its reqnum isn't reused; if we receive a packet, it's ignored.
CancelIo(w->hDir);
free_watch(fc, fr, w); // can't fail
return 0;
}
static int extract_events(FAMConnection* fc, FAMRequest* fr, Watch* w)
{
GET_APP_STATE(fc, state);
Events& events = state->pending_events;
// will be modified for each event and added to events
FAMEvent event_template;
event_template.fc = fc;
event_template.fr = *fr;
event_template.user = w->user_data;
// points to current FILE_NOTIFY_INFORMATION;
// char* simplifies advancing to the next (variable length) FNI.
char* pos = w->change_buf;
// for every packet in buffer: (there's at least one)
for(;;)
{
const FILE_NOTIFY_INFORMATION* const fni = (const FILE_NOTIFY_INFORMATION*)pos;
events.push_back(event_template);
FAMEvent& event = events.back();
// fields are set below; we need to add the event here
// so that we have a place to put the converted filename.
// interpret action
FAMCodes code = FAMChanged;
switch(fni->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
code = FAMCreated;
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
code = FAMDeleted;
break;
case FILE_ACTION_MODIFIED:
code = FAMChanged;
break;
default:
debug_warn("unknown fni->Action");
break;
};
event.code = code;
// build filename
// (prepend directory and convert from Windows BSTR)
strcpy(event.filename, w->dir_name.c_str());
char* fn = event.filename + w->dir_name.length();
// .. can't use wcstombs - FileName isn't 0-terminated
for(int i = 0; i < (int)fni->FileNameLength/2; i++)
*fn++ = (char)fni->FileName[i];
*fn = '\0';
// advance to next entry in buffer (variable length)
const DWORD ofs = fni->NextEntryOffset;
// .. this one was the last - done.
if(!ofs)
break;
pos += ofs;
}
return 0;
}
// if a packet is pending, extract its events and re-issue its watch.
static int get_packet(FAMConnection* fc)
{
GET_APP_STATE(fc, state);
const HANDLE hIOCP = state->hIOCP;
// poll for change notifications from all pending FAMRequests
DWORD bytes_transferred;
// used to determine if packet is valid or a kickoff
ULONG_PTR key;
OVERLAPPED* povl;
BOOL got_packet = GetQueuedCompletionStatus(hIOCP, &bytes_transferred, &key, &povl, 0);
if(!got_packet) // no new packet - done
return 1;
FAMRequest _fr = { (int)key };
FAMRequest* const fr = &_fr;
// if other fields are added, their value is 0;
// find_watch only looks at reqnum anyway.
GET_WATCH(fc, fr, w);
// this is an actual packet, not just a kickoff for issuing the watch.
// extract the events and push them onto AppState's queue.
if(bytes_transferred != 0)
extract_events(fc, fr, w);
// (re-)issue change notification request.
// it's safe to reuse Watch.change_buf, because we copied out all events.
const DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION;
const DWORD buf_size = sizeof(w->change_buf);
memset(&w->ovl, 0, sizeof(w->ovl));
BOOL ret = ReadDirectoryChangesW(w->hDir, w->change_buf, buf_size, FALSE, filter, &w->dummy_nbytes, &w->ovl, 0);
if(!ret)
debug_warn("ReadDirectoryChangesW failed");
return 0;
}
int FAMPending(FAMConnection* const fc)
{
GET_APP_STATE(fc, state);
Events& pending_events = state->pending_events;
// still have events in the queue?
// (slight optimization; no need to call get_packet if so)
if(!pending_events.empty())
return 1;
get_packet(fc);
return !pending_events.empty();
}
int FAMNextEvent(FAMConnection* const fc, FAMEvent* const fe)
{
GET_APP_STATE(fc, state);
Events& pending_events = state->pending_events;
if(pending_events.empty())
{
debug_warn("FAMNextEvent: no pending events");
return -1;
}
*fe = pending_events.front();
pending_events.pop_front();
return 0;
}

View File

@ -1,73 +0,0 @@
// SGI File Alteration Monitor for Win32
// Copyright (c) 2004 Jan Wassenberg
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// Contact info:
// Jan.Wassenberg@stud.uni-karlsruhe.de
// http://www.stud.uni-karlsruhe.de/~urkt/
#ifndef WFAM_H__
#define WFAM_H__
#include "posix.h" // PATH_MAX
#ifdef __cplusplus
extern "C" {
#endif
// FAM documentation: http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&fname=/SGI_Developer/books/IIDsktp_IG/sgi_html/ch08.html
// don't want to expose the internals, and opaque structs are too hard to
// keep in sync with the real definition, therefore, use the pImpl idiom.
struct FAMConnection
{
void* internal;
};
struct FAMRequest
{
uint reqnum;
};
enum FAMCodes { FAMDeleted, FAMCreated, FAMChanged };
typedef struct
{
FAMConnection* fc;
FAMRequest fr;
char filename[PATH_MAX];
void* user;
FAMCodes code;
}
FAMEvent;
// these functions must be called from the same thread!
// (Win32 overlapped I/O limitation)
extern int FAMOpen2(FAMConnection*, const char* app_name);
extern int FAMClose(FAMConnection*);
extern int FAMMonitorDirectory(FAMConnection*, const char* dir, FAMRequest* req, void* user_data);
extern int FAMCancelMonitor(FAMConnection*, FAMRequest* req);
extern int FAMPending(FAMConnection*);
extern int FAMNextEvent(FAMConnection*, FAMEvent* event);
#ifdef __cplusplus
}
#endif
#endif // #ifndef WFAM_H__