/*==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 . 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