FSMon : moved to bazaar

git-svn-id: svn://ultimatepp.org/upp/trunk@4930 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
micio 2012-05-12 07:13:11 +00:00
parent be7b18a871
commit fc1307e915
12 changed files with 1493 additions and 15 deletions

View file

@ -1,4 +1,4 @@
description "Bazaar: Docking example showing basic usage. Author: James Thomas (mrjt)";
description "Bazaar: Docking example showing basic usage. Author: James Thomas (mrjt)\377";
uses
CtrlLib,

190
bazaar/FSMon/FSMon.h Normal file
View file

@ -0,0 +1,190 @@
#ifndef _FSMon_FSMon_h
#define _FSMon_FSMon_h
#include <Core/Core.h>
#ifdef flagGUI
#include <CtrlLib/CtrlLib.h>
#endif
NAMESPACE_UPP
class FSMon
{
public:
// flags stating type of changes
typedef enum
{
FSM_NOP = 0x00,
FSM_Created = 0x01,
FSM_Deleted = 0x02,
FSM_Moved = 0x04,
FSM_FolderCreated = 0x08,
FSM_FolderDeleted = 0x10,
FSM_FolderMoved = 0x20,
FSM_Modified = 0x40,
FSM_AttribChange = 0x80
} Flags;
struct Info : public Moveable<Info>
{
// path of changed file
String path;
// new file path for renamed/moved files
String newPath;
// type of change flags
dword flags;
};
private:
#ifdef PLATFORM_WIN32
// Enable/Disable process privileges.
BOOL EnablePrivilege(LPCTSTR pszPrivName, BOOL fEnable);
// check if path is a folder or a file
bool isFolder(String const &path);
// struct containing info needed for ReadDirectoryChangesW
#define READ_DIR_CHANGE_BUFFER_SIZE 8192
struct CHANGESINFO
{
HANDLE hDir;
byte buffer[READ_DIR_CHANGE_BUFFER_SIZE];
OVERLAPPED overlapped;
bool cancelling;
};
Array<CHANGESINFO>monitoredInfo;
// error code
DWORD errCode;
// get error string from code
String GetErrorStr(HRESULT err);
// completion port for async I/O
HANDLE completionPort;
// keys for watched folders -- need to be passed to completion port
LONG lastDescriptor;
// scans result buffer for FILE_NOTIFY_INFORMATION records
// and process them
void ProcessNotify(FILE_NOTIFY_INFORMATION *buf, String const &path, bool second);
// monitored descriptors
Index<LONG> monitoredDescriptors;
#else
// error code
int errCode;
// get error string from code
String GetErrorStr(int err);
int iNotifyHandle;
// scans a newly created folder to look for files
// being created BEFORE notify handler was in place
// void ScanCreatedFolder(String path);
// recursively add or remove monitors for paths
bool AddWatch(String const &path);
bool RemoveWatch(String const &path);
// event handling selector
void EventsSelector(uint32 mask, String const &path, String const &newPath);
// monitored descriptors
Index<int> monitoredDescriptors;
#endif
// flag stating that we want to be notified only on file close
// this spares many notifications and the caveats of working on
// opened file, but don't work for files always opened
// (example : big database files)
bool notifyOnClose;
// error string
String errMsg;
// array of errors paths and codes filled by
// recursive watcher -- if can be useful to
// allow some errors when watching
VectorMap<String, int> errMap;
// mutex for thread locking
Mutex fsmMutex;
// a secondary mutex, used for locking on folder creation
// this one avoids waiting when not needed
Mutex fsmMutex2;
// the checking thread
volatile bool threadRunning;
Thread fsmThread;
// changed files/folders list
Vector<Info> changed;
// monitored paths
Index<String> monitoredPaths;
// actually opened files -- may be handy
// for a sync application and for locking purposes
Index<String>openedFiles;
// sets error code message from errno
void SetError(int err) { errCode = err; errMsg = GetErrorStr(err); }
// monitoring callback (runs in a separate thread)
volatile bool shutDown;
void monitorCb(void);
// callback to call event handler in maint thread
// (via PostCallback) when using GUI
void callHandlerCb(void);
protected:
public:
typedef FSMon CLASSNAME;
// constructor
FSMon(bool notifyOnClose = false);
// destructor
~FSMon();
// add a monitored path
bool Add(String const &path);
// remove a monitored path
bool Remove(String const &path);
// query for changed files/folders
Vector<Info> GetChanged(void);
// gets actually opened files
Index<String>GetOpenedFiles(void);
// check error contidion and get error message
bool IsError(void) { return errCode != 0; }
bool GetErrorCode(void) { return errCode; }
String GetErrorMsg(void) { return errMsg; }
VectorMap<String, int> GetErrorMap(void);
// callback to signal happened event
// avoids polling, if needed
Callback EventHandler;
};
END_UPP_NAMESPACE
#endif

7
bazaar/FSMon/FSMon.upp Normal file
View file

@ -0,0 +1,7 @@
description "Filesystem Monitor Package\377";
file
FSMon.h,
FSMonPosix.cpp,
FSMonWin.cpp;

541
bazaar/FSMon/FSMonPosix.cpp Normal file
View file

@ -0,0 +1,541 @@
#include "FSMon.h"
NAMESPACE_UPP
// code for POSIX platform
#ifdef PLATFORM_POSIX
#include <sys/inotify.h>
#include <sys/ioctl.h>
// sets error code message from errno
String FSMon::GetErrorStr(int err)
{
return strerror(err);
}
// constructor
FSMon::FSMon(bool nOnClose)
{
errCode = 0;
errMsg = "";
notifyOnClose = nOnClose;
iNotifyHandle = inotify_init1(IN_NONBLOCK);
if(iNotifyHandle == -1)
{
SetError(errno);
return;
}
// start monitor thread
shutDown = false;
fsmThread.Start(THISBACK(monitorCb));
}
// destructor
FSMon::~FSMon()
{
// stops monitoring thread
shutDown = true;
while(shutDown)
;
if(iNotifyHandle >= 0)
{
// close notify handle
// that should un-monitor all paths...
close(iNotifyHandle);
}
}
/*
struct inotify_event {
int wd; // Watch descriptor
uint32_t mask; // Mask of events
uint32_t cookie; // Unique cookie associating related events (for rename(2))
uint32_t len; // Size of name field
char name[]; // Optional null-terminated name
};
*/
// scans a newly created folder to look for files
// being created BEFORE notify handler was in place
/*
void FSMon::ScanCreatedFolder(String path)
{
FindFile ff(AppendFileName(path, "*"));
while(ff)
{
if(ff.IsFolder())
ScanCreatedFolder(ff.GetPath());
else
{
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = FSM_Created;
info.path = ff.GetPath();
info.newPath.Clear();
callHandlerCb();
}
}
ff.Next();
}
}
*/
// callback to call event handler in maint thread
// (via PostCallback) when using GUI
void FSMon::callHandlerCb(void)
{
#ifdef flagGUI
PostCallback(EventHandler);
#else
EventHandler();
#endif
}
// event handling selector
void FSMon::EventsSelector(uint32 mask, String const &path, String const &newPath)
{
int iMask = 0;
// flag stating event related to folder, not file
bool isFolder = mask & IN_ISDIR;
// mask field is a bitmask with OR-ed values, so we can't use a switch
// we just check each value in a given order
if(mask & IN_CLOSE_WRITE)
{
INTERLOCKED_(fsmMutex)
{
// remove file from opened list
int iOpened = openedFiles.Find(path);
if(iOpened >= 0)
openedFiles.Remove(iOpened);
// we propagate IN_CLOSE_WRITE event only if
// we want to be notified just after file closing
// otherwise, the change is always propagated by IN_MODIFY
if(notifyOnClose)
{
changed.Add();
Info &info = changed.Top();
info.flags = FSM_Modified;
info.path = path;
info.newPath.Clear();
callHandlerCb();
}
}
return;
}
if(mask & IN_CLOSE_NOWRITE)
{
// just update the opened files list
int iOpened = openedFiles.Find(path);
if(iOpened >= 0)
{
INTERLOCKED_(fsmMutex)
{
openedFiles.Remove(iOpened);
}
}
return;
}
if(mask & IN_OPEN)
{
// just update the opened files list
if(openedFiles.Find(path) < 0)
{
INTERLOCKED_(fsmMutex)
{
openedFiles.Add(path);
}
}
return;
}
if(mask & IN_CREATE)
{
// signal file/path creation
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = (isFolder ? FSM_FolderCreated : FSM_Created);
info.path = path;
info.newPath.Clear();
callHandlerCb();
}
// if a folder was created, we shall first setup a monitor
// in it, then ensure that in the meanwhile no subitems have been created
if(isFolder)
{
INTERLOCKED_(fsmMutex2){
AddWatch(path);
}
// ScanCreatedFolder(path);
}
return;
}
if(mask & IN_DELETE)
{
// signal file removal
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = isFolder ? FSM_FolderDeleted : FSM_Deleted;
info.path = path;
info.newPath.Clear();
callHandlerCb();
}
// for folders, we shall de-monitor all contained ones
if(isFolder)
{
INTERLOCKED_(fsmMutex)
{
for(int iFolder = monitoredPaths.GetCount() - 1; iFolder >= 0; iFolder--)
{
if(path.EndsWith(monitoredPaths[iFolder]))
{
monitoredPaths.Pop();
int desc = monitoredDescriptors.Pop();
inotify_rm_watch(iNotifyHandle, desc);
}
}
}
}
return;
}
if(mask & IN_MODIFY)
{
// if we want just notifies on close, do nothing
if(!notifyOnClose)
{
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = FSM_Modified;
info.path = path;
info.newPath.Clear();
callHandlerCb();
}
}
return;
}
// following one was pre-handled by thread to differentiate
// between true moves inside monitored folders or create/delete
// if coming/going outside
if(mask & IN_MOVE)
{
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = isFolder ? FSM_FolderMoved : FSM_Moved;
info.path = path;
info.newPath = newPath;
callHandlerCb();
// if we moved a folder, we shall update stored info
// with new ones -- we assume that monitors will stay in place...
if(isFolder)
{
INTERLOCKED_(fsmMutex2)
{
Index<String> paths;
for(int i = 0; i < monitoredPaths.GetCount(); i++)
{
String oldPth = monitoredPaths[i];
if(oldPth.StartsWith(path))
oldPth = newPath + oldPth.Mid(path.GetCount());
paths.Add(oldPth);
}
monitoredPaths = paths;
}
}
}
return;
}
if(mask & IN_ATTRIB)
{
// add the attribute-modify event
INTERLOCKED_(fsmMutex)
{
changed.Add();
Info &info = changed.Top();
info.flags = FSM_AttribChange;
info.path = path;
info.newPath.Clear();
callHandlerCb();
}
}
return;
}
// monitoring callback (runs in a separate thread)
void FSMon::monitorCb(void)
{
const size_t BASE_BUFSIZE = offsetof(struct inotify_event, name);
byte *bigBuf;
while(!shutDown)
{
// check if data is available from inotify
size_t nBytes;
if(ioctl(iNotifyHandle, FIONREAD, &nBytes) < 0)
{
Sleep(100);
continue;
}
if(nBytes == 0 || (int)nBytes == -1)
{
Sleep(100);
continue;
}
// buffer can be filled by many events at once; the variable-lenght
// name overcomplicates stuffs, as usual
// we can't also read partial data, because read returns error if buffer is too small
// so, we have to read full available data and split records later
bigBuf = (byte *)malloc(nBytes);
size_t res = read(iNotifyHandle, bigBuf, nBytes);
if(res != nBytes)
{
free(bigBuf);
Sleep(100);
continue;
}
struct inotify_event *buf = (struct inotify_event *)bigBuf;
while((byte *)buf - bigBuf < (int)nBytes)
{
int wd = buf->wd;
uint32_t mask = buf->mask;
String name = (buf->len ? buf->name : "");
buf = (struct inotify_event *)((byte *)buf + BASE_BUFSIZE + buf->len);
// skip ignored events
if(mask & IN_IGNORED)
continue;
// get path from descriptor
int idx = monitoredDescriptors.Find(wd);
if(idx < 0)
continue;
String path = AppendFileName(monitoredPaths[idx], name);
// special handling for IN_MOVED events
// an IN_MOVED_FROM must be followed by an IN_MOVE__TO
// if moving between monitored folders. Well, I hope nothing
// happens between them..... So, if we don't find the IN_MOVED_TO
// we assume it's a move outside monitored folders, so a IN_DELETE
// OTOH, if we find an IN_MOVED_TO without corresponding IN_MOVED_FROM
// we assume e move from outside, so an IN_CREATE
if(mask & IN_MOVED_FROM && (byte *)buf - bigBuf < (int)nBytes && buf->mask & IN_MOVED_TO)
{
// it's a true move
String newName = (buf->len ? buf->name : "");
EventsSelector(IN_MOVE | (mask & IN_ISDIR ? IN_ISDIR : 0), path, AppendFileName(monitoredPaths[idx], newName));
}
else if(mask & IN_MOVED_FROM)
{
// it's a delete
EventsSelector(IN_DELETE | (mask & IN_ISDIR ? IN_ISDIR : 0), path, "");
}
else if(mask & IN_MOVED_TO)
{
// it's a create
EventsSelector(IN_CREATE | (mask & IN_ISDIR ? IN_ISDIR : 0), path, "");
}
else
{
// normal event handling
EventsSelector(mask, path, "");
}
}
free(bigBuf);
}
shutDown = false;
}
/* inofify MASKS
IN_ACCESS File was accessed (read) (*).
IN_ATTRIB Metadata changed, e.g., permissions, timestamps, extended attributes, link count (since Linux 2.6.25), UID, GID, etc. (*).
IN_CLOSE_WRITE File opened for writing was closed (*).
IN_CLOSE_NOWRITE File not opened for writing was closed (*).
IN_CREATE File/directory created in watched directory (*).
IN_DELETE File/directory deleted from watched directory (*).
IN_DELETE_SELF Watched file/directory was itself deleted.
IN_MODIFY File was modified (*).
IN_MOVE_SELF Watched file/directory was itself moved.
IN_MOVED_FROM File moved out of watched directory (*).
IN_MOVED_TO File moved into watched directory (*).
IN_OPEN File was opened (*).
*/
// recursively add or remove monitors for paths
// try to monitor all he can, even if there are some errors
// returns false if some error is found
bool FSMon::AddWatch(String const &path)
{
bool res = true;
INTERLOCKED_(fsmMutex2)
{
// add a monitor to current path if not already there
if(monitoredPaths.Find(path) >= 0)
return true;
int desc = inotify_add_watch(
iNotifyHandle,
path,
IN_ATTRIB |
IN_OPEN | // this one just to see if file is busy
IN_CLOSE_WRITE |
IN_CLOSE_NOWRITE |
IN_CREATE |
IN_DELETE |
// IN_DELETE_SELF | // we handle this one in IN_DELETE anyways
IN_MODIFY |
// IN_MOVE_SELF | // we handle this one in IN_MOVE anyways
IN_MOVED_FROM | // without IN_MOVE_TO, is a DELETE from watched folders
IN_MOVED_TO
);
// error ?
if(desc < 0)
{
errMap.Add(path, errno);
SetError(errno);
res = false;
}
else
{
monitoredPaths.Add(path);
monitoredDescriptors.Add(desc);
}
// look for all subfolders
FindFile ff(AppendFileName(path, "*"));
while(ff)
{
if(ff.IsFolder())
res &= AddWatch(ff.GetPath());
ff.Next();
}
}
return res;
}
bool FSMon::RemoveWatch(String const &path)
{
bool res = true;
int pDescIdx = monitoredPaths.Find(path);
if(pDescIdx >= 0)
{
if(inotify_rm_watch(iNotifyHandle, monitoredDescriptors[pDescIdx]) < 0)
{
errMap.Add(path, errno);
SetError(errno);
res = false;
}
// look for all subfolders
FindFile ff(AppendFileName(path, "*"));
while(ff)
{
if(ff.IsFolder())
res &= RemoveWatch(ff.GetPath());
ff.Next();
}
// remove from list of monitored paths
monitoredPaths.Remove(pDescIdx);
monitoredDescriptors.Remove(pDescIdx);
}
return res;
}
///////////////////////////////////////////////////////////////////////////////////////
// add a monitored path
bool FSMon::Add(String const &path)
{
if(IsError())
return false;
// check wether folder exists
if(!DirectoryExists(path))
return false;
// clears map of errors
errMap.Clear();
// we don't want monitor again an already monitored path
// so we do a quick check about it
if(monitoredPaths.Find(path) >= 0)
return true;
// if we're monitoring an external path, we should unmonitor
// it and remonitor the external one; as doing so we could loose
// events, we just monitor the external path, that do not harm
return AddWatch(path);
}
// remove a monitored path
bool FSMon::Remove(String const &path)
{
if(IsError())
return false;
// check wether folder exists
// if not, return success anyways
if(!DirectoryExists(path))
return true;
// clears map of errors
errMap.Clear();
// do the recursive un-monitoring
return RemoveWatch(path);
}
// query for changed files/folders
Vector<FSMon::Info> FSMon::GetChanged(void)
{
Vector<Info> info;
INTERLOCKED_(fsmMutex)
{
info = changed;
changed.Clear();
}
return info;
}
// gets actually opened files
Index<String> FSMon::GetOpenedFiles(void)
{
Index<String> of;
INTERLOCKED_(fsmMutex)
{
of <<= openedFiles;
}
return of;
}
VectorMap<String, int> FSMon::GetErrorMap(void)
{
VectorMap<String, int> res = errMap;
errMap.Clear();
return res;
}
#endif
END_UPP_NAMESPACE

561
bazaar/FSMon/FSMonWin.cpp Normal file
View file

@ -0,0 +1,561 @@
#include "FSMon.h"
NAMESPACE_UPP
#ifdef PLATFORM_WIN32
// Enable/Disable process privileges.
BOOL FSMon::EnablePrivilege(LPCTSTR pszPrivName, BOOL fEnable)
{
// Assume function fails
BOOL fOk = FALSE;
// Try to open this process's access token
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
// privilege
TOKEN_PRIVILEGES tp = { 1 };
if (LookupPrivilegeValue(NULL, pszPrivName, &tp.Privileges[0].Luid))
{
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
}
CloseHandle(hToken);
}
return(fOk);
}
// check if path is a folder or a file
bool FSMon::isFolder(String const &path)
{
DWORD dwAttrib = GetFileAttributes(path);
return static_cast<bool>((dwAttrib != 0xffffffff && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)));
}
// constructor
FSMon::FSMon(bool nOnClose)
{
// must create completion port on first use
completionPort = NULL;
// reset completion keys generator
// skip 0 descriptor, we never know if 0 is returned...
lastDescriptor = 1;
// those are needed for ReadDirectoryChangesW
EnablePrivilege(SE_BACKUP_NAME, true);
EnablePrivilege(SE_RESTORE_NAME, true);
EnablePrivilege(SE_CHANGE_NOTIFY_NAME, true);
shutDown = false;
threadRunning = false;
}
// destructor
FSMon::~FSMon()
{
// if thread not started, no need to shutdown
if(threadRunning)
{
// cancels all pending IO operations
// can't use the CancelIoEx (available from Vista+)
// so we first cancel IO for current thread
// then we send a packet that causes worker thread to
// cancel its part
for(int i = 0; i < monitoredInfo.GetCount(); i++)
{
CancelIo(monitoredInfo[i].hDir);
// no way to pass valid parameters with PostQueuedCompletionStatus(),
// so we use a 'cancelling' flag inside Info recors
INTERLOCKED_(fsmMutex2){
monitoredInfo[i].cancelling = true;
}
PostQueuedCompletionStatus(completionPort, 0, monitoredDescriptors[i], NULL);
while(true)
{
bool cancelling;
INTERLOCKED_(fsmMutex2){
cancelling = monitoredInfo[i].cancelling;
}
if(!cancelling)
break;
else
Sleep(10);
}
}
// wait some time just to allow thread to cancel all pending I/O
Sleep(10);
// signal thread shutdown
shutDown = true;
// send a completion packet to completion port
// in order make it return
PostQueuedCompletionStatus(completionPort, 0, 0, NULL);
while(shutDown)
;
threadRunning = false;
}
// close handles and completion port
for(int i = 0; i < monitoredInfo.GetCount(); i++)
CloseHandle(monitoredInfo[i].hDir);
CloseHandle(completionPort);
}
// get error string from code
String FSMon::GetErrorStr(HRESULT err)
{
LPTSTR errorText;
String res;
FormatMessage(
// use system message tables to retrieve error text
FORMAT_MESSAGE_FROM_SYSTEM |
// allocate buffer on local heap for error text
FORMAT_MESSAGE_ALLOCATE_BUFFER |
// Important! will fail otherwise, since we're not
// (and CANNOT) pass insertion parameters
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errorText, // output
0, // minimum size for output buffer
NULL); // arguments - see note
if(errorText)
{
res = errorText;
LocalFree(errorText);
}
return res;
}
// add a monitored path
bool FSMon::Add(String const &path)
{
bool res = true;
for(int iType = 0; iType < 2; iType++)
{
//open the directory to watch
HANDLE hDir = CreateFile(
path,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE ,//| FILE_SHARE_DELETE, <-- removing FILE_SHARE_DELETE prevents the user or someone else from renaming or deleting the watched directory. This is a good thing to prevent.
NULL, //security attributes
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | //<- the required priviliges for this flag are: SE_BACKUP_NAME and SE_RESTORE_NAME. CPrivilegeEnabler takes care of that.
FILE_FLAG_OVERLAPPED, // for async mode
NULL
);
if (hDir == INVALID_HANDLE_VALUE)
return false;
//create a IO completion port/or associate this key with
//the existing IO completion port
completionPort = CreateIoCompletionPort(
hDir,
// if completionPort is NULL, hDir is associated with a NEW completion port,
// if completionPort is NON-NULL, hDir is associated with the existing completion
// port that the handle completionPort references
completionPort,
// the completion 'key'... this ptr is returned from GetQueuedCompletionStatus() when one of the events in the dwChangesToWatchFor filter takes place
lastDescriptor,
0
);
if(!completionPort)
{
SetError(GetLastError());
CloseHandle(hDir);
return false;
}
// completion port created, we can now store monitored path and its key
// and update key generator
// must lock here to avoid working threat to access
// removed stuffs
INTERLOCKED_(fsmMutex2)
{
monitoredPaths.Add(path);
monitoredDescriptors.Add(lastDescriptor++);
monitoredInfo.Add();
}
CHANGESINFO &info = monitoredInfo.Top();
info.hDir = hDir;
info.cancelling = false;
// overlapped MUST be zero filled, otherwise ReadDirectoryChangesW fails
memset(&info.overlapped, 0, sizeof(OVERLAPPED));
// an now, if not already done, we must start working thread
if(!threadRunning)
{
shutDown = false;
fsmThread.Start(THISBACK(monitorCb));
threadRunning = true;
}
// thread is started, we shall add the monitored folder with ReadDirectoryChangesW
DWORD bufLen;
if (!ReadDirectoryChangesW(
info.hDir,
info.buffer,
READ_DIR_CHANGE_BUFFER_SIZE,
true, // we want subdirs events
iType ?
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_DIR_NAME
:
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE,
&bufLen, //this var not set when using asynchronous mechanisms...
&info.overlapped,
NULL)) //no completion routine!
{
SetError(GetLastError());
return false;
}
}
// just in case of 1 of 2 calls went bad... we must have an EVEN
// numbero of elements in arrays
if(monitoredPaths.GetCount() & 1)
{
INTERLOCKED_(fsmMutex2)
{
monitoredPaths.Drop();
monitoredDescriptors.Drop();
monitoredInfo.Drop();
}
}
return true;
}
bool FSMon::Remove(String const &path)
{
int idx = monitoredPaths.Find(path);
if(idx < 0)
return false;
idx &= 0xFFFFFFFFFFFFFFFE;
for(int iType = 0; iType < 2; iType++)
{
CHANGESINFO &info = monitoredInfo[idx + iType];
// camcels io for corresponding path
// as usual, we can't use CancelIoEx because we want it working on XP
// so we cancel io in current thread and fake the behaviour
// sending a cancel packet to worker thread
// no way to pass valid parameters with PostQueuedCompletionStatus(),
// so we use a 'cancelling' flag inside Info recors
INTERLOCKED_(fsmMutex2) {
info.cancelling = true;
}
PostQueuedCompletionStatus(completionPort, 0, monitoredDescriptors[idx + iType], NULL);
CancelIo(info.hDir);
// wait for tread get the cancelling packet and reset the flag...
while(true)
{
bool cancelling;
INTERLOCKED_(fsmMutex2){
cancelling = info.cancelling;
}
if(!cancelling)
break;
else
Sleep(10);
}
// free dir handle
CloseHandle(info.hDir);
}
// must lock here to avoid working thread to access
// removed stuffs
INTERLOCKED_(fsmMutex2)
{
monitoredPaths.Remove(idx, 2);
monitoredInfo.Remove(idx, 2);
monitoredDescriptors.Remove(idx, 2);
}
return true;
}
// scans result buffer for FILE_NOTIFY_INFORMATION records
// and process them
/*
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
To separate the events from originating flags, we need a couple of listeners
Flags and corresponding catched events :
LISTENER 1
INPUT FLAG CATCHED EVENTS MAPPED TO
FILE_NOTIFY_CHANGE_FILE_NAME FILE_ACTION_ADDED FSM_Created
FILE_ACTION_REMOVED FSM_Deleted
FILE_ACTION_RENAMED_OLD_NAME FSM_Moved
FILE_ACTION_RENAMED_NEW_NAME
FILE_NOTIFY_CHANGE_DIR_NAME FILE_ACTION_ADDED FSM_FolderCreated
FILE_ACTION_REMOVED FSM_FolderDeleted
FILE_ACTION_RENAMED_OLD_NAME FSM_Moved
FILE_ACTION_RENAMED_NEW_NAME
FILE_NOTIFY_CHANGE_SIZE FILE_ACTION_MODIFIED FSM_Modified
FILE_NOTIFY_CHANGE_LAST_WRITE FILE_ACTION_MODIFIED FSM_Modified
LISTENER 2
FILE_NOTIFY_CHANGE_ATTRIBUTES FILE_ACTION_MODIFIED FSM_AttribChange
FILE_NOTIFY_CHANGE_SECURITY FILE_ACTION_MODIFIED FSM_AttribChange
FILE_NOTIFY_CHANGE_LAST_ACCESS FILE_ACTION_MODIFIED NOT CATCHED - USELESS
FILE_NOTIFY_CHANGE_CREATION FILE_ACTION_MODIFIED NOT CATCHED - USELESS
*/
void FSMon::ProcessNotify(FILE_NOTIFY_INFORMATION *buf, String const &path, bool second)
{
do
{
switch(buf->Action)
{
case FILE_ACTION_ADDED :
INTERLOCKED_(fsmMutex)
{
Info &info = changed.Add();
info.flags = second ? FSM_FolderCreated : FSM_Created;
WString ws = WString(buf->FileName, buf->FileNameLength / sizeof(WCHAR));
info.path = AppendFileName(path, ws.ToString());
}
callHandlerCb();
break;
case FILE_ACTION_REMOVED :
INTERLOCKED_(fsmMutex)
{
Info &info = changed.Add();
info.flags = second ? FSM_FolderDeleted : FSM_Deleted;
WString ws = WString(buf->FileName, buf->FileNameLength / sizeof(WCHAR));
info.path = AppendFileName(path, ws.ToString());
}
callHandlerCb();
break;
case FILE_ACTION_MODIFIED :
{
// skip changes on folders... wathever they can mean
WString ws = WString(buf->FileName, buf->FileNameLength / sizeof(WCHAR));
String p = AppendFileName(path, ws.ToString());
if(isFolder(p) && !second)
break;
INTERLOCKED_(fsmMutex)
{
Info &info = changed.Add();
info.flags = second ? FSM_AttribChange : FSM_Modified;
info.path = p;
}
callHandlerCb();
}
break;
case FILE_ACTION_RENAMED_OLD_NAME :
{
WString ws = WString(buf->FileName, buf->FileNameLength / sizeof(WCHAR));
String oldPath = path + ws.ToString();
if(!buf->NextEntryOffset)
break;
buf = (FILE_NOTIFY_INFORMATION *)((byte *)buf + buf->NextEntryOffset);
if(buf->Action != FILE_ACTION_RENAMED_NEW_NAME)
{
break;
}
INTERLOCKED_(fsmMutex)
{
Info &info = changed.Add();
info.flags = second ? FSM_FolderMoved : FSM_Moved;
info.path = oldPath;
ws = WString(buf->FileName, buf->FileNameLength / sizeof(WCHAR));
info.newPath = AppendFileName(path, ws.ToString());
}
callHandlerCb();
break;
}
case FILE_ACTION_RENAMED_NEW_NAME :
break;
default :
break;
}
// go to next record
buf = (FILE_NOTIFY_INFORMATION *)((byte *)buf + buf->NextEntryOffset);
}
while(buf->NextEntryOffset);
}
// monitoring callback (runs in a separate thread)
void FSMon::monitorCb(void)
{
DWORD numBytes;
LONG descriptor;
LPOVERLAPPED overlapped;
// completion port is ok, start monitoring it
while(!shutDown)
{
// Retrieve the directory info for this directory
// through the io port's completion key
if(!GetQueuedCompletionStatus(
completionPort,
&numBytes,
(ULONG *)&descriptor,
&overlapped,
INFINITE // remember to send a completion packet to shut down thread !!
))
{
// error state, if 'overlapped' is NULL, the call timed out
// with no event, otherwise we got some error
if(!overlapped)
{
// this would mean timeout, but shouldn't happen
// even if called with a null overlapped, it sets up one
// so this can't be used for cancel packets
// we use numBytes parameter, using value of 1 which should
// be never valid
}
else
{
// here we land on errors or on CancelIo operations
int err = GetLastError();
if(err == ERROR_OPERATION_ABORTED)
{
// we land here if a CancelIo was called on this handle
// just do nothing... we don't have to respawn the I/O
INTERLOCKED_(fsmMutex2)
{
int idx = monitoredDescriptors.Find(descriptor);
if(idx >= 0)
CancelIo(monitoredInfo[idx].hDir);
}
}
else
SetError(err);
}
}
else
{
// just quick exit if shutting down
if(shutDown)
break;
// get path data from descriptor
// we use a second mutex here, it just lock adding/removing paths
INTERLOCKED_(fsmMutex2)
{
int idx = monitoredDescriptors.Find(descriptor);
if(idx >= 0)
{
// check flag if we wanna cancel...
CHANGESINFO &info = monitoredInfo[idx];
if(info.cancelling)
{
CancelIo(info.hDir);
info.cancelling = false;
}
else
{
String path = monitoredPaths[idx];
// we got an event, just parse it and fill received event structures
ProcessNotify((FILE_NOTIFY_INFORMATION *)info.buffer, path, (idx & 1));
// differentiate between the 2 handlers depending on index
// EVEN indexes : CREATE/DELETE/RENAME FOR FILES, MODIFY FOR ALL
// ODD indexes : CREATE/DELETE/RENAME FOR FOLDERS, ATTRIBUTES FOR ALL
// re-post the request, we don't want to loose events here....
DWORD bufLen;
if (!ReadDirectoryChangesW(
info.hDir,
info.buffer,
READ_DIR_CHANGE_BUFFER_SIZE,
true, // we want subdirs events
(idx & 1) ?
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SECURITY |
FILE_NOTIFY_CHANGE_DIR_NAME
:
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE,
&bufLen, //this var not set when using asynchronous mechanisms...
&info.overlapped,
NULL)) //no completion routine!
SetError(GetLastError());
}
}
}
}
}
// reset shutdown flag to signal tread exiting
shutDown = false;
}
// callback to call event handler in maint thread
// (via PostCallback) when using GUI
void FSMon::callHandlerCb(void)
{
#ifdef flagGUI
if(EventHandler)
PostCallback(EventHandler);
#else
EventHandler();
#endif
}
// query for changed files/folders
Vector<FSMon::Info> FSMon::GetChanged(void)
{
Vector<Info> info;
INTERLOCKED_(fsmMutex)
{
info = changed;
changed.Clear();
}
return info;
}
// gets actually opened files
Index<String> FSMon::GetOpenedFiles(void)
{
// no way on windows to catch open/close on files
// without a dedicated filter
return Index<String>();
}
// check error contidion and get error message
VectorMap<String, int> FSMon::GetErrorMap(void)
{
// just return last error -- on windows we can't have
// sequences of errors
VectorMap<String, int> res;
res.Add("", (int)errCode);
return res;
}
#endif
END_UPP_NAMESPACE

3
bazaar/FSMon/init Normal file
View file

@ -0,0 +1,3 @@
#ifndef _FSMon_icpp_init_stub
#define _FSMon_icpp_init_stub
#endif

View file

@ -0,0 +1,33 @@
#ifndef _FSMonTest_FSMonTest_h
#define _FSMonTest_FSMonTest_h
#include <FSMon/FSMon.h>
using namespace Upp;
#define LAYOUTFILE <FSMonTest/FSMonTest.lay>
#include <CtrlCore/lay.h>
class FSMonTest : public WithFSMonTestLayout<TopWindow>
{
private:
void Log(String const &s);
void startCb(void);
void stopCb(void);
void quitCb(void);
FSMon fsmMon;
void monitorCb(void);
protected:
public:
typedef FSMonTest CLASSNAME;
FSMonTest();
};
#endif

View file

@ -0,0 +1,7 @@
LAYOUT(FSMonTestLayout, 504, 288)
ITEM(LineEdit, logEdit, LeftPosZ(8, 488).TopPosZ(4, 256))
ITEM(Button, stopBtn, SetLabel(t_("Stop")).LeftPosZ(76, 60).TopPosZ(264, 20))
ITEM(Button, quitBtn, SetLabel(t_("Quit")).LeftPosZ(436, 60).TopPosZ(264, 20))
ITEM(Button, startBtn, SetLabel(t_("Start")).LeftPosZ(12, 60).TopPosZ(264, 20))
END_LAYOUT

View file

@ -0,0 +1,14 @@
description "Test FSMon Filesystem Monitor Package\377";
uses
CtrlLib,
FSMon;
file
FSMonTest.lay,
FSMonTest.h,
main.cpp;
mainconfig
"" = "GUI MT";

5
bazaar/FSMonTest/init Normal file
View file

@ -0,0 +1,5 @@
#ifndef _FSMonTest_icpp_init_stub
#define _FSMonTest_icpp_init_stub
#include "CtrlLib/init"
#include "FSMon/init"
#endif

82
bazaar/FSMonTest/main.cpp Normal file
View file

@ -0,0 +1,82 @@
#include "FSMonTest.h"
void FSMonTest::Log(String const &s)
{
logEdit.Set(logEdit.Get() + s + "\n");
logEdit.SetCursor(INT_MAX);
}
void FSMonTest::startCb(void)
{
String path = AppendFileName(GetHomeDirectory(), "FSMonTest_A");
Log("Starting monitor for '" + path + "'");
if(!DirectoryExists(path))
RealizeDirectory(path);
fsmMon.Add(path);
}
void FSMonTest::stopCb(void)
{
Log("Stopping\n");
String path = AppendFileName(GetHomeDirectory(), "FSMonTest_A");
fsmMon.Remove(path);
}
void FSMonTest::quitCb(void)
{
stopCb();
Break();
}
void FSMonTest::monitorCb()
{
Vector<FSMon::Info> infos = fsmMon.GetChanged();
for(int iChange = 0; iChange < infos.GetCount(); iChange++)
{
FSMon::Info const &info = infos[iChange];
switch(info.flags)
{
case FSMon::FSM_NOP :
Log(String("NO-OP EVENT"));
break;
case FSMon::FSM_Created :
Log(String("Creating file '") + info.path + "'");
break;
case FSMon::FSM_Deleted :
Log(String("Deleting file '") + info.path + "'");
break;
case FSMon::FSM_Moved :
Log(String("Moving file '") + info.path + "' to '" + info.newPath + "'");
break;
case FSMon::FSM_FolderCreated :
Log(String("Creating folder '") + info.path + "'");
break;
case FSMon::FSM_FolderDeleted :
Log(String("Deleting folder '") + info.path + "'");
break;
case FSMon::FSM_FolderMoved :
Log(String("Moving folder '") + info.path + "' to '" + info.newPath + "'");
break;
case FSMon::FSM_Modified :
Log(String("Modifying file '") + info.path + "'");
break;
case FSMon::FSM_AttribChange :
Log(String("Modifying file attributes for '") + info.path + "'");
break;
}
}
}
FSMonTest::FSMonTest()
{
CtrlLayout(*this, "Window title");
fsmMon.EventHandler = THISBACK(monitorCb);
startBtn <<= THISBACK(startCb);
stopBtn <<= THISBACK(stopCb);
quitBtn <<= THISBACK(quitCb);
}
GUI_APP_MAIN
{
FSMonTest().Run();
}

View file

@ -1,44 +1,78 @@
uses(LINUX) OCE/Visualization/Xw;
uses(WIN32) OCE/Visualization/WNT;
noblitz;
uses
OCE/ModelingData,
FTGL;
options(WIN32) -D__InterfaceGraphic_DLL;
uses(LINUX) OCE/Visualization/Xw;
uses(WIN32) OCE/Visualization/WNT;
options
-I../,
-I../oce/inc,
-DHAVE_FTGL_NEWER212,
-DOCE_BUILD_STATIC_LIB;
options(WIN32) -D__InterfaceGraphic_DLL;
options(POSIX) -DHAVE_DIRENT_H;
options(POSIX) -DHAVE_DLFCN_H;
options(POSIX) -DOCE_HAVE_FSTREAM;
options(POSIX) -DHAVE_NETDB_H;
options(POSIX) -DOCE_HAVE_IOSTREAM;
options(POSIX) -DOCE_HAVE_IOMANIP;
options(POSIX) -DOCE_HAVE_LIMITS_H;
options(POSIX) -DHAVE_PWD_H;
options(POSIX) -DHAVE_SIGNAL_H;
options(POSIX) -DHAVE_STATFS;
options(POSIX) -DHAVE_STDLIB_H;
options(POSIX) -DHAVE_STRING_H;
options(POSIX) -DHAVE_SYS_IPC_H;
options(POSIX) -DHAVE_SYS_MMAN_H;
options(POSIX) -DHAVE_SYS_PARAM_H;
options(POSIX) -DHAVE_SYS_SEM_H;
options(POSIX) -DHAVE_SYS_SOCKET_H;
options(POSIX) -DHAVE_SYS_STAT_H;
options(POSIX) -DHAVE_SYS_TIME_H;
options(POSIX) -DHAVE_SYS_TIMES_H;
options(POSIX) -DHAVE_SYS_TYPES_H;
options(POSIX) -DHAVE_SYS_UNISTD_H;
options(POSIX) -DHAVE_SYS_UTSNAME_H;
options(POSIX) -DHAVE_SYS_VFS_H;
options(POSIX) -DHAVE_UNISTD_H;
options(WIN32) -DWNT;
options(WIN32) -DHAVE_NO_DLL;
include
../oce/inc;
file
../oce/src/MeshVS/MeshVS_ColorHasher.cxx
options() -I../oce/src/MeshVS
@ -3233,3 +3267,4 @@ file
../oce/src/Voxel/Voxel_Writer.cxx
options() -I../oce/src/Voxel
options() -I../oce/drv/Voxel;