⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 filereaderthread.cpp

📁 Ever wanted to just type tail -f error_log on Windows?Envious of your Unix friends who can track cha
💻 CPP
📖 第 1 页 / 共 2 页
字号:
/*
 * Tail for Win32 - a Windows version of the UNIX 'tail -f' command.
 * 
 * Author: Paul Perkins (paul@objektiv.org.uk)
 * 
 * Copyright(c)
 *
 * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple 
 * Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * $Id: FileReaderThread.cpp,v 1.12 2003/11/21 10:27:31 paulperkins Exp $
 * 
 */

#include "stdafx.h"
#include <io.h>
#include <stdio.h>

#include "utils.h"

#include "filereaderthread.h"

#define   NO_HANDLER_THREAD       (1)                 // Don't spawn a separate thread.

#define   WAIT_OBJECT_DIE         (WAIT_OBJECT_0)
#define   WAIT_OBJECT_FILEUPDATE  (WAIT_OBJECT_0 + 1)
#define   WAIT_OBJECT_RELOAD      (WAIT_OBJECT_0 + 2)

#define   WAIT_OBJECT_POLL_RELOAD (WAIT_OBJECT_0 + 1)

#define   MAX_INIT_FILE_SIZE      (100 * 1024)

typedef struct _FILE_HANDLER_PARAMS
{
  CTailView* pView;
  DWORD*  pdwOldFileSize;
  DWORD*  pdwNewFileSize;
  char    szDieEvent[_MAX_FNAME];
  char    szGoEvent[_MAX_FNAME];
  BOOL*   pbFirst;
  BOOL*   pbIgnoreHotStartup;
  HWND    hwndEdit;
  char    szFileName[_MAX_FNAME];

  //char szDieEvent[_MAX_FNAME];
  char szReloadEvent[_MAX_FNAME];
  HWND hEdit;
  BOOL bUseTimeout;
  BOOL bIgnoreHotStartup;
  BOOL bDebug;
  int  nTimeout;
  BOOL bPaused;

} FILE_HANDLER_PARAMS;

// Function pointer for update function. To be called by thread
// when an update occurs. 
// The first param is to be used as a generic placeholder for
// any type of data.
typedef BOOL (*UPDATE_PROC) (DWORD, char*, BOOL, long*, BOOL*);

// Function to call on update.
// From TailView.cpp.
extern BOOL InsertText (
  CTailView*      pView,
  char*           pszText,
  BOOL            bDoColouring,
  long*           plMatchCount,
  BOOL*           pbMatch);

// Function to update status bar.
extern void UpdateStatusBar (
  CTailView* pView,
  char*      pszText);

extern void ReloadKeywords (
  CTailView* pView);

static DWORD GetFileSizeBytes (
  const char* pszFilename)
{
  HANDLE  hFile = NULL;
  DWORD   dwFileSize = 0;

  hFile = CreateFile (pszFilename, 
                      GENERIC_READ, 
                      FILE_SHARE_READ  | FILE_SHARE_WRITE, 
                      NULL, 
                      OPEN_EXISTING, 
                      FILE_ATTRIBUTE_NORMAL, 
                      NULL);

  dwFileSize = GetFileSize (hFile, NULL);

  CloseHandle (hFile);

  return dwFileSize;
}

static DWORD GetChangedLines (
  char*       pszNewLines,
  char**      ppszChangedLines,
  const DWORD dwOldFileSize,
  const DWORD dwNewFileSize,
  DWORD*      pdwType,
  BOOL        bFullRead)
{
  if (bFullRead)
  {
    // File is now smaller than it was -- assume the file was truncated or re-written.
    if (dwNewFileSize < dwOldFileSize)
    {
      *ppszChangedLines = pszNewLines;
      *pdwType = CHANGE_FULL;

      return 0;
    }

    *ppszChangedLines = pszNewLines + dwOldFileSize;
    *pdwType = CHANGE_PARTIAL;
  }
  else
  {
    *ppszChangedLines = pszNewLines;
    *pdwType = CHANGE_PARTIAL;
  }


  return 0;
}

BOOL FileHandlerThread (
  LPVOID pParams)
{
  HANDLE  hFile = 0;
  HANDLE  hDie = 0;
  HANDLE  hGo = 0;
  DWORD   dwRtn = 0;
  HANDLE  haEvents[2];
  BOOL    bRtn = FALSE;
  BOOL    bFileGone = FALSE;
  BOOL    bLastChunk = FALSE;
  DWORD   dwBufferSize = 0;
  DWORD   dwNewFileSize = 0;
  DWORD   dwOldFileSize = 0;
  DWORD   dwChangeType = 0;
  char*   pszBuffer = NULL;
  char*   pszNewLines = NULL;
  char*   pszNewLinesSkip = NULL;
  BOOL    bReadOK = FALSE;
  DWORD   dwMaxInitFileSize = MAX_INIT_FILE_SIZE; // Size of file before considering only getting last n bytes.
  DWORD   dwInitFileSize = dwMaxInitFileSize;     // Size of chunk to bite off end of large file at startup.
  DWORD   dwBytesRead = 0;
  BOOL    bMatch = FALSE;   // PP: I think this is not used any more - only used to pass to View functions.
  BOOL    bUpdated = FALSE; // PP: I think this is not used any more.
  long    lMatches = 0;     // PP: I think this is not used any more - only used to pass to View functions.

  FILE_HANDLER_PARAMS* parms = NULL;

  LogMessage ("Debug: Started handler thread.");

  parms = (FILE_HANDLER_PARAMS*) pParams;

  if (!(hDie = OpenEvent (EVENT_ALL_ACCESS, FALSE, parms->szDieEvent)))
  {
    hDie = CreateEvent (NULL, TRUE, FALSE, parms->szDieEvent); 
  }

  if (!(hGo = OpenEvent (EVENT_ALL_ACCESS, FALSE, parms->szGoEvent)))
  {
    hGo = CreateEvent (NULL, TRUE, FALSE, parms->szGoEvent); 
  }

  // Set up the wait event array.
  haEvents[0] = hDie;
  haEvents[1] = hGo;

  while (1)
  {
    dwRtn = WaitForMultipleObjects (2, haEvents, FALSE, INFINITE);  

    if (dwRtn == WAIT_FAILED)
    {
      LogDebugMessage ("WaitForMultipleObjects failed - returned %ld", dwRtn);
    }
    else if (dwRtn == WAIT_OBJECT_DIE)
		{
	    LogDebugMessage ("WaitForMultipleObjects triggered from die event", dwRtn);
		}
    else if (dwRtn == WAIT_TIMEOUT)
    {
	    LogMessage ("Debug: WaitForMultipleObjects triggered from polling interval timeout", dwRtn);
    }
		else if (dwRtn == (WAIT_OBJECT_FILEUPDATE))
		{
	    LogMessage ("Debug: WaitForMultipleObjects triggered from file wait event", dwRtn);			
		}

    switch (dwRtn)
    {
      case WAIT_TIMEOUT:
      // Wait got bored, start waiting again.
        LogMessage ("Debug: Wait timed out.");

        continue;

      case WAIT_OBJECT_DIE:
        // We're dead.      
        LogMessage ("Debug: Received kill event.");

        CloseHandle (hDie);
        CloseHandle (hGo);

        return 0;

      case WAIT_ABANDONED:    
      case WAIT_ABANDONED + 1:    
        ::AfxMessageBox ("Wait abandoned!");

        LogMessage ("Wait abandoned!");

        CloseHandle (hDie);
        CloseHandle (hGo);

        return 1;

      default:
        LogMessage ("Debug: Received change event (or polled).");

        break;
    }

    // The main loop.

    dwNewFileSize = *(parms->pdwNewFileSize);
    dwOldFileSize = *(parms->pdwOldFileSize);

    hFile = CreateFile (parms->szFileName, 
                        GENERIC_READ, 
                        FILE_SHARE_READ | FILE_SHARE_WRITE, 
                        NULL, 
                        OPEN_EXISTING, 
                        FILE_ATTRIBUTE_NORMAL, 
                        NULL);

    if ((hFile == INVALID_HANDLE_VALUE) && !bFileGone)
    {
      ::AfxMessageBox ("Hmmm... Looks like the file has been deleted. I'll wait.");

      bFileGone = TRUE;

      UpdateStatusBar (parms->pView, "Waiting for file...");

      continue;
    }

    if (bFileGone)
    {
      // We're now here, so the file must have come back.
      bFileGone = FALSE;
    }

    // Work out the size of the buffer needed to hold the changed lines.
    // if (bFirst && bIgnoreHotStartup) // Warlock, commented out, changed below
    if (*(parms->pbFirst)) // Warlock, still load when bIgnoreHotStartup is on
    {
      // See if the file is larger than the maximum size.
      // If so, only bite off the last few lines to save memory.
      // PP: This logic will need to change for cases where the file has 
      // been truncated and the initial contents are still huge.
      if (dwNewFileSize > dwMaxInitFileSize)
      {
        bLastChunk = TRUE;
        dwBufferSize = dwInitFileSize;
      }
      else
      {
        // Org.
        dwBufferSize = dwNewFileSize + 1;
      }
    }
    else  // Most times - just load the changed lines.
    {
      if (dwNewFileSize > dwOldFileSize)
      {
        dwBufferSize = (dwNewFileSize - dwOldFileSize) + 1;
      }
      else
      {
        // File has been truncated.
  			LogMessage ("Debug: File has been truncated.");      

        dwBufferSize = dwNewFileSize + 1;

//          dwOldFileSize = 0;
        dwChangeType = CHANGE_FULL;
      }
    }

		LogMessage ("Debug: Allocating %ld bytes for buffer", dwBufferSize);      
    
    pszBuffer = (char*) malloc (dwBufferSize);

    if (pszBuffer == NULL)
    {
      ::AfxMessageBox ("Could not allocate memory for file!");

      CloseHandle (hDie);
      CloseHandle (hGo);

      return 1;
    }

    memset ((void*) pszBuffer, 0, dwBufferSize);

    if (bLastChunk)
    {
      DWORD rtn;

    	LogMessage ("Debug: File is larger than %ld, only reading last %ld bytes.", dwMaxInitFileSize, dwBufferSize);      

      rtn = SetFilePointer (hFile, 1 - dwBufferSize, NULL, FILE_END);

      LogMessage ("Debug: File pointer now set to %ld", rtn);
    }
    else
    {
      if (*(parms->pbFirst) && (dwNewFileSize > dwMaxInitFileSize))
      {
        LogMessage ("Debug: File is larger than %ld, only reading last %ld bytes.", dwMaxInitFileSize, dwBufferSize);      

        SetFilePointer (hFile, dwBufferSize, NULL, FILE_END);
      }
      else
      {
        if (dwNewFileSize < dwOldFileSize)
        {
          SetFilePointer (hFile, 0, NULL, FILE_BEGIN);
        }
        else
        {
          // Org.
          SetFilePointer (hFile, dwOldFileSize, NULL, FILE_BEGIN);
        }
      }
    }

    bReadOK = ReadFile (hFile, (LPVOID) pszBuffer, dwBufferSize - 1, &dwBytesRead, NULL);

    CloseHandle (hFile);     	

    if (bReadOK)
    {
      if (bLastChunk)
      {
        GetChangedLines (pszBuffer, &pszNewLines, dwOldFileSize, dwBufferSize, &dwChangeType, FALSE);
        bLastChunk = FALSE;
        if (*(parms->pbFirst) && (dwNewFileSize > dwMaxInitFileSize)) // Warlock, neat: skip/blank the chopped line.
        {
          pszNewLinesSkip = strchr( pszNewLines, '\n' ); 
          LogMessage ("Debug: Skipping chopped line, jumping forward %ld Bytes.", pszNewLinesSkip - pszNewLines);
          pszNewLines = pszNewLinesSkip;
        }
      }
      else
      {
        if (dwNewFileSize < dwOldFileSize)
        {
          pszNewLines = pszBuffer;
        }
        else
        {
          // Org.
          GetChangedLines (pszBuffer, &pszNewLines, dwOldFileSize, dwNewFileSize, &dwChangeType, FALSE);
        }
      }

      // Clear the window down first.
      if (dwChangeType == CHANGE_FULL)
      {
        ::SetWindowText (parms->hwndEdit, "");
      }

      bMatch = FALSE;

      // Update the window, doing syntax highlighting, setting counters if matches found.
      bUpdated = InsertText (parms->pView, pszNewLines, !(*(parms->pbFirst) && *(parms->pbIgnoreHotStartup)), &lMatches, &bMatch);
    }

    free ((void*) pszBuffer); 


    // End main loop.

  } // while (1)

}


UINT ReaderThread (
  LPVOID pParams)
{
  READER_PARAMS* parms;
  HANDLE    fch = NULL;
  HANDLE    hFile = NULL;
  HANDLE    haEvents[3];
  HANDLE    hDie;
  HANDLE    hReload;
  DWORD     dwFileSize = 0;
  DWORD     dwNewFileSize = 0;
  DWORD     dwOldFileSize = 0;
  DWORD     dwRtn;
  HWND      hwndEdit = NULL;
  char      szDrive[_MAX_DRIVE] = "";
  char      szPath[_MAX_PATH] = "";
  char      szFileName[_MAX_FNAME] = "";
  char      szNewPath[_MAX_PATH] = "";
  char      szEvent[_MAX_PATH] = "";
  BOOL      bFirst = TRUE;
  BOOL      bUseTimeout = FALSE;
  BOOL      bIgnoreHotStartup = FALSE;
  BOOL      bUpdated = FALSE;
  BOOL      bDebug = FALSE;
  BOOL      bPaused = FALSE;
  int       nTimeout = 0;
  char*     pszNewLines = NULL;
  DWORD     dwChangeType = CHANGE_FULL;
  CTailView* pView = NULL;                        
  BOOL      bRtn = FALSE;
  BOOL      bFileGone = FALSE;
  UINT      uiFlags = 0;                          // Type of drive containing file.
  char*     pszBuffer = NULL;                     // Buffer for changed lines.
  DWORD     dwBytesRead = 0;                      // Number of bytes read from file.
  BOOL      bReadOK = FALSE;                      // Read succeeded.
  DWORD     dwBufferSize = 0;                     // Size of buffer to allocate for changed segment.
  long      lMatches = 0;                         // Number of matches found against keyword list.
  BOOL      bMatch = FALSE;                       // A match against list was found in this scan.
  DWORD     dwMaxInitFileSize = 100 * 1024;       // Size of file before considering only getting last n bytes.
  DWORD     dwInitFileSize = dwMaxInitFileSize;   // Size of chunk to bite off end of large file at startup.
  BOOL      bLastChunk = FALSE;                   // Only bite off last chunk of file.
  char*     pszNewLinesSkip;                      // Warlock, skip forward passed chopped line.

#ifndef NO_HANDLER_THREAD
  HANDLE    hHandlerGo = 0;
  HANDLE    hHandlerDie = 0;
  FILE_HANDLER_PARAMS stFileHandlerParams;
#endif

  LogMessage ("Debug: Started main thread.");

  if (!pParams)
  {
    ::MessageBox (AfxGetMainWnd()->GetSafeHwnd(), "PANIC: ReaderThread: No document!", "Tail for Win32", MB_OK | MB_ICONSTOP);

    return 1;
  }

  parms = (READER_PARAMS*) pParams;

//  strcpy (gszFileName, parms->szFileName);

// Get the path of the file, and monitor the directory it is in for changes. (!)
  _splitpath (parms->szFileName, szDrive, szPath, NULL, NULL);
  _makepath (szNewPath, szDrive, szPath, NULL, NULL);

  pView = parms->pView;

  bIgnoreHotStartup = parms->bIgnoreHotStartup;

  // Determine the type of the drive. If it is not local, then
  // we cannot use file change notifications.
  _splitpath (szNewPath, szDrive, NULL, NULL, NULL);

  uiFlags = GetDriveType (szDrive);

  // PP: Not sure if this is the full list that we should use to 
  // decide whether this directory/file can be monitored with change 
  // notifications.
  if ((uiFlags & DRIVE_REMOTE) || (uiFlags & DRIVE_UNKNOWN))
  {
    LogMessage ("File '%s' is not on a local drive. Using polling to determine file changes.", parms->szFileName);                  

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -