Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CNGDiskSpaceMonitor - a Free Disk Space Monitoring class

0.00/5 (No votes)
5 Feb 2001 1  
A class to monitor free disk space on a nominated drive
  • Download demo project - 34 Kb
  • Download source - 6 Kb
  • Overview

    CNGDiskSpaceMonitor is a class which monitors the free space on a drive until either told to stop, or a predetermined alarm threshold is reached.

    If the disk space falls below the threshold, a Windows message (defined by the client) is sent to a window specified when the monitoring process was started.

    The monitoring process is carried out by a worker thread, which polls the free disk space periodically in a while loop. The thread exits when either the disk space falls below the predetermined threshold, or the client signals it to stop monitoring.

    The Worker Thread

    The worker thread is fairly simple. When monitoring is started (via the Start() method), it calls an implementation method [CreateMonitoringThread()] which calls AfxBeginThread() to start the worker thread.

    Once initialised, the worker thread enters an infinite while() loop:-

        // Loop until a the available space drops below the threshold or hEvent
    
        // (m_Event) becomes set indicating it's time for the thread to end.
    
        while (TRUE)
        {
            // Check if the event has been signaled
    
            // If so its time for this thread to die....
    
    	//
    
    	// Note that the timeout is set to zero so the wait
    
    	// will return IMMEDIATELY
    
    	if (WAIT_OBJECT_0 == ::WaitForSingleObject(hEvent, 0))
    	{
    		break;
    	}
    	if ( (NULL != m_hwndTarget) && !::IsWindow(m_hwndTarget) )
    	{
                // Stop monitoring if the target window dies
    
    	    break;
    	}
    	if (GetFreeDiskSpace(sPath, m_dwFreeSpace) )
    	{
    	    // Managed to read the disk space OK
    
    	    if (m_dwFreeSpace < m_dwThreshold)
    	    {
                    // We've dropped below the alarm threshold
    
    		// If the target window is still there, let the
    
    		// client know and then kill the thread
    
    		OnThresholdReached(sPath,
                                       m_dwFreeSpace,
                                       m_dwThreshold);
    		break;
                }
            }
            else
            {
                // Kill this thread (m_Event became signaled)
    
    	    break;
            }
            if (m_dwPollInterval > 0)
            {
                // Sleep for a little while between polls...
    
                ::Sleep(m_dwPollInterval);
            }
        }
    

    Reading Free Disk Space

    Reading the free disk space on a Windows machine is one of those tasks which ought to be easier than it is. The problem is that the preferred method - the GetDiskFreeSpaceEx() function - is not available on all Win32 platforms (it was introduced in Windows 95 OSR2).

    In order to remain portable, the class uses explicit linking [via GetModuleHandle() and GetProcAddress()] to determine whether GetDiskFreeSpaceEx() is available. If not, GetDiskFreeSpace() is used instead; the consequence of this being that disks over 2GB in size (which aren't supported by FAT16 file system of the original Windows 95 release anyway) can't be read accurately.

    The (rather messy) details of this are buried in the GetFreeDiskSpace() method:-

    BOOL CNGDiskSpaceMonitor::GetFreeDiskSpace(const CString& sPath,
    					   DWORDLONG& rdwFreeSpace)
    {
        BOOL bResult = FALSE;
    
        // Load the kernel to gain access to the functions we want
    
        // If this fails, something's REALLY wrong...
    
        HMODULE hKernel = ::GetModuleHandle( _T("Kernel32.dll") );
        ASSERT(NULL != hKernel);
    
        if (NULL != hKernel)
        {
    #ifdef _UNICODE
            LPFNGETSPACEEX	pfnGetDiskFreeSpaceEx = 
                 (LPFNGETSPACEEX)::GetProcAddress(hKernel, "GetDiskFreeSpaceExW" );
            LPFNGETSPACE	pfnGetDiskFreeSpace = 
                 (LPFNGETSPACE)::GetProcAddress(hKernel, "GetDiskFreeSpaceW" );
    #else
            LPFNGETSPACEEX	pfnGetDiskFreeSpaceEx = 
                 (LPFNGETSPACEEX)::GetProcAddress(hKernel, "GetDiskFreeSpaceExA" );
            LPFNGETSPACE	pfnGetDiskFreeSpace = 
                 (LPFNGETSPACE)::GetProcAddress(hKernel, "GetDiskFreeSpaceA" );
    #endif
    
            ASSERT (NULL != pfnGetDiskFreeSpaceEx);
            if (NULL != pfnGetDiskFreeSpaceEx)
            {
                ULARGE_INTEGER nCallerFreeBytes; // Receives the number of bytes on
    
                                                 // disk available to the caller
    
                ULARGE_INTEGER nDiskSize;        // Receives the number of bytes on disk
    
                ULARGE_INTEGER nTotalFreeBytes;  // Receives the total free bytes on 
    
                                                 // the disk
    
    
                bResult = pfnGetDiskFreeSpaceEx(sPath,
                                                &nCallerFreeBytes,
                                                &nDiskSize,
                                                &nTotalFreeBytes);
                if (bResult)
                {
                    rdwFreeSpace = nCallerFreeBytes.QuadPart;
                }
            }
    
            // Failing that try the old fashioned way...
    
    	if (!bResult)
            {
                DWORD dwSectorsPerCluster;
                DWORD dwBytesPerSector;
                DWORD dwFreeClusters;
                DWORD dwTotalClusters;
    
                bResult = pfnGetDiskFreeSpace(sPath,
                                              &dwSectorsPerCluster,
                                              &dwBytesPerSector,
                                              &dwFreeClusters,
                                              &dwTotalClusters);
                if (bResult)
                {
                    rdwFreeSpace = dwFreeClusters * dwSectorsPerCluster * dwBytesPerSector;
                }
            }
        }
        return bResult;
    }

    It's worthy of note here that the latest recommended way of doing this - the SHGetDiskFreeSpace() function (which performs this voodoo for you) - is only available on systems with version 4.71 or later of Shell32.dll. Hence, if you're running on a platform without it (i.e. first edition Windows 95), you're back where you started.

    To make the code as widely applicable as possible, I've deliberately avoided such dependencies, and done it the hard way myself.

    Operations

    The Start() method starts monitoring a specified path. Along with the path to monitor, the free space threshold must be specified, and (optionally) a target window and message. If required, the priority of the monitoring thread may also be specified (the default is THREAD_PRIORITY_LOWEST):

        // Start monitoring a path
    
        // When the disk space drops below a defined threshold
    
        // a message is sent to the specified window
    
        //
    
        BOOL Start(	const CString& sPath,	// Path to monitor
    
                    DWORDLONG dwThreshold,	// Send notification when space
    
                                            // drops below this value
    
                    CWnd* pWnd = NULL,	// Target window
    
                    UINT nMsg = 0,		// Message to send
    
                    UINT nID = 0,		// ID if message is WM_COMMAND
    
                    int ePriority = THREAD_PRIORITY_LOWEST);	// Priority
    
    
        BOOL Start(	const CString& sPath,	// Path to monitor
    
                    DWORDLONG dwThreshold,	// Send notification when space
    
                                            // drops below this value
    
                    HWND hWnd,		// Target window
    
                    UINT nMsg,		// Message to send
    
                    UINT nID = 0,		// ID if message is WM_COMMAND
    
                    int ePriority = THREAD_PRIORITY_LOWEST);  // Priority
    
    

    Note that it is perfectly OK to call Start() without specifying a notification window and message - however in this case the OnThresholdReached() virtual method must be overridden to implement the action needed when the threshold is reached.

    At any time after monitoring is started, the client can stop the process using the Stop() method:

        BOOL Stop(void);

    While the worker thread is running, the alarm threshold may be read or changed by the following methods:

        DWORDLONG GetThreshold(void) const
    
        BOOL SetThreshold(DWORDLONG dwThreshold);
    

    The following methods determine the interval at which polling takes place (the default is every 100 milliseconds):

        DWORD GetPollInterval(void) const
    
        BOOL SetPollInterval(DWORD dwInterval);
    

    Two methods to return disk space are provided. The first, which takes no parameters, returns the free space read by the worker thread on its last monitor cycle. In consequence, this method is valid only whilst the worker thread is running.

    The second method is the one used by the worker thread itself. It is provided as part of the interface to make it easier for clients to read the disk space for any drive (and can be used at any time):

        DWORDLONG GetFreeDiskSpace(void) const;
    
        static BOOL GetFreeDiskSpace( const CString& sPath,
                                      DWORDLONG& rdwFreeSpace);
    

    Finally, if the client forgets which disk is being monitored, or isn't sure whether the monitor loop is running, the GetPath() and IsRunning() methods can be used to find out:

        CString GetPath(void) const;
    
        BOOL IsRunning(void) const;
    

    Overridables

    CNGDiskSpaceMonitor has just one virtual method:

     virtual BOOL OnThresholdReached(	const CString& sPath,
                                          DWORDLONG dwFreeSpace,
                                          DWORDLONG dwThreshold);
    

    The default implementation uses a PostMessage() call to post a message to the window specified in the Start() method.

    If you need to signal the client in a different way (as will be the case if the client is not a window), this method can be overridden in a derived class to implement an alternative scheme.

    Usage

    Using the class is straightforward:

    1. Add a CNGDiskSpaceMonitor object to one of your classes
    2. Call one of the overloads of CNGDiskSpaceMonitor::Start() to start the monitoring thread
    3. If the threshold is reached, a message will be sent to a target window. You can change this behaviour by overriding the CNGDiskSpaceMonitor::OnThresholdReached() virtual method.

    The demo app (DiskSpaceMonitorTest) shows how to use the CNGDiskSpaceMonitor class to monitor the free space on a specified drive. Both ANSI and Unicode builds are provided; the code compiles cleanly at warning level 4.

    Updates

    Version 1.1 (1st February, 2001) - Initial submission

    Version 1.2 (5th February, 2001) - Bug fixes for Unicode compatibility; also simplified the implementation of GetFreeDiskSpace() as suggested by Wes Jones.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here