///////////////////////////////////////////////////////////////////////////////
/*  File: stats.cpp

    Description: These classes provide temporary storage of quota
        information for a given volume/user pair.  Creation of the object
        automatically gathers the necessary quota and user information.  
        Clients then query the objects to retrieve quota statistics when
        desired.

            CStatistics
            CStatisticsList
                                                            +----------+
                                                       +--->| CVolume  |
                                                       |    +----------+
            +-----------------+     +-------------+<---+
            | CStatisticsList |<-->>| CStatistics | contains
            +-----------------+     +-------------+<---+
                                                       |    +-------------+
                                                       +--->| CVolumeUser |
                                                            +-------------+

    Revision History:

    Date        Description                                          Programmer
    --------    ---------------------------------------------------  ----------
    07/01/97    Initial creation.                                    BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
#include <precomp.hxx>
#pragma hdrstop

#include "dskquota.h"
#include "stats.h"

//
// Gather quota statistics for a given volume-user pair.
//
CStatistics::CStatistics(
    TCHAR chVolLetter,
    LPCTSTR pszVolDisplayName,
    LPBYTE pUserSid
    ) : m_vol(chVolLetter, pszVolDisplayName),
        m_bQuotaEnabled(FALSE),
        m_bWarnAtThreshold(FALSE),
        m_bDenyAtLimit(FALSE),
        m_bValid(FALSE)
{
    //
    // If the volume doesn't support quotas, no use in continuing
    // with the expensive stuff.
    //
    if (m_vol.SupportsQuotas())
    {
        HRESULT hr;

        hr = OleInitialize(NULL);
        if (SUCCEEDED(hr))
        {
            //
            // Load dskquota.dll, creating a new disk quota controller object.
            //
            IDiskQuotaControl *pQC;
            hr = CoCreateInstance(CLSID_DiskQuotaControl,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IDiskQuotaControl,
                                  (LPVOID *)&pQC);

            if (SUCCEEDED(hr))
            {
                //
                // Initialize the quota controller for this volume.
                // We need only read access.
                //
                TCHAR szVolume[] = TEXT("X:\\");
                szVolume[0] = m_vol.GetLetter();

                hr = IDiskQuotaControl_Initialize(pQC, szVolume, GENERIC_READ);
                if (SUCCEEDED(hr))
                {
                    DWORD dwQuotaState    = 0;
                    DWORD dwQuotaLogFlags = 0;

                    //
                    // Get the volume's quota state flags.
                    //
                    hr = pQC->GetQuotaState(&dwQuotaState);
                    if (SUCCEEDED(hr))
                    {
                        m_bQuotaEnabled = !DISKQUOTA_IS_DISABLED(dwQuotaState);
                        m_bDenyAtLimit  = DISKQUOTA_IS_ENFORCED(dwQuotaState);
                    }
                    //
                    // Get the volume's quota logging flags.
                    //
                    hr = pQC->GetQuotaLogFlags(&dwQuotaLogFlags);
                    if (SUCCEEDED(hr))
                    {
                        m_bWarnAtThreshold = m_bQuotaEnabled &&
                                             DISKQUOTA_IS_LOGGED_USER_THRESHOLD(dwQuotaLogFlags);
                    }

                    //
                    // Get the quota user object for the current user.
                    //
                    oleauto_ptr<IDiskQuotaUser> ptrUser;

                    hr = pQC->FindUserSid(pUserSid,
                                          ptrUser._getoutptr(),
                                          DISKQUOTA_USERNAME_RESOLVE_SYNC);
                    if (SUCCEEDED(hr))
                    {
                        //
                        // Get the user's account and friendly names.
                        //
                        TCHAR szUserName[MAX_PATH]      = { TEXT('\0') };
                        TCHAR szUserDomain[MAX_PATH]    = { TEXT('\0') };
                        TCHAR szUserAccount[MAX_PATH]   = { TEXT('\0') };
                        TCHAR szUserEmailName[MAX_PATH] = { TEXT('\0') };

                        IDiskQuotaUser_GetName(ptrUser, 
                                               szUserDomain, ARRAYSIZE(szUserDomain), 
                                               szUserAccount, ARRAYSIZE(szUserAccount),
                                               szUserName, ARRAYSIZE(szUserName));

                        //
                        // Get the user's quota statistics for the volume.
                        //
                        DISKQUOTA_USER_INFORMATION dui;

                        hr = ptrUser->GetQuotaInformation((LPBYTE)&dui, sizeof(dui));
                        if (SUCCEEDED(hr))
                        {
                            //
                            // Make sure we didn't enumerate a non-existant user.
                            // This is because of the way NTFS enumerates quota records.
                            // Even if there is no record currently in the quota file,
                            // enumeration returns a record.  This is consistent with 
                            // automatic addition of users upon first write to the
                            // volume.  We must check the quota information to
                            // determine if it's an active user.
                            //
                            if (((__int64)-2) != dui.QuotaLimit.QuadPart &&
                                (0 != dui.QuotaLimit.QuadPart ||
                                 0 != dui.QuotaThreshold.QuadPart ||
                                 0 != dui.QuotaUsed.QuadPart))
                            {
                                //
                                // Store user-specific info.
                                //
                                CString strUserDisplayName;
                                if (TEXT('\0') != szUserName[0])
                                {
                                    //
                                    // User has a "friendly" name associated
                                    // with their account.  Include it in the display
                                    // name.
                                    //
                                    strUserDisplayName.Format(TEXT("%1\\%2 (%3)"),
                                                              szUserDomain,
                                                              szUserAccount,
                                                              szUserName);
                                }
                                else
                                {
                                    //
                                    // No "friendly" name associated with user account.
                                    //
                                    strUserDisplayName.Format(TEXT("%1\\%2"),
                                                              szUserDomain,
                                                              szUserAccount,
                                                              szUserName);
                                }
                                //
                                // Store the user's information in the volume-user object.
                                //
                                hr = m_volUser.SetUserInfo(strUserDisplayName, 
                                                           szUserEmailName,
                                                           dui.QuotaThreshold,
                                                           dui.QuotaLimit,
                                                           dui.QuotaUsed);
                                if (FAILED(hr))
                                {
                                    DebugMsg(DM_ERROR, 
                                             TEXT("Error 0x%08X setting user info on volume \"%s\"."), 
                                             hr, pszVolDisplayName);
                                }
                                m_bValid = SUCCEEDED(hr);
                            }
                        }
                        else
                        {
                            DebugMsg(DM_ERROR, 
                                     TEXT("Error 0x%08X getting user quota info on volume \"%s\"."), 
                                     hr, pszVolDisplayName);
                        }
                    }
                    else
                    {
                        DebugMsg(DM_ERROR, 
                                 TEXT("Error 0x%08X finding quota user on volume \"%s\"."), 
                                 hr, pszVolDisplayName);
                    }
                }
                else
                {
                    DebugMsg(DM_ERROR, 
                             TEXT("Error 0x%08X initializing QC for volume \"%s\"."), 
                             hr, pszVolDisplayName);
                }
                pQC->ShutdownAndRelease(TRUE);
            } // if SUCCEEDED(CoCreateInstance())
            else
                DebugMsg(DM_ERROR, TEXT("Failed CoCreateInstance.  0x%08X"), hr);

            OleUninitialize();
        } // if SUCCEEDED(OleInitialize())
        else
            DebugMsg(DM_ERROR, TEXT("Failed OleInitialize.  0x%08X"), hr);
    } // if m_vol.SupportsQuotas()
    else
        DebugMsg(DM_ERROR, TEXT("Volume \"%s\" doesn't support quotas."), pszVolDisplayName);
}

//
// Determine if a specific statistics object contains statistics that
// will generate either an email notification or a popup notification.
//
BOOL 
CStatistics::IncludeInReport(
    VOID
    ) const
{
    BOOL bReport = FALSE;

    //
    // First check data validity and volume settings.
    //
    if (IsValid() &&        // Does volume support quotas and was vol opened?
        QuotasEnabled() &&  // Are quotas enabled on the volume?
        WarnAtThreshold())  // Is the "warn at threshold" bit set on the vol?
    {
        //
        // Now see if user's quota usage warrants reporting.
        // Report if AmtUsed > Threshold.
        //
        if (GetUserQuotaUsed().QuadPart > GetUserQuotaThreshold().QuadPart)
        {
            bReport = TRUE;
        }
    }
    return bReport;
}



CStatisticsList::CStatisticsList(
    VOID
    )
{
    //
    // Nothing to do.
    //
}

CStatisticsList::~CStatisticsList(
    VOID
    )
{
    INT cEntries = m_List.Count();
    for (INT i = 0; i < cEntries; i++)
    {
        delete m_List[i];
    }
    m_List.Clear();
}


const CStatistics *
CStatisticsList::GetEntry(
    INT iEntry
    )
{
    return m_List[iEntry];
}


HRESULT 
CStatisticsList::AddEntry(
    TCHAR chVolLetter,
    LPCTSTR pszVolDisplayName,
    LPBYTE pUserSid
    )
{
    HRESULT hr = E_OUTOFMEMORY;

    CStatistics *pStats = NULL;

    //
    // Create a new statistics object.
    // The ctor will fill in the volume and user information.
    //
    pStats = new CStatistics(chVolLetter, 
                             pszVolDisplayName, 
                             pUserSid);
    if (NULL != pStats)
    {
        if (pStats->IsValid())
        {
            BOOL bDuplicate = FALSE;

            //
            // Look for a duplicate.  Shouldn't be one but just to make sure.
            //
            INT cEntries = m_List.Count();

            for (INT i = 0; i < cEntries; i++)
            {
                const CStatistics *pEntry = m_List[i];

                if (pStats->SameVolume(*pEntry))
                    bDuplicate = TRUE;
            }
            if (!bDuplicate)
            {
                hr = S_OK;
                try
                {
                    m_List.Append(pStats);
                }
                catch(OutOfMemory)
                {
                    DebugMsg(DM_ERROR, TEXT("CStatisticsList::AddEntry - Insufficient memory."));
                    hr = E_OUTOFMEMORY;
                }
            }
            else
            {
                //
                // FEATURE:  We should really assert here.
                //
                DebugMsg(DM_ERROR, TEXT("CStatisticsList::AddEntry - Duplicate entry found."));
                hr = S_FALSE;
            }
        }
        else
        {
            //
            // Stats object is invalid.  Probably because the volume
            // doesn't support quotas.
            //
            DebugMsg(DM_ERROR, TEXT("CStatisticsList::AddEntry - Volume stats not valid."));
            hr = S_FALSE;
        }
    }
    if (S_OK != hr)
    {
        //
        // CStatistics object wasn't added to the list.
        // Probably was a duplicate (shouldn't happen) or the volume
        // didn't support quotas.
        //
        delete pStats;
    }
    return hr;
}
