diff --git a/bazaar/DockingExample1/DockingExample1.upp b/bazaar/DockingExample1/DockingExample1.upp index b6f1f12d0..e46ccd08e 100644 --- a/bazaar/DockingExample1/DockingExample1.upp +++ b/bazaar/DockingExample1/DockingExample1.upp @@ -1,12 +1,12 @@ -description "Bazaar: Docking example showing basic usage. Author: James Thomas (mrjt)"; - -uses - CtrlLib, - Docking; - -file - main.cpp; - -mainconfig - "" = "GUI"; - +description "Bazaar: Docking example showing basic usage. Author: James Thomas (mrjt)\377"; + +uses + CtrlLib, + Docking; + +file + main.cpp; + +mainconfig + "" = "GUI"; + diff --git a/bazaar/FSMon/FSMon.h b/bazaar/FSMon/FSMon.h new file mode 100644 index 000000000..cca733a7d --- /dev/null +++ b/bazaar/FSMon/FSMon.h @@ -0,0 +1,190 @@ +#ifndef _FSMon_FSMon_h +#define _FSMon_FSMon_h + +#include + +#ifdef flagGUI +#include +#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 + { + // 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; + }; + ArraymonitoredInfo; + + // 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 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 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 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 changed; + + // monitored paths + Index monitoredPaths; + + // actually opened files -- may be handy + // for a sync application and for locking purposes + IndexopenedFiles; + + // 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 GetChanged(void); + + // gets actually opened files + IndexGetOpenedFiles(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 GetErrorMap(void); + + // callback to signal happened event + // avoids polling, if needed + Callback EventHandler; +}; + +END_UPP_NAMESPACE + +#endif diff --git a/bazaar/FSMon/FSMon.upp b/bazaar/FSMon/FSMon.upp new file mode 100644 index 000000000..a22505e51 --- /dev/null +++ b/bazaar/FSMon/FSMon.upp @@ -0,0 +1,7 @@ +description "Filesystem Monitor Package\377"; + +file + FSMon.h, + FSMonPosix.cpp, + FSMonWin.cpp; + diff --git a/bazaar/FSMon/FSMonPosix.cpp b/bazaar/FSMon/FSMonPosix.cpp new file mode 100644 index 000000000..236450f7e --- /dev/null +++ b/bazaar/FSMon/FSMonPosix.cpp @@ -0,0 +1,541 @@ +#include "FSMon.h" + +NAMESPACE_UPP + +// code for POSIX platform +#ifdef PLATFORM_POSIX + +#include +#include + +// 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 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::GetChanged(void) +{ + Vector info; + INTERLOCKED_(fsmMutex) + { + info = changed; + changed.Clear(); + } + return info; +} + +// gets actually opened files +Index FSMon::GetOpenedFiles(void) +{ + Index of; + INTERLOCKED_(fsmMutex) + { + of <<= openedFiles; + } + return of; +} + +VectorMap FSMon::GetErrorMap(void) +{ + VectorMap res = errMap; + errMap.Clear(); + return res; +} + +#endif + +END_UPP_NAMESPACE \ No newline at end of file diff --git a/bazaar/FSMon/FSMonWin.cpp b/bazaar/FSMon/FSMonWin.cpp new file mode 100644 index 000000000..7f7056e5c --- /dev/null +++ b/bazaar/FSMon/FSMonWin.cpp @@ -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((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::GetChanged(void) +{ + Vector info; + INTERLOCKED_(fsmMutex) + { + info = changed; + changed.Clear(); + } + return info; +} + +// gets actually opened files +Index FSMon::GetOpenedFiles(void) +{ + // no way on windows to catch open/close on files + // without a dedicated filter + return Index(); +} + +// check error contidion and get error message +VectorMap FSMon::GetErrorMap(void) +{ + // just return last error -- on windows we can't have + // sequences of errors + VectorMap res; + res.Add("", (int)errCode); + return res; +} + +#endif + +END_UPP_NAMESPACE diff --git a/bazaar/FSMon/init b/bazaar/FSMon/init new file mode 100644 index 000000000..afe0519cb --- /dev/null +++ b/bazaar/FSMon/init @@ -0,0 +1,3 @@ +#ifndef _FSMon_icpp_init_stub +#define _FSMon_icpp_init_stub +#endif diff --git a/bazaar/FSMonTest/FSMonTest.h b/bazaar/FSMonTest/FSMonTest.h new file mode 100644 index 000000000..1d20dbb7d --- /dev/null +++ b/bazaar/FSMonTest/FSMonTest.h @@ -0,0 +1,33 @@ +#ifndef _FSMonTest_FSMonTest_h +#define _FSMonTest_FSMonTest_h + +#include + +using namespace Upp; + +#define LAYOUTFILE +#include + + + +class FSMonTest : public WithFSMonTestLayout +{ + 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 diff --git a/bazaar/FSMonTest/FSMonTest.lay b/bazaar/FSMonTest/FSMonTest.lay new file mode 100644 index 000000000..95a382704 --- /dev/null +++ b/bazaar/FSMonTest/FSMonTest.lay @@ -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 + diff --git a/bazaar/FSMonTest/FSMonTest.upp b/bazaar/FSMonTest/FSMonTest.upp new file mode 100644 index 000000000..6b67ca516 --- /dev/null +++ b/bazaar/FSMonTest/FSMonTest.upp @@ -0,0 +1,14 @@ +description "Test FSMon Filesystem Monitor Package\377"; + +uses + CtrlLib, + FSMon; + +file + FSMonTest.lay, + FSMonTest.h, + main.cpp; + +mainconfig + "" = "GUI MT"; + diff --git a/bazaar/FSMonTest/init b/bazaar/FSMonTest/init new file mode 100644 index 000000000..56ae17a5a --- /dev/null +++ b/bazaar/FSMonTest/init @@ -0,0 +1,5 @@ +#ifndef _FSMonTest_icpp_init_stub +#define _FSMonTest_icpp_init_stub +#include "CtrlLib/init" +#include "FSMon/init" +#endif diff --git a/bazaar/FSMonTest/main.cpp b/bazaar/FSMonTest/main.cpp new file mode 100644 index 000000000..a298c7adc --- /dev/null +++ b/bazaar/FSMonTest/main.cpp @@ -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 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(); +} diff --git a/bazaar/OCE/Visualization/Visualization.upp b/bazaar/OCE/Visualization/Visualization.upp index ca6d19101..7a178b1f2 100644 --- a/bazaar/OCE/Visualization/Visualization.upp +++ b/bazaar/OCE/Visualization/Visualization.upp @@ -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; +