Click here to Skip to main content
Click here to Skip to main content

CNGDiskSpaceMonitor - a Free Disk Space Monitoring class

, 5 Feb 2001 CPOL
Rate this:
Please Sign up or sign in to vote.
A class to monitor free disk space on a nominated drive
  • Download demo project - 34 Kb
  • Download source - 6 Kb
  • <!-- STEP 3. Add the article text.-->

    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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    Share

    About the Author

    Anna-Jayne Metcalfe
    Founder Riverblade Limited
    United Kingdom United Kingdom
    I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
      * Also known as the Standard Army Bootswitch. But that's another story...
    Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.
     
    One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.
     
    I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:
     
    I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!
    Follow on   Twitter

    Comments and Discussions

     
    Question2Tb Virtual Disk Pinmembernhchmg27-Jun-11 21:25 
    AnswerRe: 2Tb Virtual Disk PinmemberAnna-Jayne Metcalfe27-Jun-11 22:18 
    GeneralMy vote of 1 PinmemberShahin Khorshidnia5-Jan-11 0:35 
    GeneralRe: My vote of 1 PinmemberAnna-Jayne Metcalfe17-Jan-11 4:45 
    GeneralThank you Pinmemberxiaotianwangzi23-Jun-09 23:04 
    GeneralRe: Thank you PinmemberAnna-Jayne Metcalfe23-Jun-09 23:38 
    Generalremote drive monitoring Pinmemberasmotritsky1-Jul-04 11:26 
    Generalenumerating drives/partitions Pinmemberasmotritsky2-Jul-04 5:12 
    GeneralCCriticalSection PinmemberAbraxas232-Jan-04 7:11 
    QuestionHow would you format ULARGE_INTEGER into CString PinsussWell wisher14-May-03 4:22 
    AnswerRe: How would you format ULARGE_INTEGER into CString PinmemberDavidCrow29-Sep-03 6:30 
    Generalwe all b da man! PinsussAnonymous24-Feb-03 13:28 
    GeneralSince that is how many bytes there are im assumming. PinmemberLiquidKnight15-Feb-03 14:59 
    GeneralRe: Since that is how many bytes there are im assumming. PinmemberAnna-Jayne Metcalfe15-Feb-03 15:04 
    GeneralOther peculiarity of hard disk Pinmembernencini30-Oct-02 19:17 
    GeneralGood job, but... PinmemberWes Jones2-Feb-01 9:11 
    GeneralThanks for the feedback! PinmemberAndy Metcalfe2-Feb-01 23:58 
    GeneralFound it (ouch)... PinmemberAndy Metcalfe5-Feb-01 3:18 
    GeneralThe article has now been updated PinmemberAndy Metcalfe6-Feb-01 0:31 
    GeneralRe: The article has now been updated PinsussTyhtt24-Feb-03 13:21 
    GeneralRe: Good job, but... PinsussAnonymous24-Feb-03 13:23 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Terms of Use | Mobile
    Web03 | 2.8.1411023.1 | Last Updated 6 Feb 2001
    Article Copyright 2001 by Anna-Jayne Metcalfe
    Everything else Copyright © CodeProject, 1999-2014
    Layout: fixed | fluid