now emulates FAM API so other code can use one interface. implemented but untested.
This was SVN commit r399.
This commit is contained in:
parent
cd3755b935
commit
0ca334ed39
@ -1,4 +1,4 @@
|
||||
// Windows-specific directory change notification
|
||||
// SGI File Alteration Monitor for Win32
|
||||
// Copyright (c) 2004 Jan Wassenberg
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
@ -18,90 +18,31 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "wfam.h"
|
||||
|
||||
#include "lib.h"
|
||||
#include "win_internal.h"
|
||||
|
||||
#if 0
|
||||
#include <assert.h>
|
||||
|
||||
static const size_t CHANGE_BUF_SIZE = 15000;
|
||||
// better be enough - if too small, we miss changes made to a directory.
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
|
||||
// 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 FAMRequest_
|
||||
{
|
||||
std::string dir_name;
|
||||
HANDLE hDir;
|
||||
|
||||
// history to detect series of notifications, so we can skip
|
||||
// redundant reloads (slow)
|
||||
std::string last_path;
|
||||
DWORD last_action; // FILE_ACTION_* codes or 0
|
||||
DWORD last_ticks; // timestamp via GetTickCount
|
||||
|
||||
OVERLAPPED ovl;
|
||||
// we don't use any of its fields.
|
||||
// overlapped I/O completation notification is via IOCP.
|
||||
// rationale: see below.
|
||||
char changes[CHANGE_BUF_SIZE];
|
||||
|
||||
|
||||
FAMRequest_(const char* _dir_name)
|
||||
: dir_name(_dir_name), last_path("")
|
||||
{
|
||||
last_action = 0;
|
||||
last_ticks = 0;
|
||||
|
||||
memset(&ovl, 0, sizeof(ovl));
|
||||
|
||||
// changes[] doesn't need init
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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 FAMConnection_
|
||||
{
|
||||
std::string app_name;
|
||||
|
||||
|
||||
HANDLE hIOCP;
|
||||
|
||||
// queue necessary - race condition if pass to app and re-issue
|
||||
// needs to be FIFO, and don't want to constantly shuffle items (can be rather large)
|
||||
// around => list
|
||||
typedef std::list<FAMEvent> Events;
|
||||
Events pending_events;
|
||||
|
||||
// list of all pending requests to detect duplicates and
|
||||
// for easier cleanup. only store pointer in container -
|
||||
// they're not copy-equivalent.
|
||||
typedef std::map<std::string, FAMRequest*> Requests;
|
||||
typedef Requests::iterator RequestIt;
|
||||
Requests requests;
|
||||
|
||||
FAMConnection_(const char* _app_name)
|
||||
: app_name(_app_name)
|
||||
{
|
||||
hIOCP = 0;
|
||||
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
|
||||
}
|
||||
|
||||
~FAMConnection_()
|
||||
{
|
||||
CloseHandle(hIOCP);
|
||||
hIOCP = INVALID_HANDLE_VALUE;
|
||||
|
||||
// container holds dynamically allocated Watch structs
|
||||
// for(WatchIt it = watches.begin(); it != watches.end(); ++it)
|
||||
// delete it->second;
|
||||
}
|
||||
};
|
||||
|
||||
// no module init/shutdown necessary: all global data is allocated
|
||||
// as part of a FAMConnection, which must be FAMClose-d by caller.
|
||||
|
||||
|
||||
// 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:
|
||||
@ -113,123 +54,144 @@ struct FAMConnection_
|
||||
// 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). it would be nice to be able to
|
||||
// check for notifications from the mainline - would obviate
|
||||
// the separate worker thread for RDC and 2 queues to drive it.
|
||||
// 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).
|
||||
// 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ReadDirectoryChangesW must be called again after each time it returns data,
|
||||
// so we need to pass along the associated Request.
|
||||
// since we issue RDC immediately, instead of sending a bogus packet
|
||||
// to the IOCP that triggers the issue, we don't need the key parameter
|
||||
// for anything - use it to pass along the Request.
|
||||
// cleaner than assuming &ovl = &Request, or stuffing it in an unused
|
||||
// member of OVERLAPPED.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int dir_watch_abort(const char* const dir)
|
||||
// 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
|
||||
{
|
||||
// find watch
|
||||
/* const std::string dir_s(dir);
|
||||
WatchIt it = watches.find(dir_s);
|
||||
if(it == watches.end())
|
||||
return -1;
|
||||
const std::string dir_name;
|
||||
HANDLE hDir;
|
||||
|
||||
delete it->second;
|
||||
watches.erase(it);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
// history to detect series of notifications, so we can skip
|
||||
// redundant reloads (slow)
|
||||
std::string last_path;
|
||||
DWORD last_action; // FILE_ACTION_* codes or 0
|
||||
DWORD last_ticks; // timestamp via GetTickCount
|
||||
|
||||
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.
|
||||
|
||||
// these are returned in FAMEvent. could get them via FAMNextEvent's
|
||||
// fc parameter and associating packets with FAMRequest,
|
||||
// but storing them here is more convenient.
|
||||
FAMConnection* fc;
|
||||
FAMRequest* fr;
|
||||
|
||||
|
||||
// it'd be nice to have only 1 call site of ReadDirectoryChangesW, namely
|
||||
// in the notification "callback". however, posting a dummy event to the IOCP
|
||||
// and having the callback issue RDC is a bit ugly, and loses changes made
|
||||
// before the poll routine is first called.
|
||||
Watch(const std::string& _dir_name, HANDLE _hDir)
|
||||
: dir_name(_dir_name), last_path("")
|
||||
{
|
||||
hDir = _hDir;
|
||||
|
||||
static int dir_watch_issue(FAMRequest_* fr)
|
||||
last_action = 0;
|
||||
last_ticks = 0;
|
||||
|
||||
memset(&ovl, 0, sizeof(ovl));
|
||||
|
||||
// change_buf[] doesn't need init
|
||||
}
|
||||
|
||||
~Watch()
|
||||
{
|
||||
CloseHandle(hDir);
|
||||
hDir = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// list of all active watches to detect duplicates and
|
||||
// for easier cleanup. only store pointer in container -
|
||||
// they're not copy-equivalent.
|
||||
typedef std::map<std::string, 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
|
||||
{
|
||||
// (re-)request change notification from now on
|
||||
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;
|
||||
BOOL ret = ReadDirectoryChangesW(fr->hDir, fr->changes, CHANGE_BUF_SIZE, FALSE, filter, 0, &fr->ovl, 0);
|
||||
return ret? 0 : -1;
|
||||
}
|
||||
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;
|
||||
|
||||
AppState(const char* _app_name)
|
||||
: app_name(_app_name)
|
||||
{
|
||||
hIOCP = 0;
|
||||
// not INVALID_HANDLE_VALUE! (CreateIoCompletionPort requirement)
|
||||
}
|
||||
|
||||
~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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int dir_add_watch(const char* const dir, const bool watch_subdirs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// 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)
|
||||
#define GET_APP_STATE(fc, ptr_name)\
|
||||
AppState* const ptr_name = (AppState*)fc->internal;\
|
||||
if(!ptr_name)\
|
||||
{\
|
||||
debug_warn("no FAM connection");\
|
||||
return -1;\
|
||||
}
|
||||
|
||||
void FAMCancelMonitor(FAMConnection*, FAMRequest* req)
|
||||
{
|
||||
}
|
||||
#define GET_WATCH(fr, ptr_name)\
|
||||
Watch* const ptr_name = (Watch*)fr->internal;\
|
||||
if(!ptr_name)\
|
||||
{\
|
||||
debug_warn("FAMRequest.internal invalid!");\
|
||||
return -1;\
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void wfam_shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
int FAMOpen2(FAMConnection* const fc, const char* app_name)
|
||||
int FAMOpen2(FAMConnection* const fc, const char* const app_name)
|
||||
{
|
||||
try
|
||||
{
|
||||
fc->internal = new FAMConnection_(app_name);
|
||||
fc->internal = new AppState(app_name);
|
||||
}
|
||||
catch(std::bad_alloc)
|
||||
{
|
||||
@ -246,166 +208,225 @@ int FAMOpen2(FAMConnection* const fc, const char* app_name)
|
||||
|
||||
int FAMClose(FAMConnection* const fc)
|
||||
{
|
||||
FAMConnection_*& fc_ = (FAMConnection_*)fc->internal;
|
||||
if(!fc_)
|
||||
{
|
||||
debug_warn("FAMClose: already closed");
|
||||
return -1;
|
||||
}
|
||||
GET_APP_STATE(fc, state);
|
||||
|
||||
delete fc_;
|
||||
fc_ = 0;
|
||||
delete state;
|
||||
fc->internal = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int FAMMonitorDirectory(FAMConnection* fc, char* dir, FAMRequest* fr, void* user)
|
||||
{
|
||||
FAMConnection_* fc_ = (FAMConnection_*)fc->internal;
|
||||
// HACK - see call site
|
||||
static void get_packet(AppState*);
|
||||
|
||||
|
||||
int FAMMonitorDirectory(FAMConnection* const fc, char* const _dir, FAMRequest* const fr, void* const user)
|
||||
{
|
||||
GET_APP_STATE(fc, state);
|
||||
Watches& watches = state->watches;
|
||||
HANDLE& hIOCP = state->hIOCP;
|
||||
|
||||
const std::string dir(_dir);
|
||||
|
||||
/*
|
||||
// make sure dir is not already being watched
|
||||
const std::string dir_s(dir);
|
||||
WatchIt it = watches.find(dir_s);
|
||||
WatchIt it = watches.find(dir);
|
||||
if(it != watches.end())
|
||||
return -1;
|
||||
*/
|
||||
HANDLE hDir = INVALID_HANDLE_VALUE;
|
||||
HANDLE& hIOCP = fc_->hIOCP;
|
||||
|
||||
// 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;
|
||||
hDir = CreateFile(dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
|
||||
HANDLE hDir = CreateFile(_dir, FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
|
||||
if(hDir == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
// create IOCP (if not already done) and bind dir to it
|
||||
hIOCP = CreateIoCompletionPort(hDir, hIOCP, KEY_NORMAL, 0);
|
||||
// create Watch and associate with FAM structs
|
||||
Watch* const w = new Watch(dir, hDir);
|
||||
watches[dir] = w;
|
||||
w->fc = fc;
|
||||
w->fr = fr;
|
||||
|
||||
// associate Watch* with the directory handle. when we receive a packet
|
||||
// from the IOCP, we will need to re-issue the watch and find the
|
||||
// corresponding FAMRequest.
|
||||
const ULONG_PTR key = (ULONG_PTR)w;
|
||||
|
||||
// create IOCP (if not already done) and attach hDir to it
|
||||
hIOCP = CreateIoCompletionPort(hDir, hIOCP, key, 0);
|
||||
if(hIOCP == 0 || hIOCP == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
fail:
|
||||
delete w;
|
||||
CloseHandle(hDir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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(state);
|
||||
|
||||
|
||||
// insert
|
||||
Watch* const w = new Watch(hDir, watch_subdirs);
|
||||
watches[dir_s] = w;
|
||||
|
||||
dir_watch_issue(w);
|
||||
fr->internal = w;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// added bonus: can actually "poll" for changes here - obviates a worker
|
||||
// thread, mutex, and 2 queues.
|
||||
|
||||
|
||||
|
||||
static int extract_events(FAMConnection* conn, FAMRequest* req)
|
||||
int FAMCancelMonitor(FAMConnection* const fc, FAMRequest* const fr)
|
||||
{
|
||||
FAMConnection_ conn_ = (FAMConnection_*)conn->internal;
|
||||
const FILE_NOTIFY_INFORMATION* fni = (const FILE_NOTIFY_INFORMATION*)req->changes;
|
||||
Events& events = fc_->pending_events;
|
||||
GET_APP_STATE(fc, state);
|
||||
GET_WATCH(fr, w)
|
||||
|
||||
// TODO
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static int extract_events(Watch* const w)
|
||||
{
|
||||
FAMConnection* const fc = w->fc;
|
||||
FAMRequest* const fr = w->fr;
|
||||
|
||||
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.conn = conn;
|
||||
event_template.req = req;
|
||||
event_template.fc = fc;
|
||||
event_template.fr = *fr;
|
||||
|
||||
// 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
|
||||
//
|
||||
|
||||
const char* actions[] = { "", "FILE_ACTION_ADDED", "FILE_ACTION_REMOVED", "FILE_ACTION_MODIFIED", "FILE_ACTION_RENAMED_OLD_NAME", "FILE_ACTION_RENAMED_NEW_NAME" };
|
||||
const char* action = actions[fni->Action];
|
||||
|
||||
// many apps save by creating a temp file, deleting the original,
|
||||
// and renaming the temp file. that leads to 2 reloads, which is slow.
|
||||
// try to detect this case with a simple state machine - we assume
|
||||
// the notification order is always the same.
|
||||
|
||||
// TODO:
|
||||
// TODO
|
||||
|
||||
const char* actions[] = { "", "FILE_ACTION_ADDED", "FILE_ACTION_REMOVED", "FILE_ACTION_MODIFIED", "FILE_ACTION_RENAMED_OLD_NAME", "FILE_ACTION_RENAMED_NEW_NAME" };
|
||||
const char* action = actions[fni->Action];
|
||||
FAMCodes code;
|
||||
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;
|
||||
};
|
||||
|
||||
// convert Windows BSTR-style path to
|
||||
// portable C string path for the resource manager.
|
||||
// HACK: convert in place, we copy it into
|
||||
char fn[MAX_PATH];
|
||||
char* p = fn;
|
||||
event.code = code;
|
||||
|
||||
|
||||
//
|
||||
// convert filename from Windows BSTR to portable C string
|
||||
//
|
||||
|
||||
char* fn = event.filename;
|
||||
const int num_chars = fni->FileNameLength/2;
|
||||
for(int i = 0; i < num_chars; i++)
|
||||
{
|
||||
char c = (char)fni->FileName[i];
|
||||
if(c == '\\')
|
||||
c = '/';
|
||||
*p++ = c;
|
||||
*fn++ = c;
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
// don't want to expose details
|
||||
|
||||
events.push_back(event_template);
|
||||
*fn = '\0';
|
||||
|
||||
|
||||
const DWORD ofs = fni->NextEntryOffset;
|
||||
// advance to next FILE_NOTIFY_INFORMATION (variable length)
|
||||
if(ofs)
|
||||
(char*&)fni += ofs;
|
||||
pos += ofs;
|
||||
// this was the last entry - no more elements left in buffer.
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
res_reload(fn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int FAMPending(FAMConnection* fc)
|
||||
// if a packet is pending, extract its events and re-issue its watch.
|
||||
void get_packet(AppState* const state)
|
||||
{
|
||||
FAMConnection_* const fc_ = (FAMConnection_*)fc->internal;
|
||||
Events& pending_events = fc_->pending_events;
|
||||
// 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(state->hIOCP, &bytes_transferred, &key, &povl, 0);
|
||||
if(!got_packet) // no new packet - done
|
||||
return;
|
||||
|
||||
Watch* w = (Watch*)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);
|
||||
BOOL ret = ReadDirectoryChangesW(w->hDir, w->change_buf, buf_size, FALSE, filter, 0, &w->ovl, 0);
|
||||
if(!ret)
|
||||
debug_warn("ReadDirectoryChangesW failed");
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// check if new buffer has been filled
|
||||
DWORD bytes_transferred; // unused
|
||||
ULONG_PTR key;
|
||||
OVERLAPPED* povl;
|
||||
BOOL got_packet = GetQueuedCompletionStatus(fc_->hIOCP, &bytes_transferred, &key, &povl, 0);
|
||||
if(!got_packet)
|
||||
return 0;
|
||||
get_packet(state);
|
||||
|
||||
CHECK_ERR(extract_events(conn, req));
|
||||
|
||||
// dir_watch_issue(buf);
|
||||
|
||||
|
||||
|
||||
|
||||
fc->fni = (FILE_NOTIFY_INFORMATION*)w->buf;
|
||||
return 1;
|
||||
return !pending_events.empty();
|
||||
}
|
||||
|
||||
|
||||
int FAMNextEvent(FAMConnection* const fc, FAMEvent* const fe)
|
||||
{
|
||||
FAMConnection_* const fc_ = (FAMConnection_*)fc->internal;
|
||||
Events& pending_events = fc_->pending_events;
|
||||
|
||||
if(!fe)
|
||||
{
|
||||
debug_warn("FAMNextEvent: fe = 0");
|
||||
return ERR_INVALID_PARAM;
|
||||
}
|
||||
GET_APP_STATE(fc, state);
|
||||
Events& pending_events = state->pending_events;
|
||||
|
||||
if(pending_events.empty())
|
||||
{
|
||||
@ -417,6 +438,3 @@ int FAMNextEvent(FAMConnection* const fc, FAMEvent* const fe)
|
||||
pending_events.pop_front();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -1,11 +1,37 @@
|
||||
#include "posix.h"
|
||||
// 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
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
// opaque structs are too hard to keep in sync with the real definition,
|
||||
// and we don't want to expose the internals. therefore, use the pImpl pattern.
|
||||
// and we don't want to expose the internals. therefore, use the pImpl idiom.
|
||||
|
||||
struct FAMRequest
|
||||
{
|
||||
void* internal;
|
||||
// reqnum not needed, since FAMMonitorDirectory2 isn't supported.
|
||||
};
|
||||
|
||||
struct FAMConnection
|
||||
@ -13,9 +39,7 @@ struct FAMConnection
|
||||
void* internal;
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum FAMChangeCode { FAMDeleted, FAMCreated, FAMChanged };
|
||||
enum FAMCodes { FAMDeleted, FAMCreated, FAMChanged };
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -23,17 +47,19 @@ typedef struct
|
||||
FAMRequest fr;
|
||||
char filename[PATH_MAX];
|
||||
void* user;
|
||||
FAMChangeCode code;
|
||||
FAMCodes code;
|
||||
}
|
||||
FAMEvent;
|
||||
|
||||
|
||||
extern int FAMOpen2(FAMConnection*, const char* app_name);
|
||||
extern void FAMClose(FAMConnection*);
|
||||
extern int FAMClose(FAMConnection*);
|
||||
|
||||
extern int FAMMonitorDirectory(FAMConnection*, const char* dir, FAMRequest* req, void* user);
|
||||
extern void FAMCancelMonitor(FAMConnection*, FAMRequest* req);
|
||||
extern int FAMCancelMonitor(FAMConnection*, FAMRequest* req);
|
||||
|
||||
extern int FAMPending(FAMConnection*);
|
||||
extern int FAMNextEvent(FAMConnection*, FAMEvent* event);
|
||||
|
||||
|
||||
#endif // #ifndef WFAM_H__
|
Loading…
Reference in New Issue
Block a user