You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

380 lines
11 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
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 3 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnIniExe/Private/Win32/pnW32IniChange.cpp
*
***/
#include "../../Pch.h"
#pragma hdrstop
#ifdef HS_BUILD_FOR_WIN32
/*****************************************************************************
*
* Private
*
***/
struct IniChangeFile;
struct IniChangeReg {
LINK(IniChangeReg) fLink;
FIniFileChangeCallback fNotify;
qword fLastWriteTime;
wchar fFileName[MAX_PATH];
};
static CLock s_lock;
static HANDLE s_event;
static HANDLE s_signal;
static HANDLE s_thread;
static HANDLE s_change;
static bool s_running;
static IniChangeReg * s_dispatch;
static wchar s_directory[MAX_PATH];
static LISTDECL(IniChangeReg, fLink) s_callbacks;
/****************************************************************************
*
* Change notification
*
***/
//===========================================================================
static qword GetFileTimestamp (const wchar fileName[]) {
HANDLE find;
WIN32_FIND_DATAW fd;
qword lastWriteTime;
if (INVALID_HANDLE_VALUE != (find = FindFirstFileW(fileName, &fd))) {
COMPILER_ASSERT(sizeof(lastWriteTime) == sizeof(fd.ftLastWriteTime));
lastWriteTime = * (const qword *) &fd.ftLastWriteTime;
FindClose(find);
}
else {
lastWriteTime = 1; // any non-zero, non-valid number
}
return lastWriteTime;
}
//===========================================================================
static void ChangeDispatch_WL (IniChangeReg * marker) {
while (nil != (s_dispatch = s_callbacks.Next(marker))) {
// Move the marker to the next location
s_callbacks.Link(marker, kListLinkAfter, s_dispatch);
// If the file record time matches the file data time
// then there's no need to reprocess the callbacks
qword lastWriteTime = GetFileTimestamp(s_dispatch->fFileName);
if (s_dispatch->fLastWriteTime == lastWriteTime)
continue;
s_dispatch->fLastWriteTime = lastWriteTime;
// Leave lock to perform callback
s_lock.LeaveWrite();
s_dispatch->fNotify(s_dispatch->fFileName);
s_lock.EnterWrite();
}
// List traversal complete
SetEvent(s_signal);
}
//===========================================================================
static unsigned THREADCALL IniSrvThreadProc (AsyncThread * thread) {
IniChangeReg marker;
marker.fNotify = nil;
s_lock.EnterWrite();
s_callbacks.Link(&marker, kListHead);
s_lock.LeaveWrite();
HANDLE handles[2];
handles[0] = s_change;
handles[1] = s_event;
unsigned sleepMs = INFINITE;
for (;;) {
// Wait until something happens
unsigned result = WaitForMultipleObjects(
arrsize(handles),
handles,
false,
sleepMs
);
if (!s_running)
break;
// reset the sleep time
sleepMs = INFINITE;
if (result == WAIT_OBJECT_0) {
if (!FindNextChangeNotification(s_change)) {
LogMsg(
kLogError,
"IniSrv: FindNextChangeNotification() failed %#x",
GetLastError()
);
break;
}
// a change notification occurs when a file is created, even
// though it may take a number of seconds before the file data
// has been copied. Wait a few seconds after the last change
// notification so that files have a chance to stabilize.
sleepMs = 5 * 1000;
// When the timeout occurs, reprocess the entire list
s_lock.EnterWrite();
s_callbacks.Link(&marker, kListHead);
s_lock.LeaveWrite();
}
else if ((result == WAIT_OBJECT_0 + 1) || (result == WAIT_TIMEOUT)) {
// Queue for deadlock check
#ifdef SERVER
void * check = CrashAddDeadlockCheck(thread->handle, L"plW32IniChange.NtWorkerThreadProc");
#endif
s_lock.EnterWrite();
ChangeDispatch_WL(&marker);
s_lock.LeaveWrite();
// Unqueue for deadlock check
#ifdef SERVER
CrashRemoveDeadlockCheck(check);
#endif
}
else {
LogMsg(
kLogError,
"IniChange: WaitForMultipleObjects failed %#x",
GetLastError()
);
break;
}
}
// Cleanup
s_lock.EnterWrite();
s_callbacks.Unlink(&marker);
s_lock.LeaveWrite();
return 0;
}
/****************************************************************************
*
* Exports
*
***/
//===========================================================================
void IniChangeInitialize (const wchar dir[]) {
ASSERT(!s_running);
s_running = true;
const char * function;
for (;;) {
// Create the config directory
PathGetProgramDirectory(s_directory, arrsize(s_directory));
PathAddFilename(s_directory, s_directory, dir, arrsize(s_directory));
if (EPathCreateDirError error = PathCreateDirectory(s_directory, 0))
LogMsg(kLogError, "IniChange: CreateDir failed %u", error);
// Open change notification for directory
s_change = FindFirstChangeNotificationW(
s_directory,
false, // watchSubTree = false
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME
);
if (!s_change) {
function = "FindFirstChangeNotification";
break;
}
// create thread event
s_event = CreateEvent(
(LPSECURITY_ATTRIBUTES) 0,
false, // auto-reset
false, // initial state off
(LPCTSTR) 0 // name
);
if (!s_event) {
function = "CreateEvent";
break;
}
// create signal event
s_signal = CreateEvent(
(LPSECURITY_ATTRIBUTES) 0,
true, // manual-reset
false, // initial state off
(LPCTSTR) 0 // name
);
if (!s_signal) {
function = "CreateEvent";
break;
}
// create thread
s_thread = (HANDLE) AsyncThreadCreate(
IniSrvThreadProc,
nil,
L"IniSrvChange"
);
if (!s_thread) {
function = "AsyncThreadCreate";
break;
}
// Success!
return;
}
// Failure!
LogMsg(
kLogError,
"IniChange: %s failed (%#x)",
function,
GetLastError()
);
}
//===========================================================================
void IniChangeDestroy () {
s_running = false;
if (s_thread) {
SetEvent(s_event);
WaitForSingleObject(s_thread, INFINITE);
CloseHandle(s_thread);
s_thread = nil;
}
if (s_event) {
CloseHandle(s_event);
s_event = nil;
}
if (s_signal) {
CloseHandle(s_signal);
s_signal = nil;
}
if (s_change) {
FindCloseChangeNotification(s_change);
s_change = nil;
}
ASSERT(!s_callbacks.Head());
}
//===========================================================================
void IniChangeAdd (
const wchar fileName[],
FIniFileChangeCallback callback,
IniChangeReg ** changePtr
) {
ASSERT(fileName);
ASSERT(callback);
ASSERT(changePtr);
ASSERT(s_running);
// Create a callback record
IniChangeReg * change = NEW(IniChangeReg);
change->fNotify = callback;
change->fLastWriteTime = 0;
PathAddFilename(
change->fFileName,
s_directory,
fileName,
arrsize(change->fFileName)
);
PathRemoveExtension(change->fFileName, change->fFileName, arrsize(change->fFileName));
PathAddExtension(change->fFileName, change->fFileName, L".ini", arrsize(change->fFileName));
// Set result before callback to avoid race condition
*changePtr = change;
// Signal change record for immediate callback
// and wait for callback completion
IniChangeSignal(change, true);
}
//===========================================================================
void IniChangeRemove (
IniChangeReg * change,
bool wait
) {
ASSERT(change);
s_lock.EnterWrite();
{
// Wait until the callback is no longer being dispatched
if (wait) {
while (s_dispatch == change) {
s_lock.LeaveWrite();
AsyncSleep(10);
s_lock.EnterWrite();
}
}
// Remove change object from list so that
// it can be deleted outside the lock
change->fLink.Unlink();
}
s_lock.LeaveWrite();
// Delete object outside critical section
DEL(change);
}
//===========================================================================
void IniChangeSignal (
IniChangeReg * change,
bool wait
) {
ASSERT(change);
s_lock.EnterWrite();
{
s_callbacks.Link(change, kListTail);
change->fLastWriteTime = 0;
ResetEvent(s_signal);
}
s_lock.LeaveWrite();
// Wake up the change thread to process this request
SetEvent(s_event);
// Wait until the request has been processed
if (wait)
WaitForSingleObject(s_signal, INFINITE);
}
#endif // HS_BUILD_FOR_WIN32