// // MODULE: DirMonitor.cpp // // PURPOSE: Monitor changes to LST, DSC, HTI, BES files. // // COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com // // AUTHOR: Joe Mabel // // ORIGINAL DATE: 9-17-98 // // NOTES: // // Version Date By Comments //-------------------------------------------------------------------- // V3.0 09-17-98 JM // #pragma warning(disable:4786) #include "stdafx.h" #include #include "DirMonitor.h" #include "event.h" #include "apiwraps.h" #include "CharConv.h" #ifdef LOCAL_TROUBLESHOOTER #include "LocalLSTReader.h" #include "CHMFileReader.h" #endif #include "apgts.h" // Need for Local-Online macros. const DWORD k_secsDefaultReloadDelay = 40; // In practice, this default should not matter, // because SetReloadDelay() should be called before // SetResourceDirectory(). However, 40 is a typical // reasonable value for m_secsReloadDelay. ///////////////////////////////////////////////////////////////////// // CTopicFileTracker ///////////////////////////////////////////////////////////////////// CTopicFileTracker::CTopicFileTracker() : CFileTracker() { } CTopicFileTracker::~CTopicFileTracker() { } void CTopicFileTracker::AddTopicInfo(const CTopicInfo & topicinfo) { m_topicinfo = topicinfo; // set CFileTracker member variables accordingly for files that are present. // If they are not present i.e. empty strings then adding them here results in // unnecessary event log entries. AddFile(topicinfo.GetDscFilePath()); CString strHTI = topicinfo.GetHtiFilePath(); if (!strHTI.IsEmpty()) AddFile(strHTI); CString strBES = topicinfo.GetBesFilePath(); if (!strBES.IsEmpty()) AddFile(strBES); } const CTopicInfo & CTopicFileTracker::GetTopicInfo() const { return m_topicinfo; } ///////////////////////////////////////////////////////////////////// // CTemplateFileTracker ///////////////////////////////////////////////////////////////////// CTemplateFileTracker::CTemplateFileTracker() : CFileTracker() { } CTemplateFileTracker::~CTemplateFileTracker() { } void CTemplateFileTracker::AddTemplateName( const CString& strTemplateName ) { m_strTemplateName= strTemplateName; AddFile( strTemplateName ); } const CString& CTemplateFileTracker::GetTemplateName() const { return m_strTemplateName; } ////////////////////////////////////////////////////////////////////// // CDirectoryMonitor::ThreadStatus ////////////////////////////////////////////////////////////////////// /* static */ CString CDirectoryMonitor::ThreadStatusText(ThreadStatus ts) { switch(ts) { case eBeforeInit: return _T("Before Init"); case eFail: return _T("Fail"); case eWaitDirPath: return _T("Wait For Dir Path"); case eWaitChange: return _T("Wait for Change"); case eWaitSettle: return _T("Wait to Settle"); case eRun: return _T("Run"); case eBeforeWaitChange: return _T("Before Wait Change"); case eExiting: return _T("Exiting"); default: return _T(""); } } ///////////////////////////////////////////////////////////////////// // CDirectoryMonitor // This class does the bulk of its work on a separate thread. // The thread is created in the constructor by starting static function // CDirectoryMonitor::DirectoryMonitorTask // That function, in turn does its work by calling private members of this class that // are specific to use on the DirectoryMonitorTask thread. // When this goes out of scope, its own destructor calls ShutDown to stop the thread, // waits for the thread to shut. // The following methods are available for other threads communicating with that thread: // CDirectoryMonitor::SetReloadDelay // CDirectoryMonitor::SetResourceDirectory ///////////////////////////////////////////////////////////////////// CDirectoryMonitor::CDirectoryMonitor(CTopicShop & TopicShop, const CString& strTopicName) : m_strTopicName(strTopicName), m_TopicShop(TopicShop), m_pErrorTemplate(NULL), m_strDirPath(_T("")), // Essential that this starts blank. Getting a different // value is how we start the DirectoryMonitorTask thread. m_bDirPathChanged(false), m_bShuttingDown(false), m_secsReloadDelay(k_secsDefaultReloadDelay), m_pTrackLst( NULL ), m_pTrackErrorTemplate( NULL ), m_pLst( NULL ), m_dwErr(0), m_ThreadStatus(eBeforeInit), m_time(0) { enum {eHevMon, eHevShut, eThread, eOK} Progress = eHevMon; SetThreadStatus(eBeforeInit); m_hevMonitorRequested = ::CreateEvent( NULL, FALSE, // release one thread (the DirectoryMonitorTask) on signal FALSE, // initially non-signalled NULL); if (m_hevMonitorRequested) { Progress = eHevShut; m_hevThreadIsShut = ::CreateEvent( NULL, FALSE, // release one thread (this one) on signal FALSE, // initially non-signalled NULL); if (m_hevThreadIsShut) { Progress = eThread; DWORD dwThreadID; // No need to hold onto dwThreadID in member variable. // All Win32 functions take the handle m_hThread instead. // The one reason you'd ever want to know this ID is for // debugging // Note that there is no corresponding ::CloseHandle(m_hThread). // That is because the thread goes out of existence on the implicit // ::ExitThread() when DirectoryMonitorTask returns. See documentation of // ::CreateThread for further details JM 10/22/98 m_hThread = ::CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)DirectoryMonitorTask, this, 0, &dwThreadID); if (m_hThread) Progress = eOK; } } if (Progress != eOK) { m_dwErr = GetLastError(); CString str; str.Format(_T("%d"), m_dwErr); CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), (Progress == eHevMon) ? _T("Can't create monitor event") : (Progress == eHevShut) ? _T("Can't create \"shut\" event") : _T("Can't create thread"), str, EV_GTS_ERROR_DIRMONITORTHREAD ); SetThreadStatus(eFail); if (m_hevMonitorRequested) ::CloseHandle(m_hevMonitorRequested); if (m_hevThreadIsShut) ::CloseHandle(m_hevThreadIsShut); } } CDirectoryMonitor::~CDirectoryMonitor() { ShutDown(); if (m_hevMonitorRequested) ::CloseHandle(m_hevMonitorRequested); if (m_hevThreadIsShut) ::CloseHandle(m_hevThreadIsShut); if (m_pErrorTemplate) delete m_pErrorTemplate; if (m_pTrackLst) delete m_pTrackLst; if (m_pTrackErrorTemplate) delete m_pTrackErrorTemplate; } void CDirectoryMonitor::SetThreadStatus(ThreadStatus ts) { LOCKOBJECT(); m_ThreadStatus = ts; time(&m_time); UNLOCKOBJECT(); } DWORD CDirectoryMonitor::GetStatus(ThreadStatus &ts, DWORD & seconds) const { time_t timeNow; LOCKOBJECT(); ts = m_ThreadStatus; time(&timeNow); seconds = timeNow - m_time; UNLOCKOBJECT(); return m_dwErr; } // Only for use by this class's own destructor. void CDirectoryMonitor::ShutDown() { LOCKOBJECT(); m_bShuttingDown = true; if (m_hThread) { ::SetEvent(m_hevMonitorRequested); UNLOCKOBJECT(); // Wait for a set period, if failure then log error msg and wait infinite. WAIT_INFINITE( m_hevThreadIsShut ); } else UNLOCKOBJECT(); } // For use by the DirectoryMonitorTask thread. // Read LST file and add any topics that are not already in previously read LST file contents void CDirectoryMonitor::LstFileDrivesTopics() { // previous LST file contents, saved for comparison. CAPGTSLSTReader *pLstOld = m_pLst; if (! m_strLstPath.IsEmpty() ) { try { #ifdef LOCAL_TROUBLESHOOTER m_pLst = new CLocalLSTReader( CPhysicalFileReader::makeReader( m_strLstPath ), m_strTopicName); #else m_pLst = new CAPGTSLSTReader( dynamic_cast(new CNormalFileReader(m_strLstPath)) ); #endif } catch (bad_alloc&) { // Restore old LST contents. m_pLst = pLstOld; // Rethrow exception, logging handled upstream. throw; } if (! m_pLst->Read()) { // Restore old LST contents and log error. delete m_pLst; m_pLst = pLstOld; CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_ERROR_LST_FILE_READ ); } else { CTopicInfoVector arrNewTopicInfo; m_pLst->GetDifference(pLstOld, arrNewTopicInfo); if (pLstOld) delete pLstOld; for (CTopicInfoVector::iterator itNewTopicInfo = arrNewTopicInfo.begin(); itNewTopicInfo != arrNewTopicInfo.end(); itNewTopicInfo++ ) { // Let the Topic Shop know about the new topic m_TopicShop.AddTopic(*itNewTopicInfo); // add it to our list of files to track for changes CTopicFileTracker TopicFileTracker; TopicFileTracker.AddTopicInfo(*itNewTopicInfo); LOCKOBJECT(); try { m_arrTrackTopic.push_back(TopicFileTracker); } catch (exception& x) { CString str; // Note STL exception in event log. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); } UNLOCKOBJECT(); } } } // if topic shop not already open, open it m_TopicShop.OpenShop(); } // Called by the topic shop to add alternate templates to track. void CDirectoryMonitor::AddTemplateToTrack( const CString& strTemplateName ) { LOCKOBJECT(); try { CTemplateFileTracker TemplateFileTracker; TemplateFileTracker.AddTemplateName( strTemplateName ); m_arrTrackTemplate.push_back( TemplateFileTracker ); } catch (exception& x) { CString str; // Note STL exception in event log. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); } UNLOCKOBJECT(); } // For use by the DirectoryMonitorTask thread. void CDirectoryMonitor::ReadErrorTemplate() { LOCKOBJECT(); if (m_pErrorTemplate) delete m_pErrorTemplate; CString str = k_strDefaultErrorTemplateBefore; str += k_strErrorTemplateKey; str += k_strDefaultErrorTemplateAfter; try { m_pErrorTemplate = new CSimpleTemplate( CPhysicalFileReader::makeReader( m_strErrorTemplatePath ), str ); } catch (bad_alloc&) { UNLOCKOBJECT(); // Rethrow the exception. throw; } m_pErrorTemplate->Read(); UNLOCKOBJECT(); } // For use by any thread. In this class because CDirectoryMonitor needs to own // ErrorTemplate, since it can change during run of system. void CDirectoryMonitor::CreateErrorPage(const CString & strError, CString& out) const { LOCKOBJECT(); if (m_pErrorTemplate) { vector arrTemplateInfo; CTemplateInfo info(k_strErrorTemplateKey, strError); try { arrTemplateInfo.push_back(info); m_pErrorTemplate->CreatePage( arrTemplateInfo, out ); } catch (exception& x) { CString str; // Note STL exception in event log. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), _T(""), EV_GTS_STL_EXCEPTION ); // Generate the default error page to be safe. out = k_strDefaultErrorTemplateBefore + strError + k_strDefaultErrorTemplateAfter; } } else out = k_strDefaultErrorTemplateBefore + strError + k_strDefaultErrorTemplateAfter; UNLOCKOBJECT(); } // Must be called on DirectoryMonitorTask thread. // Handles all work of monitoring the directory. Loops till shutdown. void CDirectoryMonitor::Monitor() { enum { #ifndef LOCAL_TROUBLESHOOTER eDirChange, // file in directory changed #endif eHev, // shutdown or change what directory eNumHandles }; // array of handles we use when waiting for multiple events. // Initialize first entry to default bad value. HANDLE hList[eNumHandles]= { INVALID_HANDLE_VALUE }; if (m_strDirPath.GetLength() == 0) { SetThreadStatus(eWaitDirPath); // Block this thread until notification that the directory path has been set. ::WaitForSingleObject( m_hevMonitorRequested, INFINITE); } SetThreadStatus(eRun); try { if (RUNNING_ONLINE_TS()) { // The DirPathChanged flag should be set here, enforce it if not. ASSERT( m_bDirPathChanged ); if (!m_bDirPathChanged) m_bDirPathChanged= true; } // Wait for an explicit wakeup. hList[eHev] = m_hevMonitorRequested; while (true) { LOCKOBJECT(); if (m_bShuttingDown) { UNLOCKOBJECT(); break; } if (m_bDirPathChanged) { #ifndef LOCAL_TROUBLESHOOTER // Set the directory to be monitored. if (hList[eDirChange] != INVALID_HANDLE_VALUE) ::FindCloseChangeNotification( hList[eDirChange] ); while (true) { // handle to monitor for change in the resource directory hList[eDirChange] = ::FindFirstChangeNotification(m_strDirPath, TRUE, // monitor subdirectories (for multilingual) FILE_NOTIFY_CHANGE_LAST_WRITE ); if (hList[eDirChange] == INVALID_HANDLE_VALUE) { // resource directoty does not exist. // Track creation of directories in upper directory // - it might be resource directory bool bFail = false; CString strUpperDir = m_strDirPath; // directory above resource directory (m_strDirPath) if ( strUpperDir[strUpperDir.GetLength()-1] == _T('\\') || strUpperDir[strUpperDir.GetLength()-1] == _T('/')) { strUpperDir = strUpperDir.Left(strUpperDir.GetLength() ? strUpperDir.GetLength()-1 : 0); } int slash_last = max(strUpperDir.ReverseFind(_T('\\')), strUpperDir.ReverseFind(_T('/'))); if (-1 != slash_last) { strUpperDir = strUpperDir.Left(slash_last); hList[eDirChange] = ::FindFirstChangeNotification(strUpperDir, TRUE, // monitor subdirectories (for multilingual) FILE_NOTIFY_CHANGE_DIR_NAME ); if (hList[eDirChange] == INVALID_HANDLE_VALUE) bFail = true; } else bFail = true; if (!bFail) { // We have a valid handle, exit this loop. SetThreadStatus(eRun); break; } else { // typically would mean none of resource directory or its upper // directory is valid, log this. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), m_strDirPath, _T(""), EV_GTS_ERROR_CANT_FILE_NOTIFY ); SetThreadStatus(eWaitDirPath); // Block this thread until notification that the directory path // has been correctly set. Unlock the object so that the event // can be set. UNLOCKOBJECT(); ::WaitForSingleObject( m_hevMonitorRequested, INFINITE); LOCKOBJECT(); } } else { // We have a valid handle, exit this loop. SetThreadStatus(eRun); break; } } #endif m_bDirPathChanged = false; if (m_pTrackLst) delete m_pTrackLst; m_pTrackLst = new CFileTracker; if (RUNNING_ONLINE_TS()) m_pTrackLst->AddFile(m_strLstPath); if (m_pTrackErrorTemplate) delete m_pTrackErrorTemplate; m_pTrackErrorTemplate = new CFileTracker; if (RUNNING_ONLINE_TS()) m_pTrackErrorTemplate->AddFile(m_strErrorTemplatePath); UNLOCKOBJECT(); ReadErrorTemplate(); LstFileDrivesTopics(); } else { UNLOCKOBJECT(); if (m_pTrackLst && m_pTrackLst->Changed()) LstFileDrivesTopics(); if (m_pTrackErrorTemplate && m_pTrackErrorTemplate->Changed( false )) ReadErrorTemplate(); } LOCKOBJECT(); for (vector::iterator itTopicFiles = m_arrTrackTopic.begin(); itTopicFiles != m_arrTrackTopic.end(); itTopicFiles ++ ) { #ifdef LOCAL_TROUBLESHOOTER if (m_bDirPathChanged) #else if (itTopicFiles->Changed()) #endif m_TopicShop.BuildTopic(itTopicFiles->GetTopicInfo().GetNetworkName()); if (m_bShuttingDown) break; } if (RUNNING_ONLINE_TS()) { // Check if any of the alternate template files need to be reloaded. for (vector::iterator itTemplateFiles = m_arrTrackTemplate.begin(); itTemplateFiles != m_arrTrackTemplate.end(); itTemplateFiles ++ ) { if (itTemplateFiles->Changed()) m_TopicShop.BuildTemplate( itTemplateFiles->GetTemplateName() ); if (m_bShuttingDown) break; } } ::ResetEvent(m_hevMonitorRequested); SetThreadStatus(eWaitChange); UNLOCKOBJECT(); DWORD dwNotifyObj = WaitForMultipleObjects ( eNumHandles, hList, FALSE, // only need one object, not all INFINITE); SetThreadStatus(eBeforeWaitChange); // Ideally we would update files here. // Unfortunately, we get a notification that someone has _started_ // writing to a file, not that they've finished, so we have to put in // an artificial delay. // We must let the system "settle down". while ( #ifndef LOCAL_TROUBLESHOOTER dwNotifyObj == WAIT_OBJECT_0+eDirChange && #endif !m_bShuttingDown) { #ifndef LOCAL_TROUBLESHOOTER // wait for the next change if (FindNextChangeNotification( hList[eDirChange] ) == FALSE) { // 1) we don't believe this will ever occur // 2) After a moderate amount of research, we have no idea how // to recover from it if it does occur. // SO: unless we ever actually see this, we're not going to waste // more time researching a recovery strategy. Just throw an exception, // effectively terminating this thread. throw CGenSysException( __FILE__, __LINE__, m_strDirPath, EV_GTS_ERROR_WAIT_NEXT_NFT ); } #endif SetThreadStatus(eWaitSettle); dwNotifyObj = WaitForMultipleObjects ( eNumHandles, hList, FALSE, // only need one object, not all m_secsReloadDelay * 1000); // convert to milliseconds } if (dwNotifyObj == WAIT_FAILED) { // 1) we don't believe this will ever occur // 2) After a moderate amount of research, we have no idea how // to recover from it if it does occur. // SO: unless we ever actually see this, we're not going to waste // more time researching a recovery strategy. Just throw an exception, // effectively terminating this thread. throw CGenSysException( __FILE__, __LINE__, _T("Unexpected Return State"), EV_GTS_DEBUG ); } SetThreadStatus(eRun); } } catch (CGenSysException& x) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( x.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), x.GetErrorMsg(), x.GetSystemErrStr(), x.GetErrorCode() ); } catch (CGeneralException& x) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( x.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), x.GetErrorMsg(), _T("General exception"), x.GetErrorCode() ); } catch (bad_alloc&) { CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_CANT_ALLOC ); } catch (exception& x) { // Catch any STL exceptions thrown so that Terminate() is not called. CString str; CString ErrStr; // Attempt to pull any system error code. ErrStr.Format( _T("%ld"), ::GetLastError() ); CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), CCharConversion::ConvertACharToString(x.what(), str), ErrStr, EV_GTS_GENERIC_PROBLEM ); } catch (...) { // Catch any other exception thrown. CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ ); CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(), SrcLoc.GetSrcFileLineStr(), _T(""), _T(""), EV_GTS_GEN_EXCEPTION ); } #ifndef LOCAL_TROUBLESHOOTER if (hList[eDirChange] != INVALID_HANDLE_VALUE) ::FindCloseChangeNotification( hList[eDirChange] ); #endif SetThreadStatus(eExiting); } // For general use (not part of DirectoryMonitorTask thread) // Typically, first call to this comes _before_ first call to SetResourceDirectory; // This allows caller to set reload delay before triggering any action on // DirectoryMonitorTask thread. void CDirectoryMonitor::SetReloadDelay(DWORD secsReloadDelay) { LOCKOBJECT(); m_secsReloadDelay = secsReloadDelay; UNLOCKOBJECT(); } // For general use (not part of DirectoryMonitorTask thread) // Allows indicating that the resource directory has changed // Until this is called, the DirectoryMonitorTask thread really won't do anything void CDirectoryMonitor::SetResourceDirectory(const CString & strDirPath) { LOCKOBJECT(); if (strDirPath != m_strDirPath) { m_strDirPath = strDirPath; m_strLstPath = strDirPath + LSTFILENAME; m_strErrorTemplatePath = strDirPath + k_strErrorTemplateFileName; m_bDirPathChanged = true; ::SetEvent(m_hevMonitorRequested); } UNLOCKOBJECT(); } // Must be called on DirectoryMonitorTask thread. void CDirectoryMonitor::AckShutDown() { LOCKOBJECT(); ::SetEvent(m_hevThreadIsShut); UNLOCKOBJECT(); } // Main routine of a thread responsible for monitoring the directory. // INPUT lpParams // Always returns 0. /* static */ UINT WINAPI CDirectoryMonitor::DirectoryMonitorTask(LPVOID lpParams) { reinterpret_cast(lpParams)->Monitor(); reinterpret_cast(lpParams)->AckShutDown(); return 0; }